aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/draw.c27
-rw-r--r--src/main.c70
-rw-r--r--src/screen.c8
-rw-r--r--src/vega.h7
4 files changed, 99 insertions, 13 deletions
diff --git a/src/draw.c b/src/draw.c
index bec3804..3c7a59d 100644
--- a/src/draw.c
+++ b/src/draw.c
@@ -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. */
diff --git a/src/main.c b/src/main.c
index 4f83780..4e5c79b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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;
diff --git a/src/vega.h b/src/vega.h
index 2d0a29f..80e3f7d 100644
--- a/src/vega.h
+++ b/src/vega.h
@@ -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