aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-26 10:57:03 +0100
committereunhhu <[email protected]>2026-09-26 10:57:03 +0100
commitf6be15d01ac184f070e1bb34af12689f68eb9140 (patch)
tree4ebfac5e6baf066b58136c2f71a70ab007ea5f5d /src
parent04e883a310fd3914d6f4294a92b08b3fe9f55686 (diff)
downloadvega-f6be15d01ac184f070e1bb34af12689f68eb9140.tar.gz
vega-f6be15d01ac184f070e1bb34af12689f68eb9140.zip
Move the focus from a surface: clicks, the wheel, point
Only the app's key handlers could move a list's focus. When the focus view is a plain index position (`view tasks_list_focus = at.position`), Vega now asks Pure for that index and moves it as a user pointing at a row (pure_app_point), with no code in the app. The grid remembers which item each line shows; in the TUI a click on a row focuses it and the wheel moves the focus one row (SGR mouse reports, turned off on exit). Scripts say point VIEW ROW or click LINE COLUMN on the last shown frame. The focus is stored like any event, so the next run of any surface starts from it. The model now points too: random scripts with points (some past the end) and TUI clicks still match it on every surface, a mouse click on a real pseudo-terminal moves the focus, and the stored session keeps it.
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