diff options
| author | eunhhu <[email protected]> | 2026-09-26 11:33:31 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-26 11:33:31 +0100 |
| commit | 0d5effd6afe8808296a01db102ca4c247cdcd1af (patch) | |
| tree | 4f1113880606fb6dda861fd846f8ff40d5c17a63 /src | |
| parent | 3b3e4a221fa47d0109c6ab99be65b5ba48bae254 (diff) | |
| download | vega-0d5effd6afe8808296a01db102ca4c247cdcd1af.tar.gz vega-0d5effd6afe8808296a01db102ca4c247cdcd1af.zip | |
Show filtered lists and their shared focus
A list may now be filtered (each t in tasks where !t.done), and several
filtered lists over one list share one focus: the focus view names an
item, so Vega asks Pure which row shows it and highlights it only in the
list that holds it; the other lists have no focus (null in the web
surface's JSON, and the spoken surface names the item where it is). A
click or point on a row points at the item that row shows, and the wheel
on a list without the focus focuses the row under it. Pure's rows now
take a mutable app.
examples/today.aup shows what is open and what is done; its CLI output
matches a model over random adds, moves, toggles and points into either
list, and the web surface leaves the other list without a focus.
Diffstat (limited to 'src')
| -rw-r--r-- | src/draw.c | 12 | ||||
| -rw-r--r-- | src/main.c | 6 | ||||
| -rw-r--r-- | src/screen.c | 17 | ||||
| -rw-r--r-- | src/vega.h | 10 | ||||
| -rw-r--r-- | src/web.c | 10 |
5 files changed, 38 insertions, 17 deletions
@@ -138,6 +138,7 @@ static void say_focus(FILE *out, PureApp *app, const VegaPart *part) { 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]); @@ -164,8 +165,15 @@ void vega_say(FILE *out, PureApp *app, const VegaScreen *screen, bool all) { char text[VEGA_TEXT]; size_t length; if (!all) { - for (unsigned i = 0; i < screen->count; ++i) - if (screen->parts[i].kind != VEGA_FIELD) { say_focus(out, app, &screen->parts[i]); return; } + /* 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)) { @@ -95,8 +95,10 @@ static bool mouse(unsigned button, unsigned line) { 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; + if (focus < rows) { /* else the wheel focuses the row under it */ + 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); } diff --git a/src/screen.c b/src/screen.c index bb50de1..26b9fb0 100644 --- a/src/screen.c +++ b/src/screen.c @@ -146,20 +146,23 @@ bool vega_number(PureApp *app, uint32_t view, size_t row, double *out) { return app->module->view_table[view] ? pure_app_cell(app, view, row, out) : pure_app_view(app, view, out); } -size_t vega_rows(const PureApp *app, const VegaPart *part) { +size_t vega_rows(PureApp *app, const VegaPart *part) { return part->kind == VEGA_FIELD ? 0 : pure_app_rows(app, part->view); } size_t vega_focus(PureApp *app, const VegaPart *part) { - size_t rows = vega_rows(app, part); + size_t rows = vega_rows(app, part), row; double focus; if (part->focus < 0 || !rows || !vega_number(app, (uint32_t)part->focus, 0, &focus) || !(focus >= 0)) return 0; - return focus >= (double)(rows - 1) ? rows - 1 : (size_t)focus; + bool filtered = app->module->view_code[part->view].length > 0; + size_t item = focus < (double)SIZE_MAX ? (size_t)focus : SIZE_MAX; + if (pure_app_item_row(app, part->view, item, &row)) return row; + return filtered ? VEGA_NO_ROW : rows - 1; } size_t vega_scroll(size_t first, size_t focus, size_t visible, size_t rows) { - if (focus < first) first = focus; - if (visible && focus >= first + visible) first = focus - visible + 1; + if (focus < rows && focus < first) first = focus; + if (focus < rows && visible && focus >= first + visible) first = focus - visible + 1; if (first + visible > rows) first = rows > visible ? rows - visible : 0; return first; } @@ -184,7 +187,9 @@ bool vega_commit(PureApp *app, const VegaScreen *screen, const char *text, size_ } 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; + size_t item; /* a filtered list's row shows some item of the list */ + return part->pointer >= 0 && pure_app_row_item(app, part->view, row, &item) && + pure_app_point(app, (uint32_t)part->pointer, item) == PURE_OK; } VegaTone vega_tone(PureApp *app, const VegaPart *part, size_t row) { @@ -45,14 +45,18 @@ unsigned vega_check(const PureModule *, void (*report)(void *, const char *name, * shortest form. false when it has no value (for example a missing item). */ bool vega_text(PureApp *, uint32_t view, size_t row, char out[VEGA_TEXT], size_t *length); 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_rows(PureApp *, const VegaPart *); +/* The row of the focused item: the focus view names an item (an index's + * position), shown on the row that holds it; VEGA_NO_ROW when a filtered + * list leaves that item out. Clamped to the rows without a filter; 0 + * without a focus view. */ +#define VEGA_NO_ROW SIZE_MAX 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); /* The first row to show of `rows` when `visible` fit: the least scrolling - * from first that keeps the focused row in view. */ + * from first that keeps the focused row in view (any, without one). */ size_t vega_scroll(size_t first, size_t focus, size_t visible, size_t rows); /* The key input answering a key name (Pure's names, as KeyboardEvent.code * spells them), or -1; press sends its press and release. */ @@ -123,8 +123,10 @@ static void screen_json(Buffer *b, PureApp *app, const VegaScreen *screen, size_ puts_(b, comma ? ",{\"part\":" : "{\"part\":"); putf(b, "%u,\"name\":", i); json_name(b, m->view_names[part->view]); - putf(b, ",\"kind\":\"%s\",\"rows\":%zu,\"focus\":%zu,\"first\":%zu,\"pointable\":%s,\"columns\":[", - part->kind == VEGA_LIST ? "list" : "table", rows, rows ? focus : 0, first, part->pointer >= 0 ? "true" : "false"); + putf(b, ",\"kind\":\"%s\",\"rows\":%zu,\"focus\":", part->kind == VEGA_LIST ? "list" : "table", rows); + if (focus < rows) putf(b, "%zu", focus); + else puts_(b, "null"); + putf(b, ",\"first\":%zu,\"pointable\":%s,\"columns\":[", first, part->pointer >= 0 ? "true" : "false"); if (part->kind == VEGA_TABLE) for (unsigned c = 0; c < part->column_count; ++c) { if (c) puts_(b, ","); @@ -224,12 +226,12 @@ static const char *const page_parts[] = { "el('span',{className:'label',textContent:it.label?\?''}),it.detail?el('span',{className:'detail',textContent:it.detail}):null," "it.badge?el('span',{className:'badge',textContent:it.badge}):null);item.setAttribute('role','option');" "item.setAttribute('aria-selected',it.row===p.focus);list.append(item)}" -"list.onwheel=e=>{e.preventDefault();pick(Math.max(0,Math.min(p.rows-1,p.focus+Math.sign(e.deltaY))))};" +"list.onwheel=e=>{e.preventDefault();pick(Math.max(0,Math.min(p.rows-1,(p.focus?\?-1)+Math.sign(e.deltaY))))};" "return el('section',{},head,list)}" "const table=el('table',{},el('thead',{},el('tr',{},...p.columns.map(c=>el('th',{textContent:c})))));" "const body=el('tbody');for(const it of p.items){const tr=el('tr',{className:it.tone,onclick:()=>pick(it.row)}," "...it.cells.map(c=>el('td',{textContent:c?\?'-'})));tr.setAttribute('aria-selected',it.row===p.focus);body.append(tr)}" -"table.append(body);table.onwheel=e=>{e.preventDefault();pick(Math.max(0,Math.min(p.rows-1,p.focus+Math.sign(e.deltaY))))};" +"table.append(body);table.onwheel=e=>{e.preventDefault();pick(Math.max(0,Math.min(p.rows-1,(p.focus?\?-1)+Math.sign(e.deltaY))))};" "return el('section',{},head,table)}));" "$('prompt').hidden=!s.prompt;if(s.prompt)$('entry').placeholder=s.prompt;" "const symbols={ArrowUp:'\u2191',ArrowDown:'\u2193',ArrowLeft:'\u2190',ArrowRight:'\u2192',Backspace:'Bksp'," |
