diff options
| author | 2025-04-25 15:56:56 -0400 | |
|---|---|---|
| committer | 2025-04-25 15:56:56 -0400 | |
| commit | b1985414e54edf1eebe597551a48609ad08c6cfa (patch) | |
| tree | 4a4def499348e0c9ccb52472e7c862f814993931 /src/fruits/cmc | |
| parent | 76fc9fe33dd58926e552b6bd3b79d798b452c6ed (diff) | |
| download | camu-b1985414e54edf1eebe597551a48609ad08c6cfa.tar.gz camu-b1985414e54edf1eebe597551a48609ad08c6cfa.tar.bz2 camu-b1985414e54edf1eebe597551a48609ad08c6cfa.zip | |
Partial cmc ui refactor
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/fruits/cmc')
20 files changed, 1455 insertions, 836 deletions
diff --git a/src/fruits/cmc/cli.h b/src/fruits/cmc/cli.h deleted file mode 100644 index 4373054..0000000 --- a/src/fruits/cmc/cli.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -struct cmc_cli { -}; diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c index 265550f..43f1144 100644 --- a/src/fruits/cmc/cmc.c +++ b/src/fruits/cmc/cmc.c @@ -66,6 +66,7 @@ static void parse_initial_user_state(struct cmc *c, struct nn_packet *packet) nn_packet_read_str(packet, &s); al_str_clone(&list->name, &s); list->current = nn_packet_read_s32(packet); + list->meta_changed = false; al_array_init(list->entries); u32 inner_count = nn_packet_read_s32(packet); for (u32 j = 0; j < inner_count; j++) { @@ -105,11 +106,11 @@ static void client_callback(void *userdata, u8 op, void *opaque) } struct cmc_list *list; al_array_foreach(c->lists, i, list) { - cmc_lp_add_list(&c->ui, list); + cmc_list_pane_add_list(&c->ui.lists, list); } struct cmc_search *search; al_array_foreach(c->searches, i, search) { - cmc_sp_add_search(&c->ui, search); + //cmc_sp_add_search(&c->ui, search); } break; case CLI_ADD: { @@ -137,7 +138,7 @@ 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); - cmc_sp_add_search(&c->ui, search); + //cmc_sp_add_search(&c->ui, search); break; } case CAMU_CLIENT_GOT_RESULTS: { @@ -149,6 +150,7 @@ static void client_callback(void *userdata, u8 op, void *opaque) str name; nn_packet_read_str(packet, &name); struct cmc_list *list = get_list_by_name(c, &name); + list->meta_changed = true; if (!list) break; switch (op) { case LIANA_META_ADDED_ENTRY: { @@ -177,8 +179,9 @@ static void client_callback(void *userdata, u8 op, void *opaque) for (u32 i = 0; i < count; i++) { u32 id = nn_packet_read_u32(packet); al_array_foreach(list->entries, j, entry) { - if (i != j && entry->id == id) { - SWAP(al_array_at(list->entries, i), al_array_at(list->entries, j)); + if (entry->id == id) { + if (i != j) SWAP(al_array_at(list->entries, i), al_array_at(list->entries, j)); + break; } } } @@ -214,6 +217,8 @@ static void client_callback(void *userdata, u8 op, void *opaque) entry->raw.data = al_malloc(size); al_memcpy(entry->raw.data, ptr, size); entry->raw.sample_count = nn_packet_read_u32(packet); + cmc_ui_queue_render(&c->ui); + break; } } break; diff --git a/src/fruits/cmc/cmc.h b/src/fruits/cmc/cmc.h index ea714dc..817b8b0 100644 --- a/src/fruits/cmc/cmc.h +++ b/src/fruits/cmc/cmc.h @@ -41,6 +41,7 @@ struct cmc_list_entry { struct cmc_list { str name; s32 current; + bool meta_changed; array(struct cmc_list_entry *) entries; }; diff --git a/src/fruits/cmc/meson.build b/src/fruits/cmc/meson.build index a0fa097..864af8a 100644 --- a/src/fruits/cmc/meson.build +++ b/src/fruits/cmc/meson.build @@ -10,10 +10,11 @@ use_tui = true if use_tui cmc_src += [ 'ui/ui.c', - 'ui/pane_list.c', - 'ui/pane_search.c', - 'ui/pane_log.c', - 'ui/waveform.c' + 'ui/panes/list.c', + 'ui/panes/search.c', + 'ui/panes/log.c', + 'ui/widgets/now_playing.c', + 'ui/util/waveform.c' ] cmc_deps += [notcurses, notcurses_core, codecs_client] endif diff --git a/src/fruits/cmc/ui/pane_list.c b/src/fruits/cmc/ui/pane_list.c deleted file mode 100644 index b62e0a6..0000000 --- a/src/fruits/cmc/ui/pane_list.c +++ /dev/null @@ -1,408 +0,0 @@ -#include <nnwt/thread.h> - -#include "../../liana/list.h" -#include "../../sink/util.h" - -#include "../cmc.h" - -#include "ui.h" -#include "ext.h" -#include "waveform.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 = list->current, - .leader_pressed = false - }; - 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); - s32 count = (s32)tab->list->entries.count; - bool leader = tab->leader_pressed; - if (cmc_ui_consider_input(input, false)) { - tab->leader_pressed = false; - } - - switch (input->id) { - case 'g': - CONSIDER_INPUT(); - if (leader) { - if (tab->move.active) { - tab->move.offset = -tab->move.selected; - } else { - tab->selected = 0; - } - } else { - tab->leader_pressed = true; - } - break; - case 'G': - CONSIDER_INPUT(); - if (tab->move.active) { - tab->move.offset = count - 1 - tab->move.selected; - } else { - tab->selected = count - 1; - } - break; - case 'f': - //CONSIDER_INPUT_MOD_CTRL(); - CONSIDER_INPUT_REPEAT(); - if (tab->move.active) { - if (tab->selected + tab->move.offset < count - tab->page_length) { - tab->move.offset += tab->page_length; - } else { - tab->move.offset = count - 1 - tab->move.selected; - } - } else { - if (tab->selected < count - tab->page_length) { - tab->selected += tab->page_length; - } else { - tab->selected = count - 1; - } - } - break; - case 'b': - //CONSIDER_INPUT_MOD_CTRL(); - CONSIDER_INPUT_REPEAT(); - if (tab->move.active) { - if (tab->selected + tab->move.offset > tab->page_length) { - tab->move.offset -= tab->page_length; - } else { - tab->move.offset = -tab->selected; - } - } else { - if (tab->selected > tab->page_length) { - tab->selected -= tab->page_length; - } else { - tab->selected = 0; - } - } - break; - case 'j': - CONSIDER_INPUT_REPEAT(); - if (tab->move.active) { - if (tab->selected + tab->move.offset < count - 1) { - tab->move.offset++; - } - } else { - if (tab->selected < count - 1) { - tab->selected++; - } - } - break; - case 'k': - CONSIDER_INPUT_REPEAT(); - if (tab->move.active) { - if (tab->selected + tab->move.offset > 0) { - tab->move.offset--; - } - } else { - if (tab->selected > 0) { - tab->selected--; - } - } - break; - case 'i': - CONSIDER_INPUT(); - if (!tab->move.active) { - tab->move.active = true; - tab->move.selected = tab->selected; - tab->move.offset = 0; - } - break; - case NCKEY_ESC: - CONSIDER_INPUT(); - if (tab->move.active) { - tab->move.active = false; - } - break; - case NCKEY_RETURN: - CONSIDER_INPUT(); - if (tab->move.active) { - tab->move.active = false; - } else if (tab->selected != tab->list->current) { - camu_client_skipto(&ui->c->client, &tab->list->name, tab->selected); - } - break; - case NCKEY_BUTTON1: - CONSIDER_INPUT(); - if (input->x < 0 || input->y < 0) return false; - u32 x = (u32)input->x; - u32 y = (u32)input->y; - ui->last_mouse_x = x; - ui->last_mouse_y = y; - u64 now = nn_get_timestamp(); - u64 last = ui->last_mouse_ts; - if (last != (u64)-1 && now - last < 100000) { - return false; - } - ui->last_mouse_ts = now; - u32 width = ncplane_dim_x(ui->lp.n); - u32 height = ncplane_dim_y(ui->lp.n); - if (x >= 2 && x <= width && y > height - 3) { // @TODO: Make this defined, more to nowplaying. - struct cmc_list_entry *entry = al_array_at(tab->list->entries, tab->list->current); - u64 pos = ((x - 2) / (f64)(width - 2)) * entry->duration; - camu_client_seek(&ui->c->client, &tab->list->name, entry->id, pos); - } - 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, s32 max_height) -{ - struct cmc_list *list = tab->list; - struct ncplane *n = ui->lp.n; - s32 tracked = tab->move.active ? tab->move.selected + tab->move.offset : tab->selected; - s32 count = (s32)list->entries.count; - s32 midpoint = max_height / 2; - s32 rounded = (max_height + 1) / 2; - s32 top, end; - if (tracked < midpoint) { - top = 0; - end = MIN(top + max_height, count); - } else if (tracked + rounded >= count) { - top = count - max_height; - end = count; - } else { - top = tracked - midpoint; - end = tracked + rounded; - } - s32 index; - struct cmc_list_entry *entry; - for (s32 i = top; i < end; i++) { - index = i; - if (tab->move.active) { - if (index == tracked) { - index = tab->move.selected; - } else { - if (index > tracked) index--; - if (index >= tab->move.selected) index++; - } - } - entry = al_array_at(list->entries, index); - bool on_current = list->current >= 0 && index == list->current; - bool styled = i == tracked; - if (styled) { - ncplane_set_styles(n, NCSTYLE_BOLD); - ncplane_set_fg_palindex(n, 4); - } - s32 y = i - top; - u32 length = entry->name.length; - if (tab->move.active && i == tracked) { - cmc_ui_putnwstr_yx(n, y, 0, length, &entry->name); - } else if (on_current) { - ncplane_putchar_yx(n, y, 1, '>'); - cmc_ui_putnwstr_yx(n, y, 3, length, &entry->name); - } else { - cmc_ui_putnwstr_yx(n, y, 1, length, &entry->name); - } - if (styled) { - ncplane_set_styles(n, NCSTYLE_NONE); - ncplane_set_fg_default(n); - } - } -} - -static void render_now_playing(struct cmc_ui *ui, struct cmc_list_tab *tab, u32 y, u32 height) -{ - struct ncplane *n = ui->lp.n; - u32 width = ncplane_dim_x(n); - - struct cmc_list_entry *entry = NULL; - if (tab->list->entries.count > (u32)tab->list->current) { - entry = al_array_at(tab->list->entries, tab->list->current); - } - - u64 pos = 0; - u64 duration = 0; - u64 remaining = 0; - u32 y_off = height - 7; - - ncplane_set_styles(n, NCSTYLE_BOLD); - if (entry) { - pos = entry->offset; - duration = entry->duration; - u64 now = nn_get_timestamp(); - bool paused = entry->start == LIANA_TIMESTAMP_INVALID; - if (!paused && now > entry->start) { - pos += now - entry->start; - } - pos = MIN(pos, duration); - remaining = duration - pos; - //ncplane_putstr_yx(n, y_off, 2, "▸"); - //ncplane_putstr_yx(n, y_off, 2, "▶"); - //ncplane_putstr_yx(n, y_off, 2, "⏵ "); - //ncplane_putstr_yx(n, y_off, 2, "|"); - //ncplane_putstr_yx(n, y_off, 2, "|"); - //ncplane_putstr_yx(n, y_off, 2, "⏵"); - //ncplane_putstr_yx(n, y_off, 2, "⏸"); - if (remaining == 0) { - ncplane_putstr_yx(n, y_off - 1, 2, "∨"); - } else { - ncplane_putstr_yx(n, y_off - 1, 2, paused ? "^": ">"); - } - } else { - ncplane_putstr_yx(n, y_off - 1, 2, "∨"); - } - ncplane_set_styles(n, NCSTYLE_NONE); - - f32 percent = 1.f; - if (duration > 0) percent = MIN(pos / (f32)duration, 1.f); - for (u32 i = 0; i < (width - 2) * percent; i++) { - ncplane_putchar_yx(n, y_off, i + 1, '-'); - } - - y_off--; - - char timestr[16] = { 0 }; // max = 829128280:01:49\0 - s32 offset = camu_print_time(timestr, sizeof(timestr), pos, true, 4); - ncplane_putstr_yx(n, y_off, 4, timestr); - ncplane_putstr_yx(n, y_off, 4 + offset, "⎹"); - - if (entry) { - cmc_ui_putnwstr_yx(n, y_off, 6 + offset, MIN(entry->name.length, (u32)60), &entry->name); - } - - bool show_remaining = false; - if (show_remaining) { - offset = camu_print_time(timestr, sizeof(timestr), remaining, true, 4); - } else { - offset = camu_print_time(timestr, sizeof(timestr), duration, true, 4); - } - ncplane_putstr_yx(n, y_off, width - 4 - offset, "⎸"); - // "=" Duration, "+" Segment, "-" Remaining. - if (show_remaining) { - ncplane_putstr_yx(n, y_off, width - 3 - offset, "-"); - } else { - ncplane_putstr_yx(n, y_off, width - 3 - offset, "="); - } - ncplane_putstr_yx(n, y_off, width - 2 - offset, timestr); - - /* - ncplane_putchar_yx(n, height - 5, 2, 'A'); - ncplane_putchar_yx(n, height - 4, 2, 'A'); - ncplane_putchar_yx(n, height - 3, 2, 'A'); - ncplane_putchar_yx(n, height - 2, 2, 'A'); - */ - - if (entry && entry->raw.data != NULL) { - u32 length = width - 2; - u32 top = height - 6; - u32 wave_height = 5; - if (entry->raw.drawing == NULL || entry != ui->lp.blitted) { - entry->raw.drawing = al_malloc(sizeof(char **) * wave_height); - for (u32 j = 0; j < wave_height; j++) { - entry->raw.drawing[j] = al_malloc(sizeof(char *) * length); - for (u32 i = 0; i < length; i++) { - entry->raw.drawing[j][i] = al_calloc(1, 6); - } - } - if (!ui->lp.w) { - struct ncplane_options opts = { 0 }; - opts.x = 1; - opts.y = top; - opts.cols = length; - opts.rows = wave_height; - ui->lp.w = ncplane_create(n, &opts); - } - /* - struct nccell ncl; - nccell_init(&ncl); - nccell_load(w, &ncl, " "); - nccell_set_bg_palindex(&ncl, 3); - ncplane_polyfill_yx(w, 0, 0, &ncl); - */ - u32 *rgba; - u32 pixel_width, pixel_height; - ncplane_pixel_geom(ui->lp.w, &pixel_height, &pixel_width, NULL, NULL, NULL, NULL); - //cmc_draw_waveform(entry, length, wave_height, false, entry->raw.drawing, &rgba); - cmc_draw_waveform(entry, pixel_width, pixel_height, false, entry->raw.drawing, &rgba); - if (ui->lp.vis) { - ncvisual_destroy(ui->lp.vis); - } - ui->lp.vis = ncvisual_from_rgba(rgba, pixel_height, pixel_width * 4, pixel_width); - } - - if (entry != ui->lp.blitted) { - ncplane_erase(ui->lp.w); - struct ncvisual_options vopts = { 0 }; - vopts.blitter = NCBLIT_PIXEL; - vopts.scaling = NCSCALE_STRETCH; - vopts.flags = NCVISUAL_OPTION_NOINTERPOLATE; - vopts.n = ui->lp.w; - vopts.x = 0; - vopts.y = 0; - ncvisual_blit(ui->nc, ui->lp.vis, &vopts); - ui->lp.blitted = entry; - } - - /* - ncplane_set_fg_palindex(n, 5); - for (u32 j = 0; j < wave_height; j++) { - for (u32 i = 0; i < length; i++) { - char *cell = entry->raw.drawing[j][i]; - if (cell[0] == 'R') { - ncplane_set_bg_palindex(n, 2); - } - ncplane_putstr_yx(n, top + j, 1 + i, cell + 1); - if (cell[0] == 'R') { - ncplane_set_bg_default(n); - } - } - } - ncplane_set_fg_default(n); - */ - } - - /* - 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(n, height - 5, 5, buf); - */ - - u64 c = 0; - ncchannels_set_fg_default(&c); - ncplane_cursor_move_yx(n, y, 0); - ncplane_light_box(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); - - if (ui->lp.tabs.count <= ui->lp.current) return; - struct cmc_list_tab *tab = &al_array_at(ui->lp.tabs, ui->lp.current); - - tab->page_length = height - 9; - render_list_entries(ui, tab, tab->page_length); - render_now_playing(ui, tab, tab->page_length, height); -} diff --git a/src/fruits/cmc/ui/pane_log.c b/src/fruits/cmc/ui/pane_log.c deleted file mode 100644 index a35cf20..0000000 --- a/src/fruits/cmc/ui/pane_log.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "ui.h" - -void cmc_logp_init(struct cmc_ui *ui) -{ - al_array_init(ui->logp.messages); -} - -void cmc_logp_layout(struct cmc_ui *ui, struct ncplane *parent) -{ - if (ui->logp.n) ncplane_destroy(ui->logp.n); - struct ncplane_options nopts = { 0 }; - nopts.rows = ncplane_dim_y(parent); - nopts.cols = ncplane_dim_x(parent); - ui->logp.n = ncplane_create(parent, &nopts); -} - -void cmc_logp_push_message(struct cmc_ui *ui, char *message) -{ - al_array_push(ui->logp.messages, message); -} - -void cmc_logp_erase(struct cmc_ui *ui) -{ - ncplane_erase(ui->logp.n); -} - -void cmc_logp_render(struct cmc_ui *ui) -{ - struct ncplane *n = ui->logp.n; - - u32 max_height = ncplane_dim_y(n); - - u32 size = ui->logp.messages.count; - u32 index = (size > max_height) ? size - max_height : 0; - for (u32 i = index; i < size; i++) { - // We can't use putnstr to control the width because these messages will have wide characters. - ncplane_putstr_yx(n, i - index, 0, al_array_at(ui->logp.messages, i)); - } - - for (u32 i = 0; i < index; i++) { - al_free(al_array_at(ui->logp.messages, i)); - } - al_array_remove_range(ui->logp.messages, 0, index); -} diff --git a/src/fruits/cmc/ui/pane_search.c b/src/fruits/cmc/ui/pane_search.c deleted file mode 100644 index 21c9b67..0000000 --- a/src/fruits/cmc/ui/pane_search.c +++ /dev/null @@ -1,226 +0,0 @@ -#include "../cmc.h" - -#include "ui.h" - -AL_ASSERT_TYPE_SIZE(wchar_t, 4); - -void cmc_sp_init(struct cmc_ui *ui) -{ - 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 = { 0 }; - nopts.rows = ncplane_dim_y(parent) - 1; - nopts.cols = ncplane_dim_x(parent) - 1; - ui->sp.n = ncplane_create(parent, &nopts); - struct cmc_search_tab tab = { - .search = NULL, - }; - 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); -} - -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) -{ - bool entered = false; - - switch (input->id) { - case NCKEY_BACKSPACE: - if (tab->cursor > 0) { - al_wstr_remove_at(&tab->input_text, tab->cursor - 1); - 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: - entered = true; - // fallthrough - case NCKEY_ESC: - tab->input_active = false; - tab->input_changed = true; // Erase text on next render. - break; - 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; - case 'U': - for (u32 i = 1; i <= tab->cursor; i++) { - al_wstr_remove_at(&tab->input_text, tab->cursor - i); - } - tab->cursor = 0; - tab->input_changed = true; - break; - } - break; - } - - if (ncinput_alt_p(input) || nckey_synthesized_p(input->id)) { - break; - } - - al_wstr_insert(&tab->input_text, tab->cursor, &al_wstr_w((wchar_t *)input->eff_text, 0, 1)); - tab->cursor++; - tab->input_changed = true; - - /* - // 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)); - } - } - */ - - break; - } - - tab->visual_cursor = 0; - if (tab->cursor > 0) { - for (u32 i = 0; i < tab->cursor; i++) { - tab->visual_cursor += al_wchar_width(al_wstr_at(&tab->input_text, i)); - } - } - - return entered; -} - -bool cmc_sp_handle_input(struct cmc_ui *ui, struct ncinput *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)) { - str query; - al_str_from_wstr(&query, &tab->input_text); - camu_client_create_search(&ui->c->client, &al_str_c("youtube"), &query); - al_str_free(&query); - } - if (!tab->input_active) notcurses_cursor_disable(ui->nc); - } - } else { - switch (input->id) { - case 'i': - CONSIDER_INPUT(); - 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->visual_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) -{ - 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->visual_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/panes/list.c b/src/fruits/cmc/ui/panes/list.c new file mode 100644 index 0000000..0e853db --- /dev/null +++ b/src/fruits/cmc/ui/panes/list.c @@ -0,0 +1,688 @@ +#include <nnwt/thread.h> + +#include "../../cmc.h" + +#include "list.h" + +void cmc_list_pane_init(struct cmc_list_pane *lp, struct cmc_ui *ui) +{ + al_array_init(lp->tabs); + lp->selected = -1; + cmc_now_playing_init(&lp->np); + lp->ui = ui; +} + +bool cmc_list_pane_layout(struct cmc_list_pane *lp, struct ncplane *parent) +{ + if (lp->n) ncplane_destroy(lp->n); + struct ncplane_options nopts = { 0 }; + u32 height = ncplane_dim_y(parent); + nopts.rows = height; + nopts.cols = ncplane_dim_x(parent); + lp->n = ncplane_create(parent, &nopts); + lp->page_length = height - 9; + cmc_now_playing_layout(&lp->np, lp->n, lp->page_length); + return true; +} + +void cmc_list_pane_add_list(struct cmc_list_pane *lp, struct cmc_list *list) +{ + struct cmc_list_tab tab = { 0 }; + tab.list = list; + tab.selected = list->current; + al_array_push(lp->tabs, tab); + if (lp->selected == -1) { + lp->selected = lp->tabs.count - 1; + } +} + +bool cmc_list_pane_handle_input(struct cmc_list_pane *lp, struct ncinput *input) +{ + if (lp->selected == -1) return false; + + struct cmc_list_tab *tab = &al_array_at(lp->tabs, lp->selected); + s32 count = (s32)tab->list->entries.count; + + bool leader = tab->leader_pressed; + if (cmc_ui_consider_input(input, false)) { + tab->leader_pressed = false; + } + + switch (input->id) { + case 'g': + CONSIDER_INPUT(); + if (leader) { + if (tab->move.active) { + if (tab->move.offset != -tab->move.selected) { + tab->move.offset = -tab->move.selected; + return true; + } + } else if (tab->selected != 0) { + tab->selected = 0; + return true; + } + } else { + tab->leader_pressed = true; + } + break; + case 'G': + CONSIDER_INPUT(); + if (tab->move.active) { + if (tab->move.offset != count - 1 - tab->move.selected) { + tab->move.offset = count - 1 - tab->move.selected; + return true; + } + } else if (tab->selected != count - 1) { + tab->selected = count - 1; + return true; + } + break; + case 'f': + CONSIDER_INPUT_REPEAT(); + if (tab->move.active) { + if (tab->selected + tab->move.offset < count - lp->page_length) { + tab->move.offset += lp->page_length; + return true; + } else if (tab->move.offset != count - 1 - tab->move.selected) { + tab->move.offset = count - 1 - tab->move.selected; + return true; + } + } else { + if (tab->selected < count - lp->page_length) { + tab->selected += lp->page_length; + return true; + } else if (tab->selected != count - 1) { + tab->selected = count - 1; + return true; + } + } + break; + case 'b': + CONSIDER_INPUT_REPEAT(); + if (tab->move.active) { + if (tab->selected + tab->move.offset > lp->page_length) { + tab->move.offset -= lp->page_length; + return true; + } else if (tab->move.offset != -tab->selected) { + tab->move.offset = -tab->selected; + return true; + } + } else { + if (tab->selected > lp->page_length) { + tab->selected -= lp->page_length; + return true; + } else if (tab->selected != 0) { + tab->selected = 0; + return true; + } + } + break; + case 'j': + CONSIDER_INPUT_REPEAT(); + if (tab->move.active) { + if (tab->selected + tab->move.offset < count - 1) { + tab->move.offset++; + return true; + } + } else { + if (tab->selected < count - 1) { + tab->selected++; + return true; + } + } + break; + case 'k': + CONSIDER_INPUT_REPEAT(); + if (tab->move.active) { + if (tab->selected + tab->move.offset > 0) { + tab->move.offset--; + return true; + } + } else { + if (tab->selected > 0) { + tab->selected--; + return true; + } + } + break; + case 'i': + CONSIDER_INPUT(); + if (!tab->move.active) { + tab->move.active = true; + tab->move.selected = tab->selected; + tab->move.offset = 0; + return true; + } + break; + case NCKEY_ESC: + CONSIDER_INPUT(); + if (tab->move.active) { + tab->move.active = false; + return true; + } + break; + case NCKEY_RETURN: + CONSIDER_INPUT(); + if (tab->move.active) { + tab->move.active = false; + return true; + } else if (tab->selected != tab->list->current) { + camu_client_skipto(&lp->ui->c->client, &tab->list->name, tab->selected); + } + break; + case NCKEY_BUTTON1: + /* + CONSIDER_INPUT(); + if (input->x < 0 || input->y < 0) return false; + u32 x = (u32)input->x; + u32 y = (u32)input->y; + ui->last_mouse_x = x; + ui->last_mouse_y = y; + u64 now = nn_get_timestamp(); + u64 last = ui->last_mouse_ts; + if (last != (u64)-1 && now - last < 100000) { + return false; + } + ui->last_mouse_ts = now; + u32 width = ncplane_dim_x(ui->lp.n); + u32 height = ncplane_dim_y(ui->lp.n); + if (x >= 2 && x <= width && y > height - 3) { // @TODO: Make this defined, more to nowplaying. + struct cmc_list_entry *entry = al_array_at(tab->list->entries, tab->list->current); + u64 pos = ((x - 2) / (f64)(width - 2)) * entry->duration; + camu_client_seek(&ui->c->client, &tab->list->name, entry->id, pos); + } + */ + break; + } + + return false; +} + +static struct cmc_list_entry *tab_current_entry(struct cmc_list_tab *tab) +{ + if (tab->list->current >= 0) { + return al_array_at(tab->list->entries, tab->list->current); + } + return NULL; +} + +bool cmc_list_pane_tick(struct cmc_list_pane *lp) +{ + bool tick = false; + if (lp->selected != -1) { + struct cmc_list_tab *tab = &al_array_at(lp->tabs, lp->selected); + if (tab->list->meta_changed) { + tab->list->meta_changed = false; + tick = true; + } + tick |= cmc_now_playing_tick(&lp->np, tab_current_entry(tab)); + } + return tick; +} + +void cmc_list_pane_erase(struct cmc_list_pane *lp) +{ + ncplane_erase(lp->n); + cmc_now_playing_erase(&lp->np); +} + +static void render_list_entries(struct cmc_list_pane *lp, struct cmc_list_tab *tab, s32 max_height) +{ + struct cmc_list *list = tab->list; + struct ncplane *n = lp->n; + s32 tracked = tab->move.active ? tab->move.selected + tab->move.offset : tab->selected; + s32 count = (s32)list->entries.count; + s32 midpoint = max_height / 2; + s32 rounded = (max_height + 1) / 2; + s32 top, end; + if (tracked < midpoint) { + top = 0; + end = MIN(top + max_height, count); + } else if (tracked + rounded >= count) { + top = MAX(count - max_height, 0); + end = count; + } else { + top = tracked - midpoint; + end = tracked + rounded; + } + s32 index; + struct cmc_list_entry *entry; + for (s32 i = top; i < end; i++) { + index = i; + if (tab->move.active) { + if (index == tracked) { + index = tab->move.selected; + } else { + if (index > tracked) index--; + if (index >= tab->move.selected) index++; + } + } + entry = al_array_at(list->entries, index); + bool on_current = list->current >= 0 && index == list->current; + bool styled = i == tracked; + if (styled) { + ncplane_set_styles(n, NCSTYLE_BOLD); + ncplane_set_fg_palindex(n, 4); + } + s32 y = i - top; + u32 length = entry->name.length; + if (tab->move.active && i == tracked) { + cmc_ui_putnwstr_yx(n, y, 0, length, &entry->name); + } else if (on_current) { + ncplane_putchar_yx(n, y, 1, '>'); + cmc_ui_putnwstr_yx(n, y, 3, length, &entry->name); + } else { + cmc_ui_putnwstr_yx(n, y, 1, length, &entry->name); + } + if (styled) { + ncplane_set_styles(n, NCSTYLE_NONE); + ncplane_set_fg_default(n); + } + } +} + +void cmc_list_pane_render(struct cmc_list_pane *lp) +{ + if (lp->selected == -1) return; + struct cmc_list_tab *tab = &al_array_at(lp->tabs, lp->selected); + render_list_entries(lp, tab, lp->page_length); + cmc_now_playing_render(&lp->np, tab_current_entry(tab)); +} + +//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 = list->current, +// .leader_pressed = false +// }; +// 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); +// s32 count = (s32)tab->list->entries.count; +// bool leader = tab->leader_pressed; +// if (cmc_ui_consider_input(input, false)) { +// tab->leader_pressed = false; +// } +// +// switch (input->id) { +// case 'g': +// CONSIDER_INPUT(); +// if (leader) { +// if (tab->move.active) { +// tab->move.offset = -tab->move.selected; +// } else { +// tab->selected = 0; +// } +// } else { +// tab->leader_pressed = true; +// } +// break; +// case 'G': +// CONSIDER_INPUT(); +// if (tab->move.active) { +// tab->move.offset = count - 1 - tab->move.selected; +// } else { +// tab->selected = count - 1; +// } +// break; +// case 'f': +// //CONSIDER_INPUT_MOD_CTRL(); +// CONSIDER_INPUT_REPEAT(); +// if (tab->move.active) { +// if (tab->selected + tab->move.offset < count - tab->page_length) { +// tab->move.offset += tab->page_length; +// } else { +// tab->move.offset = count - 1 - tab->move.selected; +// } +// } else { +// if (tab->selected < count - tab->page_length) { +// tab->selected += tab->page_length; +// } else { +// tab->selected = count - 1; +// } +// } +// break; +// case 'b': +// //CONSIDER_INPUT_MOD_CTRL(); +// CONSIDER_INPUT_REPEAT(); +// if (tab->move.active) { +// if (tab->selected + tab->move.offset > tab->page_length) { +// tab->move.offset -= tab->page_length; +// } else { +// tab->move.offset = -tab->selected; +// } +// } else { +// if (tab->selected > tab->page_length) { +// tab->selected -= tab->page_length; +// } else { +// tab->selected = 0; +// } +// } +// break; +// case 'j': +// CONSIDER_INPUT_REPEAT(); +// if (tab->move.active) { +// if (tab->selected + tab->move.offset < count - 1) { +// tab->move.offset++; +// } +// } else { +// if (tab->selected < count - 1) { +// tab->selected++; +// } +// } +// break; +// case 'k': +// CONSIDER_INPUT_REPEAT(); +// if (tab->move.active) { +// if (tab->selected + tab->move.offset > 0) { +// tab->move.offset--; +// } +// } else { +// if (tab->selected > 0) { +// tab->selected--; +// } +// } +// break; +// case 'i': +// CONSIDER_INPUT(); +// if (!tab->move.active) { +// tab->move.active = true; +// tab->move.selected = tab->selected; +// tab->move.offset = 0; +// } +// break; +// case NCKEY_ESC: +// CONSIDER_INPUT(); +// if (tab->move.active) { +// tab->move.active = false; +// } +// break; +// case NCKEY_RETURN: +// CONSIDER_INPUT(); +// if (tab->move.active) { +// tab->move.active = false; +// } else if (tab->selected != tab->list->current) { +// camu_client_skipto(&ui->c->client, &tab->list->name, tab->selected); +// } +// break; +// case NCKEY_BUTTON1: +// CONSIDER_INPUT(); +// if (input->x < 0 || input->y < 0) return false; +// u32 x = (u32)input->x; +// u32 y = (u32)input->y; +// ui->last_mouse_x = x; +// ui->last_mouse_y = y; +// u64 now = nn_get_timestamp(); +// u64 last = ui->last_mouse_ts; +// if (last != (u64)-1 && now - last < 100000) { +// return false; +// } +// ui->last_mouse_ts = now; +// u32 width = ncplane_dim_x(ui->lp.n); +// u32 height = ncplane_dim_y(ui->lp.n); +// if (x >= 2 && x <= width && y > height - 3) { // @TODO: Make this defined, more to nowplaying. +// struct cmc_list_entry *entry = al_array_at(tab->list->entries, tab->list->current); +// u64 pos = ((x - 2) / (f64)(width - 2)) * entry->duration; +// camu_client_seek(&ui->c->client, &tab->list->name, entry->id, pos); +// } +// 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, s32 max_height) +//{ +// struct cmc_list *list = tab->list; +// struct ncplane *n = ui->lp.n; +// s32 tracked = tab->move.active ? tab->move.selected + tab->move.offset : tab->selected; +// s32 count = (s32)list->entries.count; +// s32 midpoint = max_height / 2; +// s32 rounded = (max_height + 1) / 2; +// s32 top, end; +// if (tracked < midpoint) { +// top = 0; +// end = MIN(top + max_height, count); +// } else if (tracked + rounded >= count) { +// top = count - max_height; +// end = count; +// } else { +// top = tracked - midpoint; +// end = tracked + rounded; +// } +// s32 index; +// struct cmc_list_entry *entry; +// for (s32 i = top; i < end; i++) { +// index = i; +// if (tab->move.active) { +// if (index == tracked) { +// index = tab->move.selected; +// } else { +// if (index > tracked) index--; +// if (index >= tab->move.selected) index++; +// } +// } +// entry = al_array_at(list->entries, index); +// bool on_current = list->current >= 0 && index == list->current; +// bool styled = i == tracked; +// if (styled) { +// ncplane_set_styles(n, NCSTYLE_BOLD); +// ncplane_set_fg_palindex(n, 4); +// } +// s32 y = i - top; +// u32 length = entry->name.length; +// if (tab->move.active && i == tracked) { +// cmc_ui_putnwstr_yx(n, y, 0, length, &entry->name); +// } else if (on_current) { +// ncplane_putchar_yx(n, y, 1, '>'); +// cmc_ui_putnwstr_yx(n, y, 3, length, &entry->name); +// } else { +// cmc_ui_putnwstr_yx(n, y, 1, length, &entry->name); +// } +// if (styled) { +// ncplane_set_styles(n, NCSTYLE_NONE); +// ncplane_set_fg_default(n); +// } +// } +//} +// +//static void render_now_playing(struct cmc_ui *ui, struct cmc_list_tab *tab, u32 y, u32 height) +//{ +// struct ncplane *n = ui->lp.n; +// u32 width = ncplane_dim_x(n); +// +// struct cmc_list_entry *entry = NULL; +// if (tab->list->entries.count > (u32)tab->list->current) { +// entry = al_array_at(tab->list->entries, tab->list->current); +// } +// +// u64 pos = 0; +// u64 duration = 0; +// u64 remaining = 0; +// u32 y_off = height - 7; +// +// ncplane_set_styles(n, NCSTYLE_BOLD); +// if (entry) { +// pos = entry->offset; +// duration = entry->duration; +// u64 now = nn_get_timestamp(); +// bool paused = entry->start == LIANA_TIMESTAMP_INVALID; +// if (!paused && now > entry->start) { +// pos += now - entry->start; +// } +// pos = MIN(pos, duration); +// remaining = duration - pos; +// //ncplane_putstr_yx(n, y_off, 2, "▸"); +// //ncplane_putstr_yx(n, y_off, 2, "▶"); +// //ncplane_putstr_yx(n, y_off, 2, "⏵ "); +// //ncplane_putstr_yx(n, y_off, 2, "|"); +// //ncplane_putstr_yx(n, y_off, 2, "|"); +// //ncplane_putstr_yx(n, y_off, 2, "⏵"); +// //ncplane_putstr_yx(n, y_off, 2, "⏸"); +// if (remaining == 0) { +// ncplane_putstr_yx(n, y_off - 1, 2, "∨"); +// } else { +// ncplane_putstr_yx(n, y_off - 1, 2, paused ? "^": ">"); +// } +// } else { +// ncplane_putstr_yx(n, y_off - 1, 2, "∨"); +// } +// ncplane_set_styles(n, NCSTYLE_NONE); +// +// f32 percent = 1.f; +// if (duration > 0) percent = MIN(pos / (f32)duration, 1.f); +// for (u32 i = 0; i < (width - 2) * percent; i++) { +// ncplane_putchar_yx(n, y_off, i + 1, '-'); +// } +// +// y_off--; +// +// char timestr[16] = { 0 }; // max = 829128280:01:49\0 +// s32 offset = camu_print_time(timestr, sizeof(timestr), pos, true, 4); +// ncplane_putstr_yx(n, y_off, 4, timestr); +// ncplane_putstr_yx(n, y_off, 4 + offset, "⎹"); +// +// if (entry) { +// cmc_ui_putnwstr_yx(n, y_off, 6 + offset, MIN(entry->name.length, (u32)60), &entry->name); +// } +// +// bool show_remaining = false; +// if (show_remaining) { +// offset = camu_print_time(timestr, sizeof(timestr), remaining, true, 4); +// } else { +// offset = camu_print_time(timestr, sizeof(timestr), duration, true, 4); +// } +// ncplane_putstr_yx(n, y_off, width - 4 - offset, "⎸"); +// // "=" Duration, "+" Segment, "-" Remaining. +// if (show_remaining) { +// ncplane_putstr_yx(n, y_off, width - 3 - offset, "-"); +// } else { +// ncplane_putstr_yx(n, y_off, width - 3 - offset, "="); +// } +// ncplane_putstr_yx(n, y_off, width - 2 - offset, timestr); +// +// /* +// ncplane_putchar_yx(n, height - 5, 2, 'A'); +// ncplane_putchar_yx(n, height - 4, 2, 'A'); +// ncplane_putchar_yx(n, height - 3, 2, 'A'); +// ncplane_putchar_yx(n, height - 2, 2, 'A'); +// */ +// +// if (entry && entry->raw.data != NULL) { +// u32 length = width - 2; +// u32 top = height - 6; +// u32 wave_height = 5; +// if (entry->raw.drawing == NULL || entry != ui->lp.blitted) { +// entry->raw.drawing = al_malloc(sizeof(char **) * wave_height); +// for (u32 j = 0; j < wave_height; j++) { +// entry->raw.drawing[j] = al_malloc(sizeof(char *) * length); +// for (u32 i = 0; i < length; i++) { +// entry->raw.drawing[j][i] = al_calloc(1, 6); +// } +// } +// if (!ui->lp.w) { +// struct ncplane_options opts = { 0 }; +// opts.x = 1; +// opts.y = top; +// opts.cols = length; +// opts.rows = wave_height; +// ui->lp.w = ncplane_create(n, &opts); +// } +// /* +// struct nccell ncl; +// nccell_init(&ncl); +// nccell_load(w, &ncl, " "); +// nccell_set_bg_palindex(&ncl, 3); +// ncplane_polyfill_yx(w, 0, 0, &ncl); +// */ +// u32 *rgba; +// u32 pixel_width, pixel_height; +// ncplane_pixel_geom(ui->lp.w, &pixel_height, &pixel_width, NULL, NULL, NULL, NULL); +// //cmc_draw_waveform(entry, length, wave_height, false, entry->raw.drawing, &rgba); +// cmc_draw_waveform(entry, pixel_width, pixel_height, false, entry->raw.drawing, &rgba); +// if (ui->lp.vis) { +// ncvisual_destroy(ui->lp.vis); +// } +// ui->lp.vis = ncvisual_from_rgba(rgba, pixel_height, pixel_width * 4, pixel_width); +// } +// +// if (entry != ui->lp.blitted) { +// ncplane_erase(ui->lp.w); +// struct ncvisual_options vopts = { 0 }; +// vopts.blitter = NCBLIT_PIXEL; +// vopts.scaling = NCSCALE_STRETCH; +// vopts.flags = NCVISUAL_OPTION_NOINTERPOLATE; +// vopts.n = ui->lp.w; +// vopts.x = 0; +// vopts.y = 0; +// ncvisual_blit(ui->nc, ui->lp.vis, &vopts); +// ui->lp.blitted = entry; +// } +// +// /* +// ncplane_set_fg_palindex(n, 5); +// for (u32 j = 0; j < wave_height; j++) { +// for (u32 i = 0; i < length; i++) { +// char *cell = entry->raw.drawing[j][i]; +// if (cell[0] == 'R') { +// ncplane_set_bg_palindex(n, 2); +// } +// ncplane_putstr_yx(n, top + j, 1 + i, cell + 1); +// if (cell[0] == 'R') { +// ncplane_set_bg_default(n); +// } +// } +// } +// ncplane_set_fg_default(n); +// */ +// } +// +// /* +// 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(n, height - 5, 5, buf); +// */ +// +// u64 c = 0; +// ncchannels_set_fg_default(&c); +// ncplane_cursor_move_yx(n, y, 0); +// ncplane_light_box(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); +// +// if (ui->lp.tabs.count <= ui->lp.current) return; +// struct cmc_list_tab *tab = &al_array_at(ui->lp.tabs, ui->lp.current); +// +// tab->page_length = height - 9; +// render_list_entries(ui, tab, tab->page_length); +// render_now_playing(ui, tab, tab->page_length, height); +//} diff --git a/src/fruits/cmc/ui/panes/list.h b/src/fruits/cmc/ui/panes/list.h new file mode 100644 index 0000000..bfeed59 --- /dev/null +++ b/src/fruits/cmc/ui/panes/list.h @@ -0,0 +1,35 @@ +#pragma once + +#include <al/array.h> +#include <notcurses/notcurses.h> + +#include "../widgets/now_playing.h" + +struct cmc_list; +struct cmc_list_tab { + struct cmc_list *list; + s32 selected; + bool leader_pressed; + struct { + bool active; + s32 selected; + s32 offset; + } move; +}; + +struct cmc_list_pane { + struct ncplane *n; + array(struct cmc_list_tab) tabs; + s32 selected; + s32 page_length; + struct cmc_now_playing np; + struct cmc_ui *ui; +}; + +void cmc_list_pane_init(struct cmc_list_pane *lp, struct cmc_ui *ui); +bool cmc_list_pane_layout(struct cmc_list_pane *lp, struct ncplane *parent); +void cmc_list_pane_add_list(struct cmc_list_pane *lp, struct cmc_list *list); +bool cmc_list_pane_handle_input(struct cmc_list_pane *lp, struct ncinput *input); +bool cmc_list_pane_tick(struct cmc_list_pane *lp); +void cmc_list_pane_erase(struct cmc_list_pane *lp); +void cmc_list_pane_render(struct cmc_list_pane *lp); diff --git a/src/fruits/cmc/ui/panes/log.c b/src/fruits/cmc/ui/panes/log.c new file mode 100644 index 0000000..be9702c --- /dev/null +++ b/src/fruits/cmc/ui/panes/log.c @@ -0,0 +1,65 @@ +#include "log.h" + +void cmc_log_pane_init(struct cmc_log_pane *lp) +{ + lp->new_message = false; + al_array_init(lp->messages); +} + +bool cmc_log_pane_layout(struct cmc_log_pane *lp, struct ncplane *parent) +{ + if (lp->n) ncplane_destroy(lp->n); + struct ncplane_options nopts = { 0 }; + nopts.rows = ncplane_dim_y(parent); + nopts.cols = ncplane_dim_x(parent); + lp->n = ncplane_create(parent, &nopts); + return true; +} + +void cmc_log_pane_push_message(struct cmc_log_pane *lp, char *message) +{ + al_array_push(lp->messages, message); + lp->new_message = true; +} + +bool cmc_log_pane_handle_input(struct cmc_log_pane *lp, struct ncinput *input) +{ + (void)lp; + (void)input; + return false; +} + +bool cmc_log_pane_tick(struct cmc_log_pane *lp) +{ + if (lp->new_message) { + lp->new_message = false; + return true; + } + return false; +} + +void cmc_log_pane_erase(struct cmc_log_pane *lp) +{ + ncplane_erase(lp->n); +} + +void cmc_log_pane_render(struct cmc_log_pane *lp) +{ + struct ncplane *n = lp->n; + + u32 max_height = ncplane_dim_y(n); + + u32 count = lp->messages.count; + u32 index = (count > max_height) ? count - max_height : 0; + for (u32 i = index; i < count; i++) { + // We can't use putnstr to control the width because these messages will have wide characters. + ncplane_putstr_yx(n, i - index, 0, al_array_at(lp->messages, i)); + } + + if (index > 0) { + //for (u32 i = 0; i < index; i++) { + // al_free(al_array_at(lp->messages, i)); + //} + //al_array_remove_range(lp->messages, 0, index); + } +} diff --git a/src/fruits/cmc/ui/panes/log.h b/src/fruits/cmc/ui/panes/log.h new file mode 100644 index 0000000..d79c7e9 --- /dev/null +++ b/src/fruits/cmc/ui/panes/log.h @@ -0,0 +1,18 @@ +#pragma once + +#include <al/array.h> +#include <notcurses/notcurses.h> + +struct cmc_log_pane { + struct ncplane *n; + bool new_message; + array(char *) messages; +}; + +void cmc_log_pane_init(struct cmc_log_pane *lp); +bool cmc_log_pane_layout(struct cmc_log_pane *lp, struct ncplane *parent); +void cmc_log_pane_push_message(struct cmc_log_pane *lp, char *message); +bool cmc_log_pane_handle_input(struct cmc_log_pane *lp, struct ncinput *input); +bool cmc_log_pane_tick(struct cmc_log_pane *lp); +void cmc_log_pane_erase(struct cmc_log_pane *lp); +void cmc_log_pane_render(struct cmc_log_pane *lp); diff --git a/src/fruits/cmc/ui/panes/search.c b/src/fruits/cmc/ui/panes/search.c new file mode 100644 index 0000000..e46c27d --- /dev/null +++ b/src/fruits/cmc/ui/panes/search.c @@ -0,0 +1,226 @@ +#include "../../cmc.h" + +#include "../ui.h" + +AL_ASSERT_TYPE_SIZE(wchar_t, 4); + +//void cmc_sp_init(struct cmc_ui *ui) +//{ +// 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 = { 0 }; +// nopts.rows = ncplane_dim_y(parent) - 1; +// nopts.cols = ncplane_dim_x(parent) - 1; +// ui->sp.n = ncplane_create(parent, &nopts); +// struct cmc_search_tab tab = { +// .search = NULL, +// }; +// 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); +//} +// +//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) +//{ +// bool entered = false; +// +// switch (input->id) { +// case NCKEY_BACKSPACE: +// if (tab->cursor > 0) { +// al_wstr_remove_at(&tab->input_text, tab->cursor - 1); +// 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: +// entered = true; +// // fallthrough +// case NCKEY_ESC: +// tab->input_active = false; +// tab->input_changed = true; // Erase text on next render. +// break; +// 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; +// case 'U': +// for (u32 i = 1; i <= tab->cursor; i++) { +// al_wstr_remove_at(&tab->input_text, tab->cursor - i); +// } +// tab->cursor = 0; +// tab->input_changed = true; +// break; +// } +// break; +// } +// +// if (ncinput_alt_p(input) || nckey_synthesized_p(input->id)) { +// break; +// } +// +// al_wstr_insert(&tab->input_text, tab->cursor, &al_wstr_w((wchar_t *)input->eff_text, 0, 1)); +// tab->cursor++; +// tab->input_changed = true; +// +// /* +// // 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)); +// } +// } +// */ +// +// break; +// } +// +// tab->visual_cursor = 0; +// if (tab->cursor > 0) { +// for (u32 i = 0; i < tab->cursor; i++) { +// tab->visual_cursor += al_wchar_width(al_wstr_at(&tab->input_text, i)); +// } +// } +// +// return entered; +//} +// +//bool cmc_sp_handle_input(struct cmc_ui *ui, struct ncinput *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)) { +// str query; +// al_str_from_wstr(&query, &tab->input_text); +// camu_client_create_search(&ui->c->client, &al_str_c("youtube"), &query); +// al_str_free(&query); +// } +// if (!tab->input_active) notcurses_cursor_disable(ui->nc); +// } +// } else { +// switch (input->id) { +// case 'i': +// CONSIDER_INPUT(); +// 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->visual_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) +//{ +// 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->visual_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 88c9c5d..7a0d8a1 100644 --- a/src/fruits/cmc/ui/ui.c +++ b/src/fruits/cmc/ui/ui.c @@ -1,8 +1,9 @@ #include <al/log.h> #include <al/lib.h> +#include "util/notcurses_ext.h" + #include "ui.h" -#include "ext.h" void cmc_ui_putnwstr_yx(struct ncplane *n, u32 y, u32 x, u32 width, wstr *w) { @@ -36,70 +37,54 @@ static bool read_input(struct cmc_ui *ui, struct ncinput *input) static void erase_previous_pane(struct cmc_ui *ui, u8 previous, u8 upcoming) { - struct ncplane *pn = NULL; switch (previous) { case CMC_PANE_LIST: - pn = ui->lp.n; - break; - case CMC_PANE_SEARCH: - pn = ui->sp.n; + cmc_list_pane_erase(&ui->lists); break; case CMC_PANE_LOG: - pn = ui->logp.n; + cmc_log_pane_erase(&ui->log); break; } - ncplane_erase(pn); switch (upcoming) { case CMC_PANE_LIST: - ncplane_move_top(ui->lp.n); - //ncplane_move_family_below(pn, ui->lp.n); - break; - case CMC_PANE_SEARCH: - ncplane_move_top(ui->sp.n); - //ncplane_move_family_below(pn, ui->sp.n); + ncplane_move_family_top(ui->lists.n); break; case CMC_PANE_LOG: - ncplane_move_top(ui->logp.n); - //ncplane_move_family_below(pn, ui->logp.n); + ncplane_move_family_top(ui->log.n); break; } } -static void switch_to_pane(struct cmc_ui *ui, u8 pane) +static bool switch_to_pane(struct cmc_ui *ui, u8 pane) { - erase_previous_pane(ui, ui->pane, pane); - ui->pane = pane; + if (pane != ui->pane) { + erase_previous_pane(ui, ui->pane, pane); + ui->pane = pane; + return true; + } + return false; } void cmc_ui_queue_render(struct cmc_ui *ui) { - ncplane_erase(ui->n); + //ncplane_erase(ui->n); switch (ui->pane) { case CMC_PANE_LIST: - cmc_lp_erase(ui); - break; - case CMC_PANE_SEARCH: - cmc_sp_erase(ui); + cmc_list_pane_erase(&ui->lists); break; case CMC_PANE_LOG: - cmc_logp_erase(ui); - break; + cmc_log_pane_erase(&ui->log); } - if (ui->overlay_shown) { - ncplane_erase(ui->oln); - } + if (ui->overlay_shown) ncplane_erase(ui->oln); switch (ui->pane) { case CMC_PANE_LIST: - cmc_lp_render(ui); - break; - case CMC_PANE_SEARCH: - cmc_sp_render(ui); + cmc_list_pane_render(&ui->lists); break; case CMC_PANE_LOG: - cmc_logp_render(ui); + cmc_log_pane_render(&ui->log); break; } @@ -112,11 +97,11 @@ void cmc_ui_queue_render(struct cmc_ui *ui) 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); + u32 width = ncplane_dim_x(ui->oln); + u32 height = ncplane_dim_y(ui->oln); ncplane_light_box(ui->oln, NCSTYLE_NONE, c, height - 1, width - 1, 0); } @@ -126,8 +111,11 @@ void cmc_ui_queue_render(struct cmc_ui *ui) void cmc_ui_toggle_overlay(struct cmc_ui *ui) { ui->overlay_shown = !ui->overlay_shown; - if (!ui->overlay_shown) { + if (ui->overlay_shown) { + ncplane_move_top(ui->oln); + } else { ncplane_erase(ui->oln); + ncplane_move_bottom(ui->oln); } } @@ -145,15 +133,15 @@ static void input_poll_callback(void *userdata, s32 revents) nn_event_loop_break_one(ui->loop); break; case '1': - switch_to_pane(ui, CMC_PANE_LIST); + do_render |= switch_to_pane(ui, CMC_PANE_LIST); break; case '2': break; case '3': - switch_to_pane(ui, CMC_PANE_SEARCH); + do_render |= switch_to_pane(ui, CMC_PANE_SEARCH); break; case '4': - switch_to_pane(ui, CMC_PANE_LOG); + do_render |= switch_to_pane(ui, CMC_PANE_LOG); break; case NCKEY_TAB: cmc_ui_toggle_overlay(ui); @@ -162,22 +150,14 @@ static void input_poll_callback(void *userdata, s32 revents) } 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)) { - do_render |= true; - } + do_render |= cmc_list_pane_handle_input(&ui->lists, &input); break; case CMC_PANE_LOG: - do_render = true; + do_render |= cmc_log_pane_handle_input(&ui->log, &input); break; } if (do_render) { cmc_ui_queue_render(ui); - notcurses_render(ui->nc); } } } @@ -186,16 +166,29 @@ 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); + bool do_render = false; + switch (ui->pane) { + case CMC_PANE_LIST: + do_render |= cmc_list_pane_tick(&ui->lists); + break; + case CMC_PANE_LOG: + do_render |= cmc_log_pane_tick(&ui->log); + break; + } + if (do_render) { + cmc_ui_queue_render(ui); + } nn_timer_again(&ui->render_timer); } -static bool relayout(struct cmc_ui *ui) +static bool relayout(struct cmc_ui *ui, struct ncplane *stdplane) { - struct ncplane *stdplane = notcurses_stdplane(ui->nc); notcurses_stddim_yx(ui->nc, &ui->term_rows, &ui->term_cols); + /* TBD + if (ui->term_cols < 6 || ui->term_rows < 6) return false; + */ + if (ui->n) ncplane_destroy(ui->n); struct ncplane_options nopts = { 0 }; nopts.x = 1; @@ -205,25 +198,41 @@ static bool relayout(struct cmc_ui *ui) nopts.flags = NCPLANE_OPTION_MARGINALIZED; ui->n = ncplane_create(stdplane, &nopts); - cmc_lp_layout(ui, ui->n); - cmc_sp_layout(ui, ui->n); - cmc_logp_layout(ui, ui->n); + bool layed_out = true; + layed_out &= cmc_list_pane_layout(&ui->lists, ui->n); + layed_out &= cmc_log_pane_layout(&ui->log, ui->n); + + switch (ui->pane) { + case CMC_PANE_LIST: + ncplane_move_family_top(ui->lists.n); + break; + case CMC_PANE_LOG: + ncplane_move_family_top(ui->log.n); + break; + } if (ui->oln) ncplane_destroy(ui->oln); - nopts.margin_r = MAX((u32)6, ncplane_dim_x(ui->n) / 2); - nopts.margin_b = MAX((u32)2, ncplane_dim_y(ui->n) / 2); - nopts.x = nopts.margin_r / 2; - nopts.y = nopts.margin_b / 2; + nopts.margin_r = 0; + nopts.margin_b = 0; + nopts.x = NCALIGN_CENTER; + nopts.y = NCALIGN_CENTER; + nopts.rows = MAX((u32)1, ncplane_dim_y(ui->n) / 2); + nopts.cols = MAX((u32)6, ncplane_dim_x(ui->n) / 2); + nopts.flags = NCPLANE_OPTION_VERALIGNED | NCPLANE_OPTION_HORALIGNED; ui->oln = ncplane_create(ui->n, &nopts); - ncplane_move_below(ui->oln, ui->n); + ncplane_move_bottom(ui->oln); - return true; + return layed_out; } static s32 resize_cb(struct ncplane *p) { struct cmc_ui *ui = (struct cmc_ui *)ncplane_userptr(p); - relayout(ui); + //al_assert(false); + ui->layed_out = relayout(ui, notcurses_stdplane(ui->nc)); + if (ui->layed_out) { + cmc_ui_queue_render(ui); + } return 0; } @@ -231,7 +240,7 @@ static s32 log_callback(void *userdata, u8 level, char *message) { struct cmc_ui *ui = (struct cmc_ui *)userdata; (void)level; - cmc_logp_push_message(ui, message); + cmc_log_pane_push_message(&ui->log, message); return al_strnlen(message, AL_LOG_MESSAGE_SIZE); } @@ -249,14 +258,9 @@ bool cmc_ui_init(struct cmc_ui *ui, struct nn_event_loop *loop, struct cmc *c) ui->loop = loop; - cmc_lp_init(ui); - cmc_sp_init(ui); - - cmc_logp_init(ui); + cmc_list_pane_init(&ui->lists, ui); al_set_print(log_callback, ui); - - ui->pending_layout = false; - ui->pending_render = false; + cmc_log_pane_init(&ui->log); ui->pane = CMC_PANE_SEARCH; ui->overlay_shown = false; @@ -265,12 +269,12 @@ bool cmc_ui_init(struct cmc_ui *ui, struct nn_event_loop *loop, struct cmc *c) ui->last_mouse_y = 0; ui->last_mouse_ts = (u64)-1; - relayout(ui); - struct ncplane *stdplane = notcurses_stdplane(ui->nc); ncplane_set_resizecb(stdplane, resize_cb); ncplane_set_userptr(stdplane, ui); + ui->layed_out = relayout(ui, stdplane); + 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); diff --git a/src/fruits/cmc/ui/ui.h b/src/fruits/cmc/ui/ui.h index 302d735..7abf105 100644 --- a/src/fruits/cmc/ui/ui.h +++ b/src/fruits/cmc/ui/ui.h @@ -7,12 +7,16 @@ #include <nnwt/timer.h> #include <notcurses/notcurses.h> +#include "panes/list.h" +#include "panes/log.h" + enum { CMC_PANE_LIST = 0, CMC_PANE_SEARCH, CMC_PANE_LOG }; +/* struct cmc_list_tab { struct cmc_list *list; s32 page_length; @@ -34,6 +38,7 @@ struct cmc_search_tab { wstr input_text; bool input_changed; }; +*/ struct cmc; struct cmc_ui { @@ -41,15 +46,17 @@ struct cmc_ui { struct nn_event_loop *loop; u32 term_cols; u32 term_rows; - bool pending_layout; - bool pending_render; u8 pane; + bool layed_out; struct ncplane *n; // window. struct ncplane *oln; // overlay. bool overlay_shown; u32 last_mouse_x; u32 last_mouse_y; u64 last_mouse_ts; + struct cmc_list_pane lists; + struct cmc_log_pane log; + /* struct { struct ncplane *n; struct ncplane *w; @@ -67,6 +74,7 @@ struct cmc_ui { struct ncplane *n; array(char *) messages; } logp; + */ struct nn_poll input_poll; struct nn_timer render_timer; struct cmc *c; @@ -90,6 +98,7 @@ 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); @@ -110,3 +119,4 @@ void cmc_logp_push_message(struct cmc_ui *ui, char *message); bool cmc_logp_handle_input(struct cmc_ui *ui, struct ncinput *input); void cmc_logp_erase(struct cmc_ui *ui); void cmc_logp_render(struct cmc_ui *ui); +*/ diff --git a/src/fruits/cmc/ui/ext.h b/src/fruits/cmc/ui/util/notcurses_ext.h index b09754e..b09754e 100644 --- a/src/fruits/cmc/ui/ext.h +++ b/src/fruits/cmc/ui/util/notcurses_ext.h diff --git a/src/fruits/cmc/ui/waveform.c b/src/fruits/cmc/ui/util/waveform.c index 9bfc296..8d37543 100644 --- a/src/fruits/cmc/ui/waveform.c +++ b/src/fruits/cmc/ui/util/waveform.c @@ -131,72 +131,72 @@ void cmc_draw_waveform(struct cmc_list_entry *entry, u32 width, u32 height, bool return; #endif - u32 channels = entry->raw.fmt.channel_count; - u32 samples_per_column = entry->raw.sample_count / (width * 2); - - s32 pixel_height = height * 4; - s32 wave_min = INT16_MIN; - s32 wave_max = INT16_MAX; - s32 step = (wave_max * 2 + (pixel_height - 1)) / pixel_height; - - s16 *steps = al_malloc(sizeof(s16) * (pixel_height + 1)); - for (s32 i = 0; i <= (s32)pixel_height; i++) { - steps[i] = (s16)MAX(wave_max - step * i, wave_min); - } - al_assert(steps[pixel_height] == wave_min); - - s64 rms = 0; - s16 min, max; - s16 low, high; - s16 *data = (s16 *)entry->raw.data; - const u32 samples_stride = samples_per_column * channels; - u16 masks_for_chars[height]; - //u32 slen[height]; - //al_memset(slen, 0, sizeof(u32) * height); - for (u32 i = 0; i < width; i++) { - al_memset(masks_for_chars, 0, sizeof(u16) * height); - for (u32 k = 0; k < 2; k++) { - u32 f = (i * 2 + k) * samples_stride; - for (u32 n = 0; n < channels; n++) { - rms = 0; - min = wave_max, max = wave_min; - for (u32 m = 0; m <= samples_stride; m += channels) { - s16 ss = data[f + m + n]; - rms += ss * ss; - min = MIN(min, ss); - max = MAX(max, ss); - } - rms /= samples_per_column; - rms = sqrt(rms); - for (u32 j = 0; j < height; j++) { - for (u32 l = 0; l < 4; l++) { - u32 step = j * 4 + l; - low = steps[step + 1], high = steps[step]; - if (low < max && high >= min) { - if (include_rms && WITHIN_RMS(low, high, rms)) { - masks_for_chars[j] |= RMS_BIT; - } else { - masks_for_chars[j] |= 1 << (l * 2 + k); - } - } - } - } - } - } - for (u32 j = 0; j < height; j++) { - if (masks_for_chars[j] & RMS_BIT) { - buf[j][i][0] = 'R'; - masks_for_chars[j] &= ~RMS_BIT; - } - u32 len = masks_for_chars[j] == 0 ? 2 : 5; - al_memcpy(buf[j][i] + 1, octant_lut[masks_for_chars[j]], len); - //slen[j] += len; - } - } - - /* - for (u32 j = 0; j < height; j++) { - buf[j][slen[j]] = '\0'; - } - */ +// u32 channels = entry->raw.fmt.channel_count; +// u32 samples_per_column = entry->raw.sample_count / (width * 2); +// +// s32 pixel_height = height * 4; +// s32 wave_min = INT16_MIN; +// s32 wave_max = INT16_MAX; +// s32 step = (wave_max * 2 + (pixel_height - 1)) / pixel_height; +// +// s16 *steps = al_malloc(sizeof(s16) * (pixel_height + 1)); +// for (s32 i = 0; i <= (s32)pixel_height; i++) { +// steps[i] = (s16)MAX(wave_max - step * i, wave_min); +// } +// al_assert(steps[pixel_height] == wave_min); +// +// s64 rms = 0; +// s16 min, max; +// s16 low, high; +// s16 *data = (s16 *)entry->raw.data; +// const u32 samples_stride = samples_per_column * channels; +// u16 masks_for_chars[height]; +// //u32 slen[height]; +// //al_memset(slen, 0, sizeof(u32) * height); +// for (u32 i = 0; i < width; i++) { +// al_memset(masks_for_chars, 0, sizeof(u16) * height); +// for (u32 k = 0; k < 2; k++) { +// u32 f = (i * 2 + k) * samples_stride; +// for (u32 n = 0; n < channels; n++) { +// rms = 0; +// min = wave_max, max = wave_min; +// for (u32 m = 0; m <= samples_stride; m += channels) { +// s16 ss = data[f + m + n]; +// rms += ss * ss; +// min = MIN(min, ss); +// max = MAX(max, ss); +// } +// rms /= samples_per_column; +// rms = sqrt(rms); +// for (u32 j = 0; j < height; j++) { +// for (u32 l = 0; l < 4; l++) { +// u32 step = j * 4 + l; +// low = steps[step + 1], high = steps[step]; +// if (low < max && high >= min) { +// if (include_rms && WITHIN_RMS(low, high, rms)) { +// masks_for_chars[j] |= RMS_BIT; +// } else { +// masks_for_chars[j] |= 1 << (l * 2 + k); +// } +// } +// } +// } +// } +// } +// for (u32 j = 0; j < height; j++) { +// if (masks_for_chars[j] & RMS_BIT) { +// buf[j][i][0] = 'R'; +// masks_for_chars[j] &= ~RMS_BIT; +// } +// u32 len = masks_for_chars[j] == 0 ? 2 : 5; +// al_memcpy(buf[j][i] + 1, octant_lut[masks_for_chars[j]], len); +// //slen[j] += len; +// } +// } +// +// /* +// for (u32 j = 0; j < height; j++) { +// buf[j][slen[j]] = '\0'; +// } +// */ } diff --git a/src/fruits/cmc/ui/waveform.h b/src/fruits/cmc/ui/util/waveform.h index 464d589..763f54a 100644 --- a/src/fruits/cmc/ui/waveform.h +++ b/src/fruits/cmc/ui/util/waveform.h @@ -88,7 +88,7 @@ AAAAAAAAAAAAAAAA #include <al/types.h> -#include "../cmc.h" +#include "../../cmc.h" /* static const char *bar_lut[] = { diff --git a/src/fruits/cmc/ui/widgets/now_playing.c b/src/fruits/cmc/ui/widgets/now_playing.c new file mode 100644 index 0000000..d30590c --- /dev/null +++ b/src/fruits/cmc/ui/widgets/now_playing.c @@ -0,0 +1,223 @@ +#include <math.h> + +#include "../../../../liana/list.h" +#include "../../../../sink/util.h" + +#include "../../cmc.h" + +#include "../util/waveform.h" +#include "../util/notcurses_ext.h" + +#include "now_playing.h" + +void cmc_now_playing_init(struct cmc_now_playing *np) +{ + al_array_init(np->visuals); +} + +bool cmc_now_playing_layout(struct cmc_now_playing *np, struct ncplane *parent, u32 y) +{ + if (np->n) ncplane_destroy(np->n); + u32 width = ncplane_dim_x(parent); + u32 height = ncplane_dim_y(parent); + struct ncplane_options nopts = { 0 }; + nopts.x = 0; + nopts.y = y; + nopts.cols = width; + nopts.rows = height - y; + np->n = ncplane_create(parent, &nopts); + if (np->wave) ncplane_destroy(np->wave); + nopts.x = 1; + nopts.y = 3; + nopts.rows = 5; + nopts.cols = width - 2; + np->wave = ncplane_create(np->n, &nopts); + np->blitted = NULL; + struct cmc_now_playing_visual *visual; + al_array_foreach_ptr(np->visuals, i, visual) { + if (visual->visual) ncvisual_destroy(visual->visual); + } + np->visuals.count = 0; + return true; +} + +static void get_entry_duration_and_pos(struct cmc_list_entry *entry, u64 *duration, u64 *pos) +{ + *pos = entry->offset; + *duration = entry->duration; + u64 now = nn_get_timestamp(); + bool paused = entry->start == LIANA_TIMESTAMP_INVALID; + if (!paused && now > entry->start) { + *pos += now - entry->start; + } + *pos = MIN(*pos, *duration); +} + +bool cmc_now_playing_tick(struct cmc_now_playing *np, struct cmc_list_entry *entry) +{ + return true; + u64 duration = 0, pos = 0; + if (entry) get_entry_duration_and_pos(entry, &duration, &pos); + u32 width = ncplane_dim_x(np->n); + f32 percent = 1.f; + if (duration > 0) percent = MIN(pos / (f32)duration, 1.f); + u32 segments = (width - 2) * percent; + if (segments != np->segment_count) { + np->segment_count = segments; + return true; + } else { + // @TODO: Avoiding judder: Implement and utilize tick timing change. + if (pos < np->last_pos || pos - np->last_pos > 1000000) { + np->last_pos = pos; + return true; + } + } + return false; +} + +void cmc_now_playing_erase(struct cmc_now_playing *np) +{ + ncplane_erase(np->n); +} + +void cmc_now_playing_render(struct cmc_now_playing *np, struct cmc_list_entry *entry) +{ + u64 duration = 0, pos = 0; + if (entry) get_entry_duration_and_pos(entry, &duration, &pos); + u64 remaining = duration - pos; + + ncplane_set_styles(np->n, NCSTYLE_BOLD); + if (remaining == 0) { + ncplane_putstr_yx(np->n, 1, 2, "∨"); + } else { + al_assert(entry); + if (remaining == 0) { + ncplane_putstr_yx(np->n, 1, 2, "∨"); + } else { + bool paused = entry->start == LIANA_TIMESTAMP_INVALID; + ncplane_putstr_yx(np->n, 1, 2, paused ? "^": ">"); + } + } + ncplane_set_styles(np->n, NCSTYLE_NONE); + + u32 width = ncplane_dim_x(np->n); + u32 height = ncplane_dim_y(np->n); + + // ━ ╸╺ ╼ ╾ ╴╶ + //ncplane_set_fg_palindex(np->n, 8); + f32 percent = 1.f; + if (duration > 0) percent = MIN(pos / (f32)duration, 1.f); + u32 bar_width = width - 1; + u32 segments = roundf((bar_width * 4) * percent); + u32 head = segments % 4; + segments /= 4; + for (u32 i = 0; i < segments; i++) { + ncplane_putstr_yx(np->n, 2, i + 1, "━"); + } + char *head_segment; + switch (head) { + case 0: + head_segment = " "; + break; + case 1: + head_segment = "╴"; + break; + case 2: + head_segment = "╸"; + break; + case 3: + head_segment = "╾"; + break; + } + ncplane_putstr_yx(np->n, 2, segments + 1, head_segment); + ncplane_set_fg_default(np->n); + + char timestr[16] = { 0 }; // max = 829128280:01:49\0 + s32 offset = camu_print_time(timestr, sizeof(timestr), pos, true, 4); + ncplane_putstr_yx(np->n, 1, 4, timestr); + ncplane_putstr_yx(np->n, 1, 4 + offset, "⎹"); + + if (entry) { + cmc_ui_putnwstr_yx(np->n, 1, 6 + offset, MIN(entry->name.length, (u32)60), &entry->name); + } + + bool show_remaining = false; + if (show_remaining) { + offset = camu_print_time(timestr, sizeof(timestr), remaining, true, 4); + } else { + offset = camu_print_time(timestr, sizeof(timestr), duration, true, 4); + } + ncplane_putstr_yx(np->n, 1, width - 4 - offset, "⎸"); + // "=" Duration, "+" Segment, "-" Remaining. + if (show_remaining) { + ncplane_putstr_yx(np->n, 1, width - 3 - offset, "-"); + } else { + ncplane_putstr_yx(np->n, 1, width - 3 - offset, "="); + } + ncplane_putstr_yx(np->n, 1, width - 2 - offset, timestr); + + if (entry) { + bool visual_cached = false; + struct cmc_now_playing_visual *visual; + al_array_foreach_ptr(np->visuals, i, visual) { + if (visual->entry == entry) { + visual_cached = true; + break; + } + } + + if (!visual_cached) { + if (entry->raw.data != NULL) { + al_array_push(np->visuals, (struct cmc_now_playing_visual){}); + visual = &al_array_last(np->visuals); + visual->entry = entry; + u32 *rgba; + u32 pixel_width, pixel_height; + ncplane_pixel_geom(np->wave, &pixel_height, &pixel_width, NULL, NULL, NULL, NULL); + cmc_draw_waveform(entry, pixel_width, pixel_height, false, entry->raw.drawing, &rgba); + visual->visual = ncvisual_from_rgba(rgba, pixel_height, pixel_width * 4, pixel_width); + } else { + visual = NULL; + } + } + + if (visual && entry != np->blitted) { + ncplane_erase(np->wave); + struct ncvisual_options vopts = { 0 }; + vopts.blitter = NCBLIT_PIXEL; + vopts.scaling = NCSCALE_STRETCH; + vopts.flags = NCVISUAL_OPTION_NOINTERPOLATE; + vopts.n = np->wave; + vopts.x = 0; + vopts.y = 0; + ncvisual_blit(ncplane_notcurses(np->wave), visual->visual, &vopts); + np->blitted = entry; + } + } + + u64 c = 0; + ncchannels_set_fg_default(&c); + //ncchannels_set_fg_palindex(&c, 8); + ncchannels_set_bg_default(&c); + ncchannels_set_bg_alpha(&c, 0); + ncchannels_set_fg_alpha(&c, 255); + ncplane_cursor_move_yx(np->n, 0, 0); + //ncplane_double_box(np->n, NCSTYLE_NONE, c, height - 1, width - 1, 0); + ncplane_light_box(np->n, NCSTYLE_NONE, c, height - 1, width - 1, 0); + //ncplane_set_fg_palindex(np->n, 8); + if (segments > 0 || head > 0) { + if (segments == 0 && head == 1) { + ncplane_putstr_yx(np->n, 2, 0, "├"); + } else { + ncplane_putstr_yx(np->n, 2, 0, "┝"); + } + if (segments == bar_width || (segments == bar_width - 1 && head > 0)) { + if (head == 1) { + ncplane_putstr_yx(np->n, 2, width - 1, "┤"); + } else { + ncplane_putstr_yx(np->n, 2, width - 1, "┥"); + } + } + } + ncplane_set_fg_default(np->n); +} diff --git a/src/fruits/cmc/ui/widgets/now_playing.h b/src/fruits/cmc/ui/widgets/now_playing.h new file mode 100644 index 0000000..5993c59 --- /dev/null +++ b/src/fruits/cmc/ui/widgets/now_playing.h @@ -0,0 +1,25 @@ +#pragma once + +#include <al/array.h> +#include <notcurses/notcurses.h> + +struct cmc_now_playing_visual { + struct ncvisual *visual; + struct cmc_list_entry *entry; +}; + +struct cmc_now_playing { + struct ncplane *n; + bool tick; + u64 last_pos; + u32 segment_count; + struct ncplane *wave; + array(struct cmc_now_playing_visual) visuals; + struct cmc_list_entry *blitted; +}; + +void cmc_now_playing_init(struct cmc_now_playing *np); +bool cmc_now_playing_layout(struct cmc_now_playing *np, struct ncplane *parent, u32 y); +bool cmc_now_playing_tick(struct cmc_now_playing *np, struct cmc_list_entry *entry); +void cmc_now_playing_erase(struct cmc_now_playing *np); +void cmc_now_playing_render(struct cmc_now_playing *np, struct cmc_list_entry *entry); diff --git a/src/fruits/cmc/ui/tile.h b/src/fruits/cmc/ui/widgets/tile.h index 6f15c7d..6f15c7d 100644 --- a/src/fruits/cmc/ui/tile.h +++ b/src/fruits/cmc/ui/widgets/tile.h |