docking: Floating::stack is public, and four-way handles take the pointer only

Floating::stack builds a window from groups stacked top to bottom, so an
app's own stacked layouts migrate without building the split chain. The
four-way handle no longer takes keyboard focus: it has no label or key
action, and the two dividers it joins take arrow keys.
This commit is contained in:
aiongg committed 2026-10-23 06:11:03 +00:00
1 parent 50f9c06aa7
commit 01ce7162a4
7 files changed
+16 -14

No files matched your search

+2 -2
View File
@@ -38,7 +38,7 @@ fn workspace() -> Layout<u32> {
second: Box::new(tabs(&[0], 0)),
}),
floating: vec![
Floating::stack(vec![Group::new(vec![5], 0), Group::new(vec![6, 7], 1)], [300.0, 80.0, 240.0, 300.0]),
Floating::stack(vec![Group::new(vec![5], 0), Group::new(vec![6, 7], 1)], [300.0, 80.0, 240.0, 300.0]).unwrap(),
Floating::single(vec![8], 0, [560.0, 300.0, 200.0, 200.0]),
],
}
@@ -678,7 +678,7 @@ fn undock_panel_group_floats_one_group_of_a_floating_window() {
use egui_kittest::{Harness, kittest::Queryable};
let stacked = || Layout {
root: Some(tabs(&[1], 0)),
floating: vec![Floating::stack(vec![Group::new(vec![5], 0), Group::new(vec![6, 7], 0)], [300.0, 80.0, 300.0, 400.0])],
floating: vec![Floating::stack(vec![Group::new(vec![5], 0), Group::new(vec![6, 7], 0)], [300.0, 80.0, 300.0, 400.0]).unwrap()],
};
type State = (Content, Layout<u32>, Vec<Action<u32>>);
let mut h = Harness::builder().with_size(vec2(800.0, 600.0)).build_ui_state(
+1 -1
View File
@@ -551,7 +551,7 @@ fn drag_bar_menu_has_the_group_items_and_opens_from_the_keyboard() {
#[test]
fn a_group_dragged_out_of_a_floating_stack_tears_out_alone() {
let stack = Floating::stack(vec![Group::new(vec![5], 0), Group::new(vec![6, 7], 1)], [80.0, 70.0, 300.0, 400.0]);
let stack = Floating::stack(vec![Group::new(vec![5], 0), Group::new(vec![6, 7], 1)], [80.0, 70.0, 300.0, 400.0]).unwrap();
let layout = Layout { root: Some(tabs(&[1], 0)), floating: vec![stack] };
let mut d = Driver::new(layout);
let from = d.rect("drag-bar", 6).center();
+1 -1
View File
@@ -283,7 +283,7 @@ fn hit_rects_cover_every_gesture_source() {
first: Box::new(Node::Tabs { panels: vec![0], active: 0 }),
second: Box::new(stack),
};
let window = Floating::stack(vec![Group::new(vec![12], 0), Group::new(vec![13], 0)], [500.0, 60.0, 220.0, 300.0]);
let window = Floating::stack(vec![Group::new(vec![12], 0), Group::new(vec![13], 0)], [500.0, 60.0, 220.0, 300.0]).unwrap();
let layout = Layout {
root: Some(Node::Split { axis: SplitAxis::Horizontal, size: SplitSize::FixedFirst(200.0), first: Box::new(dock), second: Box::new(frames) }),
floating: vec![window],
+4 -4
View File
@@ -87,10 +87,10 @@ impl<P> Floating<P> {
tree_panels(&self.root).into_iter()
}
/// Groups stacked top to bottom, as a v1 layout stored them.
#[cfg(test)]
pub(crate) fn stack(groups: Vec<Group<P>>, rect: [f32; 4]) -> Self {
Self::new(super::dock_model::stack_tree(groups).expect("a group"), rect)
/// Groups stacked top to bottom, as a v1 layout stored them: each group over a vertical
/// split of the ones below it, sized by their heights. `None` without groups.
pub fn stack(groups: Vec<Group<P>>, rect: [f32; 4]) -> Option<Self> {
super::dock_model::stack_tree(groups).map(|root| Self::new(root, rect))
}
/// The window's groups, first to last.
+3 -3
View File
@@ -23,7 +23,7 @@ pub(super) fn docked() -> Layout<u32> {
Node::Dock { side: Side::End, columns: vec![Column::new(vec![g(&[2, 3], 1), g(&[4], 0)], 180.0), Column::new(vec![g(&[5], 0)], 120.0)] };
Layout {
root: Some(hsplit(SplitSize::FixedSecond(300.0), middle, dock)),
floating: vec![Floating::stack(vec![g(&[6, 7], 1), g(&[8], 0)], [100.0, 100.0, 300.0, 400.0])],
floating: vec![Floating::stack(vec![g(&[6, 7], 1), g(&[8], 0)], [100.0, 100.0, 300.0, 400.0]).unwrap()],
}
}
@@ -213,7 +213,7 @@ fn restore_into_a_column_group_seam_and_new_column_round_trips() {
let start = Node::Dock { side: Side::Start, columns: vec![Column::new(vec![g(&[10], 0)], 200.0)] };
let original = Layout {
root: Some(hsplit(SplitSize::FixedFirst(200.0), start, hsplit(SplitSize::FixedSecond(300.0), tabs(&[1], 0), end))),
floating: vec![Floating::stack(vec![g(&[6], 0), Group { height: Some(90.0), ..g(&[7], 0) }], [10.0, 10.0, 200.0, 300.0])],
floating: vec![Floating::stack(vec![g(&[6], 0), Group { height: Some(90.0), ..g(&[7], 0) }], [10.0, 10.0, 200.0, 300.0]).unwrap()],
};
original.validate().unwrap();
// 4: the seam between [2, 3] and [9]; 9: below [4]; 5: a new column after A; 10: alone in
@@ -415,7 +415,7 @@ mod serde_tests {
let plain = Floating::single(vec![4u32, 5], 1, rect);
assert_eq!(serde_json::to_value(&plain).unwrap(), json!({"panels": [4, 5], "active": 1, "rect": rect}));
let minimized = Floating { minimized: true, ..Floating::single(vec![4u32], 0, rect) };
let stacked = Floating::stack(vec![g(&[4], 0), g(&[5], 0)], rect);
let stacked = Floating::stack(vec![g(&[4], 0), g(&[5], 0)], rect).unwrap();
let native = Floating { native: Some(rect), ..Floating::single(vec![4u32], 0, rect) };
for window in [minimized, stacked, native] {
let value = serde_json::to_value(&window).unwrap();
+2 -1
View File
@@ -295,7 +295,8 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
None => self.id.with(("intersection", outer, inner_layout)),
Some((panel, _)) => self.id.with(("window-intersection", panel, outer, inner_layout)),
};
let response = ui.interact(hit, id, Sense::drag());
// Pointer only: each of the two dividers it joins already takes keyboard focus.
let response = ui.interact(hit, id, Sense::DRAG);
self.register_interaction(ui.ctx(), id);
if response.hovered() || response.dragged() {
ui.ctx().set_cursor_icon(CursorIcon::Move);
+3 -2
View File
@@ -39,8 +39,9 @@ their sides.
A `Floating` window holds a tree like the docked layout's (`root`: splits, tab groups and
accordions, no docks), a rect, an optional native-window rect and `minimized`.
`Floating::single` makes a window with one group and `Floating::new` one with a tree; `panels()`
and `contains()` read it. Saved layouts keep loading: a window stored as one group (v1) or as a
`Floating::single` makes a window with one group, `Floating::new` one with a tree, and
`Floating::stack` one with groups stacked top to bottom (a vertical split sized by the groups'
heights, as an app's own stacked layouts migrate); `panels()` and `contains()` read it. Saved layouts keep loading: a window stored as one group (v1) or as a
stack of groups loads as that group or as a vertical split of the stacked groups, sized by their
heights.