1 #include <string.h> 2 #include "file_browser.h" 3 #include "sv.h" 4 5 static int file_cmp(const void *ap, const void *bp) 6 { 7 const char *a = *(const char**)ap; 8 const char *b = *(const char**)bp; 9 return strcmp(a, b); 10 } 11 12 Errno fb_open_dir(File_Browser *fb, const char *dir_path) 13 { 14 fb->files.count = 0; 15 fb->cursor = 0; 16 Errno err = read_entire_dir(dir_path, &fb->files); 17 if (err != 0) { 18 return err; 19 } 20 qsort(fb->files.items, fb->files.count, sizeof(*fb->files.items), file_cmp); 21 22 fb->dir_path.count = 0; 23 sb_append_cstr(&fb->dir_path, dir_path); 24 sb_append_null(&fb->dir_path); 25 26 return 0; 27 } 28 29 #define PATH_SEP "/" 30 #define PATH_EMPTY "" 31 #define PATH_DOT "." 32 #define PATH_DOTDOT ".." 33 34 typedef struct { 35 String_View *items; 36 size_t count; 37 size_t capacity; 38 } Comps; 39 40 void normpath(String_View path, String_Builder *result) 41 { 42 size_t original_sb_size = result->count; 43 44 if (path.count == 0) { 45 sb_append_cstr(result, PATH_DOT); 46 return; 47 } 48 49 int initial_slashes = 0; 50 while (path.count > 0 && *path.data == *PATH_SEP) { 51 initial_slashes += 1; 52 sv_chop_left(&path, 1); 53 } 54 if (initial_slashes > 2) { 55 initial_slashes = 1; 56 } 57 58 Comps new_comps = {0}; 59 60 while (path.count > 0) { 61 String_View comp = sv_chop_by_delim(&path, '/'); 62 if (comp.count == 0 || sv_eq(comp, SV(PATH_DOT))) { 63 continue; 64 } 65 if (!sv_eq(comp, SV(PATH_DOTDOT))) { 66 da_append(&new_comps, comp); 67 continue; 68 } 69 if (initial_slashes == 0 && new_comps.count == 0) { 70 da_append(&new_comps, comp); 71 continue; 72 } 73 if (new_comps.count > 0 && sv_eq(da_last(&new_comps), SV(PATH_DOTDOT))) { 74 da_append(&new_comps, comp); 75 continue; 76 } 77 if (new_comps.count > 0) { 78 new_comps.count -= 1; 79 continue; 80 } 81 } 82 83 for (int i = 0; i < initial_slashes; ++i) { 84 sb_append_cstr(result, PATH_SEP); 85 } 86 87 for (size_t i = 0; i < new_comps.count; ++i) { 88 if (i > 0) sb_append_cstr(result, PATH_SEP); 89 sb_append_buf(result, new_comps.items[i].data, new_comps.items[i].count); 90 } 91 92 if (original_sb_size == result->count) { 93 sb_append_cstr(result, PATH_DOT); 94 } 95 96 free(new_comps.items); 97 } 98 99 Errno fb_change_dir(File_Browser *fb) 100 { 101 assert(fb->dir_path.count > 0 && "You need to call fb_open_dir() before fb_change_dir()"); 102 assert(fb->dir_path.items[fb->dir_path.count - 1] == '\0'); 103 104 if (fb->cursor >= fb->files.count) return 0; 105 106 const char *dir_name = fb->files.items[fb->cursor]; 107 108 fb->dir_path.count -= 1; 109 110 // TODO: fb->dir_path grows indefinitely if we hit the root 111 sb_append_cstr(&fb->dir_path, "/"); 112 sb_append_cstr(&fb->dir_path, dir_name); 113 114 String_Builder result = {0}; 115 normpath(sb_to_sv(fb->dir_path), &result); 116 da_move(&fb->dir_path, result); 117 sb_append_null(&fb->dir_path); 118 119 printf("Changed dir to %s\n", fb->dir_path.items); 120 121 fb->files.count = 0; 122 fb->cursor = 0; 123 Errno err = read_entire_dir(fb->dir_path.items, &fb->files); 124 125 if (err != 0) { 126 return err; 127 } 128 qsort(fb->files.items, fb->files.count, sizeof(*fb->files.items), file_cmp); 129 130 return 0; 131 } 132 133 void fb_render(const File_Browser *fb, SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr) 134 { 135 Vec2f cursor_pos = vec2f(0, -(float)fb->cursor * FREE_GLYPH_FONT_SIZE); 136 137 int w, h; 138 SDL_GetWindowSize(window, &w, &h); 139 140 float max_line_len = 0.0f; 141 142 sr->resolution = vec2f(w, h); 143 sr->time = (float) SDL_GetTicks() / 1000.0f; 144 145 simple_renderer_set_shader(sr, SHADER_FOR_COLOR); 146 if (fb->cursor < fb->files.count) { 147 const Vec2f begin = vec2f(0, -((float)fb->cursor + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE); 148 Vec2f end = begin; 149 free_glyph_atlas_measure_line_sized( 150 atlas, fb->files.items[fb->cursor], strlen(fb->files.items[fb->cursor]), 151 &end); 152 simple_renderer_solid_rect(sr, begin, vec2f(end.x - begin.x, FREE_GLYPH_FONT_SIZE), vec4f(.25, .25, .25, 1)); 153 } 154 simple_renderer_flush(sr); 155 156 simple_renderer_set_shader(sr, SHADER_FOR_EPICNESS); 157 for (size_t row = 0; row < fb->files.count; ++row) { 158 const Vec2f begin = vec2f(0, -(float)row * FREE_GLYPH_FONT_SIZE); 159 Vec2f end = begin; 160 free_glyph_atlas_render_line_sized( 161 atlas, sr, fb->files.items[row], strlen(fb->files.items[row]), 162 &end, 163 vec4fs(0)); 164 // TODO: the max_line_len should be calculated based on what's visible on the screen right now 165 float line_len = fabsf(end.x - begin.x); 166 if (line_len > max_line_len) { 167 max_line_len = line_len; 168 } 169 } 170 171 simple_renderer_flush(sr); 172 173 // Update camera 174 { 175 if (max_line_len > 1000.0f) { 176 max_line_len = 1000.0f; 177 } 178 179 float target_scale = w/3/(max_line_len*0.75); // TODO: division by 0 180 181 Vec2f target = cursor_pos; 182 float offset = 0.0f; 183 184 if (target_scale > 3.0f) { 185 target_scale = 3.0f; 186 } else { 187 offset = cursor_pos.x - w/3/sr->camera_scale; 188 if (offset < 0.0f) offset = 0.0f; 189 target = vec2f(w/3/sr->camera_scale + offset, cursor_pos.y); 190 } 191 192 sr->camera_vel = vec2f_mul( 193 vec2f_sub(target, sr->camera_pos), 194 vec2fs(2.0f)); 195 sr->camera_scale_vel = (target_scale - sr->camera_scale) * 2.0f; 196 197 sr->camera_pos = vec2f_add(sr->camera_pos, vec2f_mul(sr->camera_vel, vec2fs(DELTA_TIME))); 198 sr->camera_scale = sr->camera_scale + sr->camera_scale_vel * DELTA_TIME; 199 } 200 } 201 202 const char *fb_file_path(File_Browser *fb) 203 { 204 assert(fb->dir_path.count > 0 && "You need to call fb_open_dir() before fb_file_path()"); 205 assert(fb->dir_path.items[fb->dir_path.count - 1] == '\0'); 206 207 if (fb->cursor >= fb->files.count) return NULL; 208 209 fb->file_path.count = 0; 210 sb_append_buf(&fb->file_path, fb->dir_path.items, fb->dir_path.count - 1); 211 sb_append_buf(&fb->file_path, "/", 1); 212 sb_append_cstr(&fb->file_path, fb->files.items[fb->cursor]); 213 sb_append_null(&fb->file_path); 214 215 return fb->file_path.items; 216 }
