diff 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
line wrap: on
line diff
--- /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 <string.h>     // 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