diff --git a/crates/ui/src/docking/drop_tests.rs b/crates/ui/src/docking/drop_tests.rs index 8e74ad59..b6006aa5 100644 --- a/crates/ui/src/docking/drop_tests.rs +++ b/crates/ui/src/docking/drop_tests.rs @@ -133,13 +133,8 @@ fn a_group_that_takes_no_tabs_maps_its_whole_body_to_its_edges() { let canvas = Permissions { accepts_tabs: false, ..Permissions::default() }; let d = driver(&[(1, canvas)]); let (body, rect) = group(&d, 1); - // By default only its top and bottom: left of center goes to the nearer of those. - for (zone, at, edges) in [ - (Zone::Left, body.center() - vec2(10.0, 2.0), Sides::ALL), - (Zone::Top, body.center() - vec2(10.0, 2.0), DockStyle::default().drop_edges), - (Zone::Bottom, body.center() + vec2(-10.0, 2.0), DockStyle::default().drop_edges), - ] { - let mut d = driver(&[(1, canvas)]).with_style(move |style| style.drop_edges = edges); + for (zone, at) in [(Zone::Left, body.center() - vec2(10.0, 0.0)), (Zone::Top, body.center() - vec2(0.0, 10.0))] { + let mut d = driver(&[(1, canvas)]); d.hold(tab(&d, 3), at); assert!(d.filled(line(rect, zone)), "{zone:?}"); let out = d.release(at); @@ -171,7 +166,7 @@ fn a_side_without_room_joins_as_a_tab_or_offers_nothing() { let out = d.drag(tab(&d, 3), top); assert_eq!(out.actions, vec![Action::Move { panel: 3, anchor: 1, placement: Placement::Split(Zone::Top) }]); // A group that takes no tabs offers nothing there. - let mut d = narrow(&[(1, Permissions { accepts_tabs: false, ..Permissions::default() })]).with_style(|style| style.drop_edges = Sides::ALL); + let mut d = narrow(&[(1, Permissions { accepts_tabs: false, ..Permissions::default() })]); d.hold(tab(&d, 3), left); assert!(!d.outlined(rect.shrink(1.0))); let out = d.release(left); @@ -275,22 +270,14 @@ fn drop_edges_limit_the_outer_edges_and_the_canvas_sides() { // The canvas has no tab strip: its body is its frame. let (canvas, _) = group(&d, 0); let (side, _) = group(&d, 1); - // The bottom edge takes nothing. - let at = pos2(canvas.center().x, 590.0); - let mut d = canvas_driver(Permissions::PROTECTED, left_right); - d.hold(tab(&d, 3), at); - assert!(!d.filled(line(canvas, Zone::Bottom))); - assert!(!moves(&d.release(at))); - // The left edge splits around the canvas column. - let at = pos2(canvas.left() + 5.0, canvas.center().y); - let mut d = canvas_driver(Permissions::PROTECTED, left_right); - d.hold(tab(&d, 3), at); - assert!(d.filled(line(canvas, Zone::Left)), "a line along the canvas column's left edge"); - let out = d.release(at); - assert_eq!(out.actions, vec![Action::Move { panel: 3, anchor: 0, placement: Placement::Edge(Zone::Left) }]); - let column = split(SplitAxis::Horizontal, SplitSize::Ratio(0.25), tabs(&[3]), tabs(&[0])); - let sides = split(SplitAxis::Horizontal, SplitSize::FixedSecond(200.0), column, tabs(&[2])); - assert_eq!(d.layout.root, Some(split(SplitAxis::Horizontal, SplitSize::FixedFirst(200.0), tabs(&[1]), sides))); + // Without top and bottom, no outer edge takes a drop: left and right are docks here. + for at in [pos2(canvas.center().x, 590.0), pos2(canvas.left() + 5.0, 300.0), pos2(canvas.right() - 5.0, 300.0)] { + let mut d = canvas_driver(Permissions::PROTECTED, left_right); + d.hold(tab(&d, 3), at); + assert!(!d.filled(line(canvas, Zone::Bottom)), "{at:?}"); + let out = d.release(at); + assert!(!moves(&out), "{at:?}: {:?}", out.actions); + } // A side frame keeps all four sides. for (zone, y) in [(Zone::Bottom, 590.0), (Zone::Top, side.top() + side.height() * 0.2)] { let mut d = canvas_driver(Permissions::PROTECTED, left_right); @@ -310,13 +297,21 @@ fn a_canvas_that_takes_splits_takes_them_only_on_its_drop_edges() { let beside = Permissions { accepts_splits: true, ..Permissions::PROTECTED }; let d = canvas_driver(beside, |_| {}); let (body, _) = group(&d, 0); - let at = pos2(body.left() + body.width() * 0.3, body.bottom() - 40.0); - for (edges, zone) in [(DockStyle::default().drop_edges, Some(Zone::Bottom)), (Sides::LEFT_RIGHT, Some(Zone::Left)), (Sides::NONE, None)] { - let mut d = canvas_driver(beside, move |style| style.drop_edges = edges); - let out = d.drag(tab(&d, 3), at); - match zone { - Some(zone) => assert_eq!(out.actions, vec![Action::Move { panel: 3, anchor: 0, placement: Placement::Split(zone) }], "{edges:?}"), - None => assert!(!moves(&out), "{:?}", out.actions), + // Nearer the bottom than the left, and right of center nearer the right than the top. + let low = pos2(body.left() + body.width() * 0.3, body.bottom() - 40.0); + let high = pos2(body.right() - body.width() * 0.3, body.top() + 40.0); + for (edges, zones) in [ + (DockStyle::default().drop_edges, [Some(Zone::Bottom), Some(Zone::Top)]), + (Sides::LEFT_RIGHT, [Some(Zone::Left), Some(Zone::Right)]), + (Sides::NONE, [None, None]), + ] { + for (at, zone) in [low, high].into_iter().zip(zones) { + let mut d = canvas_driver(beside, move |style| style.drop_edges = edges); + let out = d.drag(tab(&d, 3), at); + match zone { + Some(zone) => assert_eq!(out.actions, vec![Action::Move { panel: 3, anchor: 0, placement: Placement::Split(zone) }], "{edges:?}"), + None => assert!(!moves(&out), "{edges:?}: {:?}", out.actions), + } } } } diff --git a/crates/ui/src/docking/render/frame.rs b/crates/ui/src/docking/render/frame.rs index 72071eaf..02dd25d1 100644 --- a/crates/ui/src/docking/render/frame.rs +++ b/crates/ui/src/docking/render/frame.rs @@ -170,7 +170,7 @@ impl Renderer<'_, P> { paths.and_then(|paths| paths.get(path).cloned()).unwrap_or_else(|| path.to_vec()) } - /// The outer edge band under the pointer (P8): the sides of + /// The outer edge band under the pointer (P8): the top and bottom of /// [`crate::docking::DockStyle::drop_edges`], all four with /// [`crate::docking::DockStyle::side_edge_zones`]. Returns a docked anchor that is not /// moving, the zone and the preview: a line along the edge area's whole edge, or with side @@ -181,7 +181,7 @@ impl Renderer<'_, P> { let band = if self.style.side_edge_zones { crate::docking::targets::SIDE_EDGE_BAND } else { self.style.edge_band }; let band = band.min(area.width() / 4.0).min(area.height() / 4.0); let line = |zone| crate::docking::targets::edge_line(area, zone, self.style.drop_line_width); - let sides = self.style.drop_sides(); + let sides = self.style.outer_edges(); let edges = [ (pointer.y - area.top(), Zone::Top), (area.bottom() - pointer.y, Zone::Bottom), diff --git a/crates/ui/src/docking/surface.rs b/crates/ui/src/docking/surface.rs index a4a41f2e..01e9a838 100644 --- a/crates/ui/src/docking/surface.rs +++ b/crates/ui/src/docking/surface.rs @@ -64,11 +64,11 @@ pub struct DockStyle { /// bottom edges add a full-width row, a group's zones are [`Self::side_zone_fraction`] of its /// body, and every preview is a line or a tab outline. pub side_edge_zones: bool, - /// The layout's outer edges that take a drop, a new frame along that edge, and the sides of - /// a group that takes no tabs (a canvas) that take a split (top and bottom; the left and - /// right window edges are new docks). With [`crate::docking::Sides::NONE`], palettes dock - /// only through the dock bands. Not used with [`Self::side_edge_zones`], which takes all - /// four. Groups that take tabs keep all four sides. + /// Which sides of a group that takes no tabs (a canvas), and of the layout's outer edge, take + /// a drop (all four). Without [`Self::side_edge_zones`] the outer left and right edges are + /// new docks, so only top and bottom apply there. A dock-column app whose palettes dock only + /// at the left and right sets [`crate::docking::Sides::NONE`] with [`Self::new_dock_zones`] + /// on. Not used with [`Self::side_edge_zones`]. Groups that take tabs keep all four sides. pub drop_edges: super::Sides, /// The share of a group's body, below the tab strip, that means "beside" on each side: of /// its width from the left and right edges, of its height from the top and bottom edges @@ -196,7 +196,7 @@ impl DockStyle { title_height: super::chrome::TITLE_HEIGHT, edge_band: 28.0, side_edge_zones: false, - drop_edges: super::Sides::TOP_BOTTOM, + drop_edges: super::Sides::ALL, side_zone_fraction: 1.0 / 3.0, drop_line_width: 4.0, new_dock_zones: true, @@ -234,12 +234,19 @@ impl DockStyle { } } - /// The sides [`Self::drop_edges`] gives in this profile: all four with + /// The canvas sides [`Self::drop_edges`] gives in this profile: all four with /// [`Self::side_edge_zones`]. pub(super) fn drop_sides(&self) -> super::Sides { if self.side_edge_zones { super::Sides::ALL } else { self.drop_edges } } + /// The outer edges that take a drop: all four with [`Self::side_edge_zones`], else the top + /// and bottom of [`Self::drop_edges`]. + pub(super) fn outer_edges(&self) -> super::Sides { + let sides = self.drop_sides(); + if self.side_edge_zones { sides } else { super::Sides { left: false, right: false, ..sides } } + } + /// How a group's body divides into drop zones: [`crate::docking::SIDE_EDGE_ZONE_DEPTH`] deep /// with [`Self::side_edge_zones`], else [`Self::side_zone_fraction`] of it, with /// [`Self::drop_line_width`] previews. Pass it to [`crate::docking::resolve_drop_by`]. diff --git a/docs/ui-docking.md b/docs/ui-docking.md index e2ff84a7..93a42930 100644 --- a/docs/ui-docking.md +++ b/docs/ui-docking.md @@ -250,7 +250,7 @@ A flyout fits its content the same way, without the title bar. | The seam between two stacked groups of a dock column | A new group between them | `seam_band` (28 pt) | | Each edge of a dock column | A new column on that side | `column_edge_band` (28 pt) | | The docked area's left or right edge without a dock, and an empty dock | A new dock | `new_dock_band` (28 pt) | - | The edge area's sides in `drop_edges` (top and bottom; all four with `side_edge_zones`) | A new frame along that whole side, a quarter of the edge area | `edge_band` (28 pt, 8 pt with `side_edge_zones`; at most a quarter of the edge area) | + | The edge area's top and bottom in `drop_edges` (all four sides with `side_edge_zones`) | A new frame along that whole side, a quarter of the edge area | `edge_band` (28 pt, 8 pt with `side_edge_zones`; at most a quarter of the edge area) | Where a band overlaps a group's outer third, both mean the same side. Without `side_edge_zones`, a band reaches only 4 pt past its line into a tab strip that takes tabs; @@ -261,12 +261,13 @@ A flyout fits its content the same way, without the title bar. one side holds a protected panel, that side. Side panels in split frames then get no band across the whole window, and the drop wraps only the canvas column. A drop on the band anchors on the canvas, so `Placement::Edge` narrows the same way when applied. -- `DockStyle::drop_edges` (a `Sides`) lists the edge area's sides that take a drop, and the - sides of a group that takes no tabs (a canvas) that take a split; there, the whole body goes - to the nearest listed side, and with none listed the canvas takes nothing. Groups that take - tabs keep all four sides. The default is top and bottom. An app whose palettes dock only - through the dock bands sets `Sides::NONE`; one whose palettes sit left and right of the - canvas sets `Sides::LEFT_RIGHT`. `side_edge_zones` uses all four and ignores it. +- `DockStyle::drop_edges` (a `Sides`, all four by default) lists which sides of a group that + takes no tabs (a canvas), and of the layout's outer edge, take a drop. A canvas's whole body + goes to the nearest listed side; with none listed it takes nothing. In the docking profile + the outer left and right edges are new docks, so only top and bottom apply there. Groups + that take tabs keep all four sides. A dock-column app whose palettes dock only at the left + and right, as in AutoCAD, sets `Sides::NONE` with `new_dock_zones` on. Not used with + `side_edge_zones`. - With `side_edge_zones` (the split-tree arrangement), a frame's zones are a picture frame instead: trapezoids along the four edges of the body below the tab strip, all four the same depth, `docking::SIDE_EDGE_ZONE_DEPTH` (40 pt), or a quarter of the body's smaller side when