aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-20 23:22:33 +0200
committeruser <user@clank>2026-08-20 23:22:33 +0200
commitb33e8c13ff7981effc7ec2735e6aeaaaf1705af7 (patch)
tree4933eb476b5d09e9d8131986c84bb966bca317a2 /include
parentfuzz: libFuzzer harness for all six parser surfaces (diff)
downloadslosh-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 'include')
-rw-r--r--include/app.h11
-rw-r--r--include/app_internal.h52
-rw-r--r--include/config.h18
3 files changed, 81 insertions, 0 deletions
diff --git a/include/app.h b/include/app.h
index d422995..e3cbc73 100644
--- a/include/app.h
+++ b/include/app.h
@@ -223,6 +223,17 @@ uint32_t app_move_pane_to_new_tab(app_t *a, uint32_t pane_id, const char *name);
bool app_toggle_zoom(app_t *a, uint32_t id);
bool app_pane_zoomed(app_t *a, uint32_t id);
+/* Lift a pane out of the layout and draw it on top, or put it back in the
+ * seat it kept. 0 means the focused one. False when it would leave the tab
+ * with no tiled pane to float over. */
+bool app_toggle_float(app_t *a, uint32_t id);
+bool app_pane_floating(app_t *a, uint32_t id);
+
+/* A new floating shell over the current tab, centred: the throwaway
+ * terminal. It holds a real seat in the tree beside the focused pane, so
+ * un-floating lands it there. Returns its id, or 0. */
+uint32_t app_new_float(app_t *a);
+
/* Give every visible pane in the current tab an even share of the rows and
* columns it competes for: each split's children weighted by how many visible
* panes are behind them, so "even" means the same thing at every depth. False
diff --git a/include/app_internal.h b/include/app_internal.h
index b3a64cb..68d6817 100644
--- a/include/app_internal.h
+++ b/include/app_internal.h
@@ -36,6 +36,25 @@ struct node {
* every pane on every frame. */
bool minimized;
+ /* Floating: out of the layout, drawn on top of the tiled panes and below
+ * the modals. Same pattern as `minimized` — the leaf keeps its seat in the
+ * tree, layout skips it, the siblings absorb its share, and un-floating
+ * returns it home: same id, same place, nothing that referred to it has to
+ * be told anything.
+ *
+ * `float_rect` is where it *wants* to be, in cells. Intent: what is drawn
+ * each frame is this clamped to the tab's area, and the clamp never writes
+ * back — shrink the terminal and the float is squeezed in, grow it again
+ * and it is exactly where you put it. Zero-width means "never placed",
+ * which the placement answers with a centred default.
+ *
+ * `raised` orders overlapping floats: highest paints last, so it is on
+ * top. Stamped when a float is made and when a focused float is not the
+ * top one — in layout(), once, rather than at each place focus can move. */
+ bool floating;
+ rect_t float_rect;
+ uint32_t raised;
+
/* leaf */
pane_t *pane;
uint32_t id;
@@ -112,6 +131,10 @@ struct app {
size_t ntabs, tabcap, cur;
uint32_t next_tab_id;
uint32_t next_id;
+ /* The last raise stamp handed out. A float whose `raised` equals this is
+ * the top one, which is what lets the focus rule stamp without spinning
+ * the counter every frame. */
+ uint32_t raise_seq;
uint16_t cols, rows;
/* The client's cell size in pixels, as reported when it attached. Panes are
* told so that a program can size an image, and so lib-vt can work out how
@@ -134,6 +157,12 @@ struct app {
DRAG_SELECT,
DRAG_BORDER,
DRAG_TAB,
+ /* The float verbs: the title row moves it, any other border cell
+ * resizes from the edges under the grab. Their own kinds rather than
+ * flags on DRAG_TITLE/DRAG_BORDER, because everything those two mean
+ * on release — swap, split — is exactly what a float must not do. */
+ DRAG_FLOAT_MOVE,
+ DRAG_FLOAT_RESIZE,
} kind;
uint32_t src; /* pane being dragged, or the split being resized */
uint32_t target; /* pane under the pointer, for the drop highlight */
@@ -146,6 +175,15 @@ 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' */
+ /* Which of the target's drop zones the pointer is in, or 0 for the
+ * centre. The centre is the swap; a side means "insert me beside the
+ * target, on this side" — the drop grammar that turns dragging into
+ * re-layout. */
+ char drop_side;
+ /* Which edges a float resize is holding (FEDGE_*): derived from where
+ * the press landed on the frame, so a bottom corner is two edges and
+ * follows the pointer on both axes. */
+ uint8_t fedges;
/* 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
@@ -281,6 +319,12 @@ extern config_t CFG;
#define WEIGHT_MIN 150 /* a pane can be squeezed, not squeezed out */
#define WEIGHT_STEP 120 /* one keyboard nudge */
+/* Which edges of a float a resize is moving. */
+#define FEDGE_L 1u
+#define FEDGE_R 2u
+#define FEDGE_T 4u
+#define FEDGE_B 8u
+
#define MIN_PANE_COLS (CFG.min_pane_cols)
#define MIN_PANE_ROWS (CFG.min_pane_rows)
#define FRAME_FOCUS (CFG.frame_focus)
@@ -368,6 +412,11 @@ void split_focus(app_t *a, split_dir_t dir);
void split_focus_auto(app_t *a);
void split_focus_ui(app_t *a, split_dir_t dir);
void transfer_weight(node_t *from, node_t *to, int amount);
+void float_move(app_t *a, node_t *n, int dx, int dy);
+void float_resize(app_t *a, node_t *n, unsigned edges, int dx, int dy);
+bool focus_float_move(app_t *a, int dx, int dy);
+bool focus_float_grow(app_t *a, int sign);
+uint8_t float_edges_at(const node_t *n, uint16_t mx, uint16_t my);
/* src/app_ui.c */
void picker_accept(app_t *a, const char *action);
@@ -393,6 +442,7 @@ tab_t *tab_by_id(app_t *a, uint32_t id);
size_t tab_index(app_t *a, uint32_t id);
bool swap_panes(app_t *a, uint32_t id_a, uint32_t id_b);
+bool insert_pane_beside(app_t *a, uint32_t src_id, uint32_t dst_id, char side);
struct byid {
uint32_t id;
node_t *found;
@@ -410,9 +460,11 @@ void draw_status_line(app_t *a, screen_t *s);
void draw_node(app_t *a, screen_t *s, node_t *n);
void draw_min_bar(app_t *a, screen_t *s);
void draw_corners(app_t *a, screen_t *s);
+void draw_floats(app_t *a, screen_t *s);
size_t count_leaves(node_t *n);
size_t collect_minimized(node_t *n, node_t **out, size_t cap, size_t k);
+size_t collect_floating(node_t *n, node_t **out, size_t cap, size_t k);
size_t collect_leaves(node_t *n, node_t **out, size_t cap, size_t k);
#endif /* SLOSH_APP_INTERNAL_H */
diff --git a/include/config.h b/include/config.h
index 969136a..b251aed 100644
--- a/include/config.h
+++ b/include/config.h
@@ -33,6 +33,12 @@ typedef enum {
* which is what answering a bell is. */
PSTATE_BELL,
PSTATE_SCROLLED, /* looking at scrollback rather than the present */
+ /* Lifted out of the layout, drawn on top. Above `unfocused`, so a float is
+ * never dimmed by dim_unfocused: full strength is what keeps the thing on
+ * top reading as lifted off the page — the same reason `dragging` keeps no
+ * default. Ships no chain of its own either; the shadow does the telling
+ * apart, and a config that wants colour writes `states { floating { } }`. */
+ PSTATE_FLOATING,
PSTATE_UNFOCUSED,
PSTATE_COUNT,
} pane_state_t;
@@ -49,7 +55,13 @@ typedef enum {
ACT_RERUN,
ACT_ZOOM,
ACT_MINIMIZE,
+ ACT_FLOAT,
+ ACT_NEW_FLOAT,
+ ACT_FLOAT_GROW,
+ ACT_FLOAT_SHRINK,
ACT_SET_PURPOSE,
+ ACT_RENAME_PANE,
+ ACT_RENAME_TAB,
ACT_ROTATE_LAYOUT,
ACT_FOCUS_LEFT,
ACT_FOCUS_RIGHT,
@@ -219,6 +231,12 @@ typedef struct {
* Writing the state chain by hand still wins: the knob only fills it in
* when the config has not. */
uint8_t dim_unfocused;
+ /* How dark the cell of shade a floating pane casts on what it covers,
+ * 0..255 and 0 for no shadow. The shadow is what tells a float from a tile
+ * at a glance — the frame is the same frame — which is why it ships on and
+ * why it is a number rather than a state chain: turning it off should not
+ * require knowing the states block exists (the dim_unfocused argument). */
+ uint8_t float_shadow;
/* Colour passes every pane gets, in the order they were written. Ordinary
* shaders rather than policy: the session has no opinion about these, you