From acaa801ea2c7fa3c909ca4f64759696fff002b1a Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 22 Jun 2024 13:21:09 -0700 Subject: Basic navigation. --- CMakeLists.txt | 1 + src/xplorer.c | 215 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 188 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85e1921..655eb9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ target_include_directories(xplorer PRIVATE target_link_libraries(xplorer PRIVATE SDL2-static + filesystem tinydir ui) diff --git a/src/xplorer.c b/src/xplorer.c index 8a190af..5e69d82 100644 --- a/src/xplorer.c +++ b/src/xplorer.c @@ -5,14 +5,14 @@ #include #include +#include #include #include #include -#include static const char* WindowTitle = "XPLORER"; -static const int DefaultWidth = 960; -static const int DefaultHeight = 600; +static const int DefaultWidth = 1440; +static const int DefaultHeight = 900; // #define DEBUG_EVENT_LOOP 1 @@ -26,47 +26,162 @@ typedef struct State { SDL_Window* window; uiFrame* frame; uiTable* table; - string current_dir; + path current_dir; } State; -void SetDirectory(State* state, string path) { +uiMouseButton ToUiButton(Uint8 button); + +void CreateUi(State* state) { assert(state); - state->current_dir = path; + uiFrame* frame = uiMakeFrame(); - uiTable* table = state->table; + const char* header[] = {"Name", "Size", "Modified"}; + uiTable* table = uiMakeTable(0, sizeof(header) / sizeof(char*), header); assert(table); + uiWidgetSetParent(uiMakeTablePtr(table), uiMakeFramePtr(frame)); - tinydir_dir dir; - tinydir_open(&dir, string_data(path)); - while (dir.has_next) { - tinydir_file file; - tinydir_readfile(&dir, &file); + // uiLabel* label = uiMakeLabel("Hello world, what is going on!?"); + // uiWidgetSetParent(label, frame); - const string file_size = string_format_size(file._s.st_size); + state->frame = frame; + state->table = table; +} - const char* row[3] = {file.name, string_data(file_size), ""}; - uiTableAddRow(table, row); +int compare_files(const void* _a, const void* _b) { + assert(_a); + assert(_b); - tinydir_next(&dir); + const tinydir_file* a = _a; + const tinydir_file* b = _b; + + for (size_t i = 0; + (i < _TINYDIR_FILENAME_MAX) && (a->name[i] != 0) && (b->name[i] != 0); + ++i) { + if (a->name[i] < b->name[i]) { + return -1; + } else if (a->name[i] > b->name[i]) { + return 1; + } } + + return 0; } -void CreateUi(State* state) { +size_t GetFileCount(path directory) { + size_t count = 0; + tinydir_dir dir; + if (tinydir_open(&dir, path_cstr(directory)) == 0) { + for (count = 0; dir.has_next; ++count) { + tinydir_next(&dir); + } + } + return count; +} + +bool SetDirectory(State* state, path directory) { assert(state); - uiFrame* frame = uiMakeFrame(); + bool directory_changed = false; - const char* header[] = {"Name", "Size", "Modified"}; - uiTable* table = uiMakeTable(0, sizeof(header) / sizeof(char*), header); + tinydir_dir dir; + if (tinydir_open(&dir, path_cstr(directory)) == 0) { + const size_t count = GetFileCount(directory); + + tinydir_file* files = calloc(count, sizeof(tinydir_file)); + if (!files) { + return false; + } + + for (size_t i = 0; dir.has_next; ++i) { + assert(i < count); + tinydir_readfile(&dir, &files[i]); + tinydir_next(&dir); + } + + qsort(files, count, sizeof(files[0]), compare_files); + + uiTable* table = state->table; + assert(table); + + uiTableClear(table); + for (size_t i = 0; i < count; ++i) { + tinydir_file file = files[i]; + + const string file_size = string_format_size(file._s.st_size); + + const char* row[3] = {file.name, string_data(file_size), ""}; + uiTableAddRow(table, row); + } + + free(files); + + if (!path_empty(state->current_dir)) { + path_del(&state->current_dir); + } + state->current_dir = directory; + directory_changed = true; + } + + return directory_changed; +} + +bool OnFileTableClick( + State* state, uiTable* table, const uiTableClickEvent* event) { + assert(state); assert(table); - uiWidgetSetParent(uiMakeTablePtr(table), uiMakeFramePtr(frame)); - // uiLabel* label = uiMakeLabel("Hello world, what is going on!?"); - // uiWidgetSetParent(label, frame); + if (event->col == 0) { // Clicked the file/directory name. + // TODO: Think more about uiPtr. Do we need uiConstPtr? + // Ideally: const uiLabel* label = uiGetPtr(uiTableGet(...)); + // i.e., no checks on the client code; all checks in library code. + const uiLabel* label = + (const uiLabel*)uiTableGet(table, event->row, event->col); + assert(uiWidgetGetType((const uiWidget*)label) == uiTypeLabel); + + printf("Click: %d,%d: %s\n", event->row, event->col, uiLabelGetText(label)); + + // TODO: Handle '.' and '..' better. Define a path concatenation function. + path child_dir = path_new(uiLabelGetText(label)); + path new_dir = path_concat(state->current_dir, child_dir); + const bool result = SetDirectory(state, new_dir); + if (!result) { + path_del(&new_dir); + } + path_del(&child_dir); + return result; + } + return false; +} - state->frame = frame; - state->table = table; +/// Handle widget events and return whether a redraw is needed. +bool HandleWidgetEvents(State* state) { + assert(state); + + bool redraw = false; + + const uiWidgetEvent* events; + const int numWidgetEvents = uiGetEvents(&events); + + for (int i = 0; i < numWidgetEvents; ++i) { + const uiWidgetEvent* ev = &events[i]; + + // TODO: Set and check widget IDs. + switch (ev->type) { + case uiWidgetEventClick: + if (ev->widget.type == uiTypeTable) { + if (OnFileTableClick( + state, uiGetTablePtr(ev->widget), &ev->table_click)) { + redraw = true; + } + } + break; + default: + break; + } + } + + return redraw; } static bool Render(State* state) { @@ -119,6 +234,7 @@ static bool Resize(State* state) { // TODO: Fix the white 1-pixel vertical/horizontal line that appears at odd // sizes when resizing the window. + // https://github.com/libsdl-org/SDL/issues/9653 uiResizeFrame(state->frame, width, height); return true; @@ -136,8 +252,8 @@ bool Initialize(State* state) { CreateUi(state); - const char* home = getenv("HOME"); - SetDirectory(state, string_new(home)); + path home = path_new(getenv("HOME")); + SetDirectory(state, home); return true; } @@ -167,6 +283,9 @@ int main( goto cleanup; } + // TODO: All of the window and input handling could be moved to its own + // library so that different applications can re-use it. + // Controls whether we should keep running. bool running = true; @@ -218,10 +337,43 @@ int main( break; } } - + } else if (event.type == SDL_MOUSEBUTTONDOWN) { + const uiInputEvent ev = { + .type = uiEventMouseButton, + .mouse_button = (uiMouseButtonEvent){ + .button = ToUiButton(event.button.button), + .state = uiMouseDown, + .mouse_position = + (uiPoint){.x = event.button.x, .y = event.button.y}} + }; + redraw = uiSendEvent(state.frame, &ev); + } else if (event.type == SDL_MOUSEBUTTONUP) { + const uiInputEvent ev = { + .type = uiEventMouseButton, + .mouse_button = (uiMouseButtonEvent){ + .button = ToUiButton(event.button.button), + .state = uiMouseUp, + .mouse_position = + (uiPoint){.x = event.button.x, .y = event.button.y}} + }; + redraw = uiSendEvent(state.frame, &ev); + } else if (event.type == SDL_MOUSEWHEEL) { + const uiInputEvent ev = { + .type = uiEventMouseScroll, + .mouse_scroll = (uiMouseScrollEvent){ + .scroll_offset = event.wheel.y, + .mouse_position = (uiPoint){ + .x = event.wheel.mouseX, .y = event.wheel.mouseY}} + }; + redraw = uiSendEvent(state.frame, &ev); } else { EVENT_LOOP_PRINT("event.window.event = %d\n", event.window.event); } + + if (HandleWidgetEvents(&state)) { + Resize(&state); // Trigger a re-layout of widgets. + redraw = true; + } } } @@ -243,3 +395,10 @@ cleanup: return success ? 0 : 1; } + +// ----------------------------------------------------------------------------- + +uiMouseButton ToUiButton(Uint8 button) { + // TODO: Buttons. + return uiLMB; +} -- cgit v1.2.3