summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2024-06-22 13:21:09 -0700
committer3gg <3gg@shellblade.net>2024-06-22 13:21:09 -0700
commitacaa801ea2c7fa3c909ca4f64759696fff002b1a (patch)
tree5deeffdb580948300cb3fd2516cb07b4c4e7a711
parent8222bfe56d4dabe8d92fc4b25ea1b0163b16f3e1 (diff)
Basic navigation.HEADmain
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/xplorer.c215
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
20 20
21target_link_libraries(xplorer PRIVATE 21target_link_libraries(xplorer PRIVATE
22 SDL2-static 22 SDL2-static
23 filesystem
23 tinydir 24 tinydir
24 ui) 25 ui)
25 26
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 @@
5#include <tinydir.h> 5#include <tinydir.h>
6 6
7#include <assert.h> 7#include <assert.h>
8#include <path.h>
8#include <stdbool.h> 9#include <stdbool.h>
9#include <stdio.h> 10#include <stdio.h>
10#include <stdlib.h> 11#include <stdlib.h>
11#include <string.h>
12 12
13static const char* WindowTitle = "XPLORER"; 13static const char* WindowTitle = "XPLORER";
14static const int DefaultWidth = 960; 14static const int DefaultWidth = 1440;
15static const int DefaultHeight = 600; 15static const int DefaultHeight = 900;
16 16
17// #define DEBUG_EVENT_LOOP 1 17// #define DEBUG_EVENT_LOOP 1
18 18
@@ -26,47 +26,162 @@ typedef struct State {
26 SDL_Window* window; 26 SDL_Window* window;
27 uiFrame* frame; 27 uiFrame* frame;
28 uiTable* table; 28 uiTable* table;
29 string current_dir; 29 path current_dir;
30} State; 30} State;
31 31
32void SetDirectory(State* state, string path) { 32uiMouseButton ToUiButton(Uint8 button);
33
34void CreateUi(State* state) {
33 assert(state); 35 assert(state);
34 36
35 state->current_dir = path; 37 uiFrame* frame = uiMakeFrame();
36 38
37 uiTable* table = state->table; 39 const char* header[] = {"Name", "Size", "Modified"};
40 uiTable* table = uiMakeTable(0, sizeof(header) / sizeof(char*), header);
38 assert(table); 41 assert(table);
42 uiWidgetSetParent(uiMakeTablePtr(table), uiMakeFramePtr(frame));
39 43
40 tinydir_dir dir; 44 // uiLabel* label = uiMakeLabel("Hello world, what is going on!?");
41 tinydir_open(&dir, string_data(path)); 45 // uiWidgetSetParent(label, frame);
42 while (dir.has_next) {
43 tinydir_file file;
44 tinydir_readfile(&dir, &file);
45 46
46 const string file_size = string_format_size(file._s.st_size); 47 state->frame = frame;
48 state->table = table;
49}
47 50
48 const char* row[3] = {file.name, string_data(file_size), "<date>"}; 51int compare_files(const void* _a, const void* _b) {
49 uiTableAddRow(table, row); 52 assert(_a);
53 assert(_b);
50 54
51 tinydir_next(&dir); 55 const tinydir_file* a = _a;
56 const tinydir_file* b = _b;
57
58 for (size_t i = 0;
59 (i < _TINYDIR_FILENAME_MAX) && (a->name[i] != 0) && (b->name[i] != 0);
60 ++i) {
61 if (a->name[i] < b->name[i]) {
62 return -1;
63 } else if (a->name[i] > b->name[i]) {
64 return 1;
65 }
52 } 66 }
67
68 return 0;
53} 69}
54 70
55void CreateUi(State* state) { 71size_t GetFileCount(path directory) {
72 size_t count = 0;
73 tinydir_dir dir;
74 if (tinydir_open(&dir, path_cstr(directory)) == 0) {
75 for (count = 0; dir.has_next; ++count) {
76 tinydir_next(&dir);
77 }
78 }
79 return count;
80}
81
82bool SetDirectory(State* state, path directory) {
56 assert(state); 83 assert(state);
57 84
58 uiFrame* frame = uiMakeFrame(); 85 bool directory_changed = false;
59 86
60 const char* header[] = {"Name", "Size", "Modified"}; 87 tinydir_dir dir;
61 uiTable* table = uiMakeTable(0, sizeof(header) / sizeof(char*), header); 88 if (tinydir_open(&dir, path_cstr(directory)) == 0) {
89 const size_t count = GetFileCount(directory);
90
91 tinydir_file* files = calloc(count, sizeof(tinydir_file));
92 if (!files) {
93 return false;
94 }
95
96 for (size_t i = 0; dir.has_next; ++i) {
97 assert(i < count);
98 tinydir_readfile(&dir, &files[i]);
99 tinydir_next(&dir);
100 }
101
102 qsort(files, count, sizeof(files[0]), compare_files);
103
104 uiTable* table = state->table;
105 assert(table);
106
107 uiTableClear(table);
108 for (size_t i = 0; i < count; ++i) {
109 tinydir_file file = files[i];
110
111 const string file_size = string_format_size(file._s.st_size);
112
113 const char* row[3] = {file.name, string_data(file_size), "<date>"};
114 uiTableAddRow(table, row);
115 }
116
117 free(files);
118
119 if (!path_empty(state->current_dir)) {
120 path_del(&state->current_dir);
121 }
122 state->current_dir = directory;
123 directory_changed = true;
124 }
125
126 return directory_changed;
127}
128
129bool OnFileTableClick(
130 State* state, uiTable* table, const uiTableClickEvent* event) {
131 assert(state);
62 assert(table); 132 assert(table);
63 uiWidgetSetParent(uiMakeTablePtr(table), uiMakeFramePtr(frame));
64 133
65 // uiLabel* label = uiMakeLabel("Hello world, what is going on!?"); 134 if (event->col == 0) { // Clicked the file/directory name.
66 // uiWidgetSetParent(label, frame); 135 // TODO: Think more about uiPtr. Do we need uiConstPtr?
136 // Ideally: const uiLabel* label = uiGetPtr(uiTableGet(...));
137 // i.e., no checks on the client code; all checks in library code.
138 const uiLabel* label =
139 (const uiLabel*)uiTableGet(table, event->row, event->col);
140 assert(uiWidgetGetType((const uiWidget*)label) == uiTypeLabel);
141
142 printf("Click: %d,%d: %s\n", event->row, event->col, uiLabelGetText(label));
143
144 // TODO: Handle '.' and '..' better. Define a path concatenation function.
145 path child_dir = path_new(uiLabelGetText(label));
146 path new_dir = path_concat(state->current_dir, child_dir);
147 const bool result = SetDirectory(state, new_dir);
148 if (!result) {
149 path_del(&new_dir);
150 }
151 path_del(&child_dir);
152 return result;
153 }
154 return false;
155}
67 156
68 state->frame = frame; 157/// Handle widget events and return whether a redraw is needed.
69 state->table = table; 158bool HandleWidgetEvents(State* state) {
159 assert(state);
160
161 bool redraw = false;
162
163 const uiWidgetEvent* events;
164 const int numWidgetEvents = uiGetEvents(&events);
165
166 for (int i = 0; i < numWidgetEvents; ++i) {
167 const uiWidgetEvent* ev = &events[i];
168
169 // TODO: Set and check widget IDs.
170 switch (ev->type) {
171 case uiWidgetEventClick:
172 if (ev->widget.type == uiTypeTable) {
173 if (OnFileTableClick(
174 state, uiGetTablePtr(ev->widget), &ev->table_click)) {
175 redraw = true;
176 }
177 }
178 break;
179 default:
180 break;
181 }
182 }
183
184 return redraw;
70} 185}
71 186
72static bool Render(State* state) { 187static bool Render(State* state) {
@@ -119,6 +234,7 @@ static bool Resize(State* state) {
119 234
120 // TODO: Fix the white 1-pixel vertical/horizontal line that appears at odd 235 // TODO: Fix the white 1-pixel vertical/horizontal line that appears at odd
121 // sizes when resizing the window. 236 // sizes when resizing the window.
237 // https://github.com/libsdl-org/SDL/issues/9653
122 uiResizeFrame(state->frame, width, height); 238 uiResizeFrame(state->frame, width, height);
123 239
124 return true; 240 return true;
@@ -136,8 +252,8 @@ bool Initialize(State* state) {
136 252
137 CreateUi(state); 253 CreateUi(state);
138 254
139 const char* home = getenv("HOME"); 255 path home = path_new(getenv("HOME"));
140 SetDirectory(state, string_new(home)); 256 SetDirectory(state, home);
141 257
142 return true; 258 return true;
143} 259}
@@ -167,6 +283,9 @@ int main(
167 goto cleanup; 283 goto cleanup;
168 } 284 }
169 285
286 // TODO: All of the window and input handling could be moved to its own
287 // library so that different applications can re-use it.
288
170 // Controls whether we should keep running. 289 // Controls whether we should keep running.
171 bool running = true; 290 bool running = true;
172 291
@@ -218,10 +337,43 @@ int main(
218 break; 337 break;
219 } 338 }
220 } 339 }
221 340 } else if (event.type == SDL_MOUSEBUTTONDOWN) {
341 const uiInputEvent ev = {
342 .type = uiEventMouseButton,
343 .mouse_button = (uiMouseButtonEvent){
344 .button = ToUiButton(event.button.button),
345 .state = uiMouseDown,
346 .mouse_position =
347 (uiPoint){.x = event.button.x, .y = event.button.y}}
348 };
349 redraw = uiSendEvent(state.frame, &ev);
350 } else if (event.type == SDL_MOUSEBUTTONUP) {
351 const uiInputEvent ev = {
352 .type = uiEventMouseButton,
353 .mouse_button = (uiMouseButtonEvent){
354 .button = ToUiButton(event.button.button),
355 .state = uiMouseUp,
356 .mouse_position =
357 (uiPoint){.x = event.button.x, .y = event.button.y}}
358 };
359 redraw = uiSendEvent(state.frame, &ev);
360 } else if (event.type == SDL_MOUSEWHEEL) {
361 const uiInputEvent ev = {
362 .type = uiEventMouseScroll,
363 .mouse_scroll = (uiMouseScrollEvent){
364 .scroll_offset = event.wheel.y,
365 .mouse_position = (uiPoint){
366 .x = event.wheel.mouseX, .y = event.wheel.mouseY}}
367 };
368 redraw = uiSendEvent(state.frame, &ev);
222 } else { 369 } else {
223 EVENT_LOOP_PRINT("event.window.event = %d\n", event.window.event); 370 EVENT_LOOP_PRINT("event.window.event = %d\n", event.window.event);
224 } 371 }
372
373 if (HandleWidgetEvents(&state)) {
374 Resize(&state); // Trigger a re-layout of widgets.
375 redraw = true;
376 }
225 } 377 }
226 } 378 }
227 379
@@ -243,3 +395,10 @@ cleanup:
243 395
244 return success ? 0 : 1; 396 return success ? 0 : 1;
245} 397}
398
399// -----------------------------------------------------------------------------
400
401uiMouseButton ToUiButton(Uint8 button) {
402 // TODO: Buttons.
403 return uiLMB;
404}