diff options
| author | user <user@clank> | 2026-08-14 20:47:39 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-14 20:47:39 +0200 |
| commit | 0260c1c40ba7b2fec58d99ea6daa35f8152e67d0 (patch) | |
| tree | 420c258370022b6d94f9b51fe366326bf133d81e /src/app.c | |
| parent | sl0ppty: drag the crossing to resize both ways (diff) | |
| download | slosh-0260c1c40ba7b2fec58d99ea6daa35f8152e67d0.tar.gz slosh-0260c1c40ba7b2fec58d99ea6daa35f8152e67d0.tar.xz slosh-0260c1c40ba7b2fec58d99ea6daa35f8152e67d0.zip | |
sl0ppty: hold the boundaries, not a seat number
Three faults in the corner drag, reported as glitches and picking panes up
along the way.
The drag held an index into the corner list, and that list is rebuilt from the
layout every frame — by a drag whose entire job is changing the layout. Move
the crossing and the corners are recomputed: the entry you grabbed can shift
position, or stop being a corner, and the index then names somebody else's
boundaries. That is exactly "it picks up additional panes and loses them
again", and it is the rule this codebase already states about the rect and the
collapsed flag, applied to something I stored anyway. The drag now carries the
boundaries themselves, so it moves what you grabbed until you let go, whatever
the layout does underneath. The test drags a deliberately misaligned grid in
circles and checks the boundary that was never part of the crossing is
untouched throughout.
The redraw was the dwell list again. Anything that arms after the pointer rests
has to be named in app_next_deadline_ms or it only appears when something else
happens to repaint — so it looked intermittent, which is the worst way for it
to be wrong. "corner:" is in the list now, next to the three that were already
there for the same reason.
And a crossing is two cells wide with nothing about it to suggest it is
anything, so it marks itself the moment the pointer arrives, before the dwell
that arms both boundaries. Passing over one costs a character; resting on one
still explains itself.
Diffstat (limited to 'src/app.c')
| -rw-r--r-- | src/app.c | 70 |
1 files changed, 48 insertions, 22 deletions
@@ -193,7 +193,16 @@ struct app { uint16_t x, y; /* where the pointer was at the last event */ bool moved; /* a press that never moves is a click */ char side; /* border press: 'l' 'r' 't' 'b' */ - int corner; /* index into a->corners, or -1 */ + /* A corner drag carries the boundaries it is moving, not an index into + * the corner list: that list is rebuilt from the layout every frame, and + * the layout is the thing being changed. Halfway through a drag the + * crossing you grabbed may be a different entry, or gone — an index would + * quietly start moving somebody else's panes. */ + uint32_t c_h; + size_t c_hedge; + uint32_t c_v[2]; + size_t c_vedge[2]; + size_t c_nv; } drag; graphics_t *gfx; /* kitty images, and what the client has been told */ @@ -337,7 +346,8 @@ int app_next_deadline_ms(app_t *a) { const char *action = hit_test(&a->painted->hits, a->ptr_x, a->ptr_y); bool on_border = action && (strncmp(action, "border:", 7) == 0 || strncmp(action, "title:", 6) == 0 || - strncmp(action, "edge:", 5) == 0); + strncmp(action, "edge:", 5) == 0 || + strncmp(action, "corner:", 7) == 0); if (on_border && (soonest < 0 || due < soonest)) soonest = due; } } @@ -2287,15 +2297,18 @@ static void draw_corners(app_t *a, screen_t *s) { snprintf(action, sizeof action, "corner:%zu", i); hit_add(&s->hits, c->r.x, c->r.y, c->r.w, c->r.h, action); - bool active = a->drag.kind == DRAG_EDGE && a->drag.corner == (int)i; - bool hot = active; - if (!hot && a->drag.kind == DRAG_NONE && ptr_on(a, c->r.x, c->r.y, c->r.w, c->r.h) && - now_ms_() - a->ptr_still_since >= CFG.hover_delay_ms) - hot = true; - if (!hot) continue; + bool active = a->drag.kind == DRAG_EDGE && a->drag.c_nv && + a->drag.c_h == c->h_id && a->drag.c_hedge == c->h_edge; + /* (C) The mark appears the moment the pointer is on it, before the dwell + * that arms the two boundaries. A crossing is two cells wide and gives no + * other sign it is anything: something has to say it is there, and the + * ghost costs nothing if you were only passing through. */ + bool over = a->drag.kind == DRAG_NONE && ptr_on(a, c->r.x, c->r.y, c->r.w, c->r.h); + if (!active && !over) continue; + bool armed = active || now_ms_() - a->ptr_still_since >= CFG.hover_delay_ms; for (uint16_t x = c->r.x; x < c->r.x + c->r.w; x++) - screen_text(s, x, c->r.y, active ? "\u256c" : "\u253c", RESIZE_C, - NO_COLOR, active ? ATTR_BOLD : 0); + screen_text(s, x, c->r.y, armed ? (active ? "\u256c" : "\u253c") : "\u253c", + RESIZE_C, NO_COLOR, active ? ATTR_BOLD : 0); } } @@ -2556,12 +2569,17 @@ static void draw_resize_hint(app_t *a, screen_t *s, node_t *split, size_t idx, a->drag.edge == idx; /* A corner moves two boundaries, so resting on it arms both of them: the * hint has to show what is about to move, not where the pointer is. */ - int ci = (a->drag.kind == DRAG_EDGE && a->drag.corner >= 0) - ? a->drag.corner - : (a->drag.kind == DRAG_NONE ? corner_at(a, a->ptr_x, a->ptr_y) : -1); + int ci = -1; + if (a->drag.kind == DRAG_EDGE && a->drag.c_nv) { + if ((a->drag.c_h == split->id && a->drag.c_hedge == idx)) active = true; + for (size_t k = 0; k < a->drag.c_nv; k++) + if (a->drag.c_v[k] == split->id && a->drag.c_vedge[k] == idx) active = true; + } else if (a->drag.kind == DRAG_NONE) { + ci = corner_at(a, a->ptr_x, a->ptr_y); + } if (ci >= 0 && (size_t)ci < a->ncorners && corner_uses(&a->corners[ci], split->id, idx)) { - if (a->drag.kind == DRAG_EDGE) active = true; + if (false) active = true; else if (now_ms_() - a->ptr_still_since >= CFG.hover_delay_ms) { uint16_t attrs0 = 0; if (split->dir == SPLIT_COLS) { @@ -3049,8 +3067,17 @@ static void do_action(app_t *a, const char *action, const input_event_t *ev) { } if (strncmp(action, "corner:", 7) == 0) { if (ev->maction != MOUSE_PRESS) return; + size_t ci = strtoul(action + 7, NULL, 10); + if (ci >= a->ncorners) return; + corner_t *c = &a->corners[ci]; a->drag.kind = DRAG_EDGE; - a->drag.corner = (int)strtoul(action + 7, NULL, 10); + a->drag.c_h = c->h_id; + a->drag.c_hedge = c->h_edge; + a->drag.c_nv = c->nv; + for (size_t k = 0; k < c->nv; k++) { + a->drag.c_v[k] = c->v_id[k]; + a->drag.c_vedge[k] = c->v_edge[k]; + } a->drag.moved = false; a->drag.x = ev->mx; a->drag.y = ev->my; @@ -3059,7 +3086,7 @@ static void do_action(app_t *a, const char *action, const input_event_t *ev) { if (strncmp(action, "edge:", 5) == 0) { if (ev->maction != MOUSE_PRESS) return; a->drag.kind = DRAG_EDGE; - a->drag.corner = -1; + a->drag.c_nv = 0; a->drag.src = (uint32_t)strtoul(action + 5, NULL, 10); const char *colon = strchr(action + 5, ':'); a->drag.edge = colon ? strtoul(colon + 1, NULL, 10) : 0; @@ -3312,18 +3339,17 @@ void app_event(app_t *a, const input_event_t *ev) { if (n) pane_select_extend(n->pane, (uint16_t)(ev->mx - n->content.x), (uint16_t)(ev->my - n->content.y)); - } else if (a->drag.kind == DRAG_EDGE && a->drag.corner >= 0 && - (size_t)a->drag.corner < a->ncorners) { + } else if (a->drag.kind == DRAG_EDGE && a->drag.c_nv) { /* One axis to each: the row boundary takes the vertical movement * and the column boundaries the horizontal, so the corner follows * the pointer in both at once. The column boundaries above and * below get the same delta, which is what keeps them one line. */ - corner_t c = a->corners[a->drag.corner]; int dx = (int)ev->mx - (int)a->drag.x; int dy = (int)ev->my - (int)a->drag.y; - drag_edge(a, split_by_id(a, c.h_id), c.h_edge, dy); - for (size_t i = 0; i < c.nv; i++) - drag_edge(a, split_by_id(a, c.v_id[i]), c.v_edge[i], dx); + drag_edge(a, split_by_id(a, a->drag.c_h), a->drag.c_hedge, dy); + for (size_t i = 0; i < a->drag.c_nv; i++) + drag_edge(a, split_by_id(a, a->drag.c_v[i]), a->drag.c_vedge[i], + dx); } else if (a->drag.kind == DRAG_EDGE) { node_t *sp = split_by_id(a, a->drag.src); int cells = sp && sp->dir == SPLIT_COLS |
