From 46a02444bdeac16474e31c4e836b118c07f8f57f Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Thu, 21 Jan 2021 19:13:40 -0500 Subject: trying to make clicking on directories work; not done yet --- arr.c | 18 ++++- base.h | 6 +- buffer.c | 26 ++++-- command.c | 9 +-- filesystem-posix.c | 11 +++ filesystem-win.c | 12 +++ filesystem.h | 10 ++- main.c | 47 ++++++----- menu.c | 173 ++------------------------------------- ted-base.c | 4 +- ted.h | 10 ++- ui.c | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 348 insertions(+), 212 deletions(-) create mode 100644 ui.c diff --git a/arr.c b/arr.c index 0fc856f..93f6198 100644 --- a/arr.c +++ b/arr.c @@ -109,8 +109,6 @@ static inline void *arr_add_ptr_(void **arr, size_t member_size) { return ret; } -#if 0 -// NOT TESTED static inline void arr_set_len_(void **arr, size_t member_size, size_t n) { if (n >= U32_MAX-1) { // too big; free arr. @@ -153,10 +151,10 @@ static inline void arr_set_len_(void **arr, size_t member_size, size_t n) { memset((char *)hdr->data + hdr->len, 0, (n - hdr->len) * member_size); } hdr->len = (u32)n; + *arr = hdr->data; } } #define arr_set_len(a, n) arr_set_len_((void **)&a, sizeof *(a), n) -#endif #ifdef __cplusplus #define arr_cast_typeof(a) (decltype(a)) @@ -209,6 +207,20 @@ static inline void arr_set_len_(void **arr, size_t member_size, size_t n) { } \ } while (0) + +static void arr_append_str_(char **a, char const *s) { + size_t curr_len = arr_len(*a); + size_t s_len = strlen(s); + + if (curr_len) --curr_len; // don't include null terminator + + arr_set_len(*a, curr_len + s_len + 1); + memcpy(*a + curr_len, s, s_len + 1); +} + +// appends a C-string array with +#define arr_append_str(a, s) arr_append_str_(&(a), (s)) + static void arr_test(void) { u32 *arr = NULL; u32 i; diff --git a/base.h b/base.h index 53e0a70..f6b0452 100644 --- a/base.h +++ b/base.h @@ -8,9 +8,11 @@ #if _WIN32 #include #include -#define PATH_SEPARATOR "\\" +#define PATH_SEPARATOR '\\' +#define PATH_SEPARATOR_STR "\\" #else -#define PATH_SEPARATOR "/" +#define PATH_SEPARATOR '/' +#define PATH_SEPARATOR_STR "/" #endif #include diff --git a/buffer.c b/buffer.c index 520d7d2..3663df4 100644 --- a/buffer.c +++ b/buffer.c @@ -768,8 +768,9 @@ v2 buffer_pos_to_pixels(TextBuffer *buffer, BufferPos pos) { } // convert pixel coordinates to a position in the buffer, selecting the closest character. -// returns false if the position is not inside the buffer. +// returns false if the position is not inside the buffer, but still sets *pos to the closest character. bool buffer_pixels_to_pos(TextBuffer *buffer, v2 pixel_coords, BufferPos *pos) { + bool ret = true; float x = pixel_coords.x, y = pixel_coords.y; Font *font = buffer_font(buffer); pos->line = pos->index = 0; @@ -779,11 +780,24 @@ bool buffer_pixels_to_pos(TextBuffer *buffer, v2 pixel_coords, BufferPos *pos) { x /= text_font_char_width(font); y /= text_font_char_height(font); double display_col = (double)x; - if (display_col < 0 || display_col >= buffer_display_cols(buffer)) - return false; + if (display_col < 0) { + display_col = 0; + ret = false; + } + int display_cols = buffer_display_cols(buffer), display_lines = buffer_display_lines(buffer); + if (display_col >= display_cols) { + display_col = display_cols - 1; + ret = false; + } double display_line = (double)y; - if (display_line < 0 || display_line >= buffer_display_lines(buffer)) - return false; + if (display_line < 0) { + display_line = 0; + ret = false; + } + if (display_line >= display_lines) { + display_line = display_lines - 1; + ret = false; + } u32 line = (u32)floor(display_line + buffer->scroll_y); if (line >= buffer->nlines) line = buffer->nlines - 1; @@ -792,7 +806,7 @@ bool buffer_pixels_to_pos(TextBuffer *buffer, v2 pixel_coords, BufferPos *pos) { pos->line = line; pos->index = index; - return true; + return ret; } // clip the rectangle so it's all inside the buffer. returns true if there's any rectangle left. diff --git a/command.c b/command.c index 206274a..f5ceb0c 100644 --- a/command.c +++ b/command.c @@ -156,14 +156,7 @@ void command_execute(Ted *ted, Command c, i64 argument) { assert(0); break; case MENU_OPEN: { - char *filename_cstr = str32_to_utf8_cstr(buffer_get_line(&ted->line_buffer, 0)); - if (filename_cstr) { - buffer = ted_open_file(ted, filename_cstr); - free(filename_cstr); - menu_close(ted, false); - } else { - ted_seterr(ted, "Out of memory."); - } + ted->file_selector.submitted = true; } break; } } diff --git a/filesystem-posix.c b/filesystem-posix.c index 1a23eff..ebf1407 100644 --- a/filesystem-posix.c +++ b/filesystem-posix.c @@ -69,3 +69,14 @@ int fs_mkdir(char const *path) { return -1; } } + +int fs_get_cwd(char *buf, size_t buflen) { + assert(buf && buflen); + if (getcwd(buf, buflen)) { + return 1; + } else if (errno == ERANGE) { + return 0; + } else { + return -1; + } +} diff --git a/filesystem-win.c b/filesystem-win.c index 7c40820..d35597f 100644 --- a/filesystem-win.c +++ b/filesystem-win.c @@ -65,3 +65,15 @@ int fs_mkdir(char const *path) { return -1; // some other error } } + +int fs_get_cwd(char *buf, size_t buflen) { + assert(buf && buflen); + DWORD pathlen = GetCurrentDirectory(buflen, buf); + if (pathlen == 0) { + return -1; + } else if (pathlen < buflen) { // it's confusing, but this is < and not <= + return 1; + } else { + return 0; + } +} diff --git a/filesystem.h b/filesystem.h index 238ebb1..2e561cd 100644 --- a/filesystem.h +++ b/filesystem.h @@ -16,12 +16,18 @@ bool fs_file_exists(char const *path); // When you're done with the file names, call free on each one, then on the array. // NOTE: The files aren't returned in any particular order! char **fs_list_directory(char const *dirname); -// create the directory specified by `path` -// returns: +// Create the directory specified by `path` +// Returns: // 1 if the directory was created successfully // 0 if the directory already exists // -1 if the path already exists, but it's not a directory, or if there's another error (e.g. don't have permission to create directory). int fs_mkdir(char const *path); +// Puts the current working directory into buf, including a null-terminator, writing at most buflen bytes. +// Returns: +// 1 if the working directory was inserted into buf successfully +// 0 if buf is too short to hold the cwd +// -1 if we can't get the cwd for whatever reason. +int fs_get_cwd(char *buf, size_t buflen); #endif // FILESYSTEM_H_ diff --git a/main.c b/main.c index 39e582b..eddefa1 100644 --- a/main.c +++ b/main.c @@ -37,6 +37,7 @@ no_warn_end #include "arr.c" #include "buffer.c" #include "ted-base.c" +#include "ui.c" #include "command.c" #include "config.c" #include "menu.c" @@ -81,14 +82,17 @@ int main(int argc, char **argv) { #endif setlocale(LC_ALL, ""); // allow unicode - printf("%d\n", !!stristr("foo", "x")); + Ted *ted = calloc(1, sizeof *ted); + if (!ted) { + die("Not enough memory available to run ted."); + } { // get local data directory #if _WIN32 wchar_t *appdata = NULL; KNOWNFOLDERID id = FOLDERID_LocalAppData; if (SHGetKnownFolderPath(&id, 0, NULL, &appdata) == S_OK) { - strbuf_printf(ted_local_data_dir, "%ls" PATH_SEPARATOR "ted", appdata); + strbuf_printf(ted_local_data_dir, "%ls" PATH_SEPARATOR_STR "ted", appdata); CoTaskMemFree(appdata); } #else @@ -97,12 +101,17 @@ int main(int argc, char **argv) { #endif } + { // get current working directory + fs_get_cwd(ted->cwd, sizeof ted->cwd); + } + { // check if this is the installed version of ted (as opposed to just running it from the directory with the source) - char executable_path[TED_PATH_MAX] = {0}, cwd[TED_PATH_MAX] = {0}; + char executable_path[TED_PATH_MAX] = {0}; + char const *cwd = ted->cwd; #if _WIN32 if (GetModuleFileNameA(NULL, executable_path, sizeof executable_path) > 0) { char *last_backslash = strrchr(executable_path, '\\'); - if (last_backslash && GetCurrentDirectory(sizeof cwd, cwd) > 0) { + if (last_backslash) { *last_backslash = '\0'; ted_search_cwd = streq(cwd, executable_path); } @@ -116,26 +125,18 @@ int main(int argc, char **argv) { char *last_slash = strrchr(executable_path, '/'); if (last_slash) { *last_slash = '\0'; - if (getcwd(cwd, sizeof cwd)) { - // @TODO: make sure this is working - ted_search_cwd = streq(cwd, executable_path); - } + ted_search_cwd = streq(cwd, executable_path); } } #endif } - Ted *ted = calloc(1, sizeof *ted); - if (!ted) { - die("Not enough memory available to run ted."); - } - Settings *settings = &ted->settings; { // read global configuration file first to establish defaults char global_config_filename[TED_PATH_MAX]; - strbuf_printf(global_config_filename, "%s" PATH_SEPARATOR "ted.cfg", ted_global_data_dir); + strbuf_printf(global_config_filename, "%s" PATH_SEPARATOR_STR "ted.cfg", ted_global_data_dir); if (fs_file_exists(global_config_filename)) config_read(ted, global_config_filename); } @@ -246,7 +247,7 @@ int main(int argc, char **argv) { bool ctrl_down = keyboard_state[SDL_SCANCODE_LCTRL] || keyboard_state[SDL_SCANCODE_RCTRL]; bool shift_down = keyboard_state[SDL_SCANCODE_LSHIFT] || keyboard_state[SDL_SCANCODE_RSHIFT]; bool alt_down = keyboard_state[SDL_SCANCODE_LALT] || keyboard_state[SDL_SCANCODE_RALT]; - + memset(ted->nmouse_clicks, 0, sizeof ted->nmouse_clicks); while (SDL_PollEvent(&event)) { @@ -293,18 +294,26 @@ int main(int argc, char **argv) { break; } } + ted->drag_buffer = buffer; } } } break; } } break; + case SDL_MOUSEBUTTONUP: + if (event.button.button == SDL_BUTTON_LEFT) + ted->drag_buffer = NULL; + break; case SDL_MOUSEMOTION: if (event.motion.state == SDL_BUTTON_LMASK) { - if (buffer) { + if (ted->drag_buffer != ted->active_buffer) + ted->drag_buffer = NULL; + if (ted->drag_buffer) { BufferPos pos = {0}; - if (buffer_pixels_to_pos(buffer, V2((float)event.button.x, (float)event.button.y), &pos)) { - buffer_select_to_pos(buffer, pos); - } + // drag to select + // we don't check the return value here, because it's okay to drag off the screen. + buffer_pixels_to_pos(ted->drag_buffer, V2((float)event.button.x, (float)event.button.y), &pos); + buffer_select_to_pos(ted->drag_buffer, pos); } } break; diff --git a/menu.c b/menu.c index 4d7e965..a4f9c11 100644 --- a/menu.c +++ b/menu.c @@ -32,172 +32,18 @@ static Rect menu_rect(Ted *ted) { ); } -// where is the ith entry in the file selector on the screen? -// returns false if it's completely offscreen -static bool file_selector_entry_pos(Ted const *ted, FileSelector const *fs, - u32 i, Rect *r) { - Rect bounds = fs->bounds; - float char_height = text_font_char_height(ted->font); - *r = rect(V2(bounds.pos.x, bounds.pos.y + char_height * (float)i), - V2(bounds.size.x, char_height)); - return rect_clip_to_rect(r, bounds); -} - -// clear the entries in the file selector -static void file_selector_clear_entries(FileSelector *fs) { - for (u32 i = 0; i < fs->n_entries; ++i) { - free(fs->entries[i].name); - } - free(fs->entries); - fs->entries = NULL; - fs->n_entries = 0; -} - -static void file_selector_free(FileSelector *fs) { - file_selector_clear_entries(fs); -} - -static int qsort_file_entry_cmp(void const *av, void const *bv) { - FileEntry const *a = av, *b = bv; - // put directories first - if (a->type > b->type) { - return -1; - } - if (a->type < b->type) { - return +1; - } - return strcmp_case_insensitive(a->name, b->name); -} - -// returns the entry of the selected file, or a NULL entry (check .name == NULL) -// if none was selected -static FileEntry file_selector_update(Ted *ted, FileSelector *fs, String32 const search_term32) { - char *search_term = search_term32.len ? str32_to_utf8_cstr(search_term32) : NULL; - - // check if an entry was clicked on - for (u32 i = 0; i < fs->n_entries; ++i) { - Rect r = {0}; - if (file_selector_entry_pos(ted, fs, i, &r)) { - for (u32 c = 0; c < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++c) { - if (rect_contains_point(r, ted->mouse_clicks[SDL_BUTTON_LEFT][c])) { - // this option was selected - free(search_term); - return fs->entries[i]; - } - } - } else break; - } - - // free previous entries - file_selector_clear_entries(fs); - // get new entries - char **files = fs_list_directory("."); - if (files) { - u32 nfiles; - for (nfiles = 0; files[nfiles]; ++nfiles); - - // filter entries - bool increment = true; - for (u32 i = 0; i < nfiles; i += increment, increment = true) { - // remove if the file name does not contain the search term, - bool remove = search_term && *search_term && !stristr(files[i], search_term); - // or if this is just the current directory - remove |= streq(files[i], "."); - if (remove) { - // remove this one - free(files[i]); - --nfiles; - if (nfiles) { - files[i] = files[nfiles]; - } - increment = false; - } - } - - if (nfiles) { - FileEntry *entries = ted_calloc(ted, nfiles, sizeof *entries); - if (entries) { - fs->n_entries = nfiles; - fs->entries = entries; - for (u32 i = 0; i < nfiles; ++i) { - entries[i].name = files[i]; - entries[i].type = fs_path_type(files[i]); - } - } - qsort(entries, nfiles, sizeof *entries, qsort_file_entry_cmp); - } - } else { - #if DEBUG - static bool have_warned; - if (!have_warned) { - debug_println("Warning: fs_list_directory failed."); - have_warned = true; - } - #endif - } - - free(search_term); - FileEntry ret = {0}; - return ret; -} - -static void file_selector_render(Ted const *ted, FileSelector const *fs) { - Settings const *settings = &ted->settings; - u32 const *colors = settings->colors; - Rect bounds = fs->bounds; - u32 n_entries = fs->n_entries; - FileEntry const *entries = fs->entries; - Font *font = ted->font; - float char_height = text_font_char_height(ted->font); - float x1, y1, x2, y2; - rect_coords(bounds, &x1, &y1, &x2, &y2); - float x = x1, y = y1; - for (u32 i = 0; i < n_entries; ++i) { - // highlight entry user is mousing over - if (y >= y2) break; - Rect r = rect4(x, y, x2, minf(y + char_height, y2)); - y += char_height; - if (rect_contains_point(r, ted->mouse_pos)) { - glBegin(GL_QUADS); - gl_color_rgba(colors[COLOR_MENU_HL]); - rect_render(r); - glEnd(); - } - } - - x = x1; y = y1; - TextRenderState text_render_state = {.min_x = x1, .max_x = x2, .min_y = y1, .max_y = y2, .render = true}; - // render file names themselves - for (u32 i = 0; i < n_entries; ++i) { - if (y >= y2) break; - switch (entries[i].type) { - case FS_FILE: - gl_color_rgba(colors[COLOR_TEXT]); - break; - case FS_DIRECTORY: - gl_color_rgba(colors[COLOR_TEXT_FOLDER]); - break; - default: - gl_color_rgba(colors[COLOR_TEXT_OTHER]); - break; - } - text_render_with_state(font, &text_render_state, entries[i].name, x, y); - y += char_height; - } -} - static void menu_update(Ted *ted, Menu menu) { switch (menu) { case MENU_NONE: break; case MENU_OPEN: { - FileEntry selected_entry = file_selector_update(ted, &ted->file_selector, - buffer_get_line(&ted->line_buffer, 0)); - if (selected_entry.name) { + char *selected_file = file_selector_update(ted, &ted->file_selector); + if (selected_file) { // open that file! - if (ted_open_file(ted, selected_entry.name)) { + if (ted_open_file(ted, selected_file)) { menu_close(ted, false); file_selector_free(&ted->file_selector); } + free(selected_file); } } break; } @@ -206,9 +52,7 @@ static void menu_update(Ted *ted, Menu menu) { static void menu_render(Ted *ted, Menu menu) { Settings *settings = &ted->settings; u32 *colors = settings->colors; - Font *font = ted->font; float window_width = ted->window_width, window_height = ted->window_height; - float char_height = text_font_char_height(font); // render backdrop glBegin(GL_QUADS); @@ -241,16 +85,9 @@ static void menu_render(Ted *ted, Menu menu) { menu_x2 -= inner_padding; menu_y2 -= inner_padding; - float line_buffer_height = char_height * 1.5f; - float line_buffer_x1 = menu_x1, - line_buffer_y1 = menu_y1, - line_buffer_x2 = menu_x2, - line_buffer_y2 = line_buffer_y1 + line_buffer_height; - - buffer_render(&ted->line_buffer, line_buffer_x1, line_buffer_y1, line_buffer_x2, line_buffer_y2); FileSelector *fs = &ted->file_selector; - fs->bounds = rect4(menu_x1, line_buffer_y2, menu_x2, menu_y2); + fs->bounds = rect4(menu_x1, menu_y1, menu_x2, menu_y2); file_selector_render(ted, fs); } } diff --git a/ted-base.c b/ted-base.c index c3aff0e..001cb94 100644 --- a/ted-base.c +++ b/ted-base.c @@ -56,12 +56,12 @@ static Status ted_get_file(char const *name, char *out, size_t outsz) { return true; } if (*ted_local_data_dir) { - str_printf(out, outsz, "%s" PATH_SEPARATOR "%s", ted_local_data_dir, name); + str_printf(out, outsz, "%s" PATH_SEPARATOR_STR "%s", ted_local_data_dir, name); if (fs_file_exists(out)) return true; } if (*ted_global_data_dir) { - str_printf(out, outsz, "%s" PATH_SEPARATOR "%s", ted_global_data_dir, name); + str_printf(out, outsz, "%s" PATH_SEPARATOR_STR "%s", ted_global_data_dir, name); if (fs_file_exists(out)) return true; } diff --git a/ted.h b/ted.h index 8f2958f..86f3269 100644 --- a/ted.h +++ b/ted.h @@ -76,9 +76,10 @@ ENUM_U16 { MENU_OPEN } ENUM_U16_END(Menu); -// file entries for menus involving a file selector +// file entries for file selectors typedef struct { - char *name; + char *name; // just the file name + char *path; // full path FsType type; } FileEntry; @@ -86,11 +87,15 @@ typedef struct { Rect bounds; u32 n_entries; FileEntry *entries; + char *cwd; // a dynamic null-terminated array of chars representing the current directory + bool submitted; // set to true if the line buffer was just submitted this frame. } FileSelector; typedef struct Ted { Font *font; TextBuffer *active_buffer; + // buffer we are currently drag-to-selecting in, if any + TextBuffer *drag_buffer; // while a menu or something is open, there is no active buffer. when the menu is closed, // the old active buffer needs to be restored. that's what this stores. TextBuffer *prev_active_buffer; @@ -104,5 +109,6 @@ typedef struct Ted { TextBuffer line_buffer; // general-purpose line buffer for inputs -- used for menus TextBuffer main_buffer; KeyAction key_actions[KEY_COMBO_COUNT]; + char cwd[TED_PATH_MAX]; // current working directory char error[256]; } Ted; diff --git a/ui.c b/ui.c new file mode 100644 index 0000000..58f76d9 --- /dev/null +++ b/ui.c @@ -0,0 +1,234 @@ +// where is the ith entry in the file selector on the screen? +// returns false if it's completely offscreen +static bool file_selector_entry_pos(Ted const *ted, FileSelector const *fs, + u32 i, Rect *r) { + Rect bounds = fs->bounds; + float char_height = text_font_char_height(ted->font); + *r = rect(V2(bounds.pos.x, bounds.pos.y + + char_height // make room for cwd + + char_height * 1.5f // make room for line buffer + + char_height * (float)i), + V2(bounds.size.x, char_height)); + return rect_clip_to_rect(r, bounds); +} + +// clear the entries in the file selector +static void file_selector_clear_entries(FileSelector *fs) { + for (u32 i = 0; i < fs->n_entries; ++i) { + free(fs->entries[i].name); + free(fs->entries[i].path); + } + free(fs->entries); + fs->entries = NULL; + fs->n_entries = 0; +} + +static void file_selector_free(FileSelector *fs) { + file_selector_clear_entries(fs); + arr_clear(fs->cwd); +} + +static int qsort_file_entry_cmp(void const *av, void const *bv) { + FileEntry const *a = av, *b = bv; + // put directories first + if (a->type > b->type) { + return -1; + } + if (a->type < b->type) { + return +1; + } + return strcmp_case_insensitive(a->name, b->name); +} + +// change directory of file selector. +void file_selector_cd(FileSelector *fs, char const *path) { + // @TODO: handle .. properly + if (path[0] == PATH_SEPARATOR +#if _WIN32 + // check for, e.g. C:\ at start of path + || (strlen(path) >= 3 && path[1] == ':' && path[2] == '\\') +#endif + ) { + // this is an absolute path. discard our previous cwd. + arr_clear(fs->cwd); + } + if (strlen(fs->cwd) > 0 && fs->cwd[strlen(fs->cwd) - 1] != PATH_SEPARATOR) { + // add path separator to end if not already there + arr_append_str(fs->cwd, PATH_SEPARATOR_STR); + } + arr_append_str(fs->cwd, path); +} + +// returns the name of the selected file, or NULL +// if none was selected. the returned pointer should be freed. +static char *file_selector_update(Ted *ted, FileSelector *fs) { + String32 search_term32 = buffer_get_line(&ted->line_buffer, 0); + if (!fs->cwd) { + // set the file selector's directory to our current directory. + arr_append_str(fs->cwd, ted->cwd); + } + char *search_term = search_term32.len ? str32_to_utf8_cstr(search_term32) : NULL; + + bool submitted = fs->submitted; + fs->submitted = false; + + bool on_screen = true; + for (u32 i = 0; i < fs->n_entries; ++i) { + Rect r = {0}; + FileEntry *entry = &fs->entries[i]; + char *name = entry->name, *path = entry->path; + FsType type = entry->type; + + // check if this entry was clicked on + if (on_screen && file_selector_entry_pos(ted, fs, i, &r)) { + for (u32 c = 0; c < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++c) { + if (rect_contains_point(r, ted->mouse_clicks[SDL_BUTTON_LEFT][c])) { + // this option was selected + switch (type) { + case FS_FILE: + free(search_term); + if (path) return str_dup(path); + break; + case FS_DIRECTORY: + file_selector_cd(fs, name); + break; + default: break; + } + } + } + } else on_screen = false; + + // check if we submitted this entry + if (submitted && streq(search_term, name)) { + switch (type) { + case FS_FILE: + free(search_term); + if (path) return str_dup(path); + break; + case FS_DIRECTORY: + file_selector_cd(fs, name); + break; + default: break; + } + } + } + + // user pressed enter after typing a non-existent file into the search bar + if (submitted) { + // don't do anything for now + } + + // free previous entries + file_selector_clear_entries(fs); + // get new entries + char **files = fs_list_directory(fs->cwd); + if (files) { + char const *cwd = fs->cwd; + bool cwd_has_path_sep = cwd[strlen(cwd) - 1] == PATH_SEPARATOR; + u32 nfiles; + for (nfiles = 0; files[nfiles]; ++nfiles); + + // filter entries + bool increment = true; + for (u32 i = 0; i < nfiles; i += increment, increment = true) { + // remove if the file name does not contain the search term, + bool remove = search_term && *search_term && !stristr(files[i], search_term); + // or if this is just the current directory + remove |= streq(files[i], "."); + if (remove) { + // remove this one + free(files[i]); + --nfiles; + if (nfiles) { + files[i] = files[nfiles]; + } + increment = false; + } + } + + if (nfiles) { + FileEntry *entries = ted_calloc(ted, nfiles, sizeof *entries); + if (entries) { + fs->n_entries = nfiles; + fs->entries = entries; + for (u32 i = 0; i < nfiles; ++i) { + char *name = files[i]; + entries[i].name = name; + // add cwd to start of file name + size_t path_size = strlen(name) + strlen(cwd) + 3; + char *path = ted_calloc(ted, 1, path_size); + if (path) { + snprintf(path, path_size - 1, "%s%s%s", cwd, cwd_has_path_sep ? PATH_SEPARATOR_STR : "", name); + entries[i].path = path; + entries[i].type = fs_path_type(path); + } else { + entries[i].path = NULL; // what can we do? + entries[i].type = FS_NON_EXISTENT; + } + } + } + qsort(entries, nfiles, sizeof *entries, qsort_file_entry_cmp); + } + + free(files); + } else { + ted_seterr(ted, "Couldn't list directory '%s'.", fs->cwd); + } + + free(search_term); + return NULL; +} + +static void file_selector_render(Ted *ted, FileSelector *fs) { + Settings const *settings = &ted->settings; + u32 const *colors = settings->colors; + Rect bounds = fs->bounds; + u32 n_entries = fs->n_entries; + FileEntry const *entries = fs->entries; + Font *font = ted->font; + float char_height = text_font_char_height(ted->font); + float x1, y1, x2, y2; + rect_coords(bounds, &x1, &y1, &x2, &y2); + + // current working directory @TODO + + + // search buffer + float line_buffer_height = char_height * 1.5f; + buffer_render(&ted->line_buffer, x1, y1, x2, y1 + line_buffer_height); + y1 += line_buffer_height; + + + for (u32 i = 0; i < n_entries; ++i) { + // highlight entry user is mousing over + Rect r; + if (!file_selector_entry_pos(ted, fs, i, &r)) break; + if (rect_contains_point(r, ted->mouse_pos)) { + glBegin(GL_QUADS); + gl_color_rgba(colors[COLOR_MENU_HL]); + rect_render(r); + glEnd(); + } + } + + TextRenderState text_render_state = {.min_x = x1, .max_x = x2, .min_y = y1, .max_y = y2, .render = true}; + // render file names themselves + for (u32 i = 0; i < n_entries; ++i) { + Rect r; + if (!file_selector_entry_pos(ted, fs, i, &r)) break; + float x = r.pos.x, y = r.pos.y; + switch (entries[i].type) { + case FS_FILE: + gl_color_rgba(colors[COLOR_TEXT]); + break; + case FS_DIRECTORY: + gl_color_rgba(colors[COLOR_TEXT_FOLDER]); + break; + default: + gl_color_rgba(colors[COLOR_TEXT_OTHER]); + break; + } + text_render_with_state(font, &text_render_state, entries[i].name, x, y); + } +} + -- cgit v1.2.3