diff options
author | 3gg <3gg@shellblade.net> | 2024-05-04 16:51:29 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2024-05-04 16:51:29 -0700 |
commit | 8222bfe56d4dabe8d92fc4b25ea1b0163b16f3e1 (patch) | |
tree | 763389e42276035ac134d94eb1dc32293b88d807 /src/xplorer.c |
Initial commit.
Diffstat (limited to 'src/xplorer.c')
-rw-r--r-- | src/xplorer.c | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/src/xplorer.c b/src/xplorer.c new file mode 100644 index 0000000..8a190af --- /dev/null +++ b/src/xplorer.c | |||
@@ -0,0 +1,245 @@ | |||
1 | #include <ui.h> | ||
2 | |||
3 | #include <SDL.h> | ||
4 | #include <cstring.h> | ||
5 | #include <tinydir.h> | ||
6 | |||
7 | #include <assert.h> | ||
8 | #include <stdbool.h> | ||
9 | #include <stdio.h> | ||
10 | #include <stdlib.h> | ||
11 | #include <string.h> | ||
12 | |||
13 | static const char* WindowTitle = "XPLORER"; | ||
14 | static const int DefaultWidth = 960; | ||
15 | static const int DefaultHeight = 600; | ||
16 | |||
17 | // #define DEBUG_EVENT_LOOP 1 | ||
18 | |||
19 | #ifdef DEBUG_EVENT_LOOP | ||
20 | #define EVENT_LOOP_PRINT printf | ||
21 | #else | ||
22 | #define EVENT_LOOP_PRINT(...) | ||
23 | #endif // DEBUG_EVENT_LOOP | ||
24 | |||
25 | typedef struct State { | ||
26 | SDL_Window* window; | ||
27 | uiFrame* frame; | ||
28 | uiTable* table; | ||
29 | string current_dir; | ||
30 | } State; | ||
31 | |||
32 | void SetDirectory(State* state, string path) { | ||
33 | assert(state); | ||
34 | |||
35 | state->current_dir = path; | ||
36 | |||
37 | uiTable* table = state->table; | ||
38 | assert(table); | ||
39 | |||
40 | tinydir_dir dir; | ||
41 | tinydir_open(&dir, string_data(path)); | ||
42 | while (dir.has_next) { | ||
43 | tinydir_file file; | ||
44 | tinydir_readfile(&dir, &file); | ||
45 | |||
46 | const string file_size = string_format_size(file._s.st_size); | ||
47 | |||
48 | const char* row[3] = {file.name, string_data(file_size), "<date>"}; | ||
49 | uiTableAddRow(table, row); | ||
50 | |||
51 | tinydir_next(&dir); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | void CreateUi(State* state) { | ||
56 | assert(state); | ||
57 | |||
58 | uiFrame* frame = uiMakeFrame(); | ||
59 | |||
60 | const char* header[] = {"Name", "Size", "Modified"}; | ||
61 | uiTable* table = uiMakeTable(0, sizeof(header) / sizeof(char*), header); | ||
62 | assert(table); | ||
63 | uiWidgetSetParent(uiMakeTablePtr(table), uiMakeFramePtr(frame)); | ||
64 | |||
65 | // uiLabel* label = uiMakeLabel("Hello world, what is going on!?"); | ||
66 | // uiWidgetSetParent(label, frame); | ||
67 | |||
68 | state->frame = frame; | ||
69 | state->table = table; | ||
70 | } | ||
71 | |||
72 | static bool Render(State* state) { | ||
73 | assert(state); | ||
74 | assert(state->window); | ||
75 | |||
76 | SDL_Surface* window_surface = SDL_GetWindowSurface(state->window); | ||
77 | assert(window_surface); | ||
78 | |||
79 | #ifdef DEBUG_EVENT_LOOP | ||
80 | const uiSize frame_size = uiGetFrameSize(state->frame); | ||
81 | EVENT_LOOP_PRINT( | ||
82 | "Render; surface: %dx%d; window surface; %dx%d\n", frame_size.width, | ||
83 | frame_size.height, window_surface->w, window_surface->h); | ||
84 | #endif | ||
85 | |||
86 | // Locking/unlocking SDL software surfaces is not necessary. | ||
87 | // | ||
88 | // Probably also best to avoid SDL_BlitSurface(); it does pixel format | ||
89 | // conversion while blitting one pixel at a time. Instead, make the UI pixel | ||
90 | // format match the SDL window's and write to SDL's back buffer directly. | ||
91 | uiRender( | ||
92 | state->frame, &(uiSurface){ | ||
93 | .width = window_surface->w, | ||
94 | .height = window_surface->h, | ||
95 | .pixels = window_surface->pixels, | ||
96 | }); | ||
97 | |||
98 | if (SDL_UpdateWindowSurface(state->window) != 0) { | ||
99 | return false; | ||
100 | } | ||
101 | |||
102 | return true; | ||
103 | } | ||
104 | |||
105 | static bool Resize(State* state) { | ||
106 | assert(state); | ||
107 | |||
108 | // int width, height; | ||
109 | // SDL_GetWindowSize(state->window, &width, &height); | ||
110 | |||
111 | const SDL_Surface* window_surface = SDL_GetWindowSurface(state->window); | ||
112 | if (!window_surface) { | ||
113 | return false; | ||
114 | } | ||
115 | const int width = window_surface->w; | ||
116 | const int height = window_surface->h; | ||
117 | |||
118 | EVENT_LOOP_PRINT("Resize: %dx%d\n", width, height); | ||
119 | |||
120 | // TODO: Fix the white 1-pixel vertical/horizontal line that appears at odd | ||
121 | // sizes when resizing the window. | ||
122 | uiResizeFrame(state->frame, width, height); | ||
123 | |||
124 | return true; | ||
125 | } | ||
126 | |||
127 | bool Initialize(State* state) { | ||
128 | assert(state); | ||
129 | |||
130 | if ((state->window = SDL_CreateWindow( | ||
131 | WindowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, | ||
132 | DefaultWidth, DefaultHeight, | ||
133 | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE)) == NULL) { | ||
134 | return false; | ||
135 | } | ||
136 | |||
137 | CreateUi(state); | ||
138 | |||
139 | const char* home = getenv("HOME"); | ||
140 | SetDirectory(state, string_new(home)); | ||
141 | |||
142 | return true; | ||
143 | } | ||
144 | |||
145 | int main( | ||
146 | __attribute__((unused)) int argc, | ||
147 | __attribute__((unused)) const char** argv) { | ||
148 | bool success = true; | ||
149 | |||
150 | State state = {0}; | ||
151 | |||
152 | if (SDL_Init(SDL_INIT_VIDEO) != 0) { | ||
153 | return false; | ||
154 | } | ||
155 | |||
156 | if (!uiInit()) { | ||
157 | return false; | ||
158 | } | ||
159 | |||
160 | if (!Initialize(&state)) { | ||
161 | success = false; | ||
162 | goto cleanup; | ||
163 | } | ||
164 | |||
165 | if (!Resize(&state)) { | ||
166 | success = false; | ||
167 | goto cleanup; | ||
168 | } | ||
169 | |||
170 | // Controls whether we should keep running. | ||
171 | bool running = true; | ||
172 | |||
173 | // Controls whether a redraw is required. | ||
174 | // Initially true to perform an initial draw before the window is displayed. | ||
175 | bool redraw = true; | ||
176 | |||
177 | while (running) { | ||
178 | EVENT_LOOP_PRINT("loop\n"); | ||
179 | |||
180 | // Draw if needed. | ||
181 | if (redraw && !Render(&state)) { | ||
182 | success = false; | ||
183 | break; | ||
184 | } | ||
185 | redraw = false; | ||
186 | |||
187 | // Handle events. | ||
188 | SDL_Event event = {0}; | ||
189 | if (SDL_WaitEvent(&event) == 0) { | ||
190 | success = false; | ||
191 | break; | ||
192 | } else if (event.type == SDL_QUIT) { | ||
193 | break; | ||
194 | } else { | ||
195 | if (event.type == SDL_WINDOWEVENT) { | ||
196 | // When the window is maximized, an SDL_WINDOWEVENT_MOVED comes in | ||
197 | // before an SDL_WINDOWEVENT_SIZE_CHANGED with the window already | ||
198 | // resized. This is unfortunate because we cannot rely on the latter | ||
199 | // event alone to handle resizing. | ||
200 | if ((event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) || | ||
201 | (event.window.event == SDL_WINDOWEVENT_RESIZED) || | ||
202 | (event.window.event == SDL_WINDOWEVENT_MOVED)) { | ||
203 | if (!Resize(&state)) { | ||
204 | success = false; | ||
205 | break; | ||
206 | } | ||
207 | redraw = true; | ||
208 | } | ||
209 | } else if (event.type == SDL_KEYDOWN) { | ||
210 | if (event.key.keysym.mod & KMOD_LCTRL) { | ||
211 | switch (event.key.keysym.sym) { | ||
212 | // Exit. | ||
213 | case SDLK_c: | ||
214 | case SDLK_d: | ||
215 | running = false; | ||
216 | break; | ||
217 | default: | ||
218 | break; | ||
219 | } | ||
220 | } | ||
221 | |||
222 | } else { | ||
223 | EVENT_LOOP_PRINT("event.window.event = %d\n", event.window.event); | ||
224 | } | ||
225 | } | ||
226 | } | ||
227 | |||
228 | cleanup: | ||
229 | if (!success) { | ||
230 | fprintf(stderr, "%s\n", SDL_GetError()); | ||
231 | } | ||
232 | |||
233 | if (state.frame) { | ||
234 | uiDestroyFrame(&state.frame); | ||
235 | } | ||
236 | if (state.window) { | ||
237 | SDL_DestroyWindow(state.window); | ||
238 | state.window = 0; | ||
239 | } | ||
240 | |||
241 | uiShutdown(); | ||
242 | SDL_Quit(); | ||
243 | |||
244 | return success ? 0 : 1; | ||
245 | } | ||