626 lines
26 KiB
C
626 lines
26 KiB
C
/*******************************************************************************************
|
|
*
|
|
* 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 "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 "../../src/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 (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 (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
|