diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/draw.c | 27 | ||||
| -rw-r--r-- | src/main.c | 70 | ||||
| -rw-r--r-- | src/screen.c | 8 | ||||
| -rw-r--r-- | src/vega.h | 7 |
4 files changed, 99 insertions, 13 deletions
@@ -193,11 +193,15 @@ void vega_say(FILE *out, PureApp *app, const VegaScreen *screen, bool all) { 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)); - if (!cells || !shown) { free(cells); free(shown); return false; } + 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; @@ -207,6 +211,7 @@ bool vega_grid(VegaGrid *grid, unsigned width, unsigned height) { void vega_grid_free(VegaGrid *grid) { free(grid->cells); free(grid->shown); + free(grid->rows); *grid = (VegaGrid){0}; } @@ -272,11 +277,13 @@ static const char *key_symbol(const char *key) { return key; } -static void draw_list(VegaGrid *grid, PureApp *app, const VegaPart *part, size_t first, unsigned top, unsigned height) { +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}; @@ -307,7 +314,8 @@ static void draw_list(VegaGrid *grid, PureApp *app, const VegaPart *part, size_t } } -static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, size_t first, unsigned top, unsigned height) { +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); @@ -338,6 +346,7 @@ static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, size_ 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) { @@ -351,10 +360,18 @@ static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, size_ } } +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]; @@ -398,8 +415,8 @@ void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const cha if (focus < *first) *first = focus; if (visible && focus >= *first + visible) *first = focus - visible + 1; if (*first + visible > rows) *first = rows > visible ? rows - visible : 0; - if (part->kind == VEGA_LIST) draw_list(grid, app, part, *first, row + 1, area); - else draw_table(grid, app, part, *first, row + 1, area); + 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. */ @@ -20,13 +20,16 @@ * With --store the app's states live in that tree store: the app continues * where it was, whichever surface runs it. * Script commands, one per line: key KEYNAME | text HEX|- | type HEX | enter | - * tab | frame [N] | show. key presses and releases the input bound to that key; - * text commits text to the screen's text input; type, enter and tab act on the - * TUI's input line. */ + * tab | frame [N] | point VIEW ROW | click LINE COLUMN | show. key presses and + * releases the input bound to that key; text commits text to the screen's text + * input; type, enter and tab act on the TUI's input line; point focuses a row + * (0 first) of a list or table; click is a mouse click on the last shown TUI + * frame (1 first, as terminals report it). */ static PureModule module; static PureApp app; static VegaScreen screen; +static VegaGrid *drawn; /* the TUI's last frame, which a click lands on */ static char entry[VEGA_TEXT]; static size_t entry_length; static bool typing; @@ -85,6 +88,27 @@ static bool commit(const char *text, size_t length) { return pure_app_text(&app, (uint32_t)screen.prompt, (PureSpan){(const unsigned char *)text, length}) == PURE_OK; } +static const VegaPart *part_named(const char *name) { + for (unsigned i = 0; i < screen.count; ++i) + if (screen.parts[i].kind != VEGA_FIELD && !strcmp(module.view_names[screen.parts[i].view], name)) return &screen.parts[i]; + return NULL; +} + +/* A mouse button on a line of the last frame: a click focuses the row there; + * the wheel moves the focus of the list or table under it. */ +static bool mouse(unsigned button, unsigned line) { + unsigned index; + size_t item; + if (!drawn || !vega_hit(drawn, line, &index, &item)) return false; + const VegaPart *part = &screen.parts[index]; + if (button == 64 || button == 65) { + size_t focus = vega_focus(&app, part), rows = vega_rows(&app, part); + if (button == 64 ? !focus : focus + 1 >= rows) return false; + item = button == 64 ? focus - 1 : focus + 1; + } else if (button) return false; + return vega_point(&app, part, item); +} + static int hex(char c) { return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1; } static size_t unhex(const char *digits, char *out, size_t size) { @@ -221,7 +245,7 @@ static void on_resize(int signal_number) { (void)signal_number; resized = 1; } static void restore_terminal(void) { if (!raw_terminal) return; tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_terminal); - const char *leave = "\033[0m\033[?25h\033[?1049l"; + const char *leave = "\033[?1006l\033[?1000l\033[0m\033[?25h\033[?1049l"; if (write(STDOUT_FILENO, leave, strlen(leave)) < 0) { /* nothing left to tell */ } raw_terminal = false; } @@ -238,7 +262,7 @@ static bool raw_mode(void) { if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t)) return false; raw_terminal = true; atexit(restore_terminal); - const char *enter = "\033[?1049h\033[?25l"; + const char *enter = "\033[?1049h\033[?25l\033[?1000h\033[?1006h"; /* mouse buttons, reported as SGR */ return write(STDOUT_FILENO, enter, strlen(enter)) >= 0; } @@ -286,6 +310,19 @@ static bool handle_input(const unsigned char *in, size_t length) { for (size_t at = 0; at < length;) { unsigned char c = in[at]; if (c == 0x03 || c == 0x11) return false; /* Ctrl-C, Ctrl-Q */ + if (c == 0x1B && at + 2 < length && in[at + 1] == '[' && in[at + 2] == '<') { + /* A mouse report: ESC [ < button ; column ; line, M on press, m on release. */ + unsigned fields[3] = {0, 0, 0}, field = 0; + size_t end = at + 3; + for (; end < length && field < 3; ++end) { + if (in[end] >= '0' && in[end] <= '9') fields[field] = fields[field] * 10 + (unsigned)(in[end] - '0'); + else if (in[end] == ';') ++field; + else break; + } + if (end < length && in[end] == 'M' && field == 2 && fields[2]) mouse(fields[0], fields[2] - 1); + at = end + 1; + continue; + } if (c == 0x1B) { size_t matched = 0; for (size_t i = 0; i < sizeof(sequences) / sizeof(*sequences) && !matched; ++i) { @@ -318,6 +355,7 @@ static int interactive(void) { if (!raw_mode()) { fputs("vega: the tui needs a terminal (or use --dump WxH)\n", stderr); return 2; } signal(SIGWINCH, on_resize); VegaGrid grid = {0}; + drawn = &grid; bool ticking = frames(); for (;;) { if (resized) { @@ -338,6 +376,7 @@ static int interactive(void) { if (got <= 0 || !handle_input(in, (size_t)got)) break; } restore_terminal(); + drawn = NULL; vega_grid_free(&grid); return 0; } @@ -351,6 +390,8 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin static char line[1 << 16]; VegaGrid grid = {0}; if (surface == SURFACE_TUI && !vega_grid(&grid, width, height)) return 2; + bool shown = false; + drawn = surface == SURFACE_TUI ? &grid : NULL; bool color = surface == SURFACE_CLI && isatty(STDOUT_FILENO) && !getenv("NO_COLOR"); unsigned long counted = vega_evaluations(); while (fgets(line, sizeof(line), stdin)) { @@ -359,6 +400,7 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin if (sscanf(line, "%15s %65535s", command, argument) < 1) continue; bool acted = true, ok = true; int input = -1; + const char *verb = "do that"; if (!strcmp(command, "key")) { ok = press(argument, &input); if (input < 0) fprintf(stderr, "vega: no input answers %s\n", argument); @@ -373,6 +415,19 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin } else if (!strcmp(command, "tab")) { handle_key("Tab"); acted = false; + } else if (!strcmp(command, "point")) { + char name[PURE_MODULE_NAME]; + long row = -1; + const VegaPart *part = sscanf(line, "%*s %31s %ld", name, &row) == 2 ? part_named(name) : NULL; + verb = "point"; + if (!part) fprintf(stderr, "vega: no list or table %s\n", name); + ok = part && row >= 0 && vega_point(&app, part, (size_t)row); + } else if (!strcmp(command, "click")) { + unsigned at_line = 0, at_column = 0; + verb = "point"; + if (surface != SURFACE_TUI) { fputs("vega: click needs the tui\n", stderr); continue; } + acted = sscanf(line, "%*s %u %u", &at_line, &at_column) == 2 && at_line && shown; + ok = acted && mouse(0, at_line - 1); } else if (!strcmp(command, "frame")) { long count = *argument ? strtol(argument, NULL, 10) : 1; for (long i = 0; i < count && i < 600; ++i) ok = pure_app_frame(&app) == PURE_OK && ok; @@ -380,7 +435,7 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin acted = false; if (surface == SURFACE_CLI) vega_print(stdout, &app, &screen, color); else if (surface == SURFACE_SAY) vega_say(stdout, &app, &screen, true); - else if (painting) { + else if ((shown = true) && painting) { Out out = {0}; vega_draw(&grid, &app, &screen, entry, entry_length, typing); paint(&grid, &out); @@ -400,11 +455,12 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin } if (!ok && input >= 0) fprintf(stderr, "vega: %s failed\n", module.input_names[input]); if (surface == SURFACE_SAY && acted) { - if (!ok) printf("Could not %s.\n", input >= 0 ? module.input_names[input] : "do that"); + if (!ok) printf("Could not %s.\n", input >= 0 ? module.input_names[input] : verb); else vega_say(stdout, &app, &screen, false); } fflush(stdout); } + drawn = NULL; vega_grid_free(&grid); return 0; } diff --git a/src/screen.c b/src/screen.c index 2d4c6dc..3e8b2c4 100644 --- a/src/screen.c +++ b/src/screen.c @@ -32,12 +32,14 @@ void vega_screen(VegaScreen *screen, const PureModule *m) { for (uint32_t i = 0; i < m->views && screen->count < VEGA_PARTS; ++i) { if (m->view_table[i] || (int)i == screen->title || focus_view[i]) continue; VegaPart *part = &screen->parts[screen->count++]; - *part = (VegaPart){.kind = VEGA_FIELD, .view = i, .focus = -1, .label = -1, .tone = -1, .detail = -1, .badge = -1}; + *part = (VegaPart){.kind = VEGA_FIELD, .view = i, .focus = -1, .pointer = -1, .label = -1, .tone = -1, .detail = -1, + .badge = -1}; if (m->view_type[i] != PURE_VIEW_TABLE) continue; char name[PURE_MODULE_NAME + 8]; snprintf(name, sizeof(name), "%s_focus", m->view_names[i]); part->focus = pure_module_view(m, name); if (part->focus >= 0 && !focus_view[part->focus]) part->focus = -1; + part->pointer = part->focus >= 0 ? pure_module_position(m, (uint32_t)part->focus) : -1; part->label = column(m, i, "label", PURE_VIEW_TEXT); part->tone = column(m, i, "tone", PURE_VIEW_NUM); part->kind = part->label >= 0 ? VEGA_LIST : VEGA_TABLE; @@ -155,6 +157,10 @@ size_t vega_focus(PureApp *app, const VegaPart *part) { return focus >= (double)(rows - 1) ? rows - 1 : (size_t)focus; } +bool vega_point(PureApp *app, const VegaPart *part, size_t row) { + return part->pointer >= 0 && pure_app_point(app, (uint32_t)part->pointer, row) == PURE_OK; +} + VegaTone vega_tone(PureApp *app, const VegaPart *part, size_t row) { double tone; if (part->tone < 0 || !vega_number(app, (uint32_t)part->tone, row, &tone)) return VEGA_NORMAL; @@ -20,6 +20,7 @@ typedef struct { VegaKind kind; uint32_t view; int focus; /* number view with the focused row, or -1 */ + int pointer; /* the index state that view shows, or -1: a surface may move it */ int label, tone, detail, badge; /* columns, or -1 */ uint32_t columns[PURE_MODULE_VIEWS]; /* a table's shown columns, in order */ unsigned column_count; @@ -47,6 +48,9 @@ bool vega_number(PureApp *, uint32_t view, size_t row, double *out); size_t vega_rows(const PureApp *, const VegaPart *); /* The focused row, clamped to the rows; 0 without a focus view. */ size_t vega_focus(PureApp *, const VegaPart *); +/* Focus a row, as a user pointing at it (a click); false when the part's + * focus is not a plain index position or the row is not there. */ +bool vega_point(PureApp *, const VegaPart *, size_t row); VegaTone vega_tone(PureApp *, const VegaPart *, size_t row); /* Values evaluated so far, for checking that surfaces stay lazy. */ unsigned long vega_evaluations(void); @@ -77,6 +81,7 @@ typedef struct { typedef struct { unsigned width, height; VegaCell *cells, *shown; + struct VegaRow { int part; size_t item; } *rows; /* the item each line shows, or part -1 */ bool painted; /* shown holds what the terminal shows */ size_t first[VEGA_PARTS]; int cursor_row, cursor_column; /* where the terminal cursor goes, or -1 */ @@ -86,4 +91,6 @@ void vega_grid_free(VegaGrid *); /* prompt is the text being typed for the screen's text input; typing says * whether keys go to it. */ void vega_draw(VegaGrid *, PureApp *, const VegaScreen *, const char *prompt, size_t prompt_length, bool typing); +/* The part and item a line of the last drawn grid shows. */ +bool vega_hit(const VegaGrid *, unsigned line, unsigned *part, size_t *item); #endif |
