static void menu_open(Ted *ted, Menu menu) { ted->menu = menu; ted->prev_active_buffer = ted->active_buffer; ted->active_buffer = NULL; switch (menu) { case MENU_NONE: assert(0); break; case MENU_OPEN: ted->active_buffer = &ted->line_buffer; break; } } static void menu_close(Ted *ted, bool restore_prev_active_buffer) { ted->menu = MENU_NONE; if (restore_prev_active_buffer) ted->active_buffer = ted->prev_active_buffer; ted->prev_active_buffer = NULL; buffer_clear(&ted->line_buffer); } // returns the rectangle of the screen coordinates of the menu static Rect menu_rect(Ted *ted) { Settings *settings = &ted->settings; float window_width = ted->window_width, window_height = ted->window_height; float padding = settings->padding; float menu_width = settings->max_menu_width; menu_width = minf(menu_width, window_width - 2 * padding); return rect( V2(window_width * 0.5f - 0.5f * menu_width, padding), V2(menu_width, window_height - 2 * padding) ); } // 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) { // open that file! if (ted_open_file(ted, selected_entry.name)) { menu_close(ted, false); file_selector_free(&ted->file_selector); } } } break; } } 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); gl_color_rgba(colors[COLOR_MENU_BACKDROP]); rect_render(rect(V2(0, 0), V2(window_width, window_height))); glEnd(); if (menu == MENU_OPEN) { float padding = 20; float menu_x1 = window_width * 0.5f - 300; float menu_x2 = window_width * 0.5f + 300; menu_x1 = maxf(menu_x1, padding); menu_x2 = minf(menu_x2, window_width - padding); float menu_y1 = padding; float menu_y2 = window_height - padding; Rect menu_rect = rect4(menu_x1, menu_y1, menu_x2, menu_y2); float inner_padding = 10; // menu rectangle & border glBegin(GL_QUADS); gl_color_rgba(colors[COLOR_MENU_BG]); rect_render(menu_rect); gl_color_rgba(colors[COLOR_BORDER]); rect_render_border(menu_rect, settings->border_thickness); glEnd(); menu_x1 += inner_padding; menu_y1 += inner_padding; 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); file_selector_render(ted, fs); } }