From a503e67eaf475e60be130159507a5b8fd8d2ab5f Mon Sep 17 00:00:00 2001 From: Fabien Freling Date: Fri, 13 Oct 2023 18:16:41 +0200 Subject: [PATCH] add file dialog --- 3rd-party/gui_window_file_dialog.h | 738 +++++++++++++++++++++++++++++ 3rd-party/raygui.c | 6 + build.zig | 8 + justfile | 8 +- src/main.zig | 21 +- src/raylib.zig | 2 +- 6 files changed, 775 insertions(+), 8 deletions(-) create mode 100644 3rd-party/gui_window_file_dialog.h create mode 100644 3rd-party/raygui.c diff --git a/3rd-party/gui_window_file_dialog.h b/3rd-party/gui_window_file_dialog.h new file mode 100644 index 0000000..2168a06 --- /dev/null +++ b/3rd-party/gui_window_file_dialog.h @@ -0,0 +1,738 @@ +/******************************************************************************************* + * + * Window File Dialog v1.2 - Modal file dialog to open/save files + * + * MODULE USAGE: + * #define GUI_WINDOW_FILE_DIALOG_IMPLEMENTATION + * #include "gui_window_file_dialog.h" + * + * INIT: GuiWindowFileDialogState state = GuiInitWindowFileDialog(); + * DRAW: GuiWindowFileDialog(&state); + * + * NOTE: This module depends on some raylib file system functions: + * - LoadDirectoryFiles() + * - UnloadDirectoryFiles() + * - GetWorkingDirectory() + * - DirectoryExists() + * - FileExists() + * + * LICENSE: zlib/libpng + * + * Copyright (c) 2019-2023 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" + +#ifndef GUI_WINDOW_FILE_DIALOG_H +#define GUI_WINDOW_FILE_DIALOG_H + +// Gui file dialog context data +typedef struct { + + // Window management variables + bool windowActive; + Rectangle windowBounds; + Vector2 panOffset; + bool dragMode; + bool supportDrag; + + // UI variables + bool dirPathEditMode; + char dirPathText[1024]; + + int filesListScrollIndex; + bool filesListEditMode; + int filesListActive; + + bool fileNameEditMode; + char fileNameText[1024]; + bool SelectFilePressed; + bool CancelFilePressed; + int fileTypeActive; + int itemFocused; + + // Custom state variables + FilePathList dirFiles; + char filterExt[256]; + char dirPathTextCopy[1024]; + char fileNameTextCopy[1024]; + + int prevFilesListActive; + + bool saveFileMode; + +} GuiWindowFileDialogState; + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +GuiWindowFileDialogState InitGuiWindowFileDialog(const char *initPath); +void GuiWindowFileDialog(GuiWindowFileDialogState *state); + +#ifdef __cplusplus +} +#endif + +#endif // GUI_WINDOW_FILE_DIALOG_H + +/*********************************************************************************** + * + * GUI_WINDOW_FILE_DIALOG IMPLEMENTATION + * + ************************************************************************************/ +#if defined(GUI_WINDOW_FILE_DIALOG_IMPLEMENTATION) + +#include "raygui.h" + +#include // Required for: strcpy() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define MAX_DIRECTORY_FILES 2048 +#define MAX_ICON_PATH_LENGTH 512 +#ifdef _WIN32 +#define PATH_SEPERATOR "\\" +#else +#define PATH_SEPERATOR "/" +#endif + +//---------------------------------------------------------------------------------- +// 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; // Files are just a path string +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +FileInfo *dirFilesIcon = NULL; // Path string + icon (for fancy drawing) + +//---------------------------------------------------------------------------------- +// Internal Module Functions Definition +//---------------------------------------------------------------------------------- +// Read files in new path +static void ReloadDirectoryFiles(GuiWindowFileDialogState *state); + +#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 +//---------------------------------------------------------------------------------- +GuiWindowFileDialogState InitGuiWindowFileDialog(const char *initPath) { + GuiWindowFileDialogState state = {0}; + + // Init window data + state.windowBounds = (Rectangle){GetScreenWidth() / 2 - 440 / 2, + GetScreenHeight() / 2 - 310 / 2, 440, 310}; + state.windowActive = false; + state.supportDrag = true; + state.dragMode = false; + state.panOffset = (Vector2){0, 0}; + + // Init path data + 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()); + + // TODO: Why we keep a copy? + strcpy(state.dirPathTextCopy, state.dirPathText); + strcpy(state.fileNameTextCopy, state.fileNameText); + + state.filterExt[0] = '\0'; + // strcpy(state.filterExt, "all"); + + state.dirFiles.count = 0; + + return state; +} + +// Update and draw file dialog +void GuiWindowFileDialog(GuiWindowFileDialogState *state) { + if (state->windowActive) { + // Update window dragging + //---------------------------------------------------------------------------------------- + if (state->supportDrag) { + Vector2 mousePosition = GetMousePosition(); + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + // Window can be dragged from the top window bar + if (CheckCollisionPointRec( + mousePosition, + (Rectangle){state->windowBounds.x, state->windowBounds.y, + (float)state->windowBounds.width, + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT})) { + state->dragMode = true; + state->panOffset.x = mousePosition.x - state->windowBounds.x; + state->panOffset.y = mousePosition.y - state->windowBounds.y; + } + } + + if (state->dragMode) { + state->windowBounds.x = (mousePosition.x - state->panOffset.x); + state->windowBounds.y = (mousePosition.y - state->panOffset.y); + + // Check screen limits to avoid moving out of screen + if (state->windowBounds.x < 0) + state->windowBounds.x = 0; + else if (state->windowBounds.x > + (GetScreenWidth() - state->windowBounds.width)) + state->windowBounds.x = GetScreenWidth() - state->windowBounds.width; + + if (state->windowBounds.y < 0) + state->windowBounds.y = 0; + else if (state->windowBounds.y > + (GetScreenHeight() - state->windowBounds.height)) + state->windowBounds.y = + GetScreenHeight() - state->windowBounds.height; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + state->dragMode = false; + } + } + //---------------------------------------------------------------------------------------- + + // Load dirFilesIcon and state->dirFiles lazily on windows open + // NOTE: They are automatically unloaded at fileDialog closing + //---------------------------------------------------------------------------------------- + if (dirFilesIcon == NULL) { + dirFilesIcon = (FileInfo *)RL_CALLOC( + MAX_DIRECTORY_FILES, sizeof(FileInfo)); // Max files to read + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) + dirFilesIcon[i] = + (char *)RL_CALLOC(MAX_ICON_PATH_LENGTH, 1); // Max file name length + } + + // Load current directory files + if (state->dirFiles.paths == NULL) + ReloadDirectoryFiles(state); + //---------------------------------------------------------------------------------------- + + // Draw window and controls + //---------------------------------------------------------------------------------------- + state->windowActive = + !GuiWindowBox(state->windowBounds, "#198# Select File Dialog"); + + // Draw previous directory button + logic + if (GuiButton( + (Rectangle){state->windowBounds.x + state->windowBounds.width - 48, + state->windowBounds.y + 24 + 12, 40, 24}, + "< ..")) { + // Move dir path one level up + strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText)); + + // Reload directory files (frees previous list) + ReloadDirectoryFiles(state); + + state->filesListActive = -1; + memset(state->fileNameText, 0, 1024); + memset(state->fileNameTextCopy, 0, 1024); + } + + // Draw current directory text box info + path editing logic + if (GuiTextBox((Rectangle){state->windowBounds.x + 8, + state->windowBounds.y + 24 + 12, + state->windowBounds.width - 48 - 16, 24}, + state->dirPathText, 1024, state->dirPathEditMode)) { + if (state->dirPathEditMode) { + // Verify if a valid path has been introduced + if (DirectoryExists(state->dirPathText)) { + // Reload directory files (frees previous list) + ReloadDirectoryFiles(state); + + strcpy(state->dirPathTextCopy, state->dirPathText); + } else + strcpy(state->dirPathText, state->dirPathTextCopy); + } + + state->dirPathEditMode = !state->dirPathEditMode; + } + + // List view elements are aligned left + int prevTextAlignment = GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT); + int prevElementsHeight = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 24); +#if defined(USE_CUSTOM_LISTVIEW_FILEINFO) + state->filesListActive = GuiListViewFiles( + (Rectangle){state->position.x + 8, state->position.y + 48 + 20, + state->windowBounds.width - 16, + state->windowBounds.height - 60 - 16 - 68}, + fileInfo, state->dirFiles.count, &state->itemFocused, + &state->filesListScrollIndex, state->filesListActive); +#else + GuiListViewEx((Rectangle){state->windowBounds.x + 8, + state->windowBounds.y + 48 + 20, + state->windowBounds.width - 16, + state->windowBounds.height - 60 - 16 - 68}, + (const char **)dirFilesIcon, state->dirFiles.count, + &state->filesListScrollIndex, &state->filesListActive, + &state->itemFocused); +#endif + GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, prevTextAlignment); + GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, prevElementsHeight); + + // Check if a path has been selected, if it is a directory, move to that + // directory (and reload paths) + if ((state->filesListActive >= 0) && + (state->filesListActive != state->prevFilesListActive)) + //&& (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsKeyPressed(KEY_ENTER) || + // IsKeyPressed(KEY_DPAD_A))) + { + strcpy(state->fileNameText, + GetFileName(state->dirFiles.paths[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); + + // Reload directory files (frees previous list) + ReloadDirectoryFiles(state); + + strcpy(state->dirPathTextCopy, state->dirPathText); + + state->filesListActive = -1; + strcpy(state->fileNameText, "\0"); + strcpy(state->fileNameTextCopy, state->fileNameText); + } + + state->prevFilesListActive = state->filesListActive; + } + + // Draw bottom controls + //-------------------------------------------------------------------------------------- + GuiLabel( + (Rectangle){state->windowBounds.x + 8, + state->windowBounds.y + state->windowBounds.height - 68, 60, + 24}, + "File name:"); + if (GuiTextBox( + (Rectangle){state->windowBounds.x + 72, + state->windowBounds.y + state->windowBounds.height - 68, + state->windowBounds.width - 184, 24}, + 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->dirFiles.count; i++) { + if (TextIsEqual(state->fileNameText, state->dirFiles.paths[i])) { + state->filesListActive = i; + strcpy(state->fileNameTextCopy, state->fileNameText); + break; + } + } + } else if (!state->saveFileMode) { + strcpy(state->fileNameText, state->fileNameTextCopy); + } + } + + state->fileNameEditMode = !state->fileNameEditMode; + } + + GuiLabel((Rectangle){state->windowBounds.x + 8, + state->windowBounds.y + state->windowBounds.height - + 24 - 12, + 68, 24}, + "File filter:"); + GuiComboBox((Rectangle){state->windowBounds.x + 72, + state->windowBounds.y + state->windowBounds.height - + 24 - 12, + state->windowBounds.width - 184, 24}, + "All files", &state->fileTypeActive); + + state->SelectFilePressed = GuiButton( + (Rectangle){state->windowBounds.x + state->windowBounds.width - 96 - 8, + state->windowBounds.y + state->windowBounds.height - 68, 96, + 24}, + "Select"); + + if (GuiButton( + (Rectangle){ + state->windowBounds.x + state->windowBounds.width - 96 - 8, + state->windowBounds.y + state->windowBounds.height - 24 - 12, + 96, 24}, + "Cancel")) + state->windowActive = false; + //-------------------------------------------------------------------------------------- + + // Exit on file selected + if (state->SelectFilePressed) + state->windowActive = false; + + // File dialog has been closed, free all memory before exit + if (!state->windowActive) { + // Free dirFilesIcon memory + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) + RL_FREE(dirFilesIcon[i]); + + RL_FREE(dirFilesIcon); + dirFilesIcon = NULL; + + // Unload directory file paths + UnloadDirectoryFiles(state->dirFiles); + + // Reset state variables + state->dirFiles.count = 0; + state->dirFiles.capacity = 0; + state->dirFiles.paths = 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 files in new path +static void ReloadDirectoryFiles(GuiWindowFileDialogState *state) { + UnloadDirectoryFiles(state->dirFiles); + + state->dirFiles = LoadDirectoryFilesEx( + state->dirPathText, + (state->filterExt[0] == '\0') ? NULL : state->filterExt, false); + state->itemFocused = 0; + + // Reset dirFilesIcon memory + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) + memset(dirFilesIcon[i], 0, MAX_ICON_PATH_LENGTH); + + // Copy paths as icon + fileNames into dirFilesIcon + for (int i = 0; i < state->dirFiles.count; i++) { + if (IsPathFile(state->dirFiles.paths[i])) { + // Path is a file, a file icon for convenience (for some recognized + // extensions) + if (IsFileExtension(state->dirFiles.paths[i], + ".png;.bmp;.tga;.gif;.jpg;.jpeg;.psd;.hdr;.qoi;.dds;." + "pkm;.ktx;.pvr;.astc")) { + strcpy(dirFilesIcon[i], + TextFormat("#12#%s", GetFileName(state->dirFiles.paths[i]))); + } else if (IsFileExtension( + state->dirFiles.paths[i], + ".wav;.mp3;.ogg;.flac;.xm;.mod;.it;.wma;.aiff")) { + strcpy(dirFilesIcon[i], + TextFormat("#11#%s", GetFileName(state->dirFiles.paths[i]))); + } else if (IsFileExtension(state->dirFiles.paths[i], + ".txt;.info;.md;.nfo;.xml;.json;.c;.cpp;.cs;." + "lua;.py;.glsl;.vs;.fs")) { + strcpy(dirFilesIcon[i], + TextFormat("#10#%s", GetFileName(state->dirFiles.paths[i]))); + } else if (IsFileExtension(state->dirFiles.paths[i], + ".exe;.bin;.raw;.msi")) { + strcpy(dirFilesIcon[i], + TextFormat("#200#%s", GetFileName(state->dirFiles.paths[i]))); + } else + strcpy(dirFilesIcon[i], + TextFormat("#218#%s", GetFileName(state->dirFiles.paths[i]))); + } else { + // Path is a directory, add a directory icon + strcpy(dirFilesIcon[i], + TextFormat("#1#%s", GetFileName(state->dirFiles.paths[i]))); + } + } +} + +#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) { + int result = 0; + GuiState 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; + + *active = itemSelected; + return result; +} +#endif // USE_CUSTOM_LISTVIEW_FILEINFO + +#endif // GUI_FILE_DIALOG_IMPLEMENTATION diff --git a/3rd-party/raygui.c b/3rd-party/raygui.c new file mode 100644 index 0000000..f067a73 --- /dev/null +++ b/3rd-party/raygui.c @@ -0,0 +1,6 @@ +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" +#undef RAYGUI_IMPLEMENTATION + +#define GUI_WINDOW_FILE_DIALOG_IMPLEMENTATION +#include "gui_window_file_dialog.h" diff --git a/build.zig b/build.zig index 370c4aa..8b93dc8 100644 --- a/build.zig +++ b/build.zig @@ -21,6 +21,14 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); exe.linkLibrary(raylib_dep.artifact("raylib")); + + const cflags = [_][]const u8{}; + exe.addCSourceFile(.{ + .file = .{ + .path = "3rd-party/raygui.c", + }, + .flags = &cflags, + }); exe.addIncludePath(.{ .path = "3rd-party/" }); exe.linkSystemLibrary("sqlite3"); diff --git a/justfile b/justfile index 18c1357..633dfd2 100644 --- a/justfile +++ b/justfile @@ -7,8 +7,8 @@ build: zig build web-build: - # zig build-lib src/main.zig -target wasm32-freestanding -dynamic -rdynamic - zig build -Dtarget=wasm32-emscripten --sysroot /nix/store/zll7a6ns3avx509kgx0jjghz8g89l2g0-emscripten-3.1.45 + # zig build-lib src/main.zig -target wasm32-freestanding -dynamic -rdynamic + zig build -Dtarget=wasm32-emscripten --sysroot /nix/store/zll7a6ns3avx509kgx0jjghz8g89l2g0-emscripten-3.1.45 run: nixGL zig build run @@ -21,4 +21,6 @@ format: fd -e nix -X nix fmt {} update-deps: - curl https://raw.githubusercontent.com/raysan5/raygui/master/src/raygui.h --output 3rd-party/raygui.h + # We cannot easily add header-only C library with package manager + curl https://raw.githubusercontent.com/raysan5/raygui/master/src/raygui.h --remote-name --output-dir 3rd-party/ + curl https://raw.githubusercontent.com/raysan5/raygui/master/examples/custom_file_dialog/gui_window_file_dialog.h --remote-name --output-dir 3rd-party/ diff --git a/src/main.zig b/src/main.zig index 253a226..732e699 100644 --- a/src/main.zig +++ b/src/main.zig @@ -7,15 +7,28 @@ const sqlite = @cImport({ pub fn main() !void { const screen_width = 800; const screen_height = 450; - raylib.InitWindow(screen_width, screen_height, "raylib [core] example - basic window"); + raylib.InitWindow(screen_width, screen_height, "FabApp"); raylib.SetTargetFPS(60); + + var file_dialog_state = raylib.InitGuiWindowFileDialog(raylib.GetWorkingDirectory()); + while (!raylib.WindowShouldClose()) { raylib.BeginDrawing(); defer raylib.EndDrawing(); - raylib.ClearBackground(raylib.RAYWHITE); - raylib.DrawText("Congrats! You created your first window!", 190, 200, 20, raylib.LIGHTGRAY); - if (raylib.GuiButton(.{ .x = 0, .y = 0, .width = 100, .height = 100 }, "MyButton") == 1) {} + + { + if (file_dialog_state.windowActive) raylib.GuiLock(); + defer raylib.GuiUnlock(); + + const button_size = 200; + if (raylib.GuiButton(.{ .x = (screen_width - button_size) / 2, .y = (screen_height - button_size) / 2, .width = 200, .height = 200 }, "Load db file") == 1) { + file_dialog_state.windowActive = true; + } + } + + raylib.GuiWindowFileDialog(&file_dialog_state); + // // if (GuiButton((Rectangle){ 20, 20, 140, 30 }, GuiIconText(ICON_FILE_OPEN, "Open Image"))) fileDialogState.windowActive = true; } raylib.CloseWindow(); } diff --git a/src/raylib.zig b/src/raylib.zig index e58a659..50fac2b 100644 --- a/src/raylib.zig +++ b/src/raylib.zig @@ -1,5 +1,5 @@ pub usingnamespace @cImport({ @cInclude("raylib.h"); - @cDefine("RAYGUI_IMPLEMENTATION", "1"); @cInclude("raygui.h"); + @cInclude("gui_window_file_dialog.h"); });