From 617907fb4731d67f6e7aca17b9dd7881f2093aad Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Sun, 21 Feb 2021 12:40:26 -0500 Subject: started to generalize selector code --- command.c | 32 +++----- main.c | 9 ++- ted.h | 23 +++++- ui.c | 248 +++++++++++++++++++++++++++++++------------------------------- 4 files changed, 159 insertions(+), 153 deletions(-) diff --git a/command.c b/command.c index 7c76fa2..b00e35b 100644 --- a/command.c +++ b/command.c @@ -19,7 +19,6 @@ char const *command_to_str(Command c) { void command_execute(Ted *ted, Command c, i64 argument) { TextBuffer *buffer = ted->active_buffer; - FileSelector *file_selector = &ted->file_selector; Settings *settings = &ted->settings; @@ -38,11 +37,11 @@ void command_execute(Ted *ted, Command c, i64 argument) { if (buffer) buffer_cursor_move_right(buffer, argument); break; case CMD_UP: - if (file_selector->open) file_selector_up(ted, file_selector, argument); + if (ted->selector_open) selector_up(ted, ted->selector_open, argument); else if (buffer) buffer_cursor_move_up(buffer, argument); break; case CMD_DOWN: - if (file_selector->open) file_selector_down(ted, file_selector, argument); + if (ted->selector_open) selector_down(ted, ted->selector_open, argument); else if (buffer) buffer_cursor_move_down(buffer, argument); break; case CMD_SELECT_LEFT: @@ -121,24 +120,14 @@ void command_execute(Ted *ted, Command c, i64 argument) { case CMD_NEWLINE_BACK: if (!buffer) { } else if (buffer->is_line_buffer) { - switch (ted->menu) { - case MENU_NONE: - if (ted->find) { - if (buffer == &ted->find_buffer || buffer == &ted->replace_buffer) { - if (c == CMD_NEWLINE) - find_next(ted); - else - find_prev(ted); - } + ted->line_buffer_submitted = true; + if (ted->find) { + if (buffer == &ted->find_buffer || buffer == &ted->replace_buffer) { + if (c == CMD_NEWLINE) + find_next(ted); + else + find_prev(ted); } - break; - case MENU_ASK_RELOAD: - case MENU_WARN_UNSAVED: - break; - case MENU_OPEN: - case MENU_SAVE_AS: { - ted->file_selector.submitted = true; - } break; } } else { buffer_newline(buffer); @@ -226,7 +215,8 @@ void command_execute(Ted *ted, Command c, i64 argument) { break; case CMD_CUT: if (buffer) buffer_cut(buffer); - break; case CMD_PASTE: + break; + case CMD_PASTE: if (buffer) buffer_paste(buffer); break; diff --git a/main.c b/main.c index 2a47545..8af990c 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,6 @@ // @TODO: // - Ctrl+D = :find-definition --- tag menu (see all tags, select one) +// - fix automatic horizontal scrolling // - goto line @@ -8,11 +9,11 @@ // - split -// - completion - // - Windows installation +// - on crash, output backtrace to log // - restore previously opened files (setting: restore-session) -// - on crash, output backtrace to log, save buffers to temp directory + +// - completion #include "base.h" no_warn_start @@ -392,6 +393,8 @@ int main(int argc, char **argv) { memset(ted->nmouse_clicks, 0, sizeof ted->nmouse_clicks); ted->scroll_total_x = ted->scroll_total_y = 0; + ted->line_buffer_submitted = false; + ted_update_window_dimensions(ted); u32 key_modifier = (u32)ctrl_down << KEY_MODIFIER_CTRL_BIT | (u32)shift_down << KEY_MODIFIER_SHIFT_BIT diff --git a/ted.h b/ted.h index e9b8904..6444b5f 100644 --- a/ted.h +++ b/ted.h @@ -168,6 +168,20 @@ ENUM_U16 { MENU_ASK_RELOAD, // prompt about whether to reload file which has ben changed by another program } ENUM_U16_END(Menu); +typedef struct { + char const *name; + u32 color; +} SelectorEntry; + +typedef struct { + SelectorEntry *entries; + u32 n_entries; + Rect bounds; + u32 cursor; // index where the selector thing is + float scroll; + bool enable_cursor; +} Selector; + // file entries for file selectors typedef struct { char *name; // just the file name @@ -176,14 +190,11 @@ typedef struct { } FileEntry; typedef struct { + Selector sel; Rect bounds; u32 n_entries; - u32 selected; - float scroll; FileEntry *entries; char cwd[TED_PATH_MAX]; - bool open; // is the file selector on screen? - bool submitted; // set to true if the line buffer was just submitted this frame. bool create_menu; // this is for creating files, not opening files } FileSelector; @@ -237,6 +248,7 @@ typedef struct Ted { Menu menu; FileSelector file_selector; TextBuffer line_buffer; // general-purpose line buffer for inputs -- used for menus + bool line_buffer_submitted; // set to true if the line buffer was just submitted this frame. TextBuffer find_buffer; // use for "find" term in find/find+replace TextBuffer replace_buffer; // "replace" for find+replace TextBuffer build_buffer; // buffer for build output (view only) @@ -259,6 +271,9 @@ typedef struct Ted { BuildError *build_errors; // dynamic array of build errors u32 build_error; // build error we are currently "on" + // points to a selector if any is open, otherwise NULL. + Selector *selector_open; + Process build_process; // When we read the stdout from the build process, the tail end of the read could be an // incomplete UTF-8 code point. This is where we store that "tail end" until more diff --git a/ui.c b/ui.c index 7640215..39a4dc2 100644 --- a/ui.c +++ b/ui.c @@ -2,50 +2,119 @@ #include #endif -static float file_selector_entries_start_y(Ted const *ted, FileSelector const *fs) { - Rect bounds = fs->bounds; - float padding = ted->settings.padding; - float char_height = text_font_char_height(ted->font); - return bounds.pos.y - + char_height * 0.75f + padding // make room for cwd - + char_height * 1.25f + padding; // make room for line buffer +static float selector_entries_start_y(Ted const *ted, Selector const *s) { + (void)ted; + return s->bounds.pos.y; } -// number of file entries that can be displayed on the screen -static u32 file_selector_n_display_entries(Ted const *ted, FileSelector const *fs) { +// number of entries that can be displayed on the screen +static u32 selector_n_display_entries(Ted const *ted, Selector const *s) { float char_height = text_font_char_height(ted->font); - float entries_h = rect_y2(fs->bounds) - file_selector_entries_start_y(ted, fs); + float entries_h = rect_y2(s->bounds) - selector_entries_start_y(ted, s); return (u32)(entries_h / char_height); } -static void file_selector_clamp_scroll(Ted const *ted, FileSelector *fs) { - float max_scroll = (float)fs->n_entries - (float)file_selector_n_display_entries(ted, fs); +static void selector_clamp_scroll(Ted const *ted, Selector *s) { + float max_scroll = (float)s->n_entries - (float)selector_n_display_entries(ted, s); if (max_scroll < 0) max_scroll = 0; - fs->scroll = clampf(fs->scroll, 0, max_scroll); + s->scroll = clampf(s->scroll, 0, max_scroll); } -static void file_selector_scroll_to_selected(Ted const *ted, FileSelector *fs) { - u32 n_display_entries = file_selector_n_display_entries(ted, fs); +static void selector_scroll_to_cursor(Ted const *ted, Selector *s) { + u32 n_display_entries = selector_n_display_entries(ted, s); float scrolloff = ted->settings.scrolloff; - float min_scroll = (float)fs->selected - ((float)n_display_entries - scrolloff); - float max_scroll = (float)fs->selected - scrolloff; - fs->scroll = clampf(fs->scroll, min_scroll, max_scroll); - file_selector_clamp_scroll(ted, fs); + float min_scroll = (float)s->cursor - ((float)n_display_entries - scrolloff); + float max_scroll = (float)s->cursor - scrolloff; + s->scroll = clampf(s->scroll, min_scroll, max_scroll); + selector_clamp_scroll(ted, s); } -// where is the ith entry in the file selector on the screen? +// where is the ith entry in the 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; +static bool selector_entry_pos(Ted const *ted, Selector const *s, u32 i, Rect *r) { + Rect bounds = s->bounds; float char_height = text_font_char_height(ted->font); - *r = rect(V2(bounds.pos.x, file_selector_entries_start_y(ted, fs) - - char_height * fs->scroll + *r = rect(V2(bounds.pos.x, selector_entries_start_y(ted, s) + - char_height * s->scroll + char_height * (float)i), V2(bounds.size.x, char_height)); return rect_clip_to_rect(r, bounds); } +static void selector_up(Ted const *ted, Selector *s, i64 n) { + if (!s->enable_cursor || s->n_entries == 0) { + // can't do anything + return; + } + s->cursor = (u32)mod_i64(s->cursor - n, s->n_entries); + selector_scroll_to_cursor(ted, s); +} + +static void selector_down(Ted const *ted, Selector *s, i64 n) { + selector_up(ted, s, -n); +} + +// returns index of entry selected, or -1 for none +static int selector_update(Ted *ted, Selector *s) { + int ret = -1; + for (u32 i = 0; i < s->n_entries; ++i) { + // check if this entry was clicked on + Rect entry_rect; + if (selector_entry_pos(ted, s, i, &entry_rect)) { + for (uint c = 0; c < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++c) { + if (rect_contains_point(entry_rect, ted->mouse_clicks[SDL_BUTTON_LEFT][c])) { + // this option was selected + ret = (int)i; + } + } + } + } + // apply scroll + float scroll_speed = 2.5f; + s->scroll += scroll_speed * (float)ted->scroll_total_y; + + return ret; +} + +static void selector_render(Ted *ted, Selector *s) { + Settings const *settings = &ted->settings; + u32 const *colors = settings->colors; + Font *font = ted->font; + + Rect bounds = s->bounds; + + for (u32 i = 0; i < s->n_entries; ++i) { + // highlight entry user is hovering over/selecting + Rect entry_rect; + if (selector_entry_pos(ted, s, i, &entry_rect)) { + rect_clip_to_rect(&entry_rect, bounds); + if (rect_contains_point(entry_rect, ted->mouse_pos) || (s->enable_cursor && s->cursor == i)) { + // highlight it + gl_geometry_rect(entry_rect, colors[COLOR_MENU_HL]); + } + } + } + gl_geometry_draw(); + + TextRenderState text_state = text_render_state_default; + rect_coords(bounds, &text_state.min_x, &text_state.min_y, &text_state.max_x, &text_state.max_y); + text_state.render = true; + + // render entries themselves + SelectorEntry *entries = s->entries; + for (u32 i = 0; i < s->n_entries; ++i) { + Rect r; + if (selector_entry_pos(ted, s, i, &r)) { + float x = r.pos.x, y = r.pos.y; + text_state.x = x; text_state.y = y; + rgba_u32_to_floats(entries[i].color, text_state.color); + text_utf8_with_state(font, &text_state, entries[i].name); + } + } + text_render(font); +} + + // clear the entries in the file selector static void file_selector_clear_entries(FileSelector *fs) { for (u32 i = 0; i < fs->n_entries; ++i) { @@ -53,8 +122,9 @@ static void file_selector_clear_entries(FileSelector *fs) { free(fs->entries[i].path); } free(fs->entries); + arr_free(fs->sel.entries); fs->entries = NULL; - fs->n_entries = 0; + fs->n_entries = fs->sel.n_entries = 0; } // returns true if there are any directory entries @@ -72,19 +142,6 @@ static void file_selector_free(FileSelector *fs) { memset(fs, 0, sizeof *fs); } -static void file_selector_up(Ted const *ted, FileSelector *fs, i64 n) { - if (fs->n_entries == 0) { - // can't do anything - return; - } - fs->selected = (u32)mod_i64(fs->selected - n, fs->n_entries); - file_selector_scroll_to_selected(ted, fs); -} - -static void file_selector_down(Ted const *ted, FileSelector *fs, i64 n) { - file_selector_up(ted, fs, -n); -} - static int qsort_file_entry_cmp(void *search_termv, void const *av, void const *bv) { char const *search_term = search_termv; FileEntry const *a = av, *b = bv; @@ -218,16 +275,14 @@ static Status file_selector_cd_(Ted const *ted, FileSelector *fs, char const *pa // other members of ALL_PATH_SEPARATORS // returns false if this path doesn't exist or isn't a directory static bool file_selector_cd(Ted const *ted, FileSelector *fs, char const *path) { - fs->selected = 0; - fs->scroll = 0; + fs->sel.cursor = 0; + fs->sel.scroll = 0; return file_selector_cd_(ted, fs, path, 0); } // 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) { - fs->open = true; - TextBuffer *line_buffer = &ted->line_buffer; String32 search_term32 = buffer_get_line(line_buffer, 0); char *const cwd = fs->cwd; @@ -273,36 +328,8 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) { char *search_term = search_term32.len ? str32_to_utf8_cstr(search_term32) : NULL; - 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 (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(ted, fs, name); - buffer_clear(line_buffer); // clear search term - break; - default: break; - } - } - } - } - - } - - bool submitted = fs->submitted; - fs->submitted = false; + bool submitted = ted->line_buffer_submitted; // user pressed enter in search bar if (submitted) { @@ -313,8 +340,8 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) { free(search_term); return str_dup(path); } else { - if (fs->selected < fs->n_entries) { - FileEntry *entry = &fs->entries[fs->selected]; + if (fs->sel.cursor < fs->n_entries) { + FileEntry *entry = &fs->entries[fs->sel.cursor]; switch (entry->type) { case FS_FILE: free(search_term); @@ -373,7 +400,7 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) { FileEntry *entries = ted_calloc(ted, nfiles, sizeof *entries); if (entries) { fs->n_entries = nfiles; - if (fs->selected >= fs->n_entries) fs->selected = nfiles - 1; + if (fs->sel.cursor >= fs->n_entries) fs->sel.cursor = nfiles - 1; fs->entries = entries; for (u32 i = 0; i < nfiles; ++i) { char *name = files[i]; @@ -401,11 +428,6 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) { ted_seterr(ted, "Couldn't list directory '%s'.", cwd); } - // apply scroll - float scroll_speed = 2.5f; - fs->scroll += scroll_speed * (float)ted->scroll_total_y; - file_selector_clamp_scroll(ted, fs); - free(search_term); return NULL; } @@ -414,8 +436,6 @@ 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 padding = settings->padding; float char_height = text_font_char_height(font); @@ -431,52 +451,30 @@ static void file_selector_render(Ted *ted, FileSelector *fs) { buffer_render(&ted->line_buffer, rect4(x1, y1, x2, y1 + line_buffer_height)); y1 += line_buffer_height; - - Rect text_bounds = rect4(x1, y1, x2, y2); - for (u32 i = 0; i < n_entries; ++i) { - // highlight entry user is hovering over/selecting - Rect r; - if (file_selector_entry_pos(ted, fs, i, &r)) { - rect_clip_to_rect(&r, text_bounds); - if (rect_contains_point(r, ted->mouse_pos) || - ((!fs->create_menu || buffer_empty(&ted->line_buffer)) // only highlight selected for create menus if there is no search term (because that will be the name of the file) - && fs->selected == i)) { - gl_geometry_rect(r, colors[COLOR_MENU_HL]); - } + // render selector + Selector *sel = &fs->sel; + sel->bounds = rect4(x1, y1, x2, y2); // selector takes up remaining space + arr_clear(sel->entries); + for (u32 i = 0; i < fs->n_entries; ++i) { + ColorSetting color = 0; + switch (fs->entries[i].type) { + case FS_FILE: + color = COLOR_TEXT; + break; + case FS_DIRECTORY: + color = COLOR_TEXT_FOLDER; + break; + default: + color = COLOR_TEXT_OTHER; + break; } + SelectorEntry entry = {.name = fs->entries[i].name, .color = colors[color]}; + arr_add(sel->entries, entry); } - gl_geometry_draw(); - - TextRenderState text_state = text_render_state_default; - text_state.min_x = x1; - text_state.max_x = x2; - text_state.min_y = y1; - text_state.max_y = y2; - text_state.render = true; + if (sel->entries) + sel->n_entries = fs->n_entries; - // render file names themselves - for (u32 i = 0; i < n_entries; ++i) { - Rect r; - if (file_selector_entry_pos(ted, fs, i, &r)) { - float x = r.pos.x, y = r.pos.y; - ColorSetting color = 0; - switch (entries[i].type) { - case FS_FILE: - color = COLOR_TEXT; - break; - case FS_DIRECTORY: - color = COLOR_TEXT_FOLDER; - break; - default: - color = COLOR_TEXT_OTHER; - break; - } - text_state.x = x; text_state.y = y; - rgba_u32_to_floats(colors[color], text_state.color); - text_utf8_with_state(font, &text_state, entries[i].name); - } - } - text_render(font); + selector_render(ted, sel); } static void button_render(Ted *ted, Rect button, char const *text, u32 color) { -- cgit v1.2.3