diff options
| author | 2025-01-22 08:02:14 -0500 | |
|---|---|---|
| committer | 2025-01-22 08:02:14 -0500 | |
| commit | 5073f74aefb91616fb8e1df087747105e46e265d (patch) | |
| tree | f4de264a561c19cf79b416d33f3ff999d6309630 /src/fruits/cmc/ui | |
| parent | f3590721df77b7557a80c437acca33b1a4514a9a (diff) | |
| download | camu-5073f74aefb91616fb8e1df087747105e46e265d.tar.gz camu-5073f74aefb91616fb8e1df087747105e46e265d.tar.bz2 camu-5073f74aefb91616fb8e1df087747105e46e265d.zip | |
Rough draft of some tui stuff
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/fruits/cmc/ui')
| -rw-r--r-- | src/fruits/cmc/ui/pane_list.c | 157 | ||||
| -rw-r--r-- | src/fruits/cmc/ui/pane_search.c | 177 | ||||
| -rw-r--r-- | src/fruits/cmc/ui/ui.c | 204 | ||||
| -rw-r--r-- | src/fruits/cmc/ui/ui.h | 52 |
4 files changed, 560 insertions, 30 deletions
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 <nnwt/thread.h> + +#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 <al/types.h> +#include <al/array.h> +#include <al/wstr.h> #include <nnwt/event_loop.h> +#include <nnwt/timer.h> #include <notcurses/notcurses.h> 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); |