Mercurial
comparison postdog/gui_window_file_dialog.h @ 116:7bd795bac997
[Postdog] Added scrollable area to inputs and history files, buttons to delete and view.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Wed, 07 Jan 2026 04:52:17 -0800 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 115:96db6c3f38d6 | 116:7bd795bac997 |
|---|---|
| 1 /******************************************************************************************* | |
| 2 * | |
| 3 * Window File Dialog v1.2 - Modal file dialog to open/save files | |
| 4 * | |
| 5 * MODULE USAGE: | |
| 6 * #define GUI_WINDOW_FILE_DIALOG_IMPLEMENTATION | |
| 7 * #include "gui_window_file_dialog.h" | |
| 8 * | |
| 9 * INIT: GuiWindowFileDialogState state = GuiInitWindowFileDialog(); | |
| 10 * DRAW: GuiWindowFileDialog(&state); | |
| 11 * | |
| 12 * NOTE: This module depends on some raylib file system functions: | |
| 13 * - LoadDirectoryFiles() | |
| 14 * - UnloadDirectoryFiles() | |
| 15 * - GetWorkingDirectory() | |
| 16 * - DirectoryExists() | |
| 17 * - FileExists() | |
| 18 * | |
| 19 * LICENSE: zlib/libpng | |
| 20 * | |
| 21 * Copyright (c) 2019-2024 Ramon Santamaria (@raysan5) | |
| 22 * | |
| 23 * This software is provided "as-is", without any express or implied warranty. In no event | |
| 24 * will the authors be held liable for any damages arising from the use of this software. | |
| 25 * | |
| 26 * Permission is granted to anyone to use this software for any purpose, including commercial | |
| 27 * applications, and to alter it and redistribute it freely, subject to the following restrictions: | |
| 28 * | |
| 29 * 1. The origin of this software must not be misrepresented; you must not claim that you | |
| 30 * wrote the original software. If you use this software in a product, an acknowledgment | |
| 31 * in the product documentation would be appreciated but is not required. | |
| 32 * | |
| 33 * 2. Altered source versions must be plainly marked as such, and must not be misrepresented | |
| 34 * as being the original software. | |
| 35 * | |
| 36 * 3. This notice may not be removed or altered from any source distribution. | |
| 37 * | |
| 38 **********************************************************************************************/ | |
| 39 | |
| 40 #include "third_party/raylib/include/raylib.h" | |
| 41 | |
| 42 #ifndef GUI_WINDOW_FILE_DIALOG_H | |
| 43 #define GUI_WINDOW_FILE_DIALOG_H | |
| 44 | |
| 45 // Gui file dialog context data | |
| 46 typedef struct { | |
| 47 | |
| 48 // Window management variables | |
| 49 bool windowActive; | |
| 50 Rectangle windowBounds; | |
| 51 Vector2 panOffset; | |
| 52 bool dragMode; | |
| 53 bool supportDrag; | |
| 54 | |
| 55 // UI variables | |
| 56 bool dirPathEditMode; | |
| 57 char dirPathText[1024]; | |
| 58 | |
| 59 int filesListScrollIndex; | |
| 60 bool filesListEditMode; | |
| 61 int filesListActive; | |
| 62 | |
| 63 bool fileNameEditMode; | |
| 64 char fileNameText[1024]; | |
| 65 bool SelectFilePressed; | |
| 66 bool CancelFilePressed; | |
| 67 int fileTypeActive; | |
| 68 int itemFocused; | |
| 69 | |
| 70 // Custom state variables | |
| 71 FilePathList dirFiles; | |
| 72 char filterExt[256]; | |
| 73 char dirPathTextCopy[1024]; | |
| 74 char fileNameTextCopy[1024]; | |
| 75 | |
| 76 int prevFilesListActive; | |
| 77 | |
| 78 bool saveFileMode; | |
| 79 | |
| 80 } GuiWindowFileDialogState; | |
| 81 | |
| 82 #ifdef __cplusplus | |
| 83 extern "C" { // Prevents name mangling of functions | |
| 84 #endif | |
| 85 | |
| 86 //---------------------------------------------------------------------------------- | |
| 87 // Defines and Macros | |
| 88 //---------------------------------------------------------------------------------- | |
| 89 //... | |
| 90 | |
| 91 //---------------------------------------------------------------------------------- | |
| 92 // Types and Structures Definition | |
| 93 //---------------------------------------------------------------------------------- | |
| 94 // ... | |
| 95 | |
| 96 //---------------------------------------------------------------------------------- | |
| 97 // Global Variables Definition | |
| 98 //---------------------------------------------------------------------------------- | |
| 99 //... | |
| 100 | |
| 101 //---------------------------------------------------------------------------------- | |
| 102 // Module Functions Declaration | |
| 103 //---------------------------------------------------------------------------------- | |
| 104 GuiWindowFileDialogState InitGuiWindowFileDialog(const char *initPath); | |
| 105 void GuiWindowFileDialog(GuiWindowFileDialogState *state); | |
| 106 | |
| 107 #ifdef __cplusplus | |
| 108 } | |
| 109 #endif | |
| 110 | |
| 111 #endif // GUI_WINDOW_FILE_DIALOG_H | |
| 112 | |
| 113 /*********************************************************************************** | |
| 114 * | |
| 115 * GUI_WINDOW_FILE_DIALOG IMPLEMENTATION | |
| 116 * | |
| 117 ************************************************************************************/ | |
| 118 #if defined(GUI_WINDOW_FILE_DIALOG_IMPLEMENTATION) | |
| 119 #include "third_party/raylib/include/raygui.h" | |
| 120 | |
| 121 #include <string.h> // Required for: strcpy() | |
| 122 | |
| 123 //---------------------------------------------------------------------------------- | |
| 124 // Defines and Macros | |
| 125 //---------------------------------------------------------------------------------- | |
| 126 #define MAX_DIRECTORY_FILES 2048 | |
| 127 #define MAX_ICON_PATH_LENGTH 512 | |
| 128 #ifdef _WIN32 | |
| 129 #define PATH_SEPERATOR "\\" | |
| 130 #else | |
| 131 #define PATH_SEPERATOR "/" | |
| 132 #endif | |
| 133 | |
| 134 //---------------------------------------------------------------------------------- | |
| 135 // Types and Structures Definition | |
| 136 //---------------------------------------------------------------------------------- | |
| 137 #if defined(USE_CUSTOM_LISTVIEW_FILEINFO) | |
| 138 // Detailed file info type | |
| 139 typedef struct FileInfo { | |
| 140 const char *name; | |
| 141 int size; | |
| 142 int modTime; | |
| 143 int type; | |
| 144 int icon; | |
| 145 } FileInfo; | |
| 146 #else | |
| 147 // Filename only | |
| 148 typedef char *FileInfo; // Files are just a path string | |
| 149 #endif | |
| 150 | |
| 151 //---------------------------------------------------------------------------------- | |
| 152 // Global Variables Definition | |
| 153 //---------------------------------------------------------------------------------- | |
| 154 FileInfo *dirFilesIcon = NULL; // Path string + icon (for fancy drawing) | |
| 155 | |
| 156 //---------------------------------------------------------------------------------- | |
| 157 // Internal Module Functions Definition | |
| 158 //---------------------------------------------------------------------------------- | |
| 159 // Read files in new path | |
| 160 static void ReloadDirectoryFiles(GuiWindowFileDialogState *state); | |
| 161 | |
| 162 #if defined(USE_CUSTOM_LISTVIEW_FILEINFO) | |
| 163 // List View control for files info with extended parameters | |
| 164 static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int active); | |
| 165 #endif | |
| 166 | |
| 167 //---------------------------------------------------------------------------------- | |
| 168 // Module Functions Definition | |
| 169 //---------------------------------------------------------------------------------- | |
| 170 GuiWindowFileDialogState InitGuiWindowFileDialog(const char *initPath) | |
| 171 { | |
| 172 GuiWindowFileDialogState state = { 0 }; | |
| 173 | |
| 174 // Init window data | |
| 175 state.windowBounds = (Rectangle){ GetScreenWidth()/2 - 440/2, GetScreenHeight()/2 - 310/2, 440, 310 }; | |
| 176 state.windowActive = false; | |
| 177 state.supportDrag = true; | |
| 178 state.dragMode = false; | |
| 179 state.panOffset = (Vector2){ 0, 0 }; | |
| 180 | |
| 181 // Init path data | |
| 182 state.dirPathEditMode = false; | |
| 183 state.filesListActive = -1; | |
| 184 state.prevFilesListActive = state.filesListActive; | |
| 185 state.filesListScrollIndex = 0; | |
| 186 | |
| 187 state.fileNameEditMode = false; | |
| 188 | |
| 189 state.SelectFilePressed = false; | |
| 190 state.CancelFilePressed = false; | |
| 191 | |
| 192 state.fileTypeActive = 0; | |
| 193 | |
| 194 strcpy(state.fileNameText, "\0"); | |
| 195 | |
| 196 // Custom variables initialization | |
| 197 if (initPath && DirectoryExists(initPath)) | |
| 198 { | |
| 199 strcpy(state.dirPathText, initPath); | |
| 200 } | |
| 201 else if (initPath && FileExists(initPath)) | |
| 202 { | |
| 203 strcpy(state.dirPathText, GetDirectoryPath(initPath)); | |
| 204 strcpy(state.fileNameText, GetFileName(initPath)); | |
| 205 } | |
| 206 else strcpy(state.dirPathText, GetWorkingDirectory()); | |
| 207 | |
| 208 // TODO: Why we keep a copy? | |
| 209 strcpy(state.dirPathTextCopy, state.dirPathText); | |
| 210 strcpy(state.fileNameTextCopy, state.fileNameText); | |
| 211 | |
| 212 state.filterExt[0] = '\0'; | |
| 213 //strcpy(state.filterExt, "all"); | |
| 214 | |
| 215 state.dirFiles.count = 0; | |
| 216 | |
| 217 return state; | |
| 218 } | |
| 219 | |
| 220 // Update and draw file dialog | |
| 221 void GuiWindowFileDialog(GuiWindowFileDialogState *state) | |
| 222 { | |
| 223 if (state->windowActive) | |
| 224 { | |
| 225 // Update window dragging | |
| 226 //---------------------------------------------------------------------------------------- | |
| 227 if (state->supportDrag) | |
| 228 { | |
| 229 Vector2 mousePosition = GetMousePosition(); | |
| 230 | |
| 231 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) | |
| 232 { | |
| 233 // Window can be dragged from the top window bar | |
| 234 if (CheckCollisionPointRec(mousePosition, (Rectangle){ state->windowBounds.x, state->windowBounds.y, (float)state->windowBounds.width, RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT })) | |
| 235 { | |
| 236 state->dragMode = true; | |
| 237 state->panOffset.x = mousePosition.x - state->windowBounds.x; | |
| 238 state->panOffset.y = mousePosition.y - state->windowBounds.y; | |
| 239 } | |
| 240 } | |
| 241 | |
| 242 if (state->dragMode) | |
| 243 { | |
| 244 state->windowBounds.x = (mousePosition.x - state->panOffset.x); | |
| 245 state->windowBounds.y = (mousePosition.y - state->panOffset.y); | |
| 246 | |
| 247 // Check screen limits to avoid moving out of screen | |
| 248 if (state->windowBounds.x < 0) state->windowBounds.x = 0; | |
| 249 else if (state->windowBounds.x > (GetScreenWidth() - state->windowBounds.width)) state->windowBounds.x = GetScreenWidth() - state->windowBounds.width; | |
| 250 | |
| 251 if (state->windowBounds.y < 0) state->windowBounds.y = 0; | |
| 252 else if (state->windowBounds.y > (GetScreenHeight() - state->windowBounds.height)) state->windowBounds.y = GetScreenHeight() - state->windowBounds.height; | |
| 253 | |
| 254 if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) state->dragMode = false; | |
| 255 } | |
| 256 } | |
| 257 //---------------------------------------------------------------------------------------- | |
| 258 | |
| 259 // Load dirFilesIcon and state->dirFiles lazily on windows open | |
| 260 // NOTE: They are automatically unloaded at fileDialog closing | |
| 261 //---------------------------------------------------------------------------------------- | |
| 262 if (dirFilesIcon == NULL) | |
| 263 { | |
| 264 dirFilesIcon = (FileInfo *)RL_CALLOC(MAX_DIRECTORY_FILES, sizeof(FileInfo)); // Max files to read | |
| 265 for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesIcon[i] = (char *)RL_CALLOC(MAX_ICON_PATH_LENGTH, 1); // Max file name length | |
| 266 } | |
| 267 | |
| 268 // Load current directory files | |
| 269 if (state->dirFiles.paths == NULL) ReloadDirectoryFiles(state); | |
| 270 //---------------------------------------------------------------------------------------- | |
| 271 | |
| 272 // Draw window and controls | |
| 273 //---------------------------------------------------------------------------------------- | |
| 274 state->windowActive = !GuiWindowBox(state->windowBounds, "#198# Select File Dialog"); | |
| 275 | |
| 276 // Draw previous directory button + logic | |
| 277 if (GuiButton((Rectangle){ state->windowBounds.x + state->windowBounds.width - 48, state->windowBounds.y + 24 + 12, 40, 24 }, "< ..")) | |
| 278 { | |
| 279 // Move dir path one level up | |
| 280 strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText)); | |
| 281 | |
| 282 // Reload directory files (frees previous list) | |
| 283 ReloadDirectoryFiles(state); | |
| 284 | |
| 285 state->filesListActive = -1; | |
| 286 memset(state->fileNameText, 0, 1024); | |
| 287 memset(state->fileNameTextCopy, 0, 1024); | |
| 288 } | |
| 289 | |
| 290 // Draw current directory text box info + path editing logic | |
| 291 if (GuiTextBox((Rectangle){ state->windowBounds.x + 8, state->windowBounds.y + 24 + 12, state->windowBounds.width - 48 - 16, 24 }, state->dirPathText, 1024, state->dirPathEditMode)) | |
| 292 { | |
| 293 if (state->dirPathEditMode) | |
| 294 { | |
| 295 // Verify if a valid path has been introduced | |
| 296 if (DirectoryExists(state->dirPathText)) | |
| 297 { | |
| 298 // Reload directory files (frees previous list) | |
| 299 ReloadDirectoryFiles(state); | |
| 300 | |
| 301 strcpy(state->dirPathTextCopy, state->dirPathText); | |
| 302 } | |
| 303 else strcpy(state->dirPathText, state->dirPathTextCopy); | |
| 304 } | |
| 305 | |
| 306 state->dirPathEditMode = !state->dirPathEditMode; | |
| 307 } | |
| 308 | |
| 309 // List view elements are aligned left | |
| 310 int prevTextAlignment = GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT); | |
| 311 int prevElementsHeight = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); | |
| 312 GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); | |
| 313 GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 24); | |
| 314 # if defined(USE_CUSTOM_LISTVIEW_FILEINFO) | |
| 315 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); | |
| 316 # else | |
| 317 GuiListViewEx((Rectangle){ state->windowBounds.x + 8, state->windowBounds.y + 48 + 20, state->windowBounds.width - 16, state->windowBounds.height - 60 - 16 - 68 }, | |
| 318 (const char**)dirFilesIcon, state->dirFiles.count, &state->filesListScrollIndex, &state->filesListActive, &state->itemFocused); | |
| 319 # endif | |
| 320 GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, prevTextAlignment); | |
| 321 GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, prevElementsHeight); | |
| 322 | |
| 323 // Check if a path has been selected, if it is a directory, move to that directory (and reload paths) | |
| 324 if ((state->filesListActive >= 0) && (state->filesListActive != state->prevFilesListActive)) | |
| 325 //&& (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_DPAD_A))) | |
| 326 { | |
| 327 strcpy(state->fileNameText, GetFileName(state->dirFiles.paths[state->filesListActive])); | |
| 328 | |
| 329 if (DirectoryExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText))) | |
| 330 { | |
| 331 if (TextIsEqual(state->fileNameText, "..")) strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText)); | |
| 332 else strcpy(state->dirPathText, TextFormat("%s/%s", (strcmp(state->dirPathText, "/") == 0)? "" : state->dirPathText, state->fileNameText)); | |
| 333 | |
| 334 strcpy(state->dirPathTextCopy, state->dirPathText); | |
| 335 | |
| 336 // Reload directory files (frees previous list) | |
| 337 ReloadDirectoryFiles(state); | |
| 338 | |
| 339 strcpy(state->dirPathTextCopy, state->dirPathText); | |
| 340 | |
| 341 state->filesListActive = -1; | |
| 342 strcpy(state->fileNameText, "\0"); | |
| 343 strcpy(state->fileNameTextCopy, state->fileNameText); | |
| 344 } | |
| 345 | |
| 346 state->prevFilesListActive = state->filesListActive; | |
| 347 } | |
| 348 | |
| 349 // Draw bottom controls | |
| 350 //-------------------------------------------------------------------------------------- | |
| 351 GuiLabel((Rectangle){ state->windowBounds.x + 8, state->windowBounds.y + state->windowBounds.height - 68, 60, 24 }, "File name:"); | |
| 352 if (GuiTextBox((Rectangle){ state->windowBounds.x + 72, state->windowBounds.y + state->windowBounds.height - 68, state->windowBounds.width - 184, 24 }, state->fileNameText, 128, state->fileNameEditMode)) | |
| 353 { | |
| 354 if (*state->fileNameText) | |
| 355 { | |
| 356 // Verify if a valid filename has been introduced | |
| 357 if (FileExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText))) | |
| 358 { | |
| 359 // Select filename from list view | |
| 360 for (unsigned int i = 0; i < state->dirFiles.count; i++) | |
| 361 { | |
| 362 if (TextIsEqual(state->fileNameText, state->dirFiles.paths[i])) | |
| 363 { | |
| 364 state->filesListActive = i; | |
| 365 strcpy(state->fileNameTextCopy, state->fileNameText); | |
| 366 break; | |
| 367 } | |
| 368 } | |
| 369 } | |
| 370 else if (!state->saveFileMode) | |
| 371 { | |
| 372 strcpy(state->fileNameText, state->fileNameTextCopy); | |
| 373 } | |
| 374 } | |
| 375 | |
| 376 state->fileNameEditMode = !state->fileNameEditMode; | |
| 377 } | |
| 378 | |
| 379 GuiLabel((Rectangle){ state->windowBounds.x + 8, state->windowBounds.y + state->windowBounds.height - 24 - 12, 68, 24 }, "File filter:"); | |
| 380 GuiComboBox((Rectangle){ state->windowBounds.x + 72, state->windowBounds.y + state->windowBounds.height - 24 - 12, state->windowBounds.width - 184, 24 }, "All files", &state->fileTypeActive); | |
| 381 | |
| 382 state->SelectFilePressed = GuiButton((Rectangle){ state->windowBounds.x + state->windowBounds.width - 96 - 8, state->windowBounds.y + state->windowBounds.height - 68, 96, 24 }, "Select"); | |
| 383 | |
| 384 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; | |
| 385 //-------------------------------------------------------------------------------------- | |
| 386 | |
| 387 // Exit on file selected | |
| 388 if (state->SelectFilePressed) state->windowActive = false; | |
| 389 | |
| 390 // File dialog has been closed, free all memory before exit | |
| 391 if (!state->windowActive) | |
| 392 { | |
| 393 // Free dirFilesIcon memory | |
| 394 for (int i = 0; i < MAX_DIRECTORY_FILES; i++) RL_FREE(dirFilesIcon[i]); | |
| 395 | |
| 396 RL_FREE(dirFilesIcon); | |
| 397 dirFilesIcon = NULL; | |
| 398 | |
| 399 // Unload directory file paths | |
| 400 UnloadDirectoryFiles(state->dirFiles); | |
| 401 | |
| 402 // Reset state variables | |
| 403 state->dirFiles.count = 0; | |
| 404 state->dirFiles.capacity = 0; | |
| 405 state->dirFiles.paths = NULL; | |
| 406 } | |
| 407 } | |
| 408 } | |
| 409 | |
| 410 // Compare two files from a directory | |
| 411 static inline int FileCompare(const char *d1, const char *d2, const char *dir) | |
| 412 { | |
| 413 const bool b1 = DirectoryExists(TextFormat("%s/%s", dir, d1)); | |
| 414 const bool b2 = DirectoryExists(TextFormat("%s/%s", dir, d2)); | |
| 415 | |
| 416 if (b1 && !b2) return -1; | |
| 417 if (!b1 && b2) return 1; | |
| 418 | |
| 419 if (!FileExists(TextFormat("%s/%s", dir, d1))) return 1; | |
| 420 if (!FileExists(TextFormat("%s/%s", dir, d2))) return -1; | |
| 421 | |
| 422 return strcmp(d1, d2); | |
| 423 } | |
| 424 | |
| 425 // Read files in new path | |
| 426 static void ReloadDirectoryFiles(GuiWindowFileDialogState *state) | |
| 427 { | |
| 428 UnloadDirectoryFiles(state->dirFiles); | |
| 429 | |
| 430 state->dirFiles = LoadDirectoryFilesEx(state->dirPathText, (state->filterExt[0] == '\0')? NULL : state->filterExt, false); | |
| 431 state->itemFocused = 0; | |
| 432 | |
| 433 // Reset dirFilesIcon memory | |
| 434 for (int i = 0; i < MAX_DIRECTORY_FILES; i++) memset(dirFilesIcon[i], 0, MAX_ICON_PATH_LENGTH); | |
| 435 | |
| 436 // Copy paths as icon + fileNames into dirFilesIcon | |
| 437 for (unsigned int i = 0; i < state->dirFiles.count; i++) | |
| 438 { | |
| 439 if (IsPathFile(state->dirFiles.paths[i])) | |
| 440 { | |
| 441 // Path is a file, a file icon for convenience (for some recognized extensions) | |
| 442 if (IsFileExtension(state->dirFiles.paths[i], ".png;.bmp;.tga;.gif;.jpg;.jpeg;.psd;.hdr;.qoi;.dds;.pkm;.ktx;.pvr;.astc")) | |
| 443 { | |
| 444 strcpy(dirFilesIcon[i], TextFormat("#12#%s", GetFileName(state->dirFiles.paths[i]))); | |
| 445 } | |
| 446 else if (IsFileExtension(state->dirFiles.paths[i], ".wav;.mp3;.ogg;.flac;.xm;.mod;.it;.wma;.aiff")) | |
| 447 { | |
| 448 strcpy(dirFilesIcon[i], TextFormat("#11#%s", GetFileName(state->dirFiles.paths[i]))); | |
| 449 } | |
| 450 else if (IsFileExtension(state->dirFiles.paths[i], ".txt;.info;.md;.nfo;.xml;.json;.c;.cpp;.cs;.lua;.py;.glsl;.vs;.fs")) | |
| 451 { | |
| 452 strcpy(dirFilesIcon[i], TextFormat("#10#%s", GetFileName(state->dirFiles.paths[i]))); | |
| 453 } | |
| 454 else if (IsFileExtension(state->dirFiles.paths[i], ".exe;.bin;.raw;.msi")) | |
| 455 { | |
| 456 strcpy(dirFilesIcon[i], TextFormat("#200#%s", GetFileName(state->dirFiles.paths[i]))); | |
| 457 } | |
| 458 else strcpy(dirFilesIcon[i], TextFormat("#218#%s", GetFileName(state->dirFiles.paths[i]))); | |
| 459 } | |
| 460 else | |
| 461 { | |
| 462 // Path is a directory, add a directory icon | |
| 463 strcpy(dirFilesIcon[i], TextFormat("#1#%s", GetFileName(state->dirFiles.paths[i]))); | |
| 464 } | |
| 465 } | |
| 466 } | |
| 467 | |
| 468 #if defined(USE_CUSTOM_LISTVIEW_FILEINFO) | |
| 469 // List View control for files info with extended parameters | |
| 470 static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int *active) | |
| 471 { | |
| 472 int result = 0; | |
| 473 GuiState state = guiState; | |
| 474 int itemFocused = (focus == NULL)? -1 : *focus; | |
| 475 int itemSelected = *active; | |
| 476 | |
| 477 // Check if we need a scroll bar | |
| 478 bool useScrollBar = false; | |
| 479 if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING))*count > bounds.height) useScrollBar = true; | |
| 480 | |
| 481 // Define base item rectangle [0] | |
| 482 Rectangle itemBounds = { 0 }; | |
| 483 itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING); | |
| 484 itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); | |
| 485 itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); | |
| 486 itemBounds.height = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); | |
| 487 if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); | |
| 488 | |
| 489 // Get items on the list | |
| 490 int visibleItems = bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING)); | |
| 491 if (visibleItems > count) visibleItems = count; | |
| 492 | |
| 493 int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; | |
| 494 if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; | |
| 495 int endIndex = startIndex + visibleItems; | |
| 496 | |
| 497 // Update control | |
| 498 //-------------------------------------------------------------------- | |
| 499 if ((state != GUI_STATE_DISABLED) && !guiLocked) | |
| 500 { | |
| 501 Vector2 mousePoint = GetMousePosition(); | |
| 502 | |
| 503 // Check mouse inside list view | |
| 504 if (CheckCollisionPointRec(mousePoint, bounds)) | |
| 505 { | |
| 506 state = GUI_STATE_FOCUSED; | |
| 507 | |
| 508 // Check focused and selected item | |
| 509 for (int i = 0; i < visibleItems; i++) | |
| 510 { | |
| 511 if (CheckCollisionPointRec(mousePoint, itemBounds)) | |
| 512 { | |
| 513 itemFocused = startIndex + i; | |
| 514 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) itemSelected = startIndex + i; | |
| 515 break; | |
| 516 } | |
| 517 | |
| 518 // Update item rectangle y position for next item | |
| 519 itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING)); | |
| 520 } | |
| 521 | |
| 522 if (useScrollBar) | |
| 523 { | |
| 524 int wheelMove = GetMouseWheelMove(); | |
| 525 startIndex -= wheelMove; | |
| 526 | |
| 527 if (startIndex < 0) startIndex = 0; | |
| 528 else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; | |
| 529 | |
| 530 endIndex = startIndex + visibleItems; | |
| 531 if (endIndex > count) endIndex = count; | |
| 532 } | |
| 533 } | |
| 534 else itemFocused = -1; | |
| 535 | |
| 536 // Reset item rectangle y to [0] | |
| 537 itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); | |
| 538 } | |
| 539 //-------------------------------------------------------------------- | |
| 540 | |
| 541 // Draw control | |
| 542 //-------------------------------------------------------------------- | |
| 543 DrawRectangleRec(bounds, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background | |
| 544 DrawRectangleLinesEx(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), guiAlpha)); | |
| 545 | |
| 546 // TODO: Draw list view header with file sections: icon+name | size | type | modTime | |
| 547 | |
| 548 // Draw visible items | |
| 549 for (int i = 0; i < visibleItems; i++) | |
| 550 { | |
| 551 if (state == GUI_STATE_DISABLED) | |
| 552 { | |
| 553 if ((startIndex + i) == itemSelected) | |
| 554 { | |
| 555 DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED)), guiAlpha)); | |
| 556 DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), guiAlpha)); | |
| 557 } | |
| 558 | |
| 559 // TODO: Draw full file info line: icon+name | size | type | modTime | |
| 560 | |
| 561 GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED)), guiAlpha)); | |
| 562 } | |
| 563 else | |
| 564 { | |
| 565 if ((startIndex + i) == itemSelected) | |
| 566 { | |
| 567 // Draw item selected | |
| 568 DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED)), guiAlpha)); | |
| 569 DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), guiAlpha)); | |
| 570 | |
| 571 GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED)), guiAlpha)); | |
| 572 } | |
| 573 else if ((startIndex + i) == itemFocused) | |
| 574 { | |
| 575 // Draw item focused | |
| 576 DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED)), guiAlpha)); | |
| 577 DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), guiAlpha)); | |
| 578 | |
| 579 GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED)), guiAlpha)); | |
| 580 } | |
| 581 else | |
| 582 { | |
| 583 // Draw item normal | |
| 584 GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL)), guiAlpha)); | |
| 585 } | |
| 586 } | |
| 587 | |
| 588 // Update item rectangle y position for next item | |
| 589 itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING)); | |
| 590 } | |
| 591 | |
| 592 if (useScrollBar) | |
| 593 { | |
| 594 Rectangle scrollBarBounds = { | |
| 595 bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), | |
| 596 bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), | |
| 597 bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) | |
| 598 }; | |
| 599 | |
| 600 // Calculate percentage of visible items and apply same percentage to scrollbar | |
| 601 float percentVisible = (float)(endIndex - startIndex)/count; | |
| 602 float sliderSize = bounds.height*percentVisible; | |
| 603 | |
| 604 int prevSliderSize = GuiGetStyle(SCROLLBAR, SLIDER_WIDTH); // Save default slider size | |
| 605 int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed | |
| 606 GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, sliderSize); // Change slider size | |
| 607 GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed | |
| 608 | |
| 609 startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); | |
| 610 | |
| 611 GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default | |
| 612 GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, prevSliderSize); // Reset slider size to default | |
| 613 } | |
| 614 //-------------------------------------------------------------------- | |
| 615 | |
| 616 if (focus != NULL) *focus = itemFocused; | |
| 617 if (scrollIndex != NULL) *scrollIndex = startIndex; | |
| 618 | |
| 619 *active = itemSelected; | |
| 620 return result; | |
| 621 } | |
| 622 #endif // USE_CUSTOM_LISTVIEW_FILEINFO | |
| 623 | |
| 624 #endif // GUI_FILE_DIALOG_IMPLEMENTATION |