Skip to content

Instantly share code, notes, and snippets.

@nicofretti
Created September 13, 2022 19:09
Show Gist options
  • Save nicofretti/f2fb18dfbe28c029df64219f676b3ea0 to your computer and use it in GitHub Desktop.
Save nicofretti/f2fb18dfbe28c029df64219f676b3ea0 to your computer and use it in GitHub Desktop.
Fix missing file list
/*******************************************************************************************
*
* FileDialog v1.2 - Modal file dialog to open/save files
*
* MODULE USAGE:
* #define GUI_FILE_DIALOG_IMPLEMENTATION
* #include "gui_file_dialog.h"
*
* INIT: GuiFileDialogState state = InitGuiFileDialog();
* DRAW: GuiFileDialog(&state);
*
* NOTE: This module depends on some raylib file system functions:
* - LoadDirectoryFiles()
* - UnloadDirectoryFiles()
* - GetWorkingDirectory()
* - DirectoryExists()
* - FileExists()
*
* LICENSE: zlib/libpng
*
* Copyright (c) 2019-2022 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.
*
*********************************************************************************************
* Edited solutions for the following issues:
* - Filter system not working
* - File list didn't display correctly
* - Solution for c++ compatibility
*
* Example usage:
*
* InitGuiFileDialog(550, 500, "path", false,".extension");
*
* Now is available to set the filter directly in the constructor
*
* Author: nicof
**********************************************************************************************/
#include "raylib.h"
// WARNING: raygui implementation is expected to be defined before including this header
#undef RAYGUI_IMPLEMENTATION
#include "raygui.h"
#ifndef GUI_FILE_DIALOG_H
#define GUI_FILE_DIALOG_H
typedef struct {
Vector2 position;
Vector2 size;
bool fileDialogActive;
bool dirPathEditMode;
char dirPathText[256];
int filesListScrollIndex;
bool filesListEditMode;
int filesListActive;
bool fileNameEditMode;
char fileNameText[256];
bool SelectFilePressed;
bool CancelFilePressed;
int fileTypeActive;
int itemFocused;
// Custom state variables (depend on development software)
// NOTE: This variables should be added manually if required
FilePathList dirFiles;
char filterExt[256];
char dirPathTextCopy[256];
char fileNameTextCopy[256];
// Edited - nicof: Save only the file name to be displayed on the file name text box
char realFileName[256];
int prevFilesListActive;
bool saveFileMode;
std::shared_ptr<spdlog::logger> logger;
} GuiFileDialogState;
#ifdef __cplusplus
extern "C" { // Prevents name mangling of functions
#endif
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
//...
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// ...
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
//...
//----------------------------------------------------------------------------------
// Module Functions Declaration
//----------------------------------------------------------------------------------
GuiFileDialogState InitGuiFileDialog(int width, int height, const char *initPath, bool active, std::shared_ptr<spdlog::logger> logger);
void GuiFileDialog(GuiFileDialogState *state);
#ifdef __cplusplus
}
#endif
#endif // GUI_FILE_DIALOG_H
/***********************************************************************************
*
* GUI_FILE_DIALOG IMPLEMENTATION
*
************************************************************************************/
#if defined(GUI_FILE_DIALOG_IMPLEMENTATION)
#include "raygui.h"
#include <cstring> // Required for: strcpy()
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
#define MAX_DIRECTORY_FILES 1024
#define MAX_DIR_PATH_LENGTH 1024
//----------------------------------------------------------------------------------
// 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;
#endif
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
FileInfo *dirFilesIcon = NULL;
//----------------------------------------------------------------------------------
// Internal Module Functions Definition
//----------------------------------------------------------------------------------
// Read files in new path
static void ReloadDirectoryFiles(GuiFileDialogState *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
//----------------------------------------------------------------------------------
GuiFileDialogState InitGuiFileDialog(int width, int height, const char *initPath, bool active, const char *fileExt)
{
GuiFileDialogState state = { 0 };
// Default dialog size is 440x310
state.size.x = width == -1 ? 440 : width;
state.size.y = height == -1 ? 310 : height;
state.position = (Vector2){ GetScreenWidth()/2 - state.size.x/2, GetScreenHeight()/2 - state.size.y/2 };
state.fileDialogActive = active;
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());
strcpy(state.dirPathTextCopy, state.dirPathText);
strcpy(state.fileNameTextCopy, state.fileNameText);
strcpy(state.filterExt, fileExt);
state.dirFiles.count = 0;
return state;
}
// Update and draw file dialog
void GuiFileDialog(GuiFileDialogState *state)
{
if (state->fileDialogActive)
{
const int winWidth = state->size.x;
const int winHeight = state->size.y;
// 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_DIR_PATH_LENGTH, 1); // Max file name length
}
if (state->dirFiles.paths == NULL)
{
state->dirFiles = LoadDirectoryFilesEx(state->dirPathText, state->filterExt, false);
for(int f = 0; f < state->dirFiles.count; f++)
{
if (strcmp(state->fileNameText, state->dirFiles.paths[f]) == 0)
{
if (state->filesListActive != f) state->filesListScrollIndex = state->filesListActive = f; // Make it active and visible only on first call
break;
}
}
}
//------------------------------------------------------------------------------------
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Fade(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)), 0.85f));
state->fileDialogActive = !GuiWindowBox((Rectangle){ state->position.x + 0, state->position.y + 0, winWidth, winHeight }, "#198# Select File Dialog");
// Button back
if (GuiButton((Rectangle){ state->position.x + winWidth - 50, state->position.y + 35, 40, 25 }, "< ..")) // || IsKeyReleased(KEY_DPAD_Y))
{
// Move dir path one level up
strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText));
// Reload directory files (frees previous list)
ReloadDirectoryFiles(state);
state->filesListActive = -1;
strcpy(state->fileNameText, "\0");
strcpy(state->fileNameTextCopy, state->fileNameText);
}
if (GuiTextBox((Rectangle){ state->position.x + 10, state->position.y + 35, winWidth - 65, 25 }, state->dirPathText, 256, 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;
}
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);
// TODO: ListViewElements should be aligned left
# if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
FileInfo fileInfo;
state->filesListActive = GuiListViewFiles((Rectangle){ state->position.x + 10, state->position.y + 70, winWidth - 20, winHeight - 135 }, fileInfo, state->dirFiles.count, &state->itemFocused, &state->filesListScrollIndex, state->filesListActive);
# else
// Edited: nicof - Fix filenames errors
const char** names =
static_cast<const char **>(malloc(sizeof(char *) * state->dirFiles.count));
for(int i =0; i<state->dirFiles.count; i++)
{
names[i] = GetFileName(state->dirFiles.paths[i]);
}
//state->filesListActive = GuiListViewEx((Rectangle){ state->position.x + 10, state->position.y + 70, winWidth - 20, winHeight - 135 }, (const char**)dirFilesIcon, state->dirFiles.count, &state->itemFocused, &state->filesListScrollIndex, state->filesListActive);
state->filesListActive = GuiListViewEx((Rectangle){ state->position.x + 10, state->position.y + 70, winWidth - 20, winHeight - 135 },
names, state->dirFiles.count, &state->itemFocused, &state->filesListScrollIndex, state->filesListActive);
# endif
GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, prevTextAlignment);
GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, prevElementsHeight);
if ((state->filesListActive >= 0) && (state->filesListActive != state->prevFilesListActive))
//&& (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_DPAD_A)))
{
strcpy(state->fileNameText, 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;
}
GuiLabel((Rectangle){ state->position.x + 10, state->position.y + winHeight - 60, 68, 25 }, "File name:");
// Edited: nicof - Replace filename in the textbox
strcpy(state->realFileName,GetFileName(state->fileNameText));
//if (GuiTextBox((Rectangle){ state->position.x + 75, state->position.y + winHeight - 60, winWidth - 200, 25 }, state->fileNameText, 128, state->fileNameEditMode))
if (GuiTextBox((Rectangle){ state->position.x + 75, state->position.y + winHeight - 60, winWidth - 200, 25 }, state->realFileName, 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 (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;
}
state->fileTypeActive = GuiComboBox((Rectangle){ state->position.x + 75, state->position.y + winHeight - 30, winWidth - 200, 25 }, state->filterExt, state->fileTypeActive);
GuiLabel((Rectangle){ state->position.x + 10, state->position.y + winHeight - 30, 68, 25 }, "File filter:");
state->SelectFilePressed = GuiButton((Rectangle){ state->position.x + winWidth - 120, state->position.y + winHeight - 60, 110,
#ifdef PLATFORM_DESKTOP
25
#else
25 + 30
#endif
}, "Select");// || IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_DPAD_A);
if (state->SelectFilePressed) state->fileDialogActive = false;
#ifdef PLATFORM_DESKTOP
if (GuiButton((Rectangle){ state->position.x + winWidth - 120, state->position.y + winHeight - 30, 110, 25 }, "Quit")) state->fileDialogActive = false;
#endif
// File dialog has been closed!
if (!state->fileDialogActive)
{
// Free dirFiles memory
for (int i = 0; i < MAX_DIRECTORY_FILES; i++)
{
RL_FREE(dirFilesIcon[i]);
}
UnloadDirectoryFiles(state->dirFiles);
state->dirFiles.count = 0;
state->dirFiles.capacity = 0;
state->dirFiles.paths = NULL;
RL_FREE(dirFilesIcon);
dirFilesIcon = 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(GuiFileDialogState *state)
{
UnloadDirectoryFiles(state->dirFiles);
state->dirFiles = LoadDirectoryFilesEx(state->dirPathText, state->filterExt, false);
state->itemFocused = 0;
}
#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)
{
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;
return itemSelected;
}
#endif // USE_CUSTOM_LISTVIEW_FILEINFO
#endif // GUI_FILE_DIALOG_IMPLEMENTATION
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment