diff --git a/code/game/header/utils/raylib_helpers.h b/code/game/header/utils/raylib_helpers.h index a329a66..1acec2c 100644 --- a/code/game/header/utils/raylib_helpers.h +++ b/code/game/header/utils/raylib_helpers.h @@ -45,9 +45,9 @@ void DrawCircleEco(float centerX, float centerY, float radius, Color color) } static inline -void DrawRectangleEco(float posX, float posY, int width, int height, Color color) +void DrawRectangleEco(float posX, float posY, float width, float height, Color color) { - DrawRectangleV((Vector2){ (float)posX , (float)posY }, (Vector2){ (float)width , (float)height }, color); + DrawRectangleV((Vector2){ (float)posX , (float)posY }, (Vector2){ width , height }, color); } static inline diff --git a/code/game/header/world/world.h b/code/game/header/world/world.h index 244de0c..f50abca 100644 --- a/code/game/header/world/world.h +++ b/code/game/header/world/world.h @@ -16,7 +16,7 @@ #define WORLD_TRACKER_UPDATE_FAST_MS 100 #define WORLD_TRACKER_UPDATE_NORMAL_MS 500 #define WORLD_TRACKER_UPDATE_SLOW_MS 1000 -#define WORLD_BLOCK_SIZE 16 +#define WORLD_BLOCK_SIZE 64 #define WORLD_PKT_READER(name) int32_t name(void* data, uint32_t datalen, void *udata) typedef WORLD_PKT_READER(world_pkt_reader_proc); diff --git a/code/game/source/editors/texed.c b/code/game/source/editors/texed.c index edea110..5db8c88 100644 --- a/code/game/source/editors/texed.c +++ b/code/game/source/editors/texed.c @@ -1,104 +1,440 @@ +#define ZPL_NO_WINDOWS_H +#include "zpl.h" + #include "editors/texed.h" #include "raylib.h" +#include "utils/raylib_helpers.h" +#include "cwpack/cwpack.h" #define RAYGUI_IMPLEMENTATION #define RAYGUI_SUPPORT_ICONS #include "raygui.h" +#define GUI_FILE_DIALOG_IMPLEMENTATION +#include "gui_file_dialog.h" + static uint16_t screenWidth = 1280; static uint16_t screenHeight = 720; -static void DrawStyleEditControls(void); +static float zoom = 4.0f; + +#define TD_DEFAULT_IMG_WIDTH 64 +#define TD_DEFAULT_IMG_HEIGHT 64 +#define TD_UI_PADDING 5.0f +#define TD_UI_PREVIEW_BORDER 4.0f + +typedef enum { + TPARAM_FLOAT, + TPARAM_INT, + TPARAM_COLOR, + TPARAM_STRING, + + TPARAM_FORCE_UINT8 = UINT8_MAX +} td_param_kind; + +typedef struct { + td_param_kind kind; + union { + float number; + int pix; + char *str; + Color color; + }; +} td_param; + +typedef enum { + TOP_CLEAR, + TOP_DRAW_RECT, + TOP_DRAW_LINE, + + TOP_FORCE_UINT8 = UINT8_MAX +} td_op_kind; + +typedef struct { + td_op_kind kind; + char const *name; + bool is_hidden; + + uint8_t num_params; + td_param *params; +} td_op; + +#define OP(n) .kind = n, .name = #n + +typedef struct { + char *filepath; + Image img; + Texture2D tex; + GuiFileDialogState fileDialog; + + td_op *ops; //< zpl_array +} td_ctx; + +static td_ctx ctx = {0}; + +static char filename[200]; + +static td_op default_ops[] = { + { + OP(TOP_CLEAR), + .num_params = 1, + .params = (td_param[]) { + { + .kind = TPARAM_COLOR, + .color = WHITE + } + } + }, + { + OP(TOP_DRAW_RECT), + .num_params = 5, + .params = (td_param[]) { + { + .kind = TPARAM_INT, + .pix = 0 + }, + { + .kind = TPARAM_INT, + .pix = 0 + }, + { + .kind = TPARAM_INT, + .pix = 10 + }, + { + .kind = TPARAM_INT, + .pix = 10 + }, + { + .kind = TPARAM_COLOR, + .color = RED + }, + + } + } +}; + +// NOTE(zaklaus): IMPORTANT !! keep these in sync +static char const *add_op_list = "CLEAR SOLID;DRAW RECTANGLE;PLOT LINE;DITHER"; + +#define DEF_OPS_LEN (int)(sizeof(default_ops) / (sizeof(default_ops[0]))) + +void texed_new(int32_t w, int32_t h); +void texed_destroy(void); +void texed_load(void); +void texed_save(void); +void texed_repaint_preview(void); +void texed_process_ops(void); +void texed_add_op(int idx); +void texed_rem_op(int idx); +void texed_swp_op(int idx, int idx2); + +void texed_draw_oplist_pane(zpl_aabb2 r); +void texed_draw_topbar(zpl_aabb2 r); + +static inline +void DrawAABB(zpl_aabb2 rect, Color color) { + DrawRectangleEco(rect.min.x, rect.min.y, + rect.max.x-rect.min.x, + rect.max.y-rect.min.y, + color); +} + +static inline +Rectangle aabb2_ray(zpl_aabb2 r); void texed_run(void) { InitWindow(screenWidth, screenHeight, "eco2d - texture editor"); - SetWindowState(FLAG_WINDOW_RESIZABLE); SetTargetFPS(60); + texed_new(TD_DEFAULT_IMG_WIDTH, TD_DEFAULT_IMG_HEIGHT); - Rectangle panelRec = { 20, 40, 200, 150 }; - Rectangle panelContentRec = {0, 0, 340, 340 }; - Vector2 panelScroll = { 99, -20 }; + zpl_aabb2 screen = { + .min = (zpl_vec2) {.x = 0.0f, .y = 0.0f}, + .max = (zpl_vec2) {.x = screenWidth, .y = screenHeight}, + }; - bool showContentArea = true; + // TODO(zaklaus): TEMP + texed_add_op(0); + texed_add_op(1); - while(!WindowShouldClose()) { + zpl_aabb2 topbar = zpl_aabb2_cut_top(&screen, 25.0f); + zpl_aabb2 oplist_pane = zpl_aabb2_cut_right(&screen, screenWidth / 2.0f); + zpl_aabb2 preview_window = zpl_aabb2_cut_top(&screen, (screen.max.y-screen.min.y) / 2.0f); + zpl_aabb2 property_pane = screen; + + // NOTE(zaklaus): contract all panes for a clean UI separation + oplist_pane = zpl_aabb2_contract(&oplist_pane, TD_UI_PADDING); + preview_window = zpl_aabb2_contract(&preview_window, TD_UI_PADDING); + property_pane = zpl_aabb2_contract(&property_pane, TD_UI_PADDING); + + Rectangle preview_rect = aabb2_ray(preview_window); + Image checkerboard = GenImageChecked(preview_rect.width, preview_rect.height, 16, 16, BLACK, ColorAlpha(GRAY, 0.2f)); + Texture2D checker_tex = LoadTextureFromImage(checkerboard); + UnloadImage(checkerboard); + + while (!WindowShouldClose()) { BeginDrawing(); - ClearBackground(RAYWHITE); + ClearBackground(GetColor(0x222034)); { - DrawText(TextFormat("[%f, %f]", panelScroll.x, panelScroll.y), 4, 4, 20, RED); + if (ctx.fileDialog.fileDialogActive) GuiLock(); + DrawTextureEx(checker_tex, (Vector2){ preview_window.min.x, preview_window.min.y}, 0.0f, 1.0f, WHITE); + DrawTextureEx(ctx.tex, (Vector2){ preview_window.min.x, preview_window.min.y}, 0.0f, zoom, WHITE); - Rectangle view = GuiScrollPanel(panelRec, panelContentRec, &panelScroll); + DrawAABB(topbar, RAYWHITE); + DrawAABB(property_pane, GetColor(0x422060)); + DrawAABB(oplist_pane, GetColor(0x425060)); - BeginScissorMode(view.x, view.y, view.width, view.height); - GuiGrid((Rectangle){panelRec.x + panelScroll.x, panelRec.y + panelScroll.y, panelContentRec.width, panelContentRec.height}, 16, 3); - EndScissorMode(); - - if (showContentArea) DrawRectangle(panelRec.x + panelScroll.x, panelRec.y + panelScroll.y, panelContentRec.width, panelContentRec.height, Fade(RED, 0.1)); - - DrawStyleEditControls(); - - showContentArea = GuiCheckBox((Rectangle){ 565, 80, 20, 20 }, "SHOW CONTENT AREA", showContentArea); - - panelContentRec.width = GuiSliderBar((Rectangle){ 590, 385, 145, 15}, "WIDTH", TextFormat("%i", (int)panelContentRec.width), panelContentRec.width, 1, 600); - panelContentRec.height = GuiSliderBar((Rectangle){ 590, 410, 145, 15 }, "HEIGHT", TextFormat("%i", (int)panelContentRec.height), panelContentRec.height, 1, 400); + texed_draw_topbar(topbar); + texed_draw_oplist_pane(oplist_pane); + if (ctx.fileDialog.fileDialogActive) GuiUnlock(); + GuiFileDialog(&ctx.fileDialog); } EndDrawing(); } + + UnloadTexture(checker_tex); + texed_destroy(); } -// Draw and process scroll bar style edition controls -static void DrawStyleEditControls(void) -{ - // ScrollPanel style controls - //---------------------------------------------------------- - GuiGroupBox((Rectangle){ 550, 170, 220, 205 }, "SCROLLBAR STYLE"); +void texed_new(int32_t w, int32_t h) { + ctx.img = GenImageColor(w, h, WHITE); + ctx.filepath = NULL; + zpl_array_init(ctx.ops, zpl_heap()); + texed_repaint_preview(); - int style = GuiGetStyle(SCROLLBAR, BORDER_WIDTH); - GuiLabel((Rectangle){ 555, 195, 110, 10 }, "BORDER_WIDTH"); - GuiSpinner((Rectangle){ 670, 190, 90, 20 }, NULL, &style, 0, 6, false); - GuiSetStyle(SCROLLBAR, BORDER_WIDTH, style); + ctx.fileDialog = InitGuiFileDialog(420, 310, zpl_bprintf("%s/art", GetWorkingDirectory()), false); + //zpl_strcpy(ctx.fileDialog.filterExt, ".ecotex"); +} + +void texed_destroy(void) { + UnloadTexture(ctx.tex); + UnloadImage(ctx.img); + zpl_array_free(ctx.ops); +} + +void texed_repaint_preview(void) { + UnloadTexture(ctx.tex); + ImageClearBackground(&ctx.img, ColorAlpha(BLACK, 0.0f)); + texed_process_ops(); + ctx.tex = LoadTextureFromImage(ctx.img); +} + +void texed_add_op(int idx) { + assert(idx >= 0 && idx < DEF_OPS_LEN); + td_op *dop = &default_ops[idx]; - style = GuiGetStyle(SCROLLBAR, ARROWS_SIZE); - GuiLabel((Rectangle){ 555, 220, 110, 10 }, "ARROWS_SIZE"); - GuiSpinner((Rectangle){ 670, 215, 90, 20 }, NULL, &style, 4, 14, false); - GuiSetStyle(SCROLLBAR, ARROWS_SIZE, style); + td_op op = { + .kind = dop->kind, + .name = dop->name, + .num_params = dop->num_params, + .params = (td_param*)zpl_malloc(sizeof(td_param)*dop->num_params) + }; - style = GuiGetStyle(SCROLLBAR, SLIDER_PADDING); - GuiLabel((Rectangle){ 555, 245, 110, 10 }, "SLIDER_PADDING"); - GuiSpinner((Rectangle){ 670, 240, 90, 20 }, NULL, &style, 0, 14, false); - GuiSetStyle(SCROLLBAR, SLIDER_PADDING, style); + zpl_memcopy(op.params, dop->params, sizeof(td_param)*dop->num_params); - style = GuiCheckBox((Rectangle){ 565, 280, 20, 20 }, "ARROWS_VISIBLE", GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)); - GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, style); + zpl_array_append(ctx.ops, op); - style = GuiGetStyle(SCROLLBAR, SLIDER_PADDING); - GuiLabel((Rectangle){ 555, 325, 110, 10 }, "SLIDER_PADDING"); - GuiSpinner((Rectangle){ 670, 320, 90, 20 }, NULL, &style, 0, 14, false); - GuiSetStyle(SCROLLBAR, SLIDER_PADDING, style); + texed_repaint_preview(); +} + +void texed_rem_op(int idx) { + assert(idx >= 0 && idx < (int)zpl_array_count(ctx.ops)); + zpl_mfree(ctx.ops[idx].params); + zpl_array_remove_at(ctx.ops, idx); + texed_repaint_preview(); +} + +static bool is_add_op_dropbox_open = false; +static int add_op_dropbox_selected = 0; + +void texed_draw_oplist_pane(zpl_aabb2 r) { + zpl_aabb2 oplist_header = zpl_aabb2_cut_top(&r, 40.0f); - style = GuiGetStyle(SCROLLBAR, SLIDER_WIDTH); - GuiLabel((Rectangle){ 555, 350, 110, 10 }, "SLIDER_WIDTH"); - GuiSpinner((Rectangle){ 670, 345, 90, 20 }, NULL, &style, 2, 100, false); - GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, style); + zpl_aabb2 add_op_r = zpl_aabb2_cut_left(&oplist_header, 120.0f); - const char *text = GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE? "SCROLLBAR: LEFT" : "SCROLLBAR: RIGHT"; - style = GuiToggle((Rectangle){ 560, 110, 200, 35 }, text, GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE)); - GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, style); - //---------------------------------------------------------- + if (!is_add_op_dropbox_open && GuiButton(aabb2_ray(add_op_r), "ADD OPERATION")) { + is_add_op_dropbox_open = true; + } - // ScrollBar style controls - //---------------------------------------------------------- - GuiGroupBox((Rectangle){ 550, 20, 220, 135 }, "SCROLLPANEL STYLE"); - style = GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); - GuiLabel((Rectangle){ 555, 35, 110, 10 }, "SCROLLBAR_WIDTH"); - GuiSpinner((Rectangle){ 670, 30, 90, 20 }, NULL, &style, 6, 30, false); - GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, style); + GuiSetState(ctx.filepath ? GUI_STATE_NORMAL : GUI_STATE_DISABLED); - style = GuiGetStyle(DEFAULT, BORDER_WIDTH); - GuiLabel((Rectangle){ 555, 60, 110, 10 }, "BORDER_WIDTH"); - GuiSpinner((Rectangle){ 670, 55, 90, 20 }, NULL, &style, 0, 20, false); - GuiSetStyle(DEFAULT, BORDER_WIDTH, style); - //---------------------------------------------------------- -} \ No newline at end of file + zpl_aabb2 export_code_r = zpl_aabb2_cut_left(&oplist_header, 120.0f); + + if (GuiButton(aabb2_ray(export_code_r), "BUILD TEXTURE")) { + + } + + zpl_aabb2 export_img_r = zpl_aabb2_cut_left(&oplist_header, 120.0f); + + if (GuiButton(aabb2_ray(export_img_r), "EXPORT AS IMAGE")) { + + } + + GuiSetState(GUI_STATE_NORMAL); + + // NOTE(zaklaus): operator list + for (int i = 0; i < zpl_array_count(ctx.ops); i += 1) { + zpl_aabb2 op_item_r = zpl_aabb2_cut_top(&r, 45.0f); + zpl_aabb2_cut_top(&op_item_r, 2.5f); + zpl_aabb2_cut_bottom(&op_item_r, 2.5f); + Rectangle list_item = aabb2_ray(op_item_r); + DrawRectangleRec(list_item, ColorAlpha(RED, 0.4f)); + + zpl_aabb2 swap_r = zpl_aabb2_cut_left(&op_item_r, 50.0f); + Rectangle list_text = aabb2_ray(op_item_r); + + zpl_aabb2_cut_right(&swap_r, 5.0f); + zpl_aabb2 swap_top = zpl_aabb2_cut_top(&swap_r, 20.0f); + zpl_aabb2 swap_bottom = swap_r; + + if (i > 0 && GuiButton(aabb2_ray(swap_top), "UP")) { + texed_swp_op(i, i-1); + } + + if (i+1 < zpl_array_count(ctx.ops) && GuiButton(aabb2_ray(swap_bottom), "DOWN")) { + texed_swp_op(i, i+1); + } + + zpl_aabb2 remove_r = zpl_aabb2_cut_right(&op_item_r, 60.0f); + + if (GuiButton(aabb2_ray(remove_r), "REMOVE")) { + texed_rem_op(i); + } + + zpl_aabb2 hidden_r = zpl_aabb2_cut_right(&op_item_r, 60.0f); + + if (GuiButton(aabb2_ray(hidden_r), ctx.ops[i].is_hidden ? "SHOW" : "HIDE")) { + ctx.ops[i].is_hidden = !ctx.ops[i].is_hidden; + texed_repaint_preview(); + } + + GuiDrawText(ctx.ops[i].name, GetTextBounds(LABEL, list_text), GuiGetStyle(LABEL, TEXT_ALIGNMENT), Fade(RAYWHITE, guiAlpha)); + } + + if (is_add_op_dropbox_open && GuiDropdownBox(aabb2_ray(add_op_r), add_op_list, &add_op_dropbox_selected, true)) { + is_add_op_dropbox_open = false; + texed_add_op(add_op_dropbox_selected); + } +} +static inline +Rectangle aabb2_ray(zpl_aabb2 r) { + return (Rectangle) { + .x = r.min.x, + .y = r.min.y, + .width = r.max.x-r.min.x, + .height = r.max.y-r.min.y + }; +} + +void texed_process_ops(void) { + for (int i = 0; i < zpl_array_count(ctx.ops); i += 1) { + td_op *op = &ctx.ops[i]; + if (op->is_hidden) continue; + zpl_printf("processing op: %s ... \n", op->name); + + switch (op->kind) { + case TOP_CLEAR: { + ImageClearBackground(&ctx.img, op->params[0].color); + }break; + case TOP_DRAW_RECT: { + ImageDrawRectangle(&ctx.img, + op->params[0].pix, + op->params[1].pix, + op->params[2].pix, + op->params[3].pix, + op->params[4].color); + }break; + default: { + zpl_printf("%s\n", "unsupported op!"); + }break; + } + } +} + +void texed_draw_topbar(zpl_aabb2 r) { + zpl_aabb2 zoom_ctrl_r = zpl_aabb2_cut_left(&r, 150.0f); + + zoom = GuiSlider(aabb2_ray(zoom_ctrl_r), "zoom: ", zpl_bprintf("%.02f x", zoom), zoom, 1.0f, 16.0f); + + zpl_aabb2_cut_left(&r, 100.0f); + + zpl_aabb2 new_prj_r = zpl_aabb2_cut_left(&r, 60.0f); + + if (GuiButton(aabb2_ray(new_prj_r), "NEW")) { + texed_destroy(); + texed_new(TD_DEFAULT_IMG_WIDTH, TD_DEFAULT_IMG_HEIGHT); // TODO(zaklaus): show res panel + } + + zpl_aabb2 load_prj_r = zpl_aabb2_cut_left(&r, 60.0f); + static bool load_pending = false; + + if (GuiButton(aabb2_ray(load_prj_r), "LOAD")) { + load_pending = true; + ctx.fileDialog.fileDialogActive = true; + } + + if (ctx.fileDialog.SelectFilePressed && load_pending) { + ctx.fileDialog.SelectFilePressed = false; + if (IsFileExtension(ctx.fileDialog.fileNameText, ".ecotex")) { + zpl_strcpy(filename, ctx.fileDialog.fileNameText); + ctx.filepath = filename; + load_pending = false; + } else { + ctx.fileDialog.fileDialogActive = true; + } + } + + zpl_aabb2 save_prj_r = zpl_aabb2_cut_left(&r, 60.0f); + static bool save_as_pending = false; + + if (GuiButton(aabb2_ray(save_prj_r), "SAVE")) { + if (ctx.filepath == NULL) { + save_as_pending = true; + ctx.fileDialog.fileDialogActive = true; + } else { + texed_save(); + } + } + + zpl_aabb2 save_as_prj_r = zpl_aabb2_cut_left(&r, 60.0f); + + if (GuiButton(aabb2_ray(save_as_prj_r), "SAVE AS")) { + save_as_pending = true; + ctx.fileDialog.fileDialogActive = true; + } + + if (ctx.fileDialog.SelectFilePressed && save_as_pending) { + ctx.fileDialog.SelectFilePressed = false; + zpl_strcpy(ctx.fileDialog.fileNameText, zpl_bprintf("%s.ecotex", ctx.fileDialog.fileNameText)); + zpl_strcpy(filename, ctx.fileDialog.fileNameText); + ctx.filepath = filename; + save_as_pending = false; + texed_save(); + } + + zpl_aabb2 prj_name_r = zpl_aabb2_cut_right(&r, 200.0f); + GuiDrawText(zpl_bprintf("Project: %s", ctx.filepath ? ctx.filepath : "(unnamed)"), GetTextBounds(LABEL, aabb2_ray(prj_name_r)), GuiGetStyle(LABEL, TEXT_ALIGNMENT), Fade(BLACK, guiAlpha)); +} + +void texed_swp_op(int idx, int idx2) { + assert(idx >= 0 && idx < (int)zpl_array_count(ctx.ops)); + assert(idx2 >= 0 && idx2 < (int)zpl_array_count(ctx.ops)); + + td_op tmp = ctx.ops[idx2]; + ctx.ops[idx2] = ctx.ops[idx]; + ctx.ops[idx] = tmp; + + texed_repaint_preview(); +} + +void texed_load(void) { + // TODO(zaklaus): +} + +void texed_save(void) { + assert(ctx.filepath); + + // TODO(zaklaus): +} diff --git a/code/game/source/platform_raylib.c b/code/game/source/platform_raylib.c index b53e0e2..bca70ee 100644 --- a/code/game/source/platform_raylib.c +++ b/code/game/source/platform_raylib.c @@ -15,7 +15,7 @@ static uint16_t screenWidth = 1600; static uint16_t screenHeight = 900; -static float target_zoom = 4.0f; +static float target_zoom = 1.5f; static float zoom_overlay_tran = 0.0f; static bool request_shutdown; @@ -34,7 +34,7 @@ void platform_init() { render_camera.target = (Vector2){0.0f,0.0f}; render_camera.offset = (Vector2){screenWidth/2.0f, screenHeight/2.0f}; render_camera.rotation = 0.0f; - render_camera.zoom = 4.0f; + render_camera.zoom = 1.5f; // NOTE(zaklaus): Paint the screen before we load the game // TODO(zaklaus): Render a cool loading screen background maybe? :wink: :wink: @@ -65,7 +65,7 @@ void platform_input() { float mouse_z = (GetMouseWheelMove()*0.5f); if (mouse_z != 0.0f) { - target_zoom = zpl_clamp(target_zoom+mouse_z, 0.3f, 10.0f); + target_zoom = zpl_clamp(target_zoom+mouse_z, 0.1f, 10.0f); } // NOTE(zaklaus): keystate handling @@ -198,7 +198,7 @@ void DEBUG_draw_ground(uint64_t key, entity_view * data) { static inline float lerp(float a, float b, float t) { return a * (1.0f - t) + b * t; } void DEBUG_draw_entities(uint64_t key, entity_view * data) { - uint16_t size = 4; + uint16_t size = 16; uint16_t font_size = (uint16_t)lerp(4.0f, 32.0f, 0.5f/(float)render_camera.zoom); float font_spacing = 1.1f; float title_bg_offset = 4; diff --git a/code/vendors/gui_file_dialog.h b/code/vendors/gui_file_dialog.h new file mode 100644 index 0000000..24b7210 --- /dev/null +++ b/code/vendors/gui_file_dialog.h @@ -0,0 +1,663 @@ +/******************************************************************************************* +* +* FileDialog v1.1 - Modal file dialog to open/save files +* +* MODULE USAGE: +* #define GUI_FILE_DIALOG_IMPLEMENTATION +* #include "gui_file_dialog.h" +* +* INIT: GuiFileDialogState state = InitGuiFileDialog(); +* DRAW: GuiFileDialog(&state); +* +* NOTE: This module depends on some raylib file system functions: +* - GetDirectoryFiles() +* - ClearDirectoryFiles() +* - GetWorkingDirectory() +* - DirectoryExists() +* - FileExists() +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2019-2020 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" + +// WARNING: raygui implementation is expected to be defined before including this header +#undef RAYGUI_IMPLEMENTATION +#include "raygui.h" + +#ifndef GUI_FILE_DIALOG_H +#define GUI_FILE_DIALOG_H + +typedef struct { + Vector2 position; + Vector2 size; + + bool fileDialogActive; + + bool dirPathEditMode; + char dirPathText[256]; + + int filesListScrollIndex; + bool filesListEditMode; + int filesListActive; + + bool fileNameEditMode; + char fileNameText[256]; + bool SelectFilePressed; + bool CancelFilePressed; + int fileTypeActive; + int itemFocused; + + // Custom state variables (depend on development software) + // NOTE: This variables should be added manually if required + char **dirFiles; + int dirFilesCount; + + char filterExt[256]; + + char dirPathTextCopy[256]; + char fileNameTextCopy[256]; + + int prevFilesListActive; + +} GuiFileDialogState; + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + + //---------------------------------------------------------------------------------- + // Defines and Macros + //---------------------------------------------------------------------------------- + //... + + //---------------------------------------------------------------------------------- + // Types and Structures Definition + //---------------------------------------------------------------------------------- + // ... + + //---------------------------------------------------------------------------------- + // Global Variables Definition + //---------------------------------------------------------------------------------- + //... + + //---------------------------------------------------------------------------------- + // Module Functions Declaration + //---------------------------------------------------------------------------------- + GuiFileDialogState InitGuiFileDialog(int width, int height, const char *initPath, bool active); + void GuiFileDialog(GuiFileDialogState *state); + +#ifdef __cplusplus +} +#endif + +#endif // GUI_FILE_DIALOG_H + +/*********************************************************************************** +* +* GUI_FILE_DIALOG IMPLEMENTATION +* +************************************************************************************/ +#if defined(GUI_FILE_DIALOG_IMPLEMENTATION) + +#include "raygui.h" + +#include // Required for: strcpy() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define MAX_DIRECTORY_FILES 1024 +#define MAX_DIR_PATH_LENGTH 1024 + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +#if defined(USE_CUSTOM_LISTVIEW_FILEINFO) +// Detailed file info type +typedef struct FileInfo { + const char *name; + int size; + int modTime; + int type; + int icon; +} FileInfo; +#else +// Filename only +typedef char *FileInfo; +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +FileInfo *dirFilesIcon = NULL; + +//---------------------------------------------------------------------------------- +// Internal Module Functions Definition +//---------------------------------------------------------------------------------- +// Read all filenames from directory (supported file types) +static char **ReadDirectoryFiles(const char *dir, int *filesCount, char *filterExt); + +#if defined(USE_CUSTOM_LISTVIEW_FILEINFO) +// List View control for files info with extended parameters +static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int active); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +GuiFileDialogState InitGuiFileDialog(int width, int height, const char *initPath, bool active) +{ + GuiFileDialogState state = { 0 }; + + // Default dialog size is 440x310 + state.size.x = width == -1 ? 440 : width; + state.size.y = height == -1 ? 310 : height; + state.position = (Vector2){ GetScreenWidth()/2 - state.size.x/2, GetScreenHeight()/2 - state.size.y/2 }; + + state.fileDialogActive = active; + state.dirPathEditMode = false; + + state.filesListActive = -1; + state.prevFilesListActive = state.filesListActive; + state.filesListScrollIndex = 0; + + state.fileNameEditMode = false; + + state.SelectFilePressed = false; + state.CancelFilePressed = false; + + state.fileTypeActive = 0; + + strcpy(state.fileNameText, "\0"); + + // Custom variables initialization + if (initPath && DirectoryExists(initPath)) + { + strcpy(state.dirPathText, initPath); + } + else if (initPath && FileExists(initPath)) + { + strcpy(state.dirPathText, GetDirectoryPath(initPath)); + strcpy(state.fileNameText, GetFileName(initPath)); + } + else strcpy(state.dirPathText, GetWorkingDirectory()); + + strcpy(state.dirPathTextCopy, state.dirPathText); + strcpy(state.fileNameTextCopy, state.fileNameText); + + strcpy(state.filterExt, "all"); + + state.dirFilesCount = 0; + state.dirFiles = NULL; // NOTE: Loaded lazily on window active + + return state; +} + +// Read files in new path +static void FD_RELOAD_DIRPATH(GuiFileDialogState *state) +{ + for (int i = 0; i < state->dirFilesCount; i++) RL_FREE(state->dirFiles[i]); + RL_FREE(state->dirFiles); + + state->dirFiles = ReadDirectoryFiles(state->dirPathText, &state->dirFilesCount, state->filterExt); + state->itemFocused = 0; +} + +// Update and draw file dialog +void GuiFileDialog(GuiFileDialogState *state) +{ + if (state->fileDialogActive) + { + const int winWidth = state->size.x; + const int winHeight = state->size.y; + + // Load dirFilesIcon and state->dirFiles lazily on windows open + // NOTE: they are automatically unloaded at fileDialog closing + //------------------------------------------------------------------------------------ + if (dirFilesIcon == NULL) + { + dirFilesIcon = (FileInfo *)RL_MALLOC(MAX_DIRECTORY_FILES*sizeof(FileInfo)); // Max files to read + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesIcon[i] = (char *)calloc(MAX_DIR_PATH_LENGTH, 1); // Max file name length + } + + if (state->dirFiles == NULL) + { + state->dirFiles = ReadDirectoryFiles(state->dirPathText, &state->dirFilesCount, state->filterExt); + + for(int f = 0; f < state->dirFilesCount; f++) + { + if (strcmp(state->fileNameText, state->dirFiles[f]) == 0) + { + if (state->filesListActive != f) state->filesListScrollIndex = state->filesListActive = f; // Make it active and visible only on first call + + break; + } + } + } + //------------------------------------------------------------------------------------ + + DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Fade(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)), 0.85f)); + state->fileDialogActive = !GuiWindowBox((Rectangle){ state->position.x + 0, state->position.y + 0, winWidth, winHeight }, "#198#eco2d file dialog"); + + if (GuiButton((Rectangle){ state->position.x + winWidth - 50, state->position.y + 35, 40, 25 }, "< ..")) // || IsKeyReleased(KEY_DPAD_Y)) + { + // Move dir path one level up + strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText)); + + // RL_FREE previous dirFiles (reloaded by ReadDirectoryFiles()) + FD_RELOAD_DIRPATH(state); + + state->filesListActive = -1; + strcpy(state->fileNameText, "\0"); + strcpy(state->fileNameTextCopy, state->fileNameText); + } + + if (GuiTextBox((Rectangle){ state->position.x + 10, state->position.y + 35, winWidth - 65, 25 }, state->dirPathText, 256, state->dirPathEditMode)) + { + if (state->dirPathEditMode) + { + // Verify if a valid path has been introduced + if (DirectoryExists(state->dirPathText)) + { + // RL_FREE previous dirFiles (reloaded by ReadDirectoryFiles()) + FD_RELOAD_DIRPATH(state); + + strcpy(state->dirPathTextCopy, state->dirPathText); + } + else strcpy(state->dirPathText, state->dirPathTextCopy); + } + + state->dirPathEditMode = !state->dirPathEditMode; + } + + int prevTextAlignment = GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT); + int prevElementsHeight = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_LEFT); + GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 24); + + // TODO: ListViewElements should be aligned left +# if defined(USE_CUSTOM_LISTVIEW_FILEINFO) + FileInfo fileInfo; + state->filesListActive = GuiListViewFiles((Rectangle){ state->position.x + 10, state->position.y + 70, winWidth - 20, winHeight - 135 }, fileInfo, state->dirFilesCount, &state->itemFocused, &state->filesListScrollIndex, state->filesListActive); +# else + state->filesListActive = GuiListViewEx((Rectangle){ state->position.x + 10, state->position.y + 70, winWidth - 20, winHeight - 135 }, (const char**)dirFilesIcon, state->dirFilesCount, &state->itemFocused, &state->filesListScrollIndex, state->filesListActive); +# endif + GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, prevTextAlignment); + GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, prevElementsHeight); + + if ((state->filesListActive >= 0) && (state->filesListActive != state->prevFilesListActive)) + //&& (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_DPAD_A))) + { + strcpy(state->fileNameText, state->dirFiles[state->filesListActive]); + + if (DirectoryExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText))) + { + if (TextIsEqual(state->fileNameText, "..")) strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText)); + else strcpy(state->dirPathText, TextFormat("%s/%s", strcmp(state->dirPathText, "/")==0 ? "" : state->dirPathText, state->fileNameText)); + + strcpy(state->dirPathTextCopy, state->dirPathText); + + // RL_FREE previous dirFiles (reloaded by ReadDirectoryFiles()) + FD_RELOAD_DIRPATH(state); + + strcpy(state->dirPathTextCopy, state->dirPathText); + + state->filesListActive = -1; + strcpy(state->fileNameText, "\0"); + strcpy(state->fileNameTextCopy, state->fileNameText); + } + + state->prevFilesListActive = state->filesListActive; + } + + GuiLabel((Rectangle){ state->position.x + 10, state->position.y + winHeight - 60, 68, 25 }, "File name:"); + + if (GuiTextBox((Rectangle){ state->position.x + 75, state->position.y + winHeight - 60, winWidth - 200, 25 }, state->fileNameText, 128, state->fileNameEditMode)) + { + if (*state->fileNameText) + { + // Verify if a valid filename has been introduced + //if (FileExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText))) + { + // Select filename from list view + for (int i = 0; i < state->dirFilesCount; i++) + { + if (TextIsEqual(state->fileNameText, state->dirFiles[i])) + { + state->filesListActive = i; + strcpy(state->fileNameTextCopy, state->fileNameText); + break; + } + } + } +#if 0 + else + { + strcpy(state->fileNameText, state->fileNameTextCopy); + } +#endif + } + + state->fileNameEditMode = !state->fileNameEditMode; + } + + state->fileTypeActive = GuiComboBox((Rectangle){ state->position.x + 75, state->position.y + winHeight - 30, winWidth - 200, 25 }, "ecotex files", state->fileTypeActive); + GuiLabel((Rectangle){ state->position.x + 10, state->position.y + winHeight - 30, 68, 25 }, "File filter:"); + + state->SelectFilePressed = GuiButton((Rectangle){ state->position.x + winWidth - 120, state->position.y + winHeight - 60, 110, +#ifdef PLATFORM_DESKTOP + 25 +#else + 25 + 30 +#endif + }, "Select") || IsKeyPressed(KEY_ENTER); + + if (state->SelectFilePressed) state->fileDialogActive = false; + +#ifdef PLATFORM_DESKTOP + if (GuiButton((Rectangle){ state->position.x + winWidth - 120, state->position.y + winHeight - 30, 110, 25 }, "Cancel")) state->fileDialogActive = false; +#endif + + // File dialog has been closed! + if (!state->fileDialogActive) + { + // RL_FREE dirFiles memory + for (int i = 0; i < state->dirFilesCount; i++) + { + RL_FREE(state->dirFiles[i]); + RL_FREE(dirFilesIcon[i]); + } + + RL_FREE(state->dirFiles); + RL_FREE(dirFilesIcon); + + dirFilesIcon = NULL; + state->dirFiles = NULL; + } + } +} + +// Compare two files from a directory +static inline int FileCompare(const char *d1, const char *d2, const char *dir) +{ + const bool b1 = DirectoryExists(TextFormat("%s/%s", dir, d1)); + const bool b2 = DirectoryExists(TextFormat("%s/%s", dir, d2)); + + if (b1 && !b2) return -1; + if (!b1 && b2) return 1; + + if (!FileExists(TextFormat("%s/%s", dir, d1))) return 1; + if (!FileExists(TextFormat("%s/%s", dir, d2))) return -1; + + return strcmp(d1, d2); +} + +// Read all filenames from directory (supported file types) +static char **ReadDirectoryFiles(const char *dir, int *filesCount, char *filterExt) +{ + int validFilesCount = 0; + char **validFiles = (char **)RL_MALLOC(MAX_DIRECTORY_FILES*sizeof(char *)); // Max files to read + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) validFiles[i] = (char *)RL_MALLOC(MAX_DIR_PATH_LENGTH); // Max file name length + + int filterExtCount = 0; + const char **extensions = GuiTextSplit(filterExt, &filterExtCount, NULL); + bool filterExtensions = true; + + int dirFilesCount = 0; + char **files = GetDirectoryFiles(dir, &dirFilesCount); + + // Sort files and directories: dir by name + files by name + // https://en.wikibooks.org/wiki/Algorithm_Implementation/Sorting/Quicksort#C + if (dirFilesCount > 1) + { + const int MAX = 64; + unsigned int left = 0, stack[64], pos = 0, seed = rand(), len = dirFilesCount; + + for (;;) + { + for (; left + 1 < len; len++) // Sort left to len - 1 + { + if (pos == MAX) len = stack[pos = 0]; // Stack overflow, reset + char *pivot = files[left + seed%(len - left)]; // Pick random pivot + seed = seed*69069 + 1; // Next pseudo-random number + stack[pos++] = len; // Sort right part later + + for (unsigned int right = left - 1;;) // Inner loop: partitioning + { + while (FileCompare(files[++right], pivot, dir) < 0); // Look for greater element + while (FileCompare(pivot, files[--len], dir) < 0); // Look for smaller element + if (right >= len) break; // Partition point found? + char *temp = files[right]; + files[right] = files[len]; // The only swap + files[len] = temp; + } // Partitioned, continue left part + } + + if (pos == 0) break; // Stack empty? + left = len; // Left to right is sorted + len = stack[--pos]; // Get next range to sort + } + } + + if (TextIsEqual(extensions[0], "all")) filterExtensions = false; + + for (int i = 0; (i < dirFilesCount) && (validFilesCount < MAX_DIRECTORY_FILES); i++) + { + if (TextIsEqual(files[i], ".")) continue; + + if (!filterExtensions) + { + strncpy(validFiles[validFilesCount], files[i], MAX_DIR_PATH_LENGTH); + + // Only filter files by extensions, directories should be available + if (DirectoryExists(TextFormat("%s/%s", dir, files[i]))) strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 1, files[i])); + else + { + // TODO: Assign custom filetype icons depending on file extension (image, audio, text, video, models...) + + if (IsFileExtension(files[i], ".png")) strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 12, files[i])); + else strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 10, files[i])); + } + + validFilesCount++; + } + else + { + for (int j = 0; j < filterExtCount; j++) + { + // Check file type extensions supported + // NOTE: We just store valid files list + if (IsFileExtension(files[i], extensions[j])) + { + // TODO: Assign custom filetype icons depending on file extension (image, audio, text, video, models...) + + if (IsFileExtension(files[i], ".png")) strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 12, files[i])); + else strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 10, files[i])); + + validFilesCount++; + } + } + } + } + + ClearDirectoryFiles(); + + *filesCount = validFilesCount; + return validFiles; +} + +#if defined(USE_CUSTOM_LISTVIEW_FILEINFO) +// List View control for files info with extended parameters +static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int active) +{ + GuiControlState state = guiState; + int itemFocused = (focus == NULL)? -1 : *focus; + int itemSelected = active; + + // Check if we need a scroll bar + bool useScrollBar = false; + if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING))*count > bounds.height) useScrollBar = true; + + // Define base item rectangle [0] + Rectangle itemBounds = { 0 }; + itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING); + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.height = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); + + // Get items on the list + int visibleItems = bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING)); + if (visibleItems > count) visibleItems = count; + + int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; + if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; + int endIndex = startIndex + visibleItems; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check mouse inside list view + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = GUI_STATE_FOCUSED; + + // Check focused and selected item + for (int i = 0; i < visibleItems; i++) + { + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = startIndex + i; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) itemSelected = startIndex + i; + break; + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING)); + } + + if (useScrollBar) + { + int wheelMove = GetMouseWheelMove(); + startIndex -= wheelMove; + + if (startIndex < 0) startIndex = 0; + else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; + + endIndex = startIndex + visibleItems; + if (endIndex > count) endIndex = count; + } + } + else itemFocused = -1; + + // Reset item rectangle y to [0] + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + DrawRectangleRec(bounds, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + DrawRectangleLinesEx(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), guiAlpha)); + + // TODO: Draw list view header with file sections: icon+name | size | type | modTime + + // Draw visible items + for (int i = 0; i < visibleItems; i++) + { + if (state == GUI_STATE_DISABLED) + { + if ((startIndex + i) == itemSelected) + { + DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED)), guiAlpha)); + DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), guiAlpha)); + } + + // TODO: Draw full file info line: icon+name | size | type | modTime + + GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED)), guiAlpha)); + } + else + { + if ((startIndex + i) == itemSelected) + { + // Draw item selected + DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED)), guiAlpha)); + DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), guiAlpha)); + + GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED)), guiAlpha)); + } + else if ((startIndex + i) == itemFocused) + { + // Draw item focused + DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED)), guiAlpha)); + DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), guiAlpha)); + + GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED)), guiAlpha)); + } + else + { + // Draw item normal + GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL)), guiAlpha)); + } + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING)); + } + + if (useScrollBar) + { + Rectangle scrollBarBounds = { + bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Calculate percentage of visible items and apply same percentage to scrollbar + float percentVisible = (float)(endIndex - startIndex)/count; + float sliderSize = bounds.height*percentVisible; + + int prevSliderSize = GuiGetStyle(SCROLLBAR, SLIDER_WIDTH); // Save default slider size + int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed + GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, sliderSize); // Change slider size + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed + + startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); + + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default + GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, prevSliderSize); // Reset slider size to default + } + //-------------------------------------------------------------------- + + if (focus != NULL) *focus = itemFocused; + if (scrollIndex != NULL) *scrollIndex = startIndex; + + return itemSelected; +} +#endif // USE_CUSTOM_LISTVIEW_FILEINFO + +#endif // GUI_FILE_DIALOG_IMPLEMENTATION diff --git a/code/vendors/zpl.h b/code/vendors/zpl.h index aa674d9..aeb31eb 100644 --- a/code/vendors/zpl.h +++ b/code/vendors/zpl.h @@ -31,6 +31,8 @@ GitHub: https://github.com/zpl-c/zpl Version History: + 14.1.4 - Fix win32 missing CRITICAL_SECTION definition if + - ZPL_NO_WINDOWS_H is defined 14.1.0 - add hashtable map_mut method 14.0.1 - fix zpl_array_remove_at boundary bug 14.0.0 - heap memory allocator analysis @@ -361,7 +363,7 @@ License: #define ZPL_VERSION_MAJOR 14 #define ZPL_VERSION_MINOR 1 -#define ZPL_VERSION_PATCH 2 +#define ZPL_VERSION_PATCH 4 #define ZPL_VERSION_PRE "" // file: zpl_hedley.h @@ -7464,7 +7466,7 @@ License: typedef struct zpl_mutex { #if defined(ZPL_SYSTEM_WINDOWS) - CRITICAL_SECTION win32_critical_section; + zpl_u64 win32_critical_section[sizeof(zpl_usize) / 2 + 1]; #else pthread_mutex_t pthread_mutex; #endif @@ -16186,7 +16188,7 @@ License: void zpl_mutex_init(zpl_mutex *m) { # if defined(ZPL_SYSTEM_WINDOWS) - InitializeCriticalSection(&m->win32_critical_section); + InitializeCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); # else pthread_mutex_init(&m->pthread_mutex, NULL); # endif @@ -16194,7 +16196,7 @@ License: void zpl_mutex_destroy(zpl_mutex *m) { # if defined(ZPL_SYSTEM_WINDOWS) - DeleteCriticalSection(&m->win32_critical_section); + DeleteCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); # else pthread_mutex_destroy(&m->pthread_mutex); # endif @@ -16202,7 +16204,7 @@ License: void zpl_mutex_lock(zpl_mutex *m) { # if defined(ZPL_SYSTEM_WINDOWS) - EnterCriticalSection(&m->win32_critical_section); + EnterCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); # else pthread_mutex_lock(&m->pthread_mutex); # endif @@ -16210,7 +16212,7 @@ License: zpl_b32 zpl_mutex_try_lock(zpl_mutex *m) { # if defined(ZPL_SYSTEM_WINDOWS) - return TryEnterCriticalSection(&m->win32_critical_section); + return TryEnterCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); # else return pthread_mutex_trylock(&m->pthread_mutex); # endif @@ -16218,7 +16220,7 @@ License: void zpl_mutex_unlock(zpl_mutex *m) { # if defined(ZPL_SYSTEM_WINDOWS) - LeaveCriticalSection(&m->win32_critical_section); + LeaveCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); # else pthread_mutex_unlock(&m->pthread_mutex); # endif