summaryrefslogtreecommitdiff
path: root/src/fruits/cmc/ui
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2025-01-22 08:02:14 -0500
committerAndrew Opalach <andrew@akon.city> 2025-01-22 08:02:14 -0500
commit5073f74aefb91616fb8e1df087747105e46e265d (patch)
treef4de264a561c19cf79b416d33f3ff999d6309630 /src/fruits/cmc/ui
parentf3590721df77b7557a80c437acca33b1a4514a9a (diff)
downloadcamu-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.c157
-rw-r--r--src/fruits/cmc/ui/pane_search.c177
-rw-r--r--src/fruits/cmc/ui/ui.c204
-rw-r--r--src/fruits/cmc/ui/ui.h52
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);