From b1985414e54edf1eebe597551a48609ad08c6cfa Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Fri, 25 Apr 2025 15:56:56 -0400 Subject: Partial cmc ui refactor Signed-off-by: Andrew Opalach --- src/fruits/cmc/ui/ext.h | 20 - src/fruits/cmc/ui/pane_list.c | 408 ------------------- src/fruits/cmc/ui/pane_log.c | 44 -- src/fruits/cmc/ui/pane_search.c | 226 ----------- src/fruits/cmc/ui/panes/list.c | 688 ++++++++++++++++++++++++++++++++ src/fruits/cmc/ui/panes/list.h | 35 ++ src/fruits/cmc/ui/panes/log.c | 65 +++ src/fruits/cmc/ui/panes/log.h | 18 + src/fruits/cmc/ui/panes/search.c | 226 +++++++++++ src/fruits/cmc/ui/tile.h | 5 - src/fruits/cmc/ui/ui.c | 152 +++---- src/fruits/cmc/ui/ui.h | 14 +- src/fruits/cmc/ui/util/notcurses_ext.h | 20 + src/fruits/cmc/ui/util/waveform.c | 202 ++++++++++ src/fruits/cmc/ui/util/waveform.h | 111 ++++++ src/fruits/cmc/ui/waveform.c | 202 ---------- src/fruits/cmc/ui/waveform.h | 111 ------ src/fruits/cmc/ui/widgets/now_playing.c | 223 +++++++++++ src/fruits/cmc/ui/widgets/now_playing.h | 25 ++ src/fruits/cmc/ui/widgets/tile.h | 5 + 20 files changed, 1708 insertions(+), 1092 deletions(-) delete mode 100644 src/fruits/cmc/ui/ext.h delete mode 100644 src/fruits/cmc/ui/pane_list.c delete mode 100644 src/fruits/cmc/ui/pane_log.c delete mode 100644 src/fruits/cmc/ui/pane_search.c create mode 100644 src/fruits/cmc/ui/panes/list.c create mode 100644 src/fruits/cmc/ui/panes/list.h create mode 100644 src/fruits/cmc/ui/panes/log.c create mode 100644 src/fruits/cmc/ui/panes/log.h create mode 100644 src/fruits/cmc/ui/panes/search.c delete mode 100644 src/fruits/cmc/ui/tile.h create mode 100644 src/fruits/cmc/ui/util/notcurses_ext.h create mode 100644 src/fruits/cmc/ui/util/waveform.c create mode 100644 src/fruits/cmc/ui/util/waveform.h delete mode 100644 src/fruits/cmc/ui/waveform.c delete mode 100644 src/fruits/cmc/ui/waveform.h create mode 100644 src/fruits/cmc/ui/widgets/now_playing.c create mode 100644 src/fruits/cmc/ui/widgets/now_playing.h create mode 100644 src/fruits/cmc/ui/widgets/tile.h (limited to 'src/fruits/cmc/ui') diff --git a/src/fruits/cmc/ui/ext.h b/src/fruits/cmc/ui/ext.h deleted file mode 100644 index b09754e..0000000 --- a/src/fruits/cmc/ui/ext.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include - -// Copy-paste from notcurses.h -static inline s32 ncplane_light_box(struct ncplane *n, u16 attr, u64 channels, s32 ystop, s32 xstop, u32 ctlword) -{ - s32 ret = 0; - nccell ul = NCCELL_TRIVIAL_INITIALIZER, ur = NCCELL_TRIVIAL_INITIALIZER; - nccell ll = NCCELL_TRIVIAL_INITIALIZER, lr = NCCELL_TRIVIAL_INITIALIZER; - nccell hl = NCCELL_TRIVIAL_INITIALIZER, vl = NCCELL_TRIVIAL_INITIALIZER; - if((ret = nccells_light_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){ - ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword); - } - nccell_release(n, &ul); nccell_release(n, &ur); - nccell_release(n, &ll); nccell_release(n, &lr); - nccell_release(n, &hl); nccell_release(n, &vl); - return ret; -} 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 - -#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 + +#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 +#include + +#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 +#include + +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/tile.h b/src/fruits/cmc/ui/tile.h deleted file mode 100644 index 6f15c7d..0000000 --- a/src/fruits/cmc/ui/tile.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -struct cmc_tile { - -}; 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 #include +#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 #include +#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/util/notcurses_ext.h b/src/fruits/cmc/ui/util/notcurses_ext.h new file mode 100644 index 0000000..b09754e --- /dev/null +++ b/src/fruits/cmc/ui/util/notcurses_ext.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +// Copy-paste from notcurses.h +static inline s32 ncplane_light_box(struct ncplane *n, u16 attr, u64 channels, s32 ystop, s32 xstop, u32 ctlword) +{ + s32 ret = 0; + nccell ul = NCCELL_TRIVIAL_INITIALIZER, ur = NCCELL_TRIVIAL_INITIALIZER; + nccell ll = NCCELL_TRIVIAL_INITIALIZER, lr = NCCELL_TRIVIAL_INITIALIZER; + nccell hl = NCCELL_TRIVIAL_INITIALIZER, vl = NCCELL_TRIVIAL_INITIALIZER; + if((ret = nccells_light_box(n, attr, channels, &ul, &ur, &ll, &lr, &hl, &vl)) == 0){ + ret = ncplane_box(n, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword); + } + nccell_release(n, &ul); nccell_release(n, &ur); + nccell_release(n, &ll); nccell_release(n, &lr); + nccell_release(n, &hl); nccell_release(n, &vl); + return ret; +} diff --git a/src/fruits/cmc/ui/util/waveform.c b/src/fruits/cmc/ui/util/waveform.c new file mode 100644 index 0000000..8d37543 --- /dev/null +++ b/src/fruits/cmc/ui/util/waveform.c @@ -0,0 +1,202 @@ +#include +#include +#include "waveform.h" + +#define WITHIN_RMS(low, high, rms) \ + ((low >= 0 && low <= rms) || (high < 0 && high >= -rms) || (low < 0 && high > 0 && rms > 0)) + +#define OUTPUT_WAVEFORM_PNG +//#define WAVEFORM_REFERENCE_MODE +#ifdef OUTPUT_WAVEFORM_PNG +#include +#include +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include + +// min/max: #BC615E, #F4EAE0 +// rms: #FF928F, #E4CAAF +// +// min/max: #D93A50, #b22f41 +// rms: #f44f7a, #c84064 +// extra: #f6ac7f, #F5F4FA, #5b524b +// left channel: n == 0, right channel: n == 1. +#define COLOR_FOR_PIXEL(n, rms) rms ? \ + n == 0 ? nn_htonl(0xE4CAAF99) : nn_htonl(0xFF928FFF) : \ + n == 0 ? nn_htonl(0xF4EAE099) : nn_htonl(0xBC615EFF) + +// ss: current sample, ns: next sample. +#define HIT_WAVE(low, high, ss, ns) \ + ((ss <= low && ns > low) || (ss > high && ns <= high) || (ss > low && ns <= high)) + +static void test_write_bitmap(struct cmc_list_entry *entry, u32 width, u32 height, u32 **rgba) +{ + f64 start = nn_get_tick(); + + *rgba = al_calloc(width * height, 4); + + u32 channels = entry->raw.fmt.channel_count; + u32 samples_per_column = entry->raw.sample_count / width; + + s32 wave_min = INT16_MIN; + s32 wave_max = INT16_MAX; + s32 step = (wave_max * 2 + (height - 1)) / height; + //s32 step = (wave_max * 2 + (height / 2)) / height; + + s16 *steps = al_malloc(sizeof(s16) * (height + 1)); + for (s32 i = 0; i <= (s32)height; i++) { + steps[i] = (s16)MAX(wave_max - step * i, wave_min); + } + //steps[height] = wave_min; + al_assert(steps[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; + for (u32 i = 0; i < width; i++) { + u32 f = i * 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); +#ifndef WAVEFORM_REFERENCE_MODE + for (u32 j = 0; j < height; j++) { + low = steps[j + 1], high = steps[j]; + if (low < max && high >= min) { + (*rgba)[j * width + i] = COLOR_FOR_PIXEL(n, WITHIN_RMS(low, high, rms)); + } + } +#else + for (u32 m = 0; m < samples_stride; m += channels) { + s16 ss = data[f + m + n]; + s16 ns = data[f + m + n + channels]; + for (u32 j = 0; j < height; j++) { + low = steps[j + 1], high = steps[j]; + if (HIT_WAVE(low, high, ss, ns)) { + (*rgba)[j * width + i] = COLOR_FOR_PIXEL(n, WITHIN_RMS(low, high, rms)); + } + } + } +#endif + } + } + + log_info("generating waveform took: %.2fs.", nn_get_tick() - start); + + //stbi_write_png("./waveform.png", width, height, 4, *rgba, width * 4); + + /* + struct nn_file file; + nn_file_open(&file, &al_str_c("./raw_samples.raw"), NNWT_FILE_CREATE); + size_t bytes = entry->raw.sample_count * entry->raw.fmt.channel_count * camu_audio_format_bytes_per_sample(&entry->raw.fmt); + nn_file_write(&file, entry->raw.data, bytes); + nn_file_close(&file); + */ +} +#endif + +static const char *octant_lut[] = { + " ", "𜺨", "𜺫", "🮂", "𜴀", "▘", "𜴁", "𜴂", "𜴃", "𜴄", "▝", "𜴅", "𜴆", "𜴇", "𜴈", "▀", + "𜴉", "𜴊", "𜴋", "𜴌", "🯦", "𜴍", "𜴎", "𜴏", "𜴐", "𜴑", "𜴒", "𜴓", "𜴔", "𜴕", "𜴖", "𜴗", + "𜴘", "𜴙", "𜴚", "𜴛", "𜴜", "𜴝", "𜴞", "𜴟", "🯧", "𜴠", "𜴡", "𜴢", "𜴣", "𜴤", "𜴥", "𜴦", + "𜴧", "𜴨", "𜴩", "𜴪", "𜴫", "𜴬", "𜴭", "𜴮", "𜴯", "𜴰", "𜴱", "𜴲", "𜴳", "𜴴", "𜴵", "🮅", + "𜺣", "𜴶", "𜴷", "𜴸", "𜴹", "𜴺", "𜴻", "𜴼", "𜴽", "𜴾", "𜴿", "𜵀", "𜵁", "𜵂", "𜵃", "𜵄", + "▖", "𜵅", "𜵆", "𜵇", "𜵈", "▌", "𜵉", "𜵊", "𜵋", "𜵌", "▞", "𜵍", "𜵎", "𜵏", "𜵐", "▛", + "𜵑", "𜵒", "𜵓", "𜵔", "𜵕", "𜵖", "𜵗", "𜵘", "𜵙", "𜵚", "𜵛", "𜵜", "𜵝", "𜵞", "𜵟", "𜵠", + "𜵡", "𜵢", "𜵣", "𜵤", "𜵥", "𜵦", "𜵧", "𜵨", "𜵩", "𜵪", "𜵫", "𜵬", "𜵭", "𜵮", "𜵯", "𜵰", + "𜺠", "𜵱", "𜵲", "𜵳", "𜵴", "𜵵", "𜵶", "𜵷", "𜵸", "𜵹", "𜵺", "𜵻", "𜵼", "𜵽", "𜵾", "𜵿", + "𜶀", "𜶁", "𜶂", "𜶃", "𜶄", "𜶅", "𜶆", "𜶇", "𜶈", "𜶉", "𜶊", "𜶋", "𜶌", "𜶍", "𜶎", "𜶏", + "▗", "𜶐", "𜶑", "𜶒", "𜶓", "▚", "𜶔", "𜶕", "𜶖", "𜶗", "▐", "𜶘", "𜶙", "𜶚", "𜶛", "▜", + "𜶜", "𜶝", "𜶞", "𜶟", "𜶠", "𜶡", "𜶢", "𜶣", "𜶤", "𜶥", "𜶦", "𜶧", "𜶨", "𜶩", "𜶪", "𜶫", + "▂", "𜶬", "𜶭", "𜶮", "𜶯", "𜶰", "𜶱", "𜶲", "𜶳", "𜶴", "𜶵", "𜶶", "𜶷", "𜶸", "𜶹", "𜶺", + "𜶻", "𜶼", "𜶽", "𜶾", "𜶿", "𜷀", "𜷁", "𜷂", "𜷃", "𜷄", "𜷅", "𜷆", "𜷇", "𜷈", "𜷉", "𜷊", + "𜷋", "𜷌", "𜷍", "𜷎", "𜷏", "𜷐", "𜷑", "𜷒", "𜷓", "𜷔", "𜷕", "𜷖", "𜷗", "𜷘", "𜷙", "𜷚", + "▄", "𜷛", "𜷜", "𜷝", "𜷞", "▙", "𜷟", "𜷠", "𜷡", "𜷢", "▟", "𜷣", "▆", "𜷤", "𜷥", "█" +}; + +#define RMS_BIT (1 << 8) + +void cmc_draw_waveform(struct cmc_list_entry *entry, u32 width, u32 height, bool include_rms, char ***buf, u32 **rgba) +{ +#ifdef OUTPUT_WAVEFORM_PNG + test_write_bitmap(entry, width, height, rgba); + 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'; +// } +// */ +} diff --git a/src/fruits/cmc/ui/util/waveform.h b/src/fruits/cmc/ui/util/waveform.h new file mode 100644 index 0000000..763f54a --- /dev/null +++ b/src/fruits/cmc/ui/util/waveform.h @@ -0,0 +1,111 @@ +/* +notcurses.h: +Halves + +▄ ▀ + +Quadrants + +▗ ▐ ▖ ▀ ▟ ▌ ▙ + +Sextants + +🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 + +https://wiki.ourworldoftext.com/wiki/Octant: +Octants + + 𜺨 𜺫 🮂 𜴀 ▘ 𜴁 𜴂 𜴃 𜴄 ▝ 𜴅 𜴆 𜴇 𜴈 ▀ + +𜴉 𜴊 𜴋 𜴌 🯦 𜴍 𜴎 𜴏 𜴐 𜴑 𜴒 𜴓 𜴔 𜴕 𜴖 𜴗 + +𜴘 𜴙 𜴚 𜴛 𜴜 𜴝 𜴞 𜴟 🯧 𜴠 𜴡 𜴢 𜴣 𜴤 𜴥 𜴦 + +𜴧 𜴨 𜴩 𜴪 𜴫 𜴬 𜴭 𜴮 𜴯 𜴰 𜴱 𜴲 𜴳 𜴴 𜴵 🮅 + +𜺣 𜴶 𜴷 𜴸 𜴹 𜴺 𜴻 𜴼 𜴽 𜴾 𜴿 𜵀 𜵁 𜵂 𜵃 𜵄 + +▖ 𜵅 𜵆 𜵇 𜵈 ▌ 𜵉 𜵊 𜵋 𜵌 ▞ 𜵍 𜵎 𜵏 𜵐 ▛ + +𜵑 𜵒 𜵓 𜵔 𜵕 𜵖 𜵗 𜵘 𜵙 𜵚 𜵛 𜵜 𜵝 𜵞 𜵟 𜵠 + +𜵡 𜵢 𜵣 𜵤 𜵥 𜵦 𜵧 𜵨 𜵩 𜵪 𜵫 𜵬 𜵭 𜵮 𜵯 𜵰 + +𜺠 𜵱 𜵲 𜵳 𜵴 𜵵 𜵶 𜵷 𜵸 𜵹 𜵺 𜵻 𜵼 𜵽 𜵾 𜵿 + +𜶀 𜶁 𜶂 𜶃 𜶄 𜶅 𜶆 𜶇 𜶈 𜶉 𜶊 𜶋 𜶌 𜶍 𜶎 𜶏 + +▗ 𜶐 𜶑 𜶒 𜶓 ▚ 𜶔 𜶕 𜶖 𜶗 ▐ 𜶘 𜶙 𜶚 𜶛 ▜ + +𜶜 𜶝 𜶞 𜶟 𜶠 𜶡 𜶢 𜶣 𜶤 𜶥 𜶦 𜶧 𜶨 𜶩 𜶪 𜶫 + +▂ 𜶬 𜶭 𜶮 𜶯 𜶰 𜶱 𜶲 𜶳 𜶴 𜶵 𜶶 𜶷 𜶸 𜶹 𜶺 + +𜶻 𜶼 𜶽 𜶾 𜶿 𜷀 𜷁 𜷂 𜷃 𜷄 𜷅 𜷆 𜷇 𜷈 𜷉 𜷊 + +𜷋 𜷌 𜷍 𜷎 𜷏 𜷐 𜷑 𜷒 𜷓 𜷔 𜷕 𜷖 𜷗 𜷘 𜷙 𜷚 + +▄ 𜷛 𜷜 𜷝 𜷞 ▙ 𜷟 𜷠 𜷡 𜷢 ▟ 𜷣 ▆ 𜷤 𜷥 █ + +AAAAAAAAAAAAAAAA + +▁ ▂ 𜶻 𜷋 ▃ ▄ 𜷞 𜷡 ▅ ▆ 𜷤 𜷥 ▇ █ + + ▐█ + 𜷕█ + + ▌ + ▌ + ▌ + 𜷀 + +▂𜴧𜴐 + + + ▁▂𜷋▃▄𜷡▅▆𜷥▇████▇𜷥▆▅𜷡▄▃𜷋▂▁ +▁▂𜷋▃▄𜷡▅▆𜷥▇████████████████████████████▇𜷥▆▅𜷡▄▃𜷋▂▁ + + +█▇𜷤▆▅𜷞▄▃𜶻▂▁ + +▌𜵈▐𜷀 +▁▃▂▁▄ + +0 +1 𜺨 +1 << 1 𜺫 +1 << 2 𜴀 +1 << 3 𜴃 +1 << 4 𜴉 +1 << 5 𜴘 +1 << 6 𜺣 +1 << 7 𜺠 + +𜴢 0b00101011 + +𜺨𜺫🮂𜴀▘𜴁𜴂𜴃𜴄▝𜴅𜴆𜴇𜴈▀𜴉𜴊𜴋𜴌🯦𜴍𜴎𜴏𜴐𜴑𜴒𜴓𜴔𜴕𜴖𜴗𜴘𜴙𜴚𜴛𜴜𜴝𜴞𜴟🯧𜴠𜴡𜴢𜴣𜴤𜴥𜴦𜴧𜴨𜴩𜴪𜴫𜴬𜴭𜴮𜴯𜴰𜴱𜴲𜴳𜴴𜴵🮅𜺣𜴶𜴷𜴸𜴹𜴺𜴻𜴼𜴽𜴾𜴿𜵀𜵁𜵂𜵃𜵄▖𜵅𜵆𜵇𜵈▌𜵉𜵊𜵋𜵌▞𜵍𜵎𜵏𜵐▛𜵑𜵒𜵓𜵔𜵕𜵖𜵗𜵘𜵙𜵚𜵛𜵜𜵝𜵞𜵟𜵠𜵡𜵢𜵣𜵤𜵥𜵦𜵧𜵨𜵩𜵪𜵫𜵬𜵭𜵮𜵯𜵰𜺠𜵱𜵲𜵳𜵴𜵵𜵶𜵷𜵸𜵹𜵺𜵻𜵼𜵽𜵾𜵿𜶀𜶁𜶂𜶃𜶄𜶅𜶆𜶇𜶈𜶉𜶊𜶋𜶌𜶍𜶎𜶏▗𜶐𜶑𜶒𜶓▚𜶔𜶕𜶖𜶗▐𜶘𜶙𜶚𜶛▜𜶜𜶝𜶞𜶟𜶠𜶡𜶢𜶣𜶤𜶥𜶦𜶧𜶨𜶩𜶪𜶫▂𜶬𜶭𜶮𜶯𜶰𜶱𜶲𜶳𜶴𜶵𜶶𜶷𜶸𜶹𜶺𜶻𜶼𜶽𜶾𜶿𜷀𜷁𜷂𜷃𜷄𜷅𜷆𜷇𜷈𜷉𜷊𜷋𜷌𜷍𜷎𜷏𜷐𜷑𜷒𜷓𜷔𜷕𜷖𜷗𜷘𜷙𜷚▄𜷛𜷜𜷝𜷞▙𜷟𜷠𜷡𜷢▟𜷣▆𜷤𜷥█ +*/ + +#include + +#include "../../cmc.h" + +/* +static const char *bar_lut[] = { + [0] = " 𜺠▗𜶖▐" , + [1] = "𜺣▂𜷋𜷓𜷕", + [2] = "▖𜶻▄𜷡𜷥", + [3] = "𜵈𜶿𜷞▆𜷥", + [4] = "▌𜷀▙𜷤█" +}; + +static const char *bar_bottom_lut[] = { + [0] = " 𜺫▝𜴡▐" , + [1] = "𜺨🮂𜴅𜴢𜶘", + [2] = "▘𜴂▀𜴦▜", + [3] = "𜴍𜴏𜴗🮅𜶫", + [4] = "▌𜵊▛𜵰█" +}; +*/ + +void cmc_draw_waveform(struct cmc_list_entry *entry, u32 width, u32 height, bool include_rms, char ***buf, u32 **rgba); diff --git a/src/fruits/cmc/ui/waveform.c b/src/fruits/cmc/ui/waveform.c deleted file mode 100644 index 9bfc296..0000000 --- a/src/fruits/cmc/ui/waveform.c +++ /dev/null @@ -1,202 +0,0 @@ -#include -#include -#include "waveform.h" - -#define WITHIN_RMS(low, high, rms) \ - ((low >= 0 && low <= rms) || (high < 0 && high >= -rms) || (low < 0 && high > 0 && rms > 0)) - -#define OUTPUT_WAVEFORM_PNG -//#define WAVEFORM_REFERENCE_MODE -#ifdef OUTPUT_WAVEFORM_PNG -#include -#include -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include - -// min/max: #BC615E, #F4EAE0 -// rms: #FF928F, #E4CAAF -// -// min/max: #D93A50, #b22f41 -// rms: #f44f7a, #c84064 -// extra: #f6ac7f, #F5F4FA, #5b524b -// left channel: n == 0, right channel: n == 1. -#define COLOR_FOR_PIXEL(n, rms) rms ? \ - n == 0 ? nn_htonl(0xE4CAAF99) : nn_htonl(0xFF928FFF) : \ - n == 0 ? nn_htonl(0xF4EAE099) : nn_htonl(0xBC615EFF) - -// ss: current sample, ns: next sample. -#define HIT_WAVE(low, high, ss, ns) \ - ((ss <= low && ns > low) || (ss > high && ns <= high) || (ss > low && ns <= high)) - -static void test_write_bitmap(struct cmc_list_entry *entry, u32 width, u32 height, u32 **rgba) -{ - f64 start = nn_get_tick(); - - *rgba = al_calloc(width * height, 4); - - u32 channels = entry->raw.fmt.channel_count; - u32 samples_per_column = entry->raw.sample_count / width; - - s32 wave_min = INT16_MIN; - s32 wave_max = INT16_MAX; - s32 step = (wave_max * 2 + (height - 1)) / height; - //s32 step = (wave_max * 2 + (height / 2)) / height; - - s16 *steps = al_malloc(sizeof(s16) * (height + 1)); - for (s32 i = 0; i <= (s32)height; i++) { - steps[i] = (s16)MAX(wave_max - step * i, wave_min); - } - //steps[height] = wave_min; - al_assert(steps[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; - for (u32 i = 0; i < width; i++) { - u32 f = i * 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); -#ifndef WAVEFORM_REFERENCE_MODE - for (u32 j = 0; j < height; j++) { - low = steps[j + 1], high = steps[j]; - if (low < max && high >= min) { - (*rgba)[j * width + i] = COLOR_FOR_PIXEL(n, WITHIN_RMS(low, high, rms)); - } - } -#else - for (u32 m = 0; m < samples_stride; m += channels) { - s16 ss = data[f + m + n]; - s16 ns = data[f + m + n + channels]; - for (u32 j = 0; j < height; j++) { - low = steps[j + 1], high = steps[j]; - if (HIT_WAVE(low, high, ss, ns)) { - (*rgba)[j * width + i] = COLOR_FOR_PIXEL(n, WITHIN_RMS(low, high, rms)); - } - } - } -#endif - } - } - - log_info("generating waveform took: %.2fs.", nn_get_tick() - start); - - //stbi_write_png("./waveform.png", width, height, 4, *rgba, width * 4); - - /* - struct nn_file file; - nn_file_open(&file, &al_str_c("./raw_samples.raw"), NNWT_FILE_CREATE); - size_t bytes = entry->raw.sample_count * entry->raw.fmt.channel_count * camu_audio_format_bytes_per_sample(&entry->raw.fmt); - nn_file_write(&file, entry->raw.data, bytes); - nn_file_close(&file); - */ -} -#endif - -static const char *octant_lut[] = { - " ", "𜺨", "𜺫", "🮂", "𜴀", "▘", "𜴁", "𜴂", "𜴃", "𜴄", "▝", "𜴅", "𜴆", "𜴇", "𜴈", "▀", - "𜴉", "𜴊", "𜴋", "𜴌", "🯦", "𜴍", "𜴎", "𜴏", "𜴐", "𜴑", "𜴒", "𜴓", "𜴔", "𜴕", "𜴖", "𜴗", - "𜴘", "𜴙", "𜴚", "𜴛", "𜴜", "𜴝", "𜴞", "𜴟", "🯧", "𜴠", "𜴡", "𜴢", "𜴣", "𜴤", "𜴥", "𜴦", - "𜴧", "𜴨", "𜴩", "𜴪", "𜴫", "𜴬", "𜴭", "𜴮", "𜴯", "𜴰", "𜴱", "𜴲", "𜴳", "𜴴", "𜴵", "🮅", - "𜺣", "𜴶", "𜴷", "𜴸", "𜴹", "𜴺", "𜴻", "𜴼", "𜴽", "𜴾", "𜴿", "𜵀", "𜵁", "𜵂", "𜵃", "𜵄", - "▖", "𜵅", "𜵆", "𜵇", "𜵈", "▌", "𜵉", "𜵊", "𜵋", "𜵌", "▞", "𜵍", "𜵎", "𜵏", "𜵐", "▛", - "𜵑", "𜵒", "𜵓", "𜵔", "𜵕", "𜵖", "𜵗", "𜵘", "𜵙", "𜵚", "𜵛", "𜵜", "𜵝", "𜵞", "𜵟", "𜵠", - "𜵡", "𜵢", "𜵣", "𜵤", "𜵥", "𜵦", "𜵧", "𜵨", "𜵩", "𜵪", "𜵫", "𜵬", "𜵭", "𜵮", "𜵯", "𜵰", - "𜺠", "𜵱", "𜵲", "𜵳", "𜵴", "𜵵", "𜵶", "𜵷", "𜵸", "𜵹", "𜵺", "𜵻", "𜵼", "𜵽", "𜵾", "𜵿", - "𜶀", "𜶁", "𜶂", "𜶃", "𜶄", "𜶅", "𜶆", "𜶇", "𜶈", "𜶉", "𜶊", "𜶋", "𜶌", "𜶍", "𜶎", "𜶏", - "▗", "𜶐", "𜶑", "𜶒", "𜶓", "▚", "𜶔", "𜶕", "𜶖", "𜶗", "▐", "𜶘", "𜶙", "𜶚", "𜶛", "▜", - "𜶜", "𜶝", "𜶞", "𜶟", "𜶠", "𜶡", "𜶢", "𜶣", "𜶤", "𜶥", "𜶦", "𜶧", "𜶨", "𜶩", "𜶪", "𜶫", - "▂", "𜶬", "𜶭", "𜶮", "𜶯", "𜶰", "𜶱", "𜶲", "𜶳", "𜶴", "𜶵", "𜶶", "𜶷", "𜶸", "𜶹", "𜶺", - "𜶻", "𜶼", "𜶽", "𜶾", "𜶿", "𜷀", "𜷁", "𜷂", "𜷃", "𜷄", "𜷅", "𜷆", "𜷇", "𜷈", "𜷉", "𜷊", - "𜷋", "𜷌", "𜷍", "𜷎", "𜷏", "𜷐", "𜷑", "𜷒", "𜷓", "𜷔", "𜷕", "𜷖", "𜷗", "𜷘", "𜷙", "𜷚", - "▄", "𜷛", "𜷜", "𜷝", "𜷞", "▙", "𜷟", "𜷠", "𜷡", "𜷢", "▟", "𜷣", "▆", "𜷤", "𜷥", "█" -}; - -#define RMS_BIT (1 << 8) - -void cmc_draw_waveform(struct cmc_list_entry *entry, u32 width, u32 height, bool include_rms, char ***buf, u32 **rgba) -{ -#ifdef OUTPUT_WAVEFORM_PNG - test_write_bitmap(entry, width, height, rgba); - 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'; - } - */ -} diff --git a/src/fruits/cmc/ui/waveform.h b/src/fruits/cmc/ui/waveform.h deleted file mode 100644 index 464d589..0000000 --- a/src/fruits/cmc/ui/waveform.h +++ /dev/null @@ -1,111 +0,0 @@ -/* -notcurses.h: -Halves - -▄ ▀ - -Quadrants - -▗ ▐ ▖ ▀ ▟ ▌ ▙ - -Sextants - -🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 - -https://wiki.ourworldoftext.com/wiki/Octant: -Octants - - 𜺨 𜺫 🮂 𜴀 ▘ 𜴁 𜴂 𜴃 𜴄 ▝ 𜴅 𜴆 𜴇 𜴈 ▀ - -𜴉 𜴊 𜴋 𜴌 🯦 𜴍 𜴎 𜴏 𜴐 𜴑 𜴒 𜴓 𜴔 𜴕 𜴖 𜴗 - -𜴘 𜴙 𜴚 𜴛 𜴜 𜴝 𜴞 𜴟 🯧 𜴠 𜴡 𜴢 𜴣 𜴤 𜴥 𜴦 - -𜴧 𜴨 𜴩 𜴪 𜴫 𜴬 𜴭 𜴮 𜴯 𜴰 𜴱 𜴲 𜴳 𜴴 𜴵 🮅 - -𜺣 𜴶 𜴷 𜴸 𜴹 𜴺 𜴻 𜴼 𜴽 𜴾 𜴿 𜵀 𜵁 𜵂 𜵃 𜵄 - -▖ 𜵅 𜵆 𜵇 𜵈 ▌ 𜵉 𜵊 𜵋 𜵌 ▞ 𜵍 𜵎 𜵏 𜵐 ▛ - -𜵑 𜵒 𜵓 𜵔 𜵕 𜵖 𜵗 𜵘 𜵙 𜵚 𜵛 𜵜 𜵝 𜵞 𜵟 𜵠 - -𜵡 𜵢 𜵣 𜵤 𜵥 𜵦 𜵧 𜵨 𜵩 𜵪 𜵫 𜵬 𜵭 𜵮 𜵯 𜵰 - -𜺠 𜵱 𜵲 𜵳 𜵴 𜵵 𜵶 𜵷 𜵸 𜵹 𜵺 𜵻 𜵼 𜵽 𜵾 𜵿 - -𜶀 𜶁 𜶂 𜶃 𜶄 𜶅 𜶆 𜶇 𜶈 𜶉 𜶊 𜶋 𜶌 𜶍 𜶎 𜶏 - -▗ 𜶐 𜶑 𜶒 𜶓 ▚ 𜶔 𜶕 𜶖 𜶗 ▐ 𜶘 𜶙 𜶚 𜶛 ▜ - -𜶜 𜶝 𜶞 𜶟 𜶠 𜶡 𜶢 𜶣 𜶤 𜶥 𜶦 𜶧 𜶨 𜶩 𜶪 𜶫 - -▂ 𜶬 𜶭 𜶮 𜶯 𜶰 𜶱 𜶲 𜶳 𜶴 𜶵 𜶶 𜶷 𜶸 𜶹 𜶺 - -𜶻 𜶼 𜶽 𜶾 𜶿 𜷀 𜷁 𜷂 𜷃 𜷄 𜷅 𜷆 𜷇 𜷈 𜷉 𜷊 - -𜷋 𜷌 𜷍 𜷎 𜷏 𜷐 𜷑 𜷒 𜷓 𜷔 𜷕 𜷖 𜷗 𜷘 𜷙 𜷚 - -▄ 𜷛 𜷜 𜷝 𜷞 ▙ 𜷟 𜷠 𜷡 𜷢 ▟ 𜷣 ▆ 𜷤 𜷥 █ - -AAAAAAAAAAAAAAAA - -▁ ▂ 𜶻 𜷋 ▃ ▄ 𜷞 𜷡 ▅ ▆ 𜷤 𜷥 ▇ █ - - ▐█ - 𜷕█ - - ▌ - ▌ - ▌ - 𜷀 - -▂𜴧𜴐 - - - ▁▂𜷋▃▄𜷡▅▆𜷥▇████▇𜷥▆▅𜷡▄▃𜷋▂▁ -▁▂𜷋▃▄𜷡▅▆𜷥▇████████████████████████████▇𜷥▆▅𜷡▄▃𜷋▂▁ - - -█▇𜷤▆▅𜷞▄▃𜶻▂▁ - -▌𜵈▐𜷀 -▁▃▂▁▄ - -0 -1 𜺨 -1 << 1 𜺫 -1 << 2 𜴀 -1 << 3 𜴃 -1 << 4 𜴉 -1 << 5 𜴘 -1 << 6 𜺣 -1 << 7 𜺠 - -𜴢 0b00101011 - -𜺨𜺫🮂𜴀▘𜴁𜴂𜴃𜴄▝𜴅𜴆𜴇𜴈▀𜴉𜴊𜴋𜴌🯦𜴍𜴎𜴏𜴐𜴑𜴒𜴓𜴔𜴕𜴖𜴗𜴘𜴙𜴚𜴛𜴜𜴝𜴞𜴟🯧𜴠𜴡𜴢𜴣𜴤𜴥𜴦𜴧𜴨𜴩𜴪𜴫𜴬𜴭𜴮𜴯𜴰𜴱𜴲𜴳𜴴𜴵🮅𜺣𜴶𜴷𜴸𜴹𜴺𜴻𜴼𜴽𜴾𜴿𜵀𜵁𜵂𜵃𜵄▖𜵅𜵆𜵇𜵈▌𜵉𜵊𜵋𜵌▞𜵍𜵎𜵏𜵐▛𜵑𜵒𜵓𜵔𜵕𜵖𜵗𜵘𜵙𜵚𜵛𜵜𜵝𜵞𜵟𜵠𜵡𜵢𜵣𜵤𜵥𜵦𜵧𜵨𜵩𜵪𜵫𜵬𜵭𜵮𜵯𜵰𜺠𜵱𜵲𜵳𜵴𜵵𜵶𜵷𜵸𜵹𜵺𜵻𜵼𜵽𜵾𜵿𜶀𜶁𜶂𜶃𜶄𜶅𜶆𜶇𜶈𜶉𜶊𜶋𜶌𜶍𜶎𜶏▗𜶐𜶑𜶒𜶓▚𜶔𜶕𜶖𜶗▐𜶘𜶙𜶚𜶛▜𜶜𜶝𜶞𜶟𜶠𜶡𜶢𜶣𜶤𜶥𜶦𜶧𜶨𜶩𜶪𜶫▂𜶬𜶭𜶮𜶯𜶰𜶱𜶲𜶳𜶴𜶵𜶶𜶷𜶸𜶹𜶺𜶻𜶼𜶽𜶾𜶿𜷀𜷁𜷂𜷃𜷄𜷅𜷆𜷇𜷈𜷉𜷊𜷋𜷌𜷍𜷎𜷏𜷐𜷑𜷒𜷓𜷔𜷕𜷖𜷗𜷘𜷙𜷚▄𜷛𜷜𜷝𜷞▙𜷟𜷠𜷡𜷢▟𜷣▆𜷤𜷥█ -*/ - -#include - -#include "../cmc.h" - -/* -static const char *bar_lut[] = { - [0] = " 𜺠▗𜶖▐" , - [1] = "𜺣▂𜷋𜷓𜷕", - [2] = "▖𜶻▄𜷡𜷥", - [3] = "𜵈𜶿𜷞▆𜷥", - [4] = "▌𜷀▙𜷤█" -}; - -static const char *bar_bottom_lut[] = { - [0] = " 𜺫▝𜴡▐" , - [1] = "𜺨🮂𜴅𜴢𜶘", - [2] = "▘𜴂▀𜴦▜", - [3] = "𜴍𜴏𜴗🮅𜶫", - [4] = "▌𜵊▛𜵰█" -}; -*/ - -void cmc_draw_waveform(struct cmc_list_entry *entry, u32 width, u32 height, bool include_rms, char ***buf, u32 **rgba); 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 + +#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 +#include + +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/widgets/tile.h b/src/fruits/cmc/ui/widgets/tile.h new file mode 100644 index 0000000..6f15c7d --- /dev/null +++ b/src/fruits/cmc/ui/widgets/tile.h @@ -0,0 +1,5 @@ +#pragma once + +struct cmc_tile { + +}; -- cgit v1.2.3-101-g0448