2
0

gui_file_dialog.h 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. /*******************************************************************************************
  2. *
  3. * FileDialog v1.1 - Modal file dialog to open/save files
  4. *
  5. * MODULE USAGE:
  6. * #define GUI_FILE_DIALOG_IMPLEMENTATION
  7. * #include "gui_file_dialog.h"
  8. *
  9. * INIT: GuiFileDialogState state = InitGuiFileDialog();
  10. * DRAW: GuiFileDialog(&state);
  11. *
  12. * NOTE: This module depends on some raylib file system functions:
  13. * - GetDirectoryFiles()
  14. * - ClearDirectoryFiles()
  15. * - GetWorkingDirectory()
  16. * - DirectoryExists()
  17. * - FileExists()
  18. *
  19. * LICENSE: zlib/libpng
  20. *
  21. * Copyright (c) 2019-2020 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. // WARNING: raygui implementation is expected to be defined before including this header
  41. #undef RAYGUI_IMPLEMENTATION
  42. #include "../../src/raygui.h"
  43. #ifndef GUI_FILE_DIALOG_H
  44. #define GUI_FILE_DIALOG_H
  45. typedef struct {
  46. Vector2 position;
  47. Vector2 size;
  48. bool fileDialogActive;
  49. bool dirPathEditMode;
  50. char dirPathText[256];
  51. int filesListScrollIndex;
  52. bool filesListEditMode;
  53. int filesListActive;
  54. bool fileNameEditMode;
  55. char fileNameText[256];
  56. bool SelectFilePressed;
  57. bool CancelFilePressed;
  58. int fileTypeActive;
  59. int itemFocused;
  60. // Custom state variables (depend on development software)
  61. // NOTE: This variables should be added manually if required
  62. char **dirFiles;
  63. int dirFilesCount;
  64. char filterExt[256];
  65. char dirPathTextCopy[256];
  66. char fileNameTextCopy[256];
  67. int prevFilesListActive;
  68. } GuiFileDialogState;
  69. #ifdef __cplusplus
  70. extern "C" { // Prevents name mangling of functions
  71. #endif
  72. //----------------------------------------------------------------------------------
  73. // Defines and Macros
  74. //----------------------------------------------------------------------------------
  75. //...
  76. //----------------------------------------------------------------------------------
  77. // Types and Structures Definition
  78. //----------------------------------------------------------------------------------
  79. // ...
  80. //----------------------------------------------------------------------------------
  81. // Global Variables Definition
  82. //----------------------------------------------------------------------------------
  83. //...
  84. //----------------------------------------------------------------------------------
  85. // Module Functions Declaration
  86. //----------------------------------------------------------------------------------
  87. GuiFileDialogState InitGuiFileDialog(int width, int height, const char *initPath, bool active);
  88. void GuiFileDialog(GuiFileDialogState *state);
  89. #ifdef __cplusplus
  90. }
  91. #endif
  92. #endif // GUI_FILE_DIALOG_H
  93. /***********************************************************************************
  94. *
  95. * GUI_FILE_DIALOG IMPLEMENTATION
  96. *
  97. ************************************************************************************/
  98. #if defined(GUI_FILE_DIALOG_IMPLEMENTATION)
  99. #include "../../src/raygui.h"
  100. #include <string.h> // Required for: strcpy()
  101. //----------------------------------------------------------------------------------
  102. // Defines and Macros
  103. //----------------------------------------------------------------------------------
  104. #define MAX_DIRECTORY_FILES 1024
  105. #define MAX_DIR_PATH_LENGTH 1024
  106. //----------------------------------------------------------------------------------
  107. // Types and Structures Definition
  108. //----------------------------------------------------------------------------------
  109. #if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
  110. // Detailed file info type
  111. typedef struct FileInfo {
  112. const char *name;
  113. int size;
  114. int modTime;
  115. int type;
  116. int icon;
  117. } FileInfo;
  118. #else
  119. // Filename only
  120. typedef char *FileInfo;
  121. #endif
  122. //----------------------------------------------------------------------------------
  123. // Global Variables Definition
  124. //----------------------------------------------------------------------------------
  125. FileInfo *dirFilesIcon = NULL;
  126. //----------------------------------------------------------------------------------
  127. // Internal Module Functions Definition
  128. //----------------------------------------------------------------------------------
  129. // Read all filenames from directory (supported file types)
  130. static char **ReadDirectoryFiles(const char *dir, int *filesCount, char *filterExt);
  131. #if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
  132. // List View control for files info with extended parameters
  133. static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int active);
  134. #endif
  135. //----------------------------------------------------------------------------------
  136. // Module Functions Definition
  137. //----------------------------------------------------------------------------------
  138. GuiFileDialogState InitGuiFileDialog(int width, int height, const char *initPath, bool active)
  139. {
  140. GuiFileDialogState state = { 0 };
  141. // Default dialog size is 440x310
  142. state.size.x = width == -1 ? 440 : width;
  143. state.size.y = height == -1 ? 310 : height;
  144. state.position = (Vector2){ GetScreenWidth()/2 - state.size.x/2, GetScreenHeight()/2 - state.size.y/2 };
  145. state.fileDialogActive = active;
  146. state.dirPathEditMode = false;
  147. state.filesListActive = -1;
  148. state.prevFilesListActive = state.filesListActive;
  149. state.filesListScrollIndex = 0;
  150. state.fileNameEditMode = false;
  151. state.SelectFilePressed = false;
  152. state.CancelFilePressed = false;
  153. state.fileTypeActive = 0;
  154. strcpy(state.fileNameText, "\0");
  155. // Custom variables initialization
  156. if (initPath && DirectoryExists(initPath))
  157. {
  158. strcpy(state.dirPathText, initPath);
  159. }
  160. else if (initPath && FileExists(initPath))
  161. {
  162. strcpy(state.dirPathText, GetDirectoryPath(initPath));
  163. strcpy(state.fileNameText, GetFileName(initPath));
  164. }
  165. else strcpy(state.dirPathText, GetWorkingDirectory());
  166. strcpy(state.dirPathTextCopy, state.dirPathText);
  167. strcpy(state.fileNameTextCopy, state.fileNameText);
  168. strcpy(state.filterExt, "all");
  169. state.dirFilesCount = 0;
  170. state.dirFiles = NULL; // NOTE: Loaded lazily on window active
  171. return state;
  172. }
  173. // Read files in new path
  174. static void FD_RELOAD_DIRPATH(GuiFileDialogState *state)
  175. {
  176. for (int i = 0; i < state->dirFilesCount; i++) RL_FREE(state->dirFiles[i]);
  177. RL_FREE(state->dirFiles);
  178. state->dirFiles = ReadDirectoryFiles(state->dirPathText, &state->dirFilesCount, state->filterExt);
  179. state->itemFocused = 0;
  180. }
  181. // Update and draw file dialog
  182. void GuiFileDialog(GuiFileDialogState *state)
  183. {
  184. if (state->fileDialogActive)
  185. {
  186. const int winWidth = state->size.x;
  187. const int winHeight = state->size.y;
  188. // Load dirFilesIcon and state->dirFiles lazily on windows open
  189. // NOTE: they are automatically unloaded at fileDialog closing
  190. //------------------------------------------------------------------------------------
  191. if (dirFilesIcon == NULL)
  192. {
  193. dirFilesIcon = (FileInfo *)RL_MALLOC(MAX_DIRECTORY_FILES*sizeof(FileInfo)); // Max files to read
  194. for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesIcon[i] = (char *)calloc(MAX_DIR_PATH_LENGTH, 1); // Max file name length
  195. }
  196. if (state->dirFiles == NULL)
  197. {
  198. state->dirFiles = ReadDirectoryFiles(state->dirPathText, &state->dirFilesCount, state->filterExt);
  199. for(int f = 0; f < state->dirFilesCount; f++)
  200. {
  201. if (strcmp(state->fileNameText, state->dirFiles[f]) == 0)
  202. {
  203. if (state->filesListActive != f) state->filesListScrollIndex = state->filesListActive = f; // make it active and visible only on first call
  204. break;
  205. }
  206. }
  207. }
  208. //------------------------------------------------------------------------------------
  209. DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Fade(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)), 0.85f));
  210. state->fileDialogActive = !GuiWindowBox((Rectangle){ state->position.x + 0, state->position.y + 0, winWidth, winHeight }, "#198#LuaJIT | Select File Dialog");
  211. if (GuiButton((Rectangle){ state->position.x + winWidth - 50, state->position.y + 35, 40, 25 }, "< .."))// || IsKeyReleased(KEY_DPAD_Y))
  212. {
  213. // Move dir path one level up
  214. strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText));
  215. // RL_FREE previous dirFiles (reloaded by ReadDirectoryFiles())
  216. FD_RELOAD_DIRPATH(state);
  217. state->filesListActive = -1;
  218. strcpy(state->fileNameText, "\0");
  219. strcpy(state->fileNameTextCopy, state->fileNameText);
  220. }
  221. if (GuiTextBox((Rectangle){ state->position.x + 10, state->position.y + 35, winWidth - 65, 25 }, state->dirPathText, 256, state->dirPathEditMode))
  222. {
  223. if (state->dirPathEditMode)
  224. {
  225. // Verify if a valid path has been introduced
  226. if (DirectoryExists(state->dirPathText))
  227. {
  228. // RL_FREE previous dirFiles (reloaded by ReadDirectoryFiles())
  229. FD_RELOAD_DIRPATH(state);
  230. strcpy(state->dirPathTextCopy, state->dirPathText);
  231. }
  232. else strcpy(state->dirPathText, state->dirPathTextCopy);
  233. }
  234. state->dirPathEditMode = !state->dirPathEditMode;
  235. }
  236. int prevTextAlignment = GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT);
  237. int prevElementsHeight = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT);
  238. GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_LEFT);
  239. GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 24);
  240. // TODO: ListViewElements should be aligned left
  241. # if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
  242. FileInfo fileInfo;
  243. state->filesListActive = GuiListViewFiles((Rectangle){ state->position.x + 10, state->position.y + 70, winWidth - 20, winHeight - 135 }, fileInfo, state->dirFilesCount, &state->itemFocused, &state->filesListScrollIndex, state->filesListActive);
  244. # else
  245. state->filesListActive = GuiListViewEx((Rectangle){ state->position.x + 10, state->position.y + 70, winWidth - 20, winHeight - 135 }, (const char**)dirFilesIcon, state->dirFilesCount, &state->itemFocused, &state->filesListScrollIndex, state->filesListActive);
  246. # endif
  247. GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, prevTextAlignment);
  248. GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, prevElementsHeight);
  249. if ((state->filesListActive >= 0) && (state->filesListActive != state->prevFilesListActive))
  250. //&& (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_DPAD_A)))
  251. {
  252. strcpy(state->fileNameText, state->dirFiles[state->filesListActive]);
  253. if (DirectoryExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText)))
  254. {
  255. if (TextIsEqual(state->fileNameText, "..")) strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText));
  256. else strcpy(state->dirPathText, TextFormat("%s/%s", strcmp(state->dirPathText, "/")==0 ? "" : state->dirPathText, state->fileNameText));
  257. strcpy(state->dirPathTextCopy, state->dirPathText);
  258. // RL_FREE previous dirFiles (reloaded by ReadDirectoryFiles())
  259. FD_RELOAD_DIRPATH(state);
  260. strcpy(state->dirPathTextCopy, state->dirPathText);
  261. state->filesListActive = -1;
  262. strcpy(state->fileNameText, "\0");
  263. strcpy(state->fileNameTextCopy, state->fileNameText);
  264. }
  265. state->prevFilesListActive = state->filesListActive;
  266. }
  267. GuiLabel((Rectangle){ state->position.x + 10, state->position.y + winHeight - 60, 68, 25 }, "File name:");
  268. if (GuiTextBox((Rectangle){ state->position.x + 75, state->position.y + winHeight - 60, winWidth - 200, 25 }, state->fileNameText, 128, state->fileNameEditMode))
  269. {
  270. if (*state->fileNameText)
  271. {
  272. // Verify if a valid filename has been introduced
  273. if (FileExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText)))
  274. {
  275. // Select filename from list view
  276. for (int i = 0; i < state->dirFilesCount; i++)
  277. {
  278. if (TextIsEqual(state->fileNameText, state->dirFiles[i]))
  279. {
  280. state->filesListActive = i;
  281. strcpy(state->fileNameTextCopy, state->fileNameText);
  282. break;
  283. }
  284. }
  285. }
  286. else
  287. {
  288. strcpy(state->fileNameText, state->fileNameTextCopy);
  289. }
  290. }
  291. state->fileNameEditMode = !state->fileNameEditMode;
  292. }
  293. state->fileTypeActive = GuiComboBox((Rectangle){ state->position.x + 75, state->position.y + winHeight - 30, winWidth - 200, 25 }, "All files", state->fileTypeActive);
  294. GuiLabel((Rectangle){ state->position.x + 10, state->position.y + winHeight - 30, 68, 25 }, "File filter:");
  295. state->SelectFilePressed = GuiButton((Rectangle){ state->position.x + winWidth - 120, state->position.y + winHeight - 60, 110,
  296. #ifdef PLATFORM_DESKTOP
  297. 25
  298. #else
  299. 25+30
  300. #endif
  301. }, "Select");// || IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_DPAD_A);
  302. if (state->SelectFilePressed) state->fileDialogActive = false;
  303. #ifdef PLATFORM_DESKTOP
  304. if (GuiButton((Rectangle){ state->position.x + winWidth - 120, state->position.y + winHeight - 30, 110, 25 }, "Quit")) state->fileDialogActive = false;
  305. #endif
  306. // File dialog has been closed!
  307. if (!state->fileDialogActive)
  308. {
  309. // RL_FREE dirFiles memory
  310. for (int i = 0; i < state->dirFilesCount; i++)
  311. {
  312. RL_FREE(state->dirFiles[i]);
  313. RL_FREE(dirFilesIcon[i]);
  314. }
  315. RL_FREE(state->dirFiles);
  316. RL_FREE(dirFilesIcon);
  317. dirFilesIcon = NULL;
  318. state->dirFiles = NULL;
  319. }
  320. }
  321. }
  322. // Read all filenames from directory (supported file types)
  323. static inline int _file_comp(const char *d1, const char *d2, const char *dir)
  324. {
  325. const bool b1 = DirectoryExists(TextFormat("%s/%s", dir, d1));
  326. const bool b2 = DirectoryExists(TextFormat("%s/%s", dir, d2));
  327. if (b1 && !b2) return -1;
  328. if (!b1 && b2) return 1;
  329. if (!FileExists(TextFormat("%s/%s", dir, d1))) return 1;
  330. if (!FileExists(TextFormat("%s/%s", dir, d2))) return -1;
  331. return strcmp(d1, d2);
  332. }
  333. static char **ReadDirectoryFiles(const char *dir, int *filesCount, char *filterExt)
  334. {
  335. int validFilesCount = 0;
  336. char **validFiles = (char **)RL_MALLOC(MAX_DIRECTORY_FILES*sizeof(char *)); // Max files to read
  337. for (int i = 0; i < MAX_DIRECTORY_FILES; i++) validFiles[i] = (char *)RL_MALLOC(MAX_DIR_PATH_LENGTH); // Max file name length
  338. int filterExtCount = 0;
  339. const char **extensions = GuiTextSplit(filterExt, &filterExtCount, NULL);
  340. bool filterExtensions = true;
  341. int dirFilesCount = 0;
  342. char **files = GetDirectoryFiles(dir, &dirFilesCount);
  343. // Sort files and directories: dir by name + files by name
  344. // https://en.wikibooks.org/wiki/Algorithm_Implementation/Sorting/Quicksort#C
  345. if (dirFilesCount > 1)
  346. {
  347. const int MAX = 64;
  348. unsigned int left = 0, stack[MAX], pos = 0, seed = rand(), len = dirFilesCount;
  349. for ( ; ; )
  350. {
  351. for (; left+1 < len; len++) /* sort left to len-1 */
  352. {
  353. if (pos == MAX) len = stack[pos = 0]; /* stack overflow, reset */
  354. char *pivot = files[left+seed%(len-left)]; /* pick random pivot */
  355. seed = seed*69069+1; /* next pseudorandom number */
  356. stack[pos++] = len; /* sort right part later */
  357. for (unsigned int right = left-1; ; ) /* inner loop: partitioning */
  358. {
  359. while (_file_comp(files[++right], pivot, dir) < 0);/* look for greater element */
  360. while (_file_comp(pivot, files[--len], dir) < 0); /* look for smaller element */
  361. if (right >= len) break; /* partition point found? */
  362. char *temp = files[right];
  363. files[right] = files[len]; /* the only swap */
  364. files[len] = temp;
  365. } /* partitioned, continue left part */
  366. }
  367. if (pos == 0) break; /* stack empty? */
  368. left = len; /* left to right is sorted */
  369. len = stack[--pos]; /* get next range to sort */
  370. }
  371. }
  372. if (TextIsEqual(extensions[0], "all")) filterExtensions = false;
  373. for (int i = 0; (i < dirFilesCount) && (validFilesCount < MAX_DIRECTORY_FILES); i++)
  374. {
  375. if (TextIsEqual(files[i], ".")) continue;
  376. if (!filterExtensions)
  377. {
  378. strncpy(validFiles[validFilesCount], files[i], MAX_DIR_PATH_LENGTH);
  379. // Only filter files by extensions, directories should be available
  380. if (DirectoryExists(TextFormat("%s/%s", dir, files[i]))) strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 1, files[i]));
  381. else
  382. {
  383. // TODO: Assign custom filetype icons depending on file extension (image, audio, text, video, models...)
  384. if (IsFileExtension(files[i], ".png")) strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 12, files[i]));
  385. else strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 10, files[i]));
  386. }
  387. validFilesCount++;
  388. }
  389. else
  390. {
  391. for (int j = 0; j < filterExtCount; j++)
  392. {
  393. // Check file type extensions supported
  394. // NOTE: We just store valid files list
  395. if (IsFileExtension(files[i], extensions[j]))
  396. {
  397. // TODO: Assign custom filetype icons depending on file extension (image, audio, text, video, models...)
  398. if (IsFileExtension(files[i], ".png")) strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 12, files[i]));
  399. else strcpy(dirFilesIcon[validFilesCount], TextFormat("#%i#%s", 10, files[i]));
  400. validFilesCount++;
  401. }
  402. }
  403. }
  404. }
  405. ClearDirectoryFiles();
  406. *filesCount = validFilesCount;
  407. return validFiles;
  408. }
  409. #if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
  410. // List View control for files info with extended parameters
  411. static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int active)
  412. {
  413. GuiControlState state = guiState;
  414. int itemFocused = (focus == NULL)? -1 : *focus;
  415. int itemSelected = active;
  416. // Check if we need a scroll bar
  417. bool useScrollBar = false;
  418. if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING))*count > bounds.height) useScrollBar = true;
  419. // Define base item rectangle [0]
  420. Rectangle itemBounds = { 0 };
  421. itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING);
  422. itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH);
  423. itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) - GuiGetStyle(DEFAULT, BORDER_WIDTH);
  424. itemBounds.height = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT);
  425. if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH);
  426. // Get items on the list
  427. int visibleItems = bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING));
  428. if (visibleItems > count) visibleItems = count;
  429. int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex;
  430. if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0;
  431. int endIndex = startIndex + visibleItems;
  432. // Update control
  433. //--------------------------------------------------------------------
  434. if ((state != GUI_STATE_DISABLED) && !guiLocked)
  435. {
  436. Vector2 mousePoint = GetMousePosition();
  437. // Check mouse inside list view
  438. if (CheckCollisionPointRec(mousePoint, bounds))
  439. {
  440. state = GUI_STATE_FOCUSED;
  441. // Check focused and selected item
  442. for (int i = 0; i < visibleItems; i++)
  443. {
  444. if (CheckCollisionPointRec(mousePoint, itemBounds))
  445. {
  446. itemFocused = startIndex + i;
  447. if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) itemSelected = startIndex + i;
  448. break;
  449. }
  450. // Update item rectangle y position for next item
  451. itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING));
  452. }
  453. if (useScrollBar)
  454. {
  455. int wheelMove = GetMouseWheelMove();
  456. startIndex -= wheelMove;
  457. if (startIndex < 0) startIndex = 0;
  458. else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems;
  459. endIndex = startIndex + visibleItems;
  460. if (endIndex > count) endIndex = count;
  461. }
  462. }
  463. else itemFocused = -1;
  464. // Reset item rectangle y to [0]
  465. itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH);
  466. }
  467. //--------------------------------------------------------------------
  468. // Draw control
  469. //--------------------------------------------------------------------
  470. DrawRectangleRec(bounds, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background
  471. DrawRectangleLinesEx(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), guiAlpha));
  472. // TODO: Draw list view header with file sections: icon+name | size | type | modTime
  473. // Draw visible items
  474. for (int i = 0; i < visibleItems; i++)
  475. {
  476. if (state == GUI_STATE_DISABLED)
  477. {
  478. if ((startIndex + i) == itemSelected)
  479. {
  480. DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED)), guiAlpha));
  481. DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), guiAlpha));
  482. }
  483. // TODO: Draw full file info line: icon+name | size | type | modTime
  484. GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED)), guiAlpha));
  485. }
  486. else
  487. {
  488. if ((startIndex + i) == itemSelected)
  489. {
  490. // Draw item selected
  491. DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED)), guiAlpha));
  492. DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), guiAlpha));
  493. GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED)), guiAlpha));
  494. }
  495. else if ((startIndex + i) == itemFocused)
  496. {
  497. // Draw item focused
  498. DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED)), guiAlpha));
  499. DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), guiAlpha));
  500. GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED)), guiAlpha));
  501. }
  502. else
  503. {
  504. // Draw item normal
  505. GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL)), guiAlpha));
  506. }
  507. }
  508. // Update item rectangle y position for next item
  509. itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING));
  510. }
  511. if (useScrollBar)
  512. {
  513. Rectangle scrollBarBounds = {
  514. bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH),
  515. bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH),
  516. bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH)
  517. };
  518. // Calculate percentage of visible items and apply same percentage to scrollbar
  519. float percentVisible = (float)(endIndex - startIndex)/count;
  520. float sliderSize = bounds.height*percentVisible;
  521. int prevSliderSize = GuiGetStyle(SCROLLBAR, SLIDER_WIDTH); // Save default slider size
  522. int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed
  523. GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, sliderSize); // Change slider size
  524. GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed
  525. startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems);
  526. GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default
  527. GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, prevSliderSize); // Reset slider size to default
  528. }
  529. //--------------------------------------------------------------------
  530. if (focus != NULL) *focus = itemFocused;
  531. if (scrollIndex != NULL) *scrollIndex = startIndex;
  532. return itemSelected;
  533. }
  534. #endif // USE_CUSTOM_LISTVIEW_FILEINFO
  535. #endif // GUI_FILE_DIALOG_IMPLEMENTATION