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 | } | ||
