diff options
Diffstat (limited to 'src/draw.c')
| -rw-r--r-- | src/draw.c | 456 |
1 files changed, 0 insertions, 456 deletions
diff --git a/src/draw.c b/src/draw.c deleted file mode 100644 index 31188c7..0000000 --- a/src/draw.c +++ /dev/null @@ -1,456 +0,0 @@ -#include "vega.h" -#include <stdlib.h> -#include <string.h> - -static const char *const tone_names[VEGA_TONES] = {"normal", "muted", "accent", "good", "warning", "danger"}; - -/* ---- CLI ------------------------------------------------------------ */ - -/* Text for a pipe: control characters become their pictures so a value never - * breaks a line. */ -static void put_text(FILE *out, const char *text, size_t length) { - for (size_t at = 0; at < length;) { - uint32_t codepoint; - size_t size = vega_decode(text, length, at, &codepoint); - if (vega_visible(codepoint) == codepoint) fwrite(text + at, 1, size, out); - else { - uint32_t picture = vega_visible(codepoint); - fputc(0xE0 | (int)(picture >> 12), out); - fputc(0x80 | (int)(picture >> 6 & 0x3F), out); - fputc(0x80 | (int)(picture & 0x3F), out); - } - at += size; - } -} - -static void pad(FILE *out, unsigned count) { - while (count--) fputc(' ', out); -} - -static const char *const colors[VEGA_TONES] = {"", "\033[2m", "\033[1;36m", "\033[32m", "\033[33m", "\033[31m"}; - -static void tone_start(FILE *out, VegaTone tone, bool color) { - if (color && tone) fputs(colors[tone], out); -} - -static void tone_end(FILE *out, VegaTone tone, bool color) { - if (color && tone) fputs("\033[0m", out); - else if (!color && tone) fprintf(out, " (%s)", tone_names[tone]); -} - -static void print_list(FILE *out, PureApp *app, const VegaPart *part, bool color) { - char text[VEGA_TEXT]; - size_t length, rows = vega_rows(app, part), focus = vega_focus(app, part); - for (size_t row = 0; row < rows; ++row) { - VegaTone tone = vega_tone(app, part, row); - fputs(row == focus ? "> " : " ", out); - tone_start(out, tone, color); - if (vega_text(app, (uint32_t)part->label, row, text, &length)) put_text(out, text, length); - if (part->badge >= 0 && vega_text(app, (uint32_t)part->badge, row, text, &length) && length) { - fputs(" [", out); - put_text(out, text, length); - fputc(']', out); - } - if (part->detail >= 0 && vega_text(app, (uint32_t)part->detail, row, text, &length) && length) { - fputs(" - ", out); - put_text(out, text, length); - } - tone_end(out, tone, color); - fputc('\n', out); - } -} - -static void print_table(FILE *out, PureApp *app, const VegaPart *part, bool color) { - const PureModule *m = app->module; - size_t rows = vega_rows(app, part), focus = vega_focus(app, part); - unsigned widths[PURE_MODULE_VIEWS]; - char text[VEGA_TEXT]; - size_t length; - for (unsigned c = 0; c < part->column_count; ++c) { - const char *name = m->view_names[part->columns[c]]; - widths[c] = vega_text_width(name, strlen(name)); - for (size_t row = 0; row < rows; ++row) - if (vega_text(app, part->columns[c], row, text, &length)) { - unsigned width = vega_text_width(text, length); - if (width > widths[c]) widths[c] = width; - } - } - fputs(" ", out); - for (unsigned c = 0; c < part->column_count; ++c) { - const char *name = m->view_names[part->columns[c]]; - fputs(name, out); - if (c + 1 < part->column_count) pad(out, widths[c] - (unsigned)strlen(name) + 2); - } - fputc('\n', out); - for (size_t row = 0; row < rows; ++row) { - VegaTone tone = vega_tone(app, part, row); - fputs(row == focus ? "> " : " ", out); - tone_start(out, tone, color); - for (unsigned c = 0; c < part->column_count; ++c) { - bool ok = vega_text(app, part->columns[c], row, text, &length); - if (!ok) { memcpy(text, "-", 2); length = 1; } - put_text(out, text, length); - if (c + 1 < part->column_count) pad(out, widths[c] - vega_text_width(text, length) + 2); - } - tone_end(out, tone, color); - fputc('\n', out); - } -} - -void vega_print(FILE *out, PureApp *app, const VegaScreen *screen, bool color) { - const PureModule *m = app->module; - char text[VEGA_TEXT]; - size_t length; - if (screen->title >= 0 && vega_text(app, (uint32_t)screen->title, 0, text, &length)) { - if (color) fputs("\033[1m", out); - put_text(out, text, length); - if (color) fputs("\033[0m", out); - fputc('\n', out); - } - for (unsigned i = 0; i < screen->count; ++i) { - const VegaPart *part = &screen->parts[i]; - const char *name = m->view_names[part->view]; - if (part->kind == VEGA_FIELD) { - fprintf(out, "%s: ", name); - if (vega_text(app, part->view, 0, text, &length)) put_text(out, text, length); - else fputs("-", out); - fputc('\n', out); - continue; - } - fprintf(out, "%s (%zu)\n", name, vega_rows(app, part)); - if (part->kind == VEGA_LIST) print_list(out, app, part, color); - else print_table(out, app, part, color); - } -} - -/* ---- NUI ------------------------------------------------------------ */ - -/* Spoken text: control characters become spaces, and trailing ones go. */ -static void speak(FILE *out, const char *text, size_t length) { - while (length && ((unsigned char)text[length - 1] <= 0x20)) --length; - for (size_t i = 0; i < length; ++i) fputc((unsigned char)text[i] < 0x20 ? ' ' : text[i], out); -} - -/* "Item 2 of 3: milk, done." Tones other than normal and muted are said. */ -static void say_focus(FILE *out, PureApp *app, const VegaPart *part) { - const PureModule *m = app->module; - char text[VEGA_TEXT]; - size_t length, rows = vega_rows(app, part); - if (!rows) { fprintf(out, "%s is empty.\n", m->view_names[part->view]); return; } - size_t focus = vega_focus(app, part); - if (focus >= rows) { fprintf(out, "%s: none of %zu focused.\n", m->view_names[part->view], rows); return; } - fprintf(out, "Item %zu of %zu: ", focus + 1, rows); - VegaTone tone = vega_tone(app, part, focus); - if (tone >= VEGA_ACCENT) fprintf(out, "%s, ", tone_names[tone]); - if (part->kind == VEGA_LIST) { - if (vega_text(app, (uint32_t)part->label, focus, text, &length)) speak(out, text, length); - int more[] = {part->badge, part->detail}; - for (unsigned k = 0; k < 2; ++k) - if (more[k] >= 0 && vega_text(app, (uint32_t)more[k], focus, text, &length) && length) { - fputs(", ", out); - speak(out, text, length); - } - } else { - for (unsigned c = 0; c < part->column_count; ++c) { - fprintf(out, "%s%s ", c ? ", " : "", m->view_names[part->columns[c]]); - if (vega_text(app, part->columns[c], focus, text, &length)) speak(out, text, length); - else fputs("none", out); - } - } - fputs(".\n", out); -} - -void vega_say(FILE *out, PureApp *app, const VegaScreen *screen, bool all) { - const PureModule *m = app->module; - char text[VEGA_TEXT]; - size_t length; - if (!all) { - /* The focused item where a list shows it, else the first list. */ - const VegaPart *first = NULL; - for (unsigned i = 0; i < screen->count; ++i) { - const VegaPart *part = &screen->parts[i]; - if (part->kind == VEGA_FIELD) continue; - if (!first) first = part; - if (vega_focus(app, part) < vega_rows(app, part)) { say_focus(out, app, part); return; } - } - if (first) say_focus(out, app, first); - return; - } - if (screen->title >= 0 && vega_text(app, (uint32_t)screen->title, 0, text, &length)) { - speak(out, text, length); - fputs(".\n", out); - } - for (unsigned i = 0; i < screen->count; ++i) { - const VegaPart *part = &screen->parts[i]; - const char *name = m->view_names[part->view]; - if (part->kind == VEGA_FIELD) { - fprintf(out, "%s: ", name); - if (vega_text(app, part->view, 0, text, &length)) speak(out, text, length); - else fputs("none", out); - fputs(".\n", out); - continue; - } - size_t rows = vega_rows(app, part); - fprintf(out, "%s, %zu item%s.\n", name, rows, rows == 1 ? "" : "s"); - if (rows) say_focus(out, app, part); - } -} - -/* ---- TUI ------------------------------------------------------------ */ - -bool vega_grid(VegaGrid *grid, unsigned width, unsigned height) { - size_t count = (size_t)width * height; - VegaCell *cells = calloc(count ? count : 1, sizeof(*cells)), *shown = calloc(count ? count : 1, sizeof(*shown)); - struct VegaRow *rows = calloc(height ? height : 1, sizeof(*rows)); - if (!cells || !shown || !rows) { free(cells); free(shown); free(rows); return false; } - for (unsigned r = 0; r < height; ++r) rows[r].part = -1; - free(grid->cells); - free(grid->shown); - free(grid->rows); - grid->cells = cells; - grid->shown = shown; - grid->rows = rows; - grid->painted = false; - grid->width = width; - grid->height = height; - return true; -} - -void vega_grid_free(VegaGrid *grid) { - free(grid->cells); - free(grid->shown); - free(grid->rows); - *grid = (VegaGrid){0}; -} - -typedef struct { VegaGrid *grid; unsigned row, column, end; uint8_t tone; bool focus, strong; } Pen; - -/* Write text from the pen's column up to its end; a wide character that - * does not fit is left out, and a mark joins the character before it. */ -static void write_text(Pen *pen, const char *text, size_t length) { - VegaCell *line = pen->grid->cells + (size_t)pen->row * pen->grid->width; - for (size_t at = 0; at < length;) { - uint32_t codepoint; - at += vega_decode(text, length, at, &codepoint); - codepoint = vega_visible(codepoint); - unsigned width = vega_width(codepoint); - if (!width) { - if (pen->column && !line[pen->column - 1].mark) { - unsigned base = pen->column - 1; - while (base && !line[base].ch) --base; - line[base].mark = codepoint; - } - continue; - } - if (pen->column + width > pen->end) { pen->column = pen->end; return; } - line[pen->column] = (VegaCell){codepoint, 0, pen->tone, pen->focus, pen->strong}; - if (width == 2) line[pen->column + 1] = (VegaCell){0, 0, pen->tone, pen->focus, pen->strong}; - pen->column += width; - } -} - -static void write_cstr(Pen *pen, const char *text) { write_text(pen, text, strlen(text)); } - -/* Fill the rest of the pen's span with spaces in its style (a focused row - * is highlighted across). */ -static void fill(Pen *pen) { - VegaCell *line = pen->grid->cells + (size_t)pen->row * pen->grid->width; - while (pen->column < pen->end) line[pen->column++] = (VegaCell){' ', 0, pen->tone, pen->focus, pen->strong}; -} - -/* Text cut to width with an ellipsis. */ -static void write_fit(Pen *pen, const char *text, size_t length, unsigned width) { - unsigned end = pen->end; - if (pen->column + width < end) pen->end = pen->column + width; - if (vega_text_width(text, length) > pen->end - pen->column && pen->end > pen->column) { - --pen->end; - write_text(pen, text, length); - pen->end = end; - write_cstr(pen, "\xe2\x80\xa6"); - } else { - write_text(pen, text, length); - pen->end = end; - } -} - -static const char *key_symbol(const char *key) { - static const struct { const char *key, *symbol; } symbols[] = { - {"ArrowUp", "\xe2\x86\x91"}, {"ArrowDown", "\xe2\x86\x93"}, {"ArrowLeft", "\xe2\x86\x90"}, - {"ArrowRight", "\xe2\x86\x92"}, {"Space", "Space"}, {"Enter", "Enter"}, {"Backspace", "Bksp"}, - {"Delete", "Del"}, {"Escape", "Esc"}, {"PageUp", "PgUp"}, {"PageDown", "PgDn"}}; - for (size_t i = 0; i < sizeof(symbols) / sizeof(*symbols); ++i) - if (!strcmp(symbols[i].key, key)) return symbols[i].symbol; - if (!strncmp(key, "Key", 3)) return key + 3; - if (!strncmp(key, "Digit", 5)) return key + 5; - return key; -} - -static void draw_list(VegaGrid *grid, PureApp *app, const VegaPart *part, int index, size_t first, unsigned top, - unsigned height) { - char text[VEGA_TEXT]; - size_t length, rows = vega_rows(app, part), focus = vega_focus(app, part); - for (unsigned r = 0; r < height && first + r < rows; ++r) { - size_t row = first + r; - grid->rows[top + r] = (struct VegaRow){index, row}; - bool focused = row == focus; - VegaTone tone = vega_tone(app, part, row); - Pen pen = {grid, top + r, 0, grid->width, (uint8_t)tone, focused, false}; - write_cstr(&pen, focused ? "> " : " "); - char badge[VEGA_TEXT]; - size_t badge_length = 0; - if (part->badge >= 0 && !vega_text(app, (uint32_t)part->badge, row, badge, &badge_length)) badge_length = 0; - unsigned badge_width = badge_length ? vega_text_width(badge, badge_length) + 2 : 0; - unsigned room = grid->width > pen.column + badge_width ? grid->width - pen.column - badge_width : 0; - if (vega_text(app, (uint32_t)part->label, row, text, &length)) write_fit(&pen, text, length, room); - if (part->detail >= 0 && vega_text(app, (uint32_t)part->detail, row, text, &length) && length) { - unsigned used = pen.column; - uint8_t saved = pen.tone; - pen.tone = pen.tone ? pen.tone : VEGA_MUTED; - write_cstr(&pen, " "); - unsigned left = grid->width > badge_width + pen.column ? grid->width - badge_width - pen.column : 0; - if (pen.column - used == 2) write_fit(&pen, text, length, left); - pen.tone = saved; - } - if (badge_length && grid->width >= badge_width) { - /* The badge ends at the last column, two cells after the label at least. */ - unsigned start = grid->width - (badge_width - 2); - if (pen.column < start) fill(&(Pen){grid, pen.row, pen.column, start, pen.tone, focused, false}); - pen.column = start > pen.column ? start : pen.column; - write_fit(&pen, badge, badge_length, badge_width - 2); - } - fill(&pen); - } -} - -static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, int index, size_t first, unsigned top, - unsigned height) { - const PureModule *m = app->module; - char text[VEGA_TEXT]; - size_t length, rows = vega_rows(app, part), focus = vega_focus(app, part); - unsigned visible = height ? height - 1 : 0, widths[PURE_MODULE_VIEWS], total = 2; - /* Columns as wide as their header and the rows on screen, shared out when too wide. */ - for (unsigned c = 0; c < part->column_count; ++c) { - const char *name = m->view_names[part->columns[c]]; - widths[c] = vega_text_width(name, strlen(name)); - for (unsigned r = 0; r < visible && first + r < rows; ++r) - if (vega_text(app, part->columns[c], first + r, text, &length)) { - unsigned width = vega_text_width(text, length); - if (width > widths[c]) widths[c] = width; - } - total += widths[c] + 2; - } - if (part->column_count && total > grid->width) { - unsigned share = grid->width > 2 + 2 * part->column_count ? (grid->width - 2) / part->column_count - 2 : 1; - for (unsigned c = 0; c < part->column_count; ++c) if (widths[c] > share) widths[c] = share; - } - if (!height) return; - Pen head = {grid, top, 2, grid->width, VEGA_MUTED, false, true}; - for (unsigned c = 0; c < part->column_count; ++c) { - const char *name = m->view_names[part->columns[c]]; - unsigned start = head.column; - write_fit(&head, name, strlen(name), widths[c]); - head.column = start + widths[c] + 2 < grid->width ? start + widths[c] + 2 : grid->width; - } - for (unsigned r = 0; r < visible && first + r < rows; ++r) { - size_t row = first + r; - bool focused = row == focus; - grid->rows[top + 1 + r] = (struct VegaRow){index, row}; - Pen pen = {grid, top + 1 + r, 0, grid->width, (uint8_t)vega_tone(app, part, row), focused, false}; - write_cstr(&pen, focused ? "> " : " "); - for (unsigned c = 0; c < part->column_count; ++c) { - unsigned start = pen.column, end = start + widths[c] + 2 < grid->width ? start + widths[c] + 2 : grid->width; - if (vega_text(app, part->columns[c], row, text, &length)) write_fit(&pen, text, length, widths[c]); - else write_cstr(&pen, "-"); - if (pen.column < end) fill(&(Pen){grid, pen.row, pen.column, end, pen.tone, focused, false}); - pen.column = end; - } - fill(&pen); - } -} - -bool vega_hit(const VegaGrid *grid, unsigned line, unsigned *part, size_t *item) { - if (line >= grid->height || grid->rows[line].part < 0) return false; - *part = (unsigned)grid->rows[line].part; - *item = grid->rows[line].item; - return true; -} - -void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const char *prompt, size_t prompt_length, - bool typing) { - const PureModule *m = app->module; - for (size_t i = 0; i < (size_t)grid->width * grid->height; ++i) grid->cells[i] = (VegaCell){' ', 0, 0, false, false}; - for (unsigned r = 0; r < grid->height; ++r) grid->rows[r].part = -1; - grid->cursor_row = grid->cursor_column = -1; - if (!grid->width || !grid->height) return; - char text[VEGA_TEXT]; - size_t length; - unsigned row = 0, bottom = grid->height - 1 - (screen->prompt >= 0 && grid->height > 1); - if (screen->title >= 0 && row < bottom && vega_text(app, (uint32_t)screen->title, 0, text, &length)) { - Pen pen = {grid, row++, 0, grid->width, VEGA_NORMAL, false, true}; - write_fit(&pen, text, length, grid->width); - } - unsigned groups = 0; - for (unsigned i = 0; i < screen->count; ++i) { - const VegaPart *part = &screen->parts[i]; - if (part->kind != VEGA_FIELD) { ++groups; continue; } - if (row >= bottom) continue; - Pen pen = {grid, row++, 0, grid->width, VEGA_MUTED, false, false}; - write_cstr(&pen, m->view_names[part->view]); - write_cstr(&pen, " "); - pen.tone = VEGA_NORMAL; - if (vega_text(app, part->view, 0, text, &length)) write_fit(&pen, text, length, grid->width); - else { pen.tone = VEGA_MUTED; write_cstr(&pen, "-"); } - } - /* Lists and tables share the rows left; each keeps its focused row in view - * and only its visible rows are evaluated. */ - unsigned left = bottom > row ? bottom - row : 0; - for (unsigned i = 0, group = 0; i < screen->count; ++i) { - const VegaPart *part = &screen->parts[i]; - if (part->kind == VEGA_FIELD) continue; - unsigned share = left / groups + (group < left % groups); - ++group; - if (!share) continue; - size_t rows = vega_rows(app, part), focus = vega_focus(app, part); - Pen head = {grid, row, 0, grid->width, VEGA_NORMAL, false, true}; - write_cstr(&head, m->view_names[part->view]); - char count[32]; - snprintf(count, sizeof(count), " (%zu)", rows); - head.strong = false; - head.tone = VEGA_MUTED; - write_cstr(&head, count); - unsigned area = share - 1, visible = part->kind == VEGA_TABLE && area ? area - 1 : area; - size_t *first = &grid->first[i]; - *first = vega_scroll(*first, focus, visible, rows); - if (part->kind == VEGA_LIST) draw_list(grid, app, part, (int)i, *first, row + 1, area); - else draw_table(grid, app, part, (int)i, *first, row + 1, area); - row += share; - } - /* Keys the app answers to, then the text being typed. */ - Pen help = {grid, bottom, 0, grid->width, VEGA_MUTED, false, false}; - if (screen->prompt >= 0) write_cstr(&help, typing ? "Tab keys " : "Tab type "); - for (uint32_t i = 0; i < m->inputs; ++i) { - if (m->input[i].kind != PURE_KIND_KEY) continue; - bool bound = false; - for (uint32_t h = 0; h < m->handlers && !bound; ++h) bound = m->handler[h].event == 1 + 2 * i; - if (!bound) continue; - write_cstr(&help, key_symbol(m->input_keys[i])); - write_cstr(&help, " "); - write_cstr(&help, m->input_names[i]); - write_cstr(&help, " "); - } - if (screen->prompt >= 0 && grid->height > 1) { - Pen pen = {grid, grid->height - 1, 0, grid->width, typing ? VEGA_ACCENT : VEGA_MUTED, false, false}; - write_cstr(&pen, m->input_names[screen->prompt]); - write_cstr(&pen, "> "); - pen.tone = typing ? VEGA_NORMAL : VEGA_MUTED; - /* Show the end of a long entry. */ - size_t start = 0; - unsigned room = grid->width > pen.column + 1 ? grid->width - pen.column - 1 : 0; - while (start < prompt_length && vega_text_width(prompt + start, prompt_length - start) > room) { - uint32_t skip; - start += vega_decode(prompt, prompt_length, start, &skip); - } - write_text(&pen, prompt + start, prompt_length - start); - if (typing) { grid->cursor_row = (int)pen.row; grid->cursor_column = (int)pen.column; } - } -} |
