diff options
| author | user <user@clank> | 2026-08-20 23:22:33 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-20 23:22:33 +0200 |
| commit | b33e8c13ff7981effc7ec2735e6aeaaaf1705af7 (patch) | |
| tree | 4933eb476b5d09e9d8131986c84bb966bca317a2 /src/cmd.c | |
| parent | fuzz: libFuzzer harness for all six parser surfaces (diff) | |
| download | slosh-b33e8c13ff7981effc7ec2735e6aeaaaf1705af7.tar.gz slosh-b33e8c13ff7981effc7ec2735e6aeaaaf1705af7.tar.xz slosh-b33e8c13ff7981effc7ec2735e6aeaaaf1705af7.zip | |
slosh: floating panes and the drop grammar
A pane can float: lifted out of the layout, drawn over the tiled panes and
under the modals, freely moved and shaped. The model is a flag and a rect on
the leaf, exactly like `minimized` -- the seat in the tree is kept, layout
skips it, the siblings absorb its share, and un-floating returns it home.
The drawn rect is the wanted one clamped to the tab every frame, never
written back, so a terminal squeeze round-trips exactly; an explicit move or
resize *does* write back, because the hand is the author of the intent.
Floating pops the pane to the centre: the jump is what says it happened.
Painted after everything tiled, so the hit list -- searched backwards --
resolves an overlap to the float you can see: the z-order and the click
order are the same list. It casts a shadow (float_shadow, one number), and
`floating` joins the states table above `unfocused`, so a float is never
dimmed as merely-unfocused.
The hands: the title moves it, any other border resizes it (a bottom corner
on both axes; the grabbed edge pins at walls and floors, never sliding the
window), H J K L move a focused float, the =/+ key grows it about its
centre, `-` shrinks it (its split was a refusal anyway), and hovering shows
which edges a grab would take, from the same derivation the press uses.
Equalize moved to `0` to free the key; `C-a f` floats (finder went to `s`,
where it reads as search); `C-a F` opens a new floating shell -- the
throwaway terminal; the function row f1..f12 became nameable in configs
along the way. Guards throughout: the last tiled pane cannot float or
minimize away, closing it lands the top float, splits are refused with
their own words, floats are not swap targets.
Dropping got a grammar: while a pane is carried, the pane under the pointer
subdivides into a swap centre and four edge bands meaning "insert me beside
this pane, on this side" -- detach_leaf + place_beside, the surgery
cross-tab moves already had. Bands are thirds (the first map of quarters
showed the swap owning half of each axis), the hovered band fills the half
the drop would hand over, the release resolves against the painted frame's
hits so it cannot disagree with the fill, and a drop between siblings along
the axis is a reorder that always fits -- refusing it was refusing the most
ordinary drag there is.
Also: rename-pane/rename-tab as palette actions (renaming was a
double-click and nothing else, the one verb the palette could not reach),
and C-u clears the rename editor like it clears the picker's query.
Design log in PLAN-FLOATING-PANES.md; to fold into DESIGN.md as D22/M10-11.
Diffstat (limited to 'src/cmd.c')
| -rw-r--r-- | src/cmd.c | 38 |
1 files changed, 36 insertions, 2 deletions
@@ -120,6 +120,22 @@ static char *cmd_json(app_t *a, screen_t *s, input_parser_t *in, free(txt); return j.buf; } + /* The emitter's own output: what a client's terminal would be sent for + * this frame. The model and the bytes are two different things, and + * asserting on the model while the bytes are wrong is a lesson this + * session has already paid for once (the graphics cmd says the same). + * Rendering updates the diff state, so a second call is the delta — + * which is itself worth testing. */ + if (strcmp(fmt, "bytes") == 0) { + screen_render(s); + json_t j; + json_init(&j); + json_obj_open(&j, NULL); + json_bool(&j, "ok", true); + json_str(&j, "bytes", s->out ? s->out : "", s->out_len); + json_obj_close(&j); + return j.buf; + } return jok_raw("screen", screen_dump_json(s)); } if (strcmp(cmd, "deadline") == 0) { @@ -156,9 +172,11 @@ static char *cmd_json(app_t *a, screen_t *s, input_parser_t *in, return jok_int(NULL, 0); } if (strcmp(cmd, "split") == 0) { + uint32_t id = (uint32_t)jv_geti(req, "id", 0); bool rows = strcmp(jv_gets(req, "dir", "cols"), "rows") == 0; - if (!app_split_pane(a, (uint32_t)jv_geti(req, "id", 0), rows)) - return jerr("no such pane"); + if (!app_split_pane(a, id, rows)) + return jerr(app_pane_floating(a, id) ? "cannot split a floating pane" + : "no such pane"); return jok_int("id", app_focused_pane_id(a)); } if (strcmp(cmd, "focus") == 0) { @@ -166,6 +184,22 @@ static char *cmd_json(app_t *a, screen_t *s, input_parser_t *in, return jerr("no such pane"); return jok_int("id", app_focused_pane_id(a)); } + /* The throwaway terminal, scripted: a fresh shell floating over the + * current tab. */ + if (strcmp(cmd, "new-float") == 0) { + uint32_t id = app_new_float(a); + if (!id) return jerr("cannot open a pane"); + return jok_int("id", id); + } + /* Toggle, like the button and the palette entry: M10c grows placement + * arguments here, this is the same one verb everything else runs. */ + if (strcmp(cmd, "float") == 0) { + uint32_t id = (uint32_t)jv_geti(req, "id", 0); + /* Two refusals share the word: no such pane, and no tiled pane left to + * float over. */ + if (!app_toggle_float(a, id)) return jerr("cannot float that"); + return jok_int("floating", app_pane_floating(a, id) ? 1 : 0); + } if (strcmp(cmd, "edit-config") == 0) { return app_edit_config(a) ? jok_int("id", app_focused_pane_id(a)) : jerr("no room for another pane"); |
