gui_window_file_dialog.h 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  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. #include "raylib.h"
  40. #ifndef GUI_WINDOW_FILE_DIALOG_H
  41. #define GUI_WINDOW_FILE_DIALOG_H
  42. // Gui file dialog context data
  43. typedef struct {
  44. // Window management variables
  45. bool windowActive;
  46. Rectangle windowBounds;
  47. Vector2 panOffset;
  48. bool dragMode;
  49. bool supportDrag;
  50. // UI variables
  51. bool dirPathEditMode;
  52. char dirPathText[1024];
  53. int filesListScrollIndex;
  54. bool filesListEditMode;
  55. int filesListActive;
  56. bool fileNameEditMode;
  57. char fileNameText[1024];
  58. bool SelectFilePressed;
  59. bool CancelFilePressed;
  60. int fileTypeActive;
  61. int itemFocused;
  62. // Custom state variables
  63. FilePathList dirFiles;
  64. char filterExt[256];
  65. char dirPathTextCopy[1024];
  66. char fileNameTextCopy[1024];
  67. int prevFilesListActive;
  68. bool saveFileMode;
  69. } GuiWindowFileDialogState;
  70. #ifdef __cplusplus
  71. extern "C" { // Prevents name mangling of functions
  72. #endif
  73. //----------------------------------------------------------------------------------
  74. // Defines and Macros
  75. //----------------------------------------------------------------------------------
  76. //...
  77. //----------------------------------------------------------------------------------
  78. // Types and Structures Definition
  79. //----------------------------------------------------------------------------------
  80. // ...
  81. //----------------------------------------------------------------------------------
  82. // Global Variables Definition
  83. //----------------------------------------------------------------------------------
  84. //...
  85. //----------------------------------------------------------------------------------
  86. // Module Functions Declaration
  87. //----------------------------------------------------------------------------------
  88. GuiWindowFileDialogState InitGuiWindowFileDialog(const char *initPath);
  89. void GuiWindowFileDialog(GuiWindowFileDialogState *state);
  90. #ifdef __cplusplus
  91. }
  92. #endif
  93. #endif // GUI_WINDOW_FILE_DIALOG_H
  94. /***********************************************************************************
  95. *
  96. * GUI_WINDOW_FILE_DIALOG IMPLEMENTATION
  97. *
  98. ************************************************************************************/
  99. #if defined(GUI_WINDOW_FILE_DIALOG_IMPLEMENTATION)
  100. #include "../../src/raygui.h"
  101. #include <string.h> // Required for: strcpy()
  102. //----------------------------------------------------------------------------------
  103. // Defines and Macros
  104. //----------------------------------------------------------------------------------
  105. #define MAX_DIRECTORY_FILES 2048
  106. #define MAX_ICON_PATH_LENGTH 512
  107. #ifdef _WIN32
  108. #define PATH_SEPERATOR "\\"
  109. #else
  110. #define PATH_SEPERATOR "/"
  111. #endif
  112. //----------------------------------------------------------------------------------
  113. // Types and Structures Definition
  114. //----------------------------------------------------------------------------------
  115. #if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
  116. // Detailed file info type
  117. typedef struct FileInfo {
  118. const char *name;
  119. int size;
  120. int modTime;
  121. int type;
  122. int icon;
  123. } FileInfo;
  124. #else
  125. // Filename only
  126. typedef char *FileInfo; // Files are just a path string
  127. #endif
  128. //----------------------------------------------------------------------------------
  129. // Global Variables Definition
  130. //----------------------------------------------------------------------------------
  131. FileInfo *dirFilesIcon = NULL; // Path string + icon (for fancy drawing)
  132. //----------------------------------------------------------------------------------
  133. // Internal Module Functions Definition
  134. //----------------------------------------------------------------------------------
  135. // Read files in new path
  136. static void ReloadDirectoryFiles(GuiWindowFileDialogState *state);
  137. #if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
  138. // List View control for files info with extended parameters
  139. static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int active);
  140. #endif
  141. //----------------------------------------------------------------------------------
  142. // Module Functions Definition
  143. //----------------------------------------------------------------------------------
  144. GuiWindowFileDialogState InitGuiWindowFileDialog(const char *initPath)
  145. {
  146. GuiWindowFileDialogState state = { 0 };
  147. // Init window data
  148. state.windowBounds = (Rectangle){ GetScreenWidth()/2 - 440/2, GetScreenHeight()/2 - 310/2, 440, 310 };
  149. state.windowActive = false;
  150. state.supportDrag = true;
  151. state.dragMode = false;
  152. state.panOffset = (Vector2){ 0, 0 };
  153. // Init path data
  154. state.dirPathEditMode = false;
  155. state.filesListActive = -1;
  156. state.prevFilesListActive = state.filesListActive;
  157. state.filesListScrollIndex = 0;
  158. state.fileNameEditMode = false;
  159. state.SelectFilePressed = false;
  160. state.CancelFilePressed = false;
  161. state.fileTypeActive = 0;
  162. strcpy(state.fileNameText, "\0");
  163. // Custom variables initialization
  164. if (initPath && DirectoryExists(initPath))
  165. {
  166. strcpy(state.dirPathText, initPath);
  167. }
  168. else if (initPath && FileExists(initPath))
  169. {
  170. strcpy(state.dirPathText, GetDirectoryPath(initPath));
  171. strcpy(state.fileNameText, GetFileName(initPath));
  172. }
  173. else strcpy(state.dirPathText, GetWorkingDirectory());
  174. // TODO: Why we keep a copy?
  175. strcpy(state.dirPathTextCopy, state.dirPathText);
  176. strcpy(state.fileNameTextCopy, state.fileNameText);
  177. state.filterExt[0] = '\0';
  178. //strcpy(state.filterExt, "all");
  179. state.dirFiles.count = 0;
  180. return state;
  181. }
  182. // Update and draw file dialog
  183. void GuiWindowFileDialog(GuiWindowFileDialogState *state)
  184. {
  185. if (state->windowActive)
  186. {
  187. // Update window dragging
  188. //----------------------------------------------------------------------------------------
  189. if (state->supportDrag)
  190. {
  191. Vector2 mousePosition = GetMousePosition();
  192. if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
  193. {
  194. // Window can be dragged from the top window bar
  195. if (CheckCollisionPointRec(mousePosition, (Rectangle){ state->windowBounds.x, state->windowBounds.y, (float)state->windowBounds.width, RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }))
  196. {
  197. state->dragMode = true;
  198. state->panOffset.x = mousePosition.x - state->windowBounds.x;
  199. state->panOffset.y = mousePosition.y - state->windowBounds.y;
  200. }
  201. }
  202. if (state->dragMode)
  203. {
  204. state->windowBounds.x = (mousePosition.x - state->panOffset.x);
  205. state->windowBounds.y = (mousePosition.y - state->panOffset.y);
  206. // Check screen limits to avoid moving out of screen
  207. if (state->windowBounds.x < 0) state->windowBounds.x = 0;
  208. else if (state->windowBounds.x > (GetScreenWidth() - state->windowBounds.width)) state->windowBounds.x = GetScreenWidth() - state->windowBounds.width;
  209. if (state->windowBounds.y < 0) state->windowBounds.y = 0;
  210. else if (state->windowBounds.y > (GetScreenHeight() - state->windowBounds.height)) state->windowBounds.y = GetScreenHeight() - state->windowBounds.height;
  211. if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) state->dragMode = false;
  212. }
  213. }
  214. //----------------------------------------------------------------------------------------
  215. // Load dirFilesIcon and state->dirFiles lazily on windows open
  216. // NOTE: They are automatically unloaded at fileDialog closing
  217. //----------------------------------------------------------------------------------------
  218. if (dirFilesIcon == NULL)
  219. {
  220. dirFilesIcon = (FileInfo *)RL_CALLOC(MAX_DIRECTORY_FILES, sizeof(FileInfo)); // Max files to read
  221. for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesIcon[i] = (char *)RL_CALLOC(MAX_ICON_PATH_LENGTH, 1); // Max file name length
  222. }
  223. // Load current directory files
  224. if (state->dirFiles.paths == NULL) ReloadDirectoryFiles(state);
  225. //----------------------------------------------------------------------------------------
  226. // Draw window and controls
  227. //----------------------------------------------------------------------------------------
  228. state->windowActive = !GuiWindowBox(state->windowBounds, "#198# Select File Dialog");
  229. // Draw previous directory button + logic
  230. if (GuiButton((Rectangle){ state->windowBounds.x + state->windowBounds.width - 48, state->windowBounds.y + 24 + 12, 40, 24 }, "< .."))
  231. {
  232. // Move dir path one level up
  233. strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText));
  234. // Reload directory files (frees previous list)
  235. ReloadDirectoryFiles(state);
  236. state->filesListActive = -1;
  237. memset(state->fileNameText, 0, 1024);
  238. memset(state->fileNameTextCopy, 0, 1024);
  239. }
  240. // Draw current directory text box info + path editing logic
  241. if (GuiTextBox((Rectangle){ state->windowBounds.x + 8, state->windowBounds.y + 24 + 12, state->windowBounds.width - 48 - 16, 24 }, state->dirPathText, 1024, state->dirPathEditMode))
  242. {
  243. if (state->dirPathEditMode)
  244. {
  245. // Verify if a valid path has been introduced
  246. if (DirectoryExists(state->dirPathText))
  247. {
  248. // Reload directory files (frees previous list)
  249. ReloadDirectoryFiles(state);
  250. strcpy(state->dirPathTextCopy, state->dirPathText);
  251. }
  252. else strcpy(state->dirPathText, state->dirPathTextCopy);
  253. }
  254. state->dirPathEditMode = !state->dirPathEditMode;
  255. }
  256. // List view elements are aligned left
  257. int prevTextAlignment = GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT);
  258. int prevElementsHeight = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT);
  259. GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT);
  260. GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 24);
  261. # if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
  262. 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);
  263. # else
  264. GuiListViewEx((Rectangle){ state->windowBounds.x + 8, state->windowBounds.y + 48 + 20, state->windowBounds.width - 16, state->windowBounds.height - 60 - 16 - 68 },
  265. (const char**)dirFilesIcon, state->dirFiles.count, &state->filesListScrollIndex, &state->filesListActive, &state->itemFocused);
  266. # endif
  267. GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, prevTextAlignment);
  268. GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, prevElementsHeight);
  269. // Check if a path has been selected, if it is a directory, move to that directory (and reload paths)
  270. if ((state->filesListActive >= 0) && (state->filesListActive != state->prevFilesListActive))
  271. //&& (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_DPAD_A)))
  272. {
  273. strcpy(state->fileNameText, GetFileName(state->dirFiles.paths[state->filesListActive]));
  274. if (DirectoryExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText)))
  275. {
  276. if (TextIsEqual(state->fileNameText, "..")) strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText));
  277. else strcpy(state->dirPathText, TextFormat("%s/%s", (strcmp(state->dirPathText, "/") == 0)? "" : state->dirPathText, state->fileNameText));
  278. strcpy(state->dirPathTextCopy, state->dirPathText);
  279. // Reload directory files (frees previous list)
  280. ReloadDirectoryFiles(state);
  281. strcpy(state->dirPathTextCopy, state->dirPathText);
  282. state->filesListActive = -1;
  283. strcpy(state->fileNameText, "\0");
  284. strcpy(state->fileNameTextCopy, state->fileNameText);
  285. }
  286. state->prevFilesListActive = state->filesListActive;
  287. }
  288. // Draw bottom controls
  289. //--------------------------------------------------------------------------------------
  290. GuiLabel((Rectangle){ state->windowBounds.x + 8, state->windowBounds.y + state->windowBounds.height - 68, 60, 24 }, "File name:");
  291. if (GuiTextBox((Rectangle){ state->windowBounds.x + 72, state->windowBounds.y + state->windowBounds.height - 68, state->windowBounds.width - 184, 24 }, state->fileNameText, 128, state->fileNameEditMode))
  292. {
  293. if (*state->fileNameText)
  294. {
  295. // Verify if a valid filename has been introduced
  296. if (FileExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText)))
  297. {
  298. // Select filename from list view
  299. for (unsigned int i = 0; i < state->dirFiles.count; i++)
  300. {
  301. if (TextIsEqual(state->fileNameText, state->dirFiles.paths[i]))
  302. {
  303. state->filesListActive = i;
  304. strcpy(state->fileNameTextCopy, state->fileNameText);
  305. break;
  306. }
  307. }
  308. }
  309. else if (!state->saveFileMode)
  310. {
  311. strcpy(state->fileNameText, state->fileNameTextCopy);
  312. }
  313. }
  314. state->fileNameEditMode = !state->fileNameEditMode;
  315. }
  316. GuiLabel((Rectangle){ state->windowBounds.x + 8, state->windowBounds.y + state->windowBounds.height - 24 - 12, 68, 24 }, "File filter:");
  317. GuiComboBox((Rectangle){ state->windowBounds.x + 72, state->windowBounds.y + state->windowBounds.height - 24 - 12, state->windowBounds.width - 184, 24 }, "All files", &state->fileTypeActive);
  318. state->SelectFilePressed = GuiButton((Rectangle){ state->windowBounds.x + state->windowBounds.width - 96 - 8, state->windowBounds.y + state->windowBounds.height - 68, 96, 24 }, "Select");
  319. 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;
  320. //--------------------------------------------------------------------------------------
  321. // Exit on file selected
  322. if (state->SelectFilePressed) state->windowActive = false;
  323. // File dialog has been closed, free all memory before exit
  324. if (!state->windowActive)
  325. {
  326. // Free dirFilesIcon memory
  327. for (int i = 0; i < MAX_DIRECTORY_FILES; i++) RL_FREE(dirFilesIcon[i]);
  328. RL_FREE(dirFilesIcon);
  329. dirFilesIcon = NULL;
  330. // Unload directory file paths
  331. UnloadDirectoryFiles(state->dirFiles);
  332. // Reset state variables
  333. state->dirFiles.count = 0;
  334. state->dirFiles.capacity = 0;
  335. state->dirFiles.paths = NULL;
  336. }
  337. }
  338. }
  339. // Compare two files from a directory
  340. static inline int FileCompare(const char *d1, const char *d2, const char *dir)
  341. {
  342. const bool b1 = DirectoryExists(TextFormat("%s/%s", dir, d1));
  343. const bool b2 = DirectoryExists(TextFormat("%s/%s", dir, d2));
  344. if (b1 && !b2) return -1;
  345. if (!b1 && b2) return 1;
  346. if (!FileExists(TextFormat("%s/%s", dir, d1))) return 1;
  347. if (!FileExists(TextFormat("%s/%s", dir, d2))) return -1;
  348. return strcmp(d1, d2);
  349. }
  350. // Read files in new path
  351. static void ReloadDirectoryFiles(GuiWindowFileDialogState *state)
  352. {
  353. UnloadDirectoryFiles(state->dirFiles);
  354. state->dirFiles = LoadDirectoryFilesEx(state->dirPathText, (state->filterExt[0] == '\0')? NULL : state->filterExt, false);
  355. state->itemFocused = 0;
  356. // Reset dirFilesIcon memory
  357. for (int i = 0; i < MAX_DIRECTORY_FILES; i++) memset(dirFilesIcon[i], 0, MAX_ICON_PATH_LENGTH);
  358. // Copy paths as icon + fileNames into dirFilesIcon
  359. for (unsigned int i = 0; i < state->dirFiles.count; i++)
  360. {
  361. if (IsPathFile(state->dirFiles.paths[i]))
  362. {
  363. // Path is a file, a file icon for convenience (for some recognized extensions)
  364. if (IsFileExtension(state->dirFiles.paths[i], ".png;.bmp;.tga;.gif;.jpg;.jpeg;.psd;.hdr;.qoi;.dds;.pkm;.ktx;.pvr;.astc"))
  365. {
  366. strcpy(dirFilesIcon[i], TextFormat("#12#%s", GetFileName(state->dirFiles.paths[i])));
  367. }
  368. else if (IsFileExtension(state->dirFiles.paths[i], ".wav;.mp3;.ogg;.flac;.xm;.mod;.it;.wma;.aiff"))
  369. {
  370. strcpy(dirFilesIcon[i], TextFormat("#11#%s", GetFileName(state->dirFiles.paths[i])));
  371. }
  372. else if (IsFileExtension(state->dirFiles.paths[i], ".txt;.info;.md;.nfo;.xml;.json;.c;.cpp;.cs;.lua;.py;.glsl;.vs;.fs"))
  373. {
  374. strcpy(dirFilesIcon[i], TextFormat("#10#%s", GetFileName(state->dirFiles.paths[i])));
  375. }
  376. else if (IsFileExtension(state->dirFiles.paths[i], ".exe;.bin;.raw;.msi"))
  377. {
  378. strcpy(dirFilesIcon[i], TextFormat("#200#%s", GetFileName(state->dirFiles.paths[i])));
  379. }
  380. else strcpy(dirFilesIcon[i], TextFormat("#218#%s", GetFileName(state->dirFiles.paths[i])));
  381. }
  382. else
  383. {
  384. // Path is a directory, add a directory icon
  385. strcpy(dirFilesIcon[i], TextFormat("#1#%s", GetFileName(state->dirFiles.paths[i])));
  386. }
  387. }
  388. }
  389. #if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
  390. // List View control for files info with extended parameters
  391. static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int *active)
  392. {
  393. int result = 0;
  394. GuiState state = guiState;
  395. int itemFocused = (focus == NULL)? -1 : *focus;
  396. int itemSelected = *active;
  397. // Check if we need a scroll bar
  398. bool useScrollBar = false;
  399. if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING))*count > bounds.height) useScrollBar = true;
  400. // Define base item rectangle [0]
  401. Rectangle itemBounds = { 0 };
  402. itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING);
  403. itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH);
  404. itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) - GuiGetStyle(DEFAULT, BORDER_WIDTH);
  405. itemBounds.height = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT);
  406. if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH);
  407. // Get items on the list
  408. int visibleItems = bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING));
  409. if (visibleItems > count) visibleItems = count;
  410. int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex;
  411. if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0;
  412. int endIndex = startIndex + visibleItems;
  413. // Update control
  414. //--------------------------------------------------------------------
  415. if ((state != GUI_STATE_DISABLED) && !guiLocked)
  416. {
  417. Vector2 mousePoint = GetMousePosition();
  418. // Check mouse inside list view
  419. if (CheckCollisionPointRec(mousePoint, bounds))
  420. {
  421. state = GUI_STATE_FOCUSED;
  422. // Check focused and selected item
  423. for (int i = 0; i < visibleItems; i++)
  424. {
  425. if (CheckCollisionPointRec(mousePoint, itemBounds))
  426. {
  427. itemFocused = startIndex + i;
  428. if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) itemSelected = startIndex + i;
  429. break;
  430. }
  431. // Update item rectangle y position for next item
  432. itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING));
  433. }
  434. if (useScrollBar)
  435. {
  436. int wheelMove = GetMouseWheelMove();
  437. startIndex -= wheelMove;
  438. if (startIndex < 0) startIndex = 0;
  439. else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems;
  440. endIndex = startIndex + visibleItems;
  441. if (endIndex > count) endIndex = count;
  442. }
  443. }
  444. else itemFocused = -1;
  445. // Reset item rectangle y to [0]
  446. itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH);
  447. }
  448. //--------------------------------------------------------------------
  449. // Draw control
  450. //--------------------------------------------------------------------
  451. DrawRectangleRec(bounds, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background
  452. DrawRectangleLinesEx(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), guiAlpha));
  453. // TODO: Draw list view header with file sections: icon+name | size | type | modTime
  454. // Draw visible items
  455. for (int i = 0; i < visibleItems; i++)
  456. {
  457. if (state == GUI_STATE_DISABLED)
  458. {
  459. if ((startIndex + i) == itemSelected)
  460. {
  461. DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED)), guiAlpha));
  462. DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), guiAlpha));
  463. }
  464. // TODO: Draw full file info line: icon+name | size | type | modTime
  465. GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED)), guiAlpha));
  466. }
  467. else
  468. {
  469. if ((startIndex + i) == itemSelected)
  470. {
  471. // Draw item selected
  472. DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED)), guiAlpha));
  473. DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), guiAlpha));
  474. GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED)), guiAlpha));
  475. }
  476. else if ((startIndex + i) == itemFocused)
  477. {
  478. // Draw item focused
  479. DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED)), guiAlpha));
  480. DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), guiAlpha));
  481. GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED)), guiAlpha));
  482. }
  483. else
  484. {
  485. // Draw item normal
  486. GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL)), guiAlpha));
  487. }
  488. }
  489. // Update item rectangle y position for next item
  490. itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING));
  491. }
  492. if (useScrollBar)
  493. {
  494. Rectangle scrollBarBounds = {
  495. bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH),
  496. bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH),
  497. bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH)
  498. };
  499. // Calculate percentage of visible items and apply same percentage to scrollbar
  500. float percentVisible = (float)(endIndex - startIndex)/count;
  501. float sliderSize = bounds.height*percentVisible;
  502. int prevSliderSize = GuiGetStyle(SCROLLBAR, SLIDER_WIDTH); // Save default slider size
  503. int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed
  504. GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, sliderSize); // Change slider size
  505. GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed
  506. startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems);
  507. GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default
  508. GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, prevSliderSize); // Reset slider size to default
  509. }
  510. //--------------------------------------------------------------------
  511. if (focus != NULL) *focus = itemFocused;
  512. if (scrollIndex != NULL) *scrollIndex = startIndex;
  513. *active = itemSelected;
  514. return result;
  515. }
  516. #endif // USE_CUSTOM_LISTVIEW_FILEINFO
  517. #endif // GUI_FILE_DIALOG_IMPLEMENTATION