From 5073f74aefb91616fb8e1df087747105e46e265d Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Wed, 22 Jan 2025 08:02:14 -0500 Subject: Rough draft of some tui stuff Signed-off-by: Andrew Opalach --- src/fruits/cmc/cmc.c | 129 ++++++++++++++++++------- src/fruits/cmc/cmc.h | 16 ++++ src/fruits/cmc/meson.build | 3 +- src/fruits/cmc/ui/pane_list.c | 157 +++++++++++++++++++++++++++++++ src/fruits/cmc/ui/pane_search.c | 177 ++++++++++++++++++++++++++++++---- src/fruits/cmc/ui/ui.c | 204 +++++++++++++++++++++++++++++++++++++--- src/fruits/cmc/ui/ui.h | 52 +++++++++- 7 files changed, 675 insertions(+), 63 deletions(-) create mode 100644 src/fruits/cmc/ui/pane_list.c (limited to 'src/fruits/cmc') diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c index 7691f99..58bb3b1 100644 --- a/src/fruits/cmc/cmc.c +++ b/src/fruits/cmc/cmc.c @@ -4,6 +4,7 @@ #include "../../portal/src/packet_ext.h" #include "../../server/common.h" +#include "../../liana/list.h" #include "../common.h" @@ -26,10 +27,22 @@ static struct cmc_search *get_search_by_id(struct cmc *c, s32 id) return NULL; } -static void parse_user_state(struct cmc *c, struct nn_packet *packet) +static void parse_list_entry(struct nn_packet *packet, struct cmc_list_entry *entry) { - u32 size = nn_packet_read_u32(packet); - for (u32 i = 0; i < size; i++) { + entry->id = nn_packet_read_u32(packet); + entry->duration = nn_packet_read_u64(packet); + entry->start = nn_packet_read_u64(packet); + entry->paused_at = nn_packet_read_u64(packet); + entry->offset = nn_packet_read_u64(packet); + wstr w; + nn_packet_read_wstr(packet, &w); + al_wstr_clone(&entry->name, &w); +} + +static void parse_initial_user_state(struct cmc *c, struct nn_packet *packet) +{ + u32 count = nn_packet_read_u32(packet); + for (u32 i = 0; i < count; i++) { struct cmc_search *search = al_alloc_object(struct cmc_search); search->id = nn_packet_read_s32(packet); str s; @@ -40,28 +53,46 @@ static void parse_user_state(struct cmc *c, struct nn_packet *packet) al_array_init(search->pages); al_array_push(c->searches, search); } + count = nn_packet_read_u32(packet); + for (u32 i = 0; i < count; i++) { + struct cmc_list *list = al_alloc_object(struct cmc_list); + str s; + nn_packet_read_str(packet, &s); + al_str_clone(&list->name, &s); + list->current = nn_packet_read_s32(packet); + al_array_init(list->entries); + u32 inner_count = nn_packet_read_s32(packet); + for (u32 j = 0; j < inner_count; j++) { + struct cmc_list_entry entry; + parse_list_entry(packet, &entry); + al_array_push(list->entries, entry); + } + al_array_push(c->lists, list); + } } static void client_callback(void *userdata, u8 op, void *opaque) { struct cmc *c = (struct cmc *)userdata; switch (op) { - case CAMU_CLIENT_LOGIN: { + case CAMU_CLIENT_LOGGED_IN: { struct nn_packet *packet = (struct nn_packet *)opaque; switch (c->command) { case CLI_OPEN_UI: - parse_user_state(c, packet); + parse_initial_user_state(c, packet); if (!cmc_ui_init(&c->ui, &c->loop, c)) { nn_event_loop_break_one(&c->loop); } + struct cmc_list *list; + al_array_foreach(c->lists, i, list) { + cmc_lp_add_list(&c->ui, list); + } + struct cmc_search *search; + al_array_foreach(c->searches, i, search) { + cmc_sp_add_search(&c->ui, search); + } break; case CLI_ADD: { - str *arg; - al_array_foreach_ptr(c->args, i, arg) { - camu_client_add_from_path(&c->client, &al_str_c("default"), arg); - al_str_free(arg); - } - camu_client_disconnect(&c->client); break; } case CLI_SEARCH: { @@ -81,32 +112,65 @@ static void client_callback(void *userdata, u8 op, void *opaque) search->id = *(s32 *)opaque; al_array_init(search->pages); al_array_push(c->searches, search); - camu_client_get_page(&c->client, search->id, 0); + cmc_sp_add_search(&c->ui, search); + break; + } + case CAMU_CLIENT_GOT_RESULTS: { break; } - case CAMU_CLIENT_PAGE_RESULTS: { + case CAMU_CLIENT_GOT_META: { struct nn_packet *packet = (struct nn_packet *)opaque; - s32 id = nn_packet_read_s32(packet); - struct cmc_search *search = get_search_by_id(c, id); - if (!search) return; - struct cmc_search_page *page = al_alloc_object(struct cmc_search_page); - page->num = nn_packet_read_u32(packet); - u32 posts = nn_packet_read_u32(packet); - for (u32 i = 0; i < posts; i++) { - struct camu_post post; - nn_packet_read_post(packet, &post); - camu_post_cache_push(&c->cache, &post); + u8 op = nn_packet_read_u8(packet); + switch (op) { + case LIANA_META_CURRENT_CHANGED: { + str name; + nn_packet_read_str(packet, &name); + struct cmc_list *list; + al_array_foreach(c->lists, i, list) { + if (al_str_eq(&list->name, &name)) { + list->current = nn_packet_read_s32(packet); + struct cmc_list_entry *entry = &al_array_at(list->entries, list->current); + parse_list_entry(packet, entry); + break; + } + } + cmc_ui_queue_render(&c->ui); + break; + } + case LIANA_META_ADDED_ENTRY: { + str name; + nn_packet_read_str(packet, &name); + struct cmc_list_entry entry; + parse_list_entry(packet, &entry); + struct cmc_list *list; + al_array_foreach(c->lists, i, list) { + if (al_str_eq(&list->name, &name)) { + al_array_push(list->entries, entry); + break; + } + } + cmc_ui_queue_render(&c->ui); + break; + } + case LIANA_META_ENTRY_SEEKED: { + str name; + nn_packet_read_str(packet, &name); + struct cmc_list *list; + al_array_foreach(c->lists, i, list) { + if (al_str_eq(&list->name, &name)) { + u32 id = nn_packet_peek_u32(packet); + struct cmc_list_entry *entry; + al_array_foreach_ptr(list->entries, j, entry) { + if (entry->id == id) { + parse_list_entry(packet, entry); + break; + } + } + } + } + break; } - u32 ids = nn_packet_read_u32(packet); - for (u32 i = 0; i < ids; i++) { - str unique_id; - nn_packet_read_str(packet, &unique_id); - str s; - al_str_clone(&s, &unique_id); - al_array_push(page->list, s); } - al_array_push(search->pages, page); - camu_client_add_from_post(&c->client, &al_str_c("default"), &al_array_at(page->list, 0), 0); break; } } @@ -166,6 +230,7 @@ s32 main(s32 argc, char *argv[]) al_array_init(c.args); if (!parse_cmd(argc, argv)) return EXIT_FAILURE; + al_array_init(c.lists); camu_post_cache_init(&c.cache); al_array_init(c.searches); diff --git a/src/fruits/cmc/cmc.h b/src/fruits/cmc/cmc.h index 96de27b..0a3cbca 100644 --- a/src/fruits/cmc/cmc.h +++ b/src/fruits/cmc/cmc.h @@ -21,11 +21,27 @@ struct cmc_search { array(struct cmc_search_page *) pages; }; +struct cmc_list_entry { + u32 id; + u64 duration; + u64 start; + u64 paused_at; + u64 offset; + wstr name; +}; + +struct cmc_list { + str name; + s32 current; + array(struct cmc_list_entry) entries; +}; + struct cmc { struct nn_event_loop loop; u8 command; array(str) args; struct camu_client client; + array(struct cmc_list *) lists; struct camu_post_cache cache; array(struct cmc_search *) searches; struct cmc_ui ui; diff --git a/src/fruits/cmc/meson.build b/src/fruits/cmc/meson.build index 76efff5..6b7060d 100644 --- a/src/fruits/cmc/meson.build +++ b/src/fruits/cmc/meson.build @@ -10,9 +10,10 @@ use_tui = true if use_tui cmc_src += [ 'ui/ui.c', + 'ui/pane_list.c', 'ui/pane_search.c', ] - cmc_deps += [dependency('notcurses')] + cmc_deps += [notcurses, notcurses_core] endif if is_windows and meson.is_cross_build() diff --git a/src/fruits/cmc/ui/pane_list.c b/src/fruits/cmc/ui/pane_list.c new file mode 100644 index 0000000..9a04211 --- /dev/null +++ b/src/fruits/cmc/ui/pane_list.c @@ -0,0 +1,157 @@ +#include + +#include "../../liana/list.h" + +#include "../cmc.h" + +#include "ui.h" + +void cmc_lp_init(struct cmc_ui *ui) +{ + al_array_init(ui->lp.tabs); + ui->lp.current = 0; +} + +void cmc_lp_layout(struct cmc_ui *ui, struct ncplane *parent) +{ + if (ui->lp.n) ncplane_destroy(ui->lp.n); + struct ncplane_options nopts = { 0 }; + nopts.rows = ncplane_dim_y(parent); + nopts.cols = ncplane_dim_x(parent); + ui->lp.n = ncplane_create(parent, &nopts); +} + +void cmc_lp_add_list(struct cmc_ui *ui, struct cmc_list *list) +{ + struct cmc_list_tab tab = { + .list = list, + .selected = 0 + }; + al_array_push(ui->lp.tabs, tab); +} + +bool cmc_lp_handle_input(struct cmc_ui *ui, struct ncinput *input) +{ + if (!ui->lp.tabs.count) { + return false; + } + + struct cmc_list_tab *tab = &al_array_at(ui->lp.tabs, ui->lp.current); + u32 count = tab->list->entries.count; + switch (input->id) { + case 'j': + if (!cmc_ui_consider_input(input, true) || tab->selected >= count - 1) { + return false; + } + tab->selected++; + break; + case 'k': + if (!cmc_ui_consider_input(input, true) || tab->selected == 0) { + return false; + } + tab->selected--; + break; + case NCKEY_RETURN: + if (!cmc_ui_consider_input(input, false)) { + return false; + } + camu_client_skipto(&ui->c->client, &tab->list->name, tab->selected); + break; + case NCKEY_BUTTON1: { + if (!cmc_ui_consider_input(input, false)) { + return false; + } + u32 height = ncplane_dim_y(ui->lp.n); + u32 width = ncplane_dim_x(ui->lp.n); + if (input->x > 0 && input->y > 0) { + u32 x = (u32)input->x; + u32 y = (u32)input->y; + if (y > height - 3 && x >= 2 && x <= width) { + struct cmc_list_entry *entry = &al_array_at(tab->list->entries, tab->list->current); + f64 percent = (x - 2) / (f64)(width - 2); + camu_client_seek(&ui->c->client, &tab->list->name, entry->id, percent); + } + ui->last_mouse_x = x; + ui->last_mouse_y = y; + } + break; + } + } + + return true; +} + +void cmc_lp_erase(struct cmc_ui *ui) +{ + ncplane_erase(ui->lp.n); +} + +static void render_list_entries(struct cmc_ui *ui, struct cmc_list_tab *tab, u32 max_height) +{ + struct cmc_list *list = tab->list; + u32 count = list->entries.count; + u32 index = tab->selected - (tab->selected % max_height); + u32 end = MIN(index + max_height, count); + struct cmc_list_entry *entry; + for (u32 i = index; i < end; i++) { + entry = &al_array_at(list->entries, i); + if (i == tab->selected) { + ncplane_set_styles(ui->lp.n, NCSTYLE_BOLD); + ncplane_set_fg_palindex(ui->lp.n, 6); + } else { + ncplane_set_styles(ui->lp.n, NCSTYLE_NONE); + ncplane_set_fg_default(ui->lp.n); + } + if (list->current >= 0 && i == (u32)list->current) { + ncplane_putchar_yx(ui->lp.n, i - index, 1, '>'); + cmc_ui_putnwstr_yx(ui->lp.n, i - index, 3, entry->name.length, &entry->name); + } else { + cmc_ui_putnwstr_yx(ui->lp.n, i - index, 0, entry->name.length, &entry->name); + } + } +} + +static void render_now_playing(struct cmc_ui *ui, struct cmc_list_tab *tab, u32 y, u32 height) +{ + u32 width = ncplane_dim_x(ui->lp.n); + if (tab && tab->list->entries.count > 0) { + struct cmc_list *list = tab->list; + struct cmc_list_entry *entry = &al_array_at(list->entries, list->current); + f32 percent = 1.0; + if (entry->duration > 0) { + u64 pos = entry->offset; + u64 now = nn_get_timestamp(); + if (entry->start != LIANA_TIMESTAMP_INVALID && now > entry->start) { + pos += now - entry->start; + } + percent = MIN(pos / (f32)entry->duration, 1.f); + } + for (u32 i = 0; i < (width - 2) * percent; i++) { + ncplane_putchar_yx(ui->lp.n, height - 2, i + 1, '-'); + } + cmc_ui_putnwstr_yx(ui->lp.n, height - 4, 1, entry->name.length, &entry->name); + } + char buf[24]; + al_snprintf(buf, sizeof(buf), "%u, %u | %u, %u", ui->last_mouse_x, ui->last_mouse_y, width, height); + ncplane_putstr_yx(ui->lp.n, height - 5, 1, buf); + u64 c = 0; + ncchannels_set_fg_default(&c); + ncplane_cursor_move_yx(ui->lp.n, y, 0); + ncplane_rounded_box(ui->lp.n, NCSTYLE_NONE, c, height - 1, width - 1, 0); +} + +void cmc_lp_render(struct cmc_ui *ui) +{ + u32 height = ncplane_dim_y(ui->lp.n); + + struct cmc_list_tab *tab = NULL; + if (ui->lp.tabs.count > 0) { + tab = &al_array_at(ui->lp.tabs, ui->lp.current); + } + + if (tab) { + render_list_entries(ui, tab, height - 8); + } + + render_now_playing(ui, tab, height - 8, height); +} diff --git a/src/fruits/cmc/ui/pane_search.c b/src/fruits/cmc/ui/pane_search.c index c3d1165..021c42f 100644 --- a/src/fruits/cmc/ui/pane_search.c +++ b/src/fruits/cmc/ui/pane_search.c @@ -4,36 +4,181 @@ void cmc_sp_init(struct cmc_ui *ui) { - if (ui->c->searches.count > 0) { - ui->sp.search = al_array_at(ui->c->searches, 0); - } - cmc_sp_layout(ui, notcurses_stdplane(ui->nc)); + al_array_init(ui->sp.tabs); + ui->sp.current = 0; } void cmc_sp_layout(struct cmc_ui *ui, struct ncplane *parent) { if (ui->sp.n) ncplane_destroy(ui->sp.n); - struct ncplane_options nopts = { - .rows = ui->term_rows, - .cols = ui->term_cols, - .x = 0, - .y = 0 - }; + struct ncplane_options nopts = { 0 }; + nopts.rows = ncplane_dim_y(parent); + nopts.cols = ncplane_dim_x(parent); ui->sp.n = ncplane_create(parent, &nopts); } +void cmc_sp_add_search(struct cmc_ui *ui, struct cmc_search *search) +{ + struct cmc_search_tab tab = { + .search = search, + }; + struct ncplane_options nopts = { 0 }; + nopts.x = 3; + nopts.y = 2; + nopts.rows = 10; + nopts.cols = ncplane_dim_x(ui->sp.n) - 3; + tab.input = ncplane_create(ui->sp.n, &nopts); + tab.cursor = 0; + tab.input_active = false; + tab.input_text = al_wstr_null(); + tab.input_changed = false; + al_array_push(ui->sp.tabs, tab); +} + +static bool handle_text_input(struct cmc_search_tab *tab, struct ncinput *input) +{ + switch (input->id) { + case NCKEY_BACKSPACE: + if (tab->cursor > 0) { + al_wstr_remove_at(&tab->input_text, tab->cursor); + tab->cursor--; + tab->input_changed = true; + } + break; + case NCKEY_LEFT: + if (tab->cursor > 0) { + tab->cursor--; + } + break; + case NCKEY_RIGHT: + if (tab->cursor < tab->input_text.length) { + tab->cursor++; + } + break; + case NCKEY_RETURN: + tab->input_active = false; + // Erase text on next render. + tab->input_changed = true; + return true; + default: + if (ncinput_ctrl_p(input)) { + switch (input->id) { + case 'A': + tab->cursor = 0; + break; + case 'E': + tab->cursor = tab->input_text.length; + break; + case 'B': + if (tab->cursor > 0) { + tab->cursor--; + } + break; + case 'F': + if (tab->cursor < tab->input_text.length) { + tab->cursor++; + } + break; + } + break; + } + // https://github.com/dankamongmen/notcurses/blob/c11efe877f2245901f5c9ce110de7a2c83cfa2ed/src/lib/reader.c#L404 + for (s32 c = 0; input->eff_text[c] != 0; c++){ + uchar egc[5] = { 0 }; + if (notcurses_ucs32_to_utf8(&input->eff_text[c], 1, egc, 4) >= 0) { + // This probably doesn't work on Windows. + al_wstr_insert(&tab->input_text, tab->cursor, &al_wstr_w((wchar_t *)egc, 0, 1)); + tab->cursor++; + tab->input_changed = true; + } + } + break; + } + return false; +} + bool cmc_sp_handle_input(struct cmc_ui *ui, struct ncinput *input) { - (void)ui; - (void)input; + struct cmc_search_tab *tab = NULL; + if (ui->sp.tabs.count > 0) { + tab = &al_array_at(ui->sp.tabs, ui->sp.current); + } + + if (tab) { + if (tab->input_active) { + if (cmc_ui_consider_input(input, true)) { + if (handle_text_input(tab, input)) { + notcurses_cursor_disable(ui->nc); + str query; + al_wstr_to_str(&tab->input_text, &query); + camu_client_create_search(&ui->c->client, &al_str_c("youtube"), &query); + al_str_free(&query); + } + } + } else { + switch (input->id) { + case 'i': + if (!cmc_ui_consider_input(input, false)) { + return false; + } + tab->input_active = true; + tab->input_changed = true; + s32 absx = ncplane_abs_x(tab->input); + s32 absy = ncplane_abs_y(tab->input); + notcurses_cursor_enable(ui->nc, absy, absx + tab->cursor); + break; + } + } + } + return true; } +void cmc_sp_erase(struct cmc_ui *ui) +{ + ncplane_erase(ui->sp.n); +} + void cmc_sp_render(struct cmc_ui *ui) { - if (ui->sp.search) { - char *c_str = al_str_to_c_str(&ui->sp.search->query); - ncplane_putstr_yx(ui->sp.n, 0, 0, c_str); - al_free(c_str); + struct cmc_search_tab *tab = NULL; + if (ui->sp.tabs.count > 0) { + tab = &al_array_at(ui->sp.tabs, ui->sp.current); + } + + if (tab) { + if (tab->input_changed) { + ncplane_erase(tab->input); + if (tab->input_active) { + cmc_ui_putnwstr_yx(tab->input, 0, 0, tab->input_text.length, &tab->input_text); + } + tab->input_changed = false; + } + if (tab->input_active) { + s32 absx = ncplane_abs_x(tab->input); + s32 absy = ncplane_abs_y(tab->input); + notcurses_cursor_enable(ui->nc, absy, absx + tab->cursor); + } + } + + /* + if (ui->overlay_shown) { + ncplane_erase(ui->oln); + u32 max_height = ncplane_dim_y(ui->oln) - 2; + u32 count = ui->c->searches.count; + u32 index = ui->sp.overlay_index - (ui->sp.overlay_index % max_height); + u32 end = MIN(index + max_height, count); + for (u32 i = index; i < end; i++) { + struct cmc_search *search = al_array_at(ui->c->searches, i); + if (i == ui->sp.overlay_index) { + ncplane_set_fg_palindex(ui->oln, 6); + ncplane_putchar_yx(ui->oln, i - index + 1, 2, '>'); + cmc_ui_putstr_maxwidth_yx(ui->oln, i - index + 1, 4, search->query.length, &search->query); + } else { + ncplane_set_fg_default(ui->oln); + cmc_ui_putstr_maxwidth_yx(ui->oln, i - index + 1, 2, search->query.length, &search->query); + } + } } + */ } diff --git a/src/fruits/cmc/ui/ui.c b/src/fruits/cmc/ui/ui.c index 72e4df2..84e514e 100644 --- a/src/fruits/cmc/ui/ui.c +++ b/src/fruits/cmc/ui/ui.c @@ -2,11 +2,27 @@ #include "ui.h" -static s32 resize_cb(struct ncplane *p) +void cmc_ui_putnwstr_yx(struct ncplane *n, u32 y, u32 x, u32 width, wstr *w) { - struct cmc_ui *ui = (struct cmc_ui *)ncplane_userptr(p); - (void)ui; - return 0; + ncplane_cursor_move_yx(n, y, x); + u32 end = MIN(w->length, width); + for (u32 i = 0; i < end; i++) { + ncplane_putwc(n, al_wstr_at(w, i)); + } +} + +void cmc_ui_putnstr_yx(struct ncplane *n, u32 y, u32 x, u32 width, str *s) +{ + ncplane_cursor_move_yx(n, y, x); + u32 end = MIN(s->length, width); + for (u32 i = 0; i < end; i++) { + ncplane_putchar(n, al_str_at(s, i)); + } +} + +bool cmc_ui_consider_input(struct ncinput *input, bool repeat) +{ + return (input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN || (repeat && input->evtype == NCTYPE_REPEAT)); } static bool read_input(struct cmc_ui *ui, struct ncinput *input) @@ -16,51 +32,217 @@ static bool read_input(struct cmc_ui *ui, struct ncinput *input) return !(ret == (u32)-1 || ret == 0); } +static void erase_previous_pane(struct cmc_ui *ui) +{ + switch (ui->pane) { + case CMC_PANE_LIST: + ncplane_erase(ui->lp.n); + break; + case CMC_PANE_SEARCH: + ncplane_erase(ui->sp.n); + break; + } +} + +static void switch_to_pane(struct cmc_ui *ui, u8 pane) +{ + erase_previous_pane(ui); + ui->pane = pane; +} + +void cmc_ui_queue_render(struct cmc_ui *ui) +{ + ncplane_erase(ui->n); + + switch (ui->pane) { + case CMC_PANE_LIST: + cmc_lp_erase(ui); + break; + case CMC_PANE_SEARCH: + cmc_sp_erase(ui); + break; + } + + if (ui->overlay_shown) { + ncplane_erase(ui->oln); + } + + notcurses_render(ui->nc); + + switch (ui->pane) { + case CMC_PANE_LIST: + cmc_lp_render(ui); + break; + case CMC_PANE_SEARCH: + cmc_sp_render(ui); + break; + } + + if (ui->overlay_shown) { + /* + struct nccell ncl; + nccell_init(&ncl); + nccell_load(ui->oln, &ncl, "I"); + //nccell_set_bg_palindex(&ncl, 3); + nccell_set_fg_palindex(&ncl, 3); + ncplane_polyfill_yx(ui->oln, 0, 0, &ncl); + */ + u32 width = ncplane_dim_x(ui->oln); + u32 height = ncplane_dim_y(ui->oln); + u64 c = 0; + ncchannels_set_fg_default(&c); + ncplane_cursor_move_yx(ui->oln, 0, 0); + ncplane_rounded_box(ui->oln, NCSTYLE_NONE, c, height - 1, width - 1, 0); + } + + notcurses_render(ui->nc); +} + +void cmc_ui_toggle_overlay(struct cmc_ui *ui) +{ + ui->overlay_shown = !ui->overlay_shown; + if (!ui->overlay_shown) { + ncplane_erase(ui->oln); + } +} + static void input_poll_callback(void *userdata, s32 revents) { struct cmc_ui *ui = (struct cmc_ui *)userdata; (void)revents; + bool do_render; struct ncinput input; for (;;) { - if (!read_input(ui, &input)) break; - bool do_render = false; - if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) { + if (!read_input(ui, &input)) { + break; + } + + do_render = false; + + if (cmc_ui_consider_input(&input, false)) { switch (input.id) { case 'q': nn_event_loop_break_one(ui->loop); break; + case '1': + switch_to_pane(ui, CMC_PANE_LIST); + break; + case '2': + switch_to_pane(ui, CMC_PANE_SEARCH); + break; + case '3': + break; + case '4': + break; + case NCKEY_TAB: + cmc_ui_toggle_overlay(ui); + break; } } + switch (ui->pane) { + case CMC_PANE_LIST: + if (cmc_lp_handle_input(ui, &input)) { + do_render |= true; + } + break; case CMC_PANE_SEARCH: if (cmc_sp_handle_input(ui, &input)) { - cmc_sp_render(ui); do_render |= true; } + break; } + if (do_render) { + cmc_ui_queue_render(ui); notcurses_render(ui->nc); } } } +static void render_timer_callback(void *userdata, struct nn_timer *timer) +{ + struct cmc_ui *ui = (struct cmc_ui *)userdata; + (void)timer; + cmc_ui_queue_render(ui); + notcurses_render(ui->nc); + nn_timer_again(&ui->render_timer); +} + +static bool relayout(struct cmc_ui *ui) +{ + struct ncplane *stdplane = notcurses_stdplane(ui->nc); + + notcurses_stddim_yx(ui->nc, &ui->term_rows, &ui->term_cols); + + if (ui->n) ncplane_destroy(ui->n); + struct ncplane_options nopts = { 0 }; + nopts.x = 2; + nopts.y = 1; + nopts.margin_r = 4; + nopts.margin_b = 1; + nopts.flags = NCPLANE_OPTION_MARGINALIZED; + ui->n = ncplane_create(stdplane, &nopts); + + cmc_lp_layout(ui, ui->n); + cmc_sp_layout(ui, ui->n); + + if (ui->oln) ncplane_destroy(ui->oln); + u32 width = ncplane_dim_x(ui->n); + u32 height = ncplane_dim_y(ui->n); + nopts.margin_r = MAX(6u, (u32)(width / 1.5)); + nopts.margin_b = MAX(2u, height / 3); + nopts.x = nopts.margin_r / 2; + nopts.y = nopts.margin_b / 2; + ui->oln = ncplane_create(ui->n, &nopts); + + return true; +} + +static s32 resize_cb(struct ncplane *p) +{ + struct cmc_ui *ui = (struct cmc_ui *)ncplane_userptr(p); + relayout(ui); + return 0; +} + bool cmc_ui_init(struct cmc_ui *ui, struct nn_event_loop *loop, struct cmc *c) { al_memset(ui, 0, sizeof(struct cmc_ui)); + ui->c = c; - if (!(ui->nc = notcurses_init(NULL, stdin))) { + struct notcurses_options opts = { 0 }; + opts.flags = NCOPTION_INHIBIT_SETLOCALE; + if (!(ui->nc = notcurses_init(&opts, stdin))) { return false; } + notcurses_mice_enable(ui->nc, NCKEY_BUTTON1); + ui->loop = loop; - notcurses_stddim_yx(ui->nc, &ui->term_rows, &ui->term_cols); + + cmc_lp_init(ui); cmc_sp_init(ui); + + ui->pending_layout = false; + ui->pending_render = false; + + ui->pane = CMC_PANE_SEARCH; + ui->overlay_shown = false; + + relayout(ui); + struct ncplane *stdplane = notcurses_stdplane(ui->nc); ncplane_set_resizecb(stdplane, resize_cb); ncplane_set_userptr(stdplane, ui); + nn_poll_init(&ui->input_poll, input_poll_callback, ui); nn_poll_set(&ui->input_poll, notcurses_inputready_fd(ui->nc), NNWT_POLL_READ); nn_poll_start(&ui->input_poll, ui->loop); - ui->pane = CMC_PANE_SEARCH; + + nn_timer_init(&ui->render_timer, ui->loop, render_timer_callback, ui); + nn_timer_set_repeat(&ui->render_timer, NNWT_TS_FROM_USEC(300000)); + nn_timer_again(&ui->render_timer); + return true; } diff --git a/src/fruits/cmc/ui/ui.h b/src/fruits/cmc/ui/ui.h index 1c3b9d9..996bb73 100644 --- a/src/fruits/cmc/ui/ui.h +++ b/src/fruits/cmc/ui/ui.h @@ -1,11 +1,29 @@ #pragma once #include +#include +#include #include +#include #include enum { - CMC_PANE_SEARCH = 0, + CMC_PANE_LIST = 0, + CMC_PANE_SEARCH, +}; + +struct cmc_list_tab { + struct cmc_list *list; + u32 selected; +}; + +struct cmc_search_tab { + struct cmc_search *search; + struct ncplane *input; + u32 cursor; + bool input_active; + wstr input_text; + bool input_changed; }; struct cmc; @@ -15,19 +33,47 @@ struct cmc_ui { u32 term_cols; u32 term_rows; bool pending_layout; - struct nn_poll input_poll; + bool pending_render; u8 pane; + struct ncplane *n; // window. + struct ncplane *oln; // overlay. + bool overlay_shown; + u32 last_mouse_x; + u32 last_mouse_y; struct { struct ncplane *n; - struct cmc_search *search; + array(struct cmc_list_tab) tabs; + u32 current; + } lp; // list pane. + struct { + struct ncplane *n; + array(struct cmc_search_tab) tabs; + u32 current; } sp; // search pane. + struct nn_poll input_poll; + struct nn_timer render_timer; struct cmc *c; }; +void cmc_ui_putnwstr_yx(struct ncplane *n, u32 y, u32 x, u32 width, wstr *w); +void cmc_ui_putnstr_yx(struct ncplane *n, u32 y, u32 x, u32 width, str *s); +bool cmc_ui_consider_input(struct ncinput *input, bool repeat); + bool cmc_ui_init(struct cmc_ui *ui, struct nn_event_loop *loop, struct cmc *c); +void cmc_ui_queue_render(struct cmc_ui *ui); +void cmc_ui_toggle_overlay(struct cmc_ui *ui); void cmc_ui_close(struct cmc_ui *ui); +void cmc_lp_init(struct cmc_ui *ui); +void cmc_lp_layout(struct cmc_ui *ui, struct ncplane *parent); +void cmc_lp_add_list(struct cmc_ui *ui, struct cmc_list *list); +bool cmc_lp_handle_input(struct cmc_ui *ui, struct ncinput *input); +void cmc_lp_erase(struct cmc_ui *ui); +void cmc_lp_render(struct cmc_ui *ui); + void cmc_sp_init(struct cmc_ui *ui); void cmc_sp_layout(struct cmc_ui *ui, struct ncplane *parent); +void cmc_sp_add_search(struct cmc_ui *ui, struct cmc_search *search); bool cmc_sp_handle_input(struct cmc_ui *ui, struct ncinput *input); +void cmc_sp_erase(struct cmc_ui *ui); void cmc_sp_render(struct cmc_ui *ui); -- cgit v1.2.3-101-g0448