# HG changeset patch # User June Park # Date 1767790337 28800 # Node ID 7bd795bac99731f17d21dc47d83382914722b634 # Parent 96db6c3f38d60a567af022233fd728b22e75759c [Postdog] Added scrollable area to inputs and history files, buttons to delete and view. diff -r 96db6c3f38d6 -r 7bd795bac997 .hgignore --- a/.hgignore Tue Jan 06 08:19:07 2026 -0800 +++ b/.hgignore Wed Jan 07 04:52:17 2026 -0800 @@ -21,6 +21,9 @@ # Playground playground/ +# Postdog +postdog/history + # Node **/node_modules/** diff -r 96db6c3f38d6 -r 7bd795bac997 mrjunejune/src/public/epi_all_colors.png Binary file mrjunejune/src/public/epi_all_colors.png has changed diff -r 96db6c3f38d6 -r 7bd795bac997 postdog/BUILD --- a/postdog/BUILD Tue Jan 06 08:19:07 2026 -0800 +++ b/postdog/BUILD Wed Jan 07 04:52:17 2026 -0800 @@ -2,6 +2,18 @@ load("//gui_ze:gui_ze.bzl", "macos_app_and_dmg") raylib_binary( + name = "slider_example", + srcs = [ + "slider_example.c", + "gui_window_file_dialog.h", + ], + deps = [ + "//third_party/raylib:raylib", + "//dowa:dowa", + ], +) + +raylib_binary( name = "postdog", srcs = [ "main.c", @@ -42,5 +54,6 @@ srcs = glob([ "**/*.ttf", "**/*.txt", + "**/*.png", ], allow_empty=True) ) diff -r 96db6c3f38d6 -r 7bd795bac997 postdog/epi_all_colors.png Binary file postdog/epi_all_colors.png has changed diff -r 96db6c3f38d6 -r 7bd795bac997 postdog/gui_window_file_dialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/postdog/gui_window_file_dialog.h Wed Jan 07 04:52:17 2026 -0800 @@ -0,0 +1,624 @@ +/******************************************************************************************* +* +* 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-2024 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 "third_party/raylib/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 "third_party/raylib/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 (unsigned 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 (unsigned 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 -r 96db6c3f38d6 -r 7bd795bac997 postdog/main.c --- a/postdog/main.c Tue Jan 06 08:19:07 2026 -0800 +++ b/postdog/main.c Wed Jan 07 04:52:17 2026 -0800 @@ -16,19 +16,13 @@ #define SCREEN_WIDTH 1280 #define SCREEN_HEIGHT 780 -#define TEXT_SIZE 10 - -#define JSON_INPUT_BUFFER_LEN 8192 -#define HEADER_INPUT_BUFFER_LEN 4096 -#define PARAM_INPUT_BUFFER_LEN 4096 -#define MAX_HISTORY_ITEMS 100 +#define MAX_SCROLL_HEIGHT 10000 #define HEADER_BUFFER_LENGTH 1024 * 4 #define DEFAULT_TEXT_BUFFER_LENGTH 1024 * 4 -#define URL_TEXT_BUFFER 1024 * 10 -#define BODY_BUFFER_LENGTH 1024 * 1025 * 5 -#define RESULT_BUFFER_LENGTH 1024 * 1025 * 5 -#define AREANA_BUFFER_LENGTH 1024 * 1025 * 15 +#define URL_TEXT_BUFFER_LENGTH 1024 * 10 +#define BODY_BUFFER_LENGTH 1024 * 1024 * 5 +#define RESULT_BUFFER_LENGTH 1024 * 1024 * 5 #ifdef _WIN32 @@ -171,6 +165,13 @@ return 0; } +char *PostDog_Construct_URL(char *filename) +{ + char full_file_path[512] = {0}; + snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename); + return &full_file_path; +} + void PostDog_History_CreateFile(char *filename, char* values) { char full_file_path[512] = {0}; @@ -263,7 +264,7 @@ const char *method, const char *headers, const char *body, - char *response, size_t responseSize) + char *response, size_t response_size) { CURL *curl; CURLcode res; @@ -273,7 +274,7 @@ if (buffer.data == NULL) { - snprintf(response, responseSize, "Error: Failed to allocate memory"); + snprintf(response, response_size, "Error: Failed to allocate memory"); return -1; } buffer.data[0] = '\0'; @@ -335,22 +336,25 @@ res = curl_easy_perform(curl); if (res != CURLE_OK) - snprintf(response, responseSize, "Error: %s\n", curl_easy_strerror(res)); + snprintf(response, response_size, "Error: %s\n", curl_easy_strerror(res)); else { long response_code; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); - snprintf(response, responseSize, "HTTP Status: %ld\n\n%s", + if (buffer.size > response_size) + printf("TODO: Realloc\n"); + + snprintf(response, response_size, "HTTP Status: %ld\n\n%s", response_code, buffer.data ? buffer.data : ""); } // Cleanup if (headerList) curl_slist_free_all(headerList); curl_easy_cleanup(curl); - } else { - snprintf(response, responseSize, "Error: Failed to initialize curl"); } + else + snprintf(response, response_size, "Error: Failed to initialize curl"); free(buffer.data); curl_global_cleanup(); @@ -461,8 +465,10 @@ Dowa_Arena *split_arena = Dowa_Arena_Create(file_size * 2); char *file_buffer = Dowa_Arena_Allocate(init_arena, file_size+1); fread(file_buffer, 1, file_size, file); + fclose(file); + char **values = Dowa_String_Split(file_buffer, "---\n", file_size, 4, split_arena); - Dowa_Arena_Free(init_arena); + for (int i = 0; i < Dowa_Array_Length(values); i++) { if (i == 0) @@ -487,6 +493,9 @@ else snprintf(url_result_text, strlen(values[i]) + 1, "%s", values[i]); } + + Dowa_Arena_Free(init_arena); + Dowa_Arena_Free(split_arena); } Rectangle AddPadding(Rectangle rect, float padding) @@ -499,6 +508,26 @@ }; } +Rectangle AddPaddingHorizontal(Rectangle rect, float padding) +{ + return (Rectangle){ + rect.x + padding, + rect.y, + rect.width - (2 * padding), + rect.height + }; +} + +Rectangle AddPaddingVertical(Rectangle rect, float padding) +{ + return (Rectangle){ + rect.x, + rect.y + padding, + rect.width, + rect.height - (2 * padding) + }; +} + // Layout helper functions Rectangle RightOf(Rectangle ref, float padding) { @@ -557,20 +586,20 @@ SetWindowState(FLAG_WINDOW_RESIZABLE); SetTargetFPS(60); - Dowa_Arena *arena = Dowa_Arena_Create(AREANA_BUFFER_LENGTH); - Font customFont = LoadFontEx("postdog/Roboto-Regular.ttf", 20, 0, 0); GuiSetFont(customFont); - GuiSetStyle(DEFAULT, TEXT_SIZE, 20); + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); + Image logo_original = LoadImage("postdog/epi_all_colors.png"); + ImageResize(&logo_original, 60, 60); + Texture2D logo_texture = LoadTextureFromImage(logo_original); + UnloadImage(logo_original); // -- Starting pos ---// - // Everyhting is relative to sidebar at this point lol - float side_bar_x = 10; - float side_bar_y = 10; - Rectangle history_sidebar = { .x = side_bar_x, .y = side_bar_y, .width = 0, .height = 0 }; + Rectangle history_sidebar = { 0 }; Dowa_Array_Reserve(history_items, 10); Dowa_Array_Reserve(new_history_items, 10); PostDog_History_Load(&history_items); + int32 *history_deleted_items = NULL; Rectangle url_area = { 0 }; Rectangle textBounds = { 0 }; @@ -581,19 +610,19 @@ Rectangle method_dropdown = { 0 }; - char *url_input_text = (char *)Dowa_Arena_Allocate(arena, URL_TEXT_BUFFER); - snprintf(url_input_text, URL_TEXT_BUFFER, "https://httpbin.org/get"); + char *url_input_text = (char *)malloc(sizeof(char) * URL_TEXT_BUFFER_LENGTH); + snprintf(url_input_text, URL_TEXT_BUFFER_LENGTH, "https://httpbin.org/get"); char **url_body_map = NULL; - Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, HEADER_BUFFER_LENGTH), arena); - Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, BODY_BUFFER_LENGTH), arena); - Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena); - Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena); + Dowa_Array_Push(url_body_map, (char *)malloc(sizeof(char) * HEADER_BUFFER_LENGTH)); + Dowa_Array_Push(url_body_map, (char *)malloc(sizeof(char) * BODY_BUFFER_LENGTH)); + Dowa_Array_Push(url_body_map, (char *)malloc(sizeof(char) * DEFAULT_TEXT_BUFFER_LENGTH)); + Dowa_Array_Push(url_body_map, (char *)malloc(sizeof(char) * DEFAULT_TEXT_BUFFER_LENGTH)); snprintf(url_body_map[TAB_HEADER], HEADER_BUFFER_LENGTH, "Content-Type: application/json"); snprintf(url_body_map[TAB_BODY], HEADER_BUFFER_LENGTH, ""); - char *url_result_text = (char *)Dowa_Arena_Allocate(arena, RESULT_BUFFER_LENGTH); + char *url_result_text = (char *)malloc(sizeof(char) * RESULT_BUFFER_LENGTH); int active_method_dropdown = 0; bool method_edit = false; @@ -614,39 +643,73 @@ // General styling. float padding = 10; // TODO make it % based? int active_input_tab = 0; + int prev_input_tab = 0; + + // Scroll offsets + float history_scroll_offset = 0; + float input_body_scroll_offset = 0; + float result_body_scroll_offset = 0; while (!WindowShouldClose()) { int screen_width = GetScreenWidth(); int screen_height = GetScreenHeight(); - // Define main screen container + if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_EQUAL)) + GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) + 1); + + if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_MINUS)) + GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) - 1); + Rectangle screen = { 0, 0, screen_width, screen_height }; - // Layout: Sidebar (left 15%) and Content Area (right 85%) + // -- Side bar --// history_sidebar = LeftColumn(screen, 0.15, padding); Rectangle content_area = RightColumn(screen, history_sidebar, padding); + Rectangle logo_area = (Rectangle){ + .x = history_sidebar.x, + .y = history_sidebar.y, + .width = history_sidebar.width, + .height = 80 + }; - // History items inside sidebar + Rectangle history_list_area = Below(logo_area, padding); + history_list_area.width = history_sidebar.width; + history_list_area.height = history_sidebar.height - logo_area.height - padding; + int32 new_history_items_length = Dowa_Array_Length(new_history_items); int32 history_item_length = Dowa_Array_Length(history_items); int32 total = new_history_items_length + history_item_length; - float item_height = history_sidebar.height * 0.05; + float item_height = history_list_area.height * 0.10; + int32 number_of_skipped_items = 0; for (int i = 0; i < total; i++) { + boolean skip = FALSE; + for (int32 j = 0; j < Dowa_Array_Length(history_deleted_items); j++) + { + if (i == j) + { + skip = TRUE; + number_of_skipped_items++; + break; + } + } + if (skip) + continue; + HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length]; curr_history_items->rect = (Rectangle){ - .x = history_sidebar.x, - .y = history_sidebar.y + (padding + item_height) * i, - .width = history_sidebar.width, + .x = history_list_area.x, + .y = history_list_area.y + (padding + item_height) * (i - number_of_skipped_items) + history_scroll_offset, + .width = history_list_area.width, .height = item_height }; } - // Content area: split into URL bar (top 10%) and body (bottom 90%) + // --- URL --- // url_area = (Rectangle){ .x = content_area.x, .y = content_area.y, @@ -654,7 +717,6 @@ .height = content_area.height * 0.1 }; - // URL bar elements laid out horizontally float url_control_y = url_area.y + (url_area.height - TEXT_SIZE * 2) / 2; url_text_bounds = (Rectangle){ @@ -676,7 +738,7 @@ method_dropdown.width = url_area.width * 0.1; method_dropdown.height = TEXT_SIZE * 2; - // Body area: split into input (left 50%) and result (right 50%) + // -- Body -- // Rectangle body_area = Below(url_area, 0); body_area.height = content_area.height - url_area.height; @@ -684,7 +746,6 @@ result_area = RightOf(input_area, 0); result_area.width = body_area.width - input_area.width; - // Input area: tabs at top (10%) and text box below (90%) input_tab = (Rectangle){ .x = input_area.x + padding, .y = input_area.y + padding, @@ -699,7 +760,7 @@ input_body.width = input_tab.width; input_body.height = input_area.height - input_tab.height - (2 * padding); - // Result area: aligned with input tabs + // -- Result -- / result_body = (Rectangle){ .x = result_area.x + padding, .y = input_body.y, @@ -708,16 +769,122 @@ }; Vector2 mouse_position = GetMousePosition(); + float mouse_wheel = GetMouseWheelMove(); + + // Reset input body scroll when tab changes + if (prev_input_tab != active_input_tab) { + input_body_scroll_offset = 0; + prev_input_tab = active_input_tab; + } + + // Handle scroll wheel for history + if (InArea(mouse_position, history_list_area) && mouse_wheel != 0) { + history_scroll_offset += mouse_wheel * 30; // 30 pixels per wheel tick + // Clamp scroll offset + float max_scroll = (total * (item_height + padding)) - history_list_area.height; + if (history_scroll_offset > 0) history_scroll_offset = 0; + if (history_scroll_offset < -max_scroll && max_scroll > 0) history_scroll_offset = -max_scroll; + } + + // Handle scroll wheel for input body + if (InArea(mouse_position, input_body) && mouse_wheel != 0) { + input_body_scroll_offset += mouse_wheel * 30; + if (input_body_scroll_offset > 0) input_body_scroll_offset = 0; + } + + // Handle scroll wheel for result body + if (InArea(mouse_position, result_body) && mouse_wheel != 0) { + result_body_scroll_offset += mouse_wheel * 30; + if (result_body_scroll_offset > 0) result_body_scroll_offset = 0; + } BeginDrawing(); ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); DrawRectangleRec(history_sidebar, Fade(GRAY, 0.1f)); - for (int i = 0; i < total; i++) + + // DrawRectangleRec(logo_area, Fade(BLUE, 0.2f)); + Rectangle logo_image_rect = AddPadding(logo_area, padding); + // Fit logo to area while maintaining aspect ratio + float logo_size = logo_image_rect.height < logo_image_rect.width ? logo_image_rect.height : logo_image_rect.width; + Rectangle dest = { + .x = logo_image_rect.x + (logo_image_rect.width - logo_size) / 2, // Center horizontally + .y = logo_image_rect.y, + .width = logo_size, + .height = logo_size + }; + + Rectangle source = { 0, 0, logo_texture.width, logo_texture.height }; + DrawTexturePro(logo_texture, source, dest, (Vector2){0, 0}, 0.0f, WHITE); + + // Draw history items with scissor mode for clipping + BeginScissorMode(history_list_area.x, history_list_area.y, history_list_area.width, history_list_area.height); + for (int i = 0; i < total; i++) + { + boolean skip = FALSE; + for (int32 j = 0; j < Dowa_Array_Length(history_deleted_items); j++) + { + if (i == j) + { + skip = TRUE; + break; + } + } + if (skip) + continue; + HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length]; + + float diff = curr_history_items->rect.height*0.3; + // DrawRectangleRec(curr_history_items->rect, Fade(RED, 0.1f)); + Rectangle filename_area = curr_history_items->rect; + + filename_area.height -= diff; + Rectangle icon_area = Below(filename_area, 0); + icon_area.height = diff; + + DrawRectangleRec(filename_area, Fade(BLUE, 0.1f)); + DrawRectangleRec(icon_area, Fade(YELLOW, 0.1f)); + + Rectangle icon_area_left_column = LeftColumn(icon_area, 0.5, 0); + Rectangle icon_area_right_column = RightColumn(icon_area, icon_area_left_column, 0); + + GuiDrawText(curr_history_items->filename, AddPadding(filename_area, padding), TEXT_ALIGN_CENTER, RED); + if (GuiButton(AddPaddingHorizontal(icon_area_left_column, padding), "view")) + PostDog_Load_File( + curr_history_items->filename, + &url_input_text, + &active_method_dropdown, + url_body_map, + &url_result_text + ); + if (GuiButton(AddPaddingHorizontal(icon_area_right_column,padding), "delete")) + { + if (!remove(PostDog_Construct_URL(curr_history_items->filename))) + Dowa_Array_Push(history_deleted_items, i); + else + { + fprintf(stderr, "Wasn't able to delete file: %s \n", curr_history_items->filename); + } + } + } + EndScissorMode(); + + // Draw scroll indicator for history + if (total > 0) { - HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length]; - DrawRectangleRec(curr_history_items->rect, Fade(RED, 0.1f)); - GuiDrawText(curr_history_items->filename, AddPadding(curr_history_items->rect, padding), TEXT_ALIGN_CENTER, RED); + float content_height = total * (item_height + padding); + if (content_height > history_list_area.height) + { + float scrollbar_height = (history_list_area.height / content_height) * history_list_area.height; + float scrollbar_y = history_list_area.y - (history_scroll_offset / content_height) * history_list_area.height; + Rectangle scrollbar = { + history_list_area.x + history_list_area.width - 5, + scrollbar_y, + 5, + scrollbar_height + }; + DrawRectangleRec(scrollbar, Fade(WHITE, 0.5f)); + } } // URL area Rect @@ -743,8 +910,31 @@ DrawRectangleRec(input_area, Fade(BLUE, 0.1f)); DrawRectangleRec(input_tab, Fade(DARKBLUE, 0.1f)); GuiSetStyle(TOGGLE, GROUP_PADDING, 0); - if (JUNE_GuiTextBox(input_body, url_body_map[active_input_tab], DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool)) - input_body_bool = !input_body_bool; + GuiDrawRectangle(input_body, 1, GetColor(GuiGetStyle(TEXTBOX, BORDER)), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); + + // Create scrollable input body with offset + BeginScissorMode(input_body.x, input_body.y, input_body.width, input_body.height); + Rectangle scrolled_input = AddPadding(input_body, padding * 2); + scrolled_input.y += input_body_scroll_offset; + scrolled_input.height = MAX_SCROLL_HEIGHT; + if (JUNE_GuiTextBox(scrolled_input, url_body_map[active_input_tab], DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool)) + input_body_bool = !input_body_bool; + EndScissorMode(); + + // Draw scroll indicator for input body + if (input_body_scroll_offset < 0) { + float scrollbar_height = 50; // Fixed size indicator + float max_scroll = 1000; // Estimated max scroll + float scrollbar_y = input_body.y - (input_body_scroll_offset / max_scroll) * (input_body.height - scrollbar_height); + Rectangle scrollbar = { + input_body.x + input_body.width - 5, + scrollbar_y, + 5, + scrollbar_height + }; + DrawRectangleRec(scrollbar, Fade(BLUE, 0.5f)); + } + GuiToggleGroup(input_tab_item, "Header;Body;Get Param;Bar", &active_input_tab); PostDog_Update_URL(&url_input_text, url_body_map[TAB_GET_PARAMS]); @@ -752,7 +942,28 @@ // Result Rect DrawRectangleRec(result_area, Fade(GREEN, 0.1f)); DrawRectangleRec(result_body, Fade(DARKGREEN, 0.1f)); - GuiTextBoxMulti(result_body, url_result_text, RESULT_BUFFER_LENGTH, false); + + // Create scrollable result body with offset + BeginScissorMode(result_body.x, result_body.y, result_body.width, result_body.height); + Rectangle scrolled_result = result_body; + scrolled_result.y += result_body_scroll_offset; + scrolled_result.height = MAX_SCROLL_HEIGHT; + GuiTextBoxMulti(scrolled_result, url_result_text, RESULT_BUFFER_LENGTH, false); + EndScissorMode(); + + // Draw scroll indicator for result body + if (result_body_scroll_offset < 0) { + float scrollbar_height = 50; // Fixed size indicator + float max_scroll = 1000; // Estimated max scroll + float scrollbar_y = result_body.y - (result_body_scroll_offset / max_scroll) * (result_body.height - scrollbar_height); + Rectangle scrollbar = { + result_body.x + result_body.width - 5, + scrollbar_y, + 5, + scrollbar_height + }; + DrawRectangleRec(scrollbar, Fade(GREEN, 0.5f)); + } if (url_input_edit && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C)) { @@ -768,18 +979,6 @@ if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C)) SetClipboardText(url_result_text); } - - for (int i = 0; i < Dowa_Array_Length(history_items); i++) - { - if (Clicked(mouse_position, history_items[i].rect)) - PostDog_Load_File( - history_items[i].filename, - &url_input_text, - &active_method_dropdown, - url_body_map, - &url_result_text - ); - } EndDrawing(); } CloseWindow(); diff -r 96db6c3f38d6 -r 7bd795bac997 postdog/slider_example.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/postdog/slider_example.c Wed Jan 07 04:52:17 2026 -0800 @@ -0,0 +1,115 @@ + +/******************************************************************************************* +* +* raygui - custom file dialog to load image +* +* DEPENDENCIES: +* raylib 4.0 - Windowing/input management and drawing. +* raygui 3.0 - Immediate-mode GUI controls. +* +* COMPILATION (Windows - MinGW): +* gcc -o $(NAME_PART).exe $(FILE_NAME) -I../../src -lraylib -lopengl32 -lgdi32 -std=c99 +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2016-2024 Ramon Santamaria (@raysan5) +* +**********************************************************************************************/ + +#include "third_party/raylib/include/raylib.h" + +#define RAYGUI_IMPLEMENTATION +#include "third_party/raylib/include/raygui.h" + +#undef RAYGUI_IMPLEMENTATION // Avoid including raygui implementation again + +#define GUI_WINDOW_FILE_DIALOG_IMPLEMENTATION +#include "postdog/gui_window_file_dialog.h" +#define POSTDOG_PATHS "/Users/mrjunejune/zenbu/postdog/history" + + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main() +{ + // Initialization + //--------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 560; + + InitWindow(screenWidth, screenHeight, "raygui - custom modal dialog"); + SetExitKey(0); + + // Custom file dialog + GuiWindowFileDialogState fileDialogState = InitGuiWindowFileDialog(POSTDOG_PATHS); + + bool exitWindow = false; + + char fileNameToLoad[512] = { 0 }; + + Texture texture = { 0 }; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!exitWindow) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + exitWindow = WindowShouldClose(); + + if (fileDialogState.SelectFilePressed) + { + // Load image file (if supported extension) + if (IsFileExtension(fileDialogState.fileNameText, ".png")) + { + strcpy(fileNameToLoad, TextFormat("%s" PATH_SEPERATOR "%s", fileDialogState.dirPathText, fileDialogState.fileNameText)); + UnloadTexture(texture); + texture = LoadTexture(fileNameToLoad); + } + + fileDialogState.SelectFilePressed = false; + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); + + DrawTexture(texture, GetScreenWidth()/2 - texture.width/2, GetScreenHeight()/2 - texture.height/2 - 5, WHITE); + DrawRectangleLines(GetScreenWidth()/2 - texture.width/2, GetScreenHeight()/2 - texture.height/2 - 5, texture.width, texture.height, BLACK); + + DrawText(fileNameToLoad, 208, GetScreenHeight() - 20, 10, GRAY); + + // raygui: controls drawing + //---------------------------------------------------------------------------------- + if (fileDialogState.windowActive) GuiLock(); + + if (GuiButton((Rectangle){ 20, 20, 140, 30 }, GuiIconText(ICON_FILE_OPEN, "Open Image"))) fileDialogState.windowActive = true; + + GuiUnlock(); + + // GUI: Dialog Window + //-------------------------------------------------------------------------------- + GuiWindowFileDialog(&fileDialogState); + //-------------------------------------------------------------------------------- + + //---------------------------------------------------------------------------------- + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadTexture(texture); // Unload texture + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff -r 96db6c3f38d6 -r 7bd795bac997 third_party/raylib/custom.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/third_party/raylib/custom.c Wed Jan 07 04:52:17 2026 -0800 @@ -0,0 +1,86 @@ +#include "third_party/raylib/include/raylib.h" +#define RAYGUI_IMPLEMENTATION +#include "third_party/raylib/include/raygui.h" + +// -- forward declarations --// + +// --- Default Behaviour that should be part of all raylib gui ---/ +void DefaultBehaviours(); +// --- Increase Font Sizes on key press --- // +void IncreaseFontSize(); +// --- Decrease Font Sizes on key press --- // +void DecreaseFontSize(); + + +void DefaultBehaviours() +{ + // Font sizes + IncreaseFontSize(); + DecreaseFontSize(); +} + +void IncreaseFontSize() +{ + if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_EQUAL)) + GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) + 1); +} + +void DecreaseFontSize() +{ + if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_MINUS)) + GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) - 1); +} + + +// --- Layout helper --- // +Rectangle RightOf(Rectangle ref, float padding) +{ + return (Rectangle){ + .x = ref.x + ref.width + padding, + .y = ref.y, + .width = 0, + .height = ref.height + }; +} + +Rectangle Below(Rectangle ref, float padding) +{ + return (Rectangle){ + .x = ref.x, + .y = ref.y + ref.height + padding, + .width = ref.width, + .height = 0 + }; +} + +Rectangle LeftColumn(Rectangle container, float ratio, float padding) +{ + return (Rectangle){ + .x = container.x + padding, + .y = container.y + padding, + .width = (container.width * ratio) - padding, + .height = container.height - (2 * padding) + }; +} + +Rectangle RightColumn(Rectangle container, Rectangle leftCol, float padding) +{ + return (Rectangle){ + .x = leftCol.x + leftCol.width + padding, + .y = container.y + padding, + .width = container.width - leftCol.width - (3 * padding), + .height = container.height - (2 * padding) + }; +} + +Rectangle HorizontalSplit(Rectangle container, float ratio) +{ + return (Rectangle){ + .x = container.x, + .y = container.y, + .width = container.width * ratio, + .height = container.height + }; +} + + diff -r 96db6c3f38d6 -r 7bd795bac997 third_party/raylib/include/raygui.h --- a/third_party/raylib/include/raygui.h Tue Jan 06 08:19:07 2026 -0800 +++ b/third_party/raylib/include/raygui.h Wed Jan 07 04:52:17 2026 -0800 @@ -6588,13 +6588,13 @@ //-------------------------------------------------------------------- if (state == STATE_PRESSED) { - GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); + GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); } else if (state == STATE_DISABLED) { - GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); + GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); } - else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); + else GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); // Draw text considering index offset if required // NOTE: Text index offset depends on cursor position