#include #include #include "util/notcurses_ext.h" #include "ui.h" void cmc_ui_putnwstr_yx(struct ncplane *n, u32 y, u32 x, u32 width, wstr *w) { ncplane_cursor_move_yx(n, y, x); u32 end = MIN(w->length, width); for (u32 i = 0; i < end; i++) { ncplane_putwc(n, al_wstr_at(w, i)); } } void cmc_ui_putnstr_yx(struct ncplane *n, u32 y, u32 x, u32 width, str *s) { ncplane_cursor_move_yx(n, y, x); u32 end = MIN(s->length, width); for (u32 i = 0; i < end; i++) { ncplane_putchar(n, al_str_at(s, i)); } } bool cmc_ui_consider_input(struct ncinput *input, bool repeat) { return (input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN || (repeat && input->evtype == NCTYPE_REPEAT)); } static bool read_input(struct cmc_ui *ui, struct ncinput *input) { al_memset(input, 0, sizeof(struct ncinput)); u32 ret = notcurses_get_nblock(ui->nc, input); return !(ret == (u32)-1 || ret == 0); } static void erase_previous_pane(struct cmc_ui *ui, u8 previous, u8 upcoming) { switch (previous) { case CMC_PANE_LIST: cmc_list_pane_clear(&ui->lists); break; case CMC_PANE_LOG: cmc_log_pane_clear(&ui->log); break; } switch (upcoming) { case CMC_PANE_LIST: ncplane_move_family_top(ui->lists.n); break; case CMC_PANE_LOG: ncplane_move_family_top(ui->log.n); break; } } static bool switch_to_pane(struct cmc_ui *ui, u8 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); if (ui->overlay_shown) ncplane_erase(ui->oln); switch (ui->pane) { case CMC_PANE_LIST: cmc_list_pane_render(&ui->lists); break; case CMC_PANE_LOG: cmc_log_pane_render(&ui->log); break; } if (ui->overlay_shown) { 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); } notcurses_render(ui->nc); } void cmc_ui_toggle_overlay(struct cmc_ui *ui) { ui->overlay_shown = !ui->overlay_shown; if (ui->overlay_shown) { ncplane_move_top(ui->oln); } else { ncplane_erase(ui->oln); ncplane_move_bottom(ui->oln); } } static void input_poll_callback(void *userdata, s32 revents) { struct cmc_ui *ui = (struct cmc_ui *)userdata; (void)revents; bool do_render; struct ncinput input; while (read_input(ui, &input)) { do_render = false; if (cmc_ui_consider_input(&input, false)) { switch (input.id) { case 'q': nn_event_loop_break_one(ui->loop); break; case '1': do_render |= switch_to_pane(ui, CMC_PANE_LIST); break; case '2': break; case '3': do_render |= switch_to_pane(ui, CMC_PANE_SEARCH); break; case '4': do_render |= switch_to_pane(ui, CMC_PANE_LOG); break; case NCKEY_TAB: cmc_ui_toggle_overlay(ui); break; } } switch (ui->pane) { case CMC_PANE_LIST: do_render |= cmc_list_pane_handle_input(&ui->lists, &input); break; case CMC_PANE_LOG: do_render |= cmc_log_pane_handle_input(&ui->log, &input); break; } if (do_render) { cmc_ui_queue_render(ui); } } } static void render_timer_callback(void *userdata, struct nn_timer *timer) { struct cmc_ui *ui = (struct cmc_ui *)userdata; (void)timer; 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, struct ncplane *stdplane) { 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; nopts.y = 1; nopts.margin_r = 2; nopts.margin_b = 1; nopts.flags = NCPLANE_OPTION_MARGINALIZED; ui->n = ncplane_create(stdplane, &nopts); 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 = 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_bottom(ui->oln); return layed_out; } static s32 resize_cb(struct ncplane *p) { struct cmc_ui *ui = (struct cmc_ui *)ncplane_userptr(p); ui->layed_out = relayout(ui, notcurses_stdplane(ui->nc)); if (ui->layed_out) { cmc_ui_queue_render(ui); } return 0; } static s32 log_callback(void *userdata, u8 level, char *message) { struct cmc_ui *ui = (struct cmc_ui *)userdata; (void)level; cmc_log_pane_push_message(&ui->log, message); return al_strnlen(message, AL_LOG_MESSAGE_SIZE); } bool cmc_ui_init(struct cmc_ui *ui, struct nn_event_loop *loop, struct cmc *c) { al_memset(ui, 0, sizeof(struct cmc_ui)); ui->c = c; struct notcurses_options opts = { 0 }; opts.flags = NCOPTION_INHIBIT_SETLOCALE; if (!(ui->nc = notcurses_init(&opts, stdin))) { return false; } notcurses_mice_enable(ui->nc, NCKEY_BUTTON1); ui->loop = loop; cmc_list_pane_init(&ui->lists, ui); al_set_print(log_callback, ui); cmc_log_pane_init(&ui->log); ui->pane = CMC_PANE_SEARCH; ui->overlay_shown = false; ui->last_mouse_x = 0; ui->last_mouse_y = 0; ui->last_mouse_ts = (u64)-1; 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); nn_timer_init(&ui->render_timer, ui->loop, render_timer_callback, ui); nn_timer_set_repeat(&ui->render_timer, NNWT_TS_FROM_USEC(200000)); nn_timer_again(&ui->render_timer); return true; } void cmc_ui_close(struct cmc_ui *ui) { if (ui->nc) notcurses_stop(ui->nc); }