mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-01 16:24:51 +00:00
docking: a canvas body takes no drop in the docking profile (#323)
With new_dock_zones (and without side_edge_zones), the body of a frame or floating group that takes no tabs is no drop target, whatever its accepts_splits. A floating window released there stays floating and a docked panel floats as over empty space; the outer edge band, dock columns, new docks and drops beside other groups still dock. Without new_dock_zones the whole canvas body still goes to the nearest of drop_edges.
This commit is contained in:
1 parent
7010c5e93e
commit
bab74e1f4c
6 files changed
+115
-30
No files matched your search
@@ -122,16 +122,17 @@ fn a_drop_on_a_groups_outer_third_docks_beside_it_with_its_rect_and_its_middle_j
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_group_that_takes_no_tabs_maps_its_whole_body_to_its_edges() {
|
||||
fn without_new_dock_zones_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 start = || driver(&[(1, canvas)]).with_style(|style| style.new_dock_zones = false);
|
||||
let d = start();
|
||||
let (body, rect) = group(&d, 1);
|
||||
for (zone, at) in [(Zone::Left, body.center() - vec2(10.0, 0.0)), (Zone::Top, body.center() - vec2(0.0, 10.0))] {
|
||||
let out = previews_result(driver(&[(1, canvas)]), tab(&d, 3), at, |d| group(d, 3).1);
|
||||
let out = previews_result(start(), tab(&d, 3), at, |d| group(d, 3).1);
|
||||
assert_eq!(out, vec![Action::Move { panel: 3, anchor: 1, placement: Placement::Split(zone) }], "{zone:?}");
|
||||
}
|
||||
// Its strip joins nothing.
|
||||
let mut d = driver(&[(1, canvas)]);
|
||||
let mut d = start();
|
||||
let out = d.drag(tab(&d, 3), pos2(rect.center().x, rect.top() + 13.0));
|
||||
assert!(!out.actions.iter().any(|a| matches!(a, Action::Move { anchor: 1, placement: Placement::Tab { .. }, .. })), "{:?}", out.actions);
|
||||
}
|
||||
@@ -345,7 +346,7 @@ fn drop_edges_limit_the_outer_edges_and_the_canvas_sides() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_canvas_that_takes_splits_takes_them_only_on_its_drop_edges() {
|
||||
fn without_new_dock_zones_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);
|
||||
@@ -358,12 +359,20 @@ fn a_canvas_that_takes_splits_takes_them_only_on_its_drop_edges() {
|
||||
(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 split_frames = move |style: &mut DockStyle| {
|
||||
style.new_dock_zones = false;
|
||||
style.drop_edges = edges;
|
||||
};
|
||||
let mut d = canvas_driver(beside, split_frames);
|
||||
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),
|
||||
}
|
||||
// With new dock zones the canvas body takes no drop on any side.
|
||||
let mut d = canvas_driver(beside, move |style| style.drop_edges = edges);
|
||||
let out = d.drag(tab(&d, 3), at);
|
||||
assert!(!moves(&out), "{edges:?}: {:?}", out.actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ const ORIGIN: Pos2 = pos2(100.0, 50.0);
|
||||
struct Content {
|
||||
/// Panels that may not close.
|
||||
fixed: Vec<u32>,
|
||||
/// The protected canvas that takes splits beside it, as PhotoCraft's does.
|
||||
canvas: Option<u32>,
|
||||
/// Panels that stay in-window.
|
||||
in_window: Vec<u32>,
|
||||
/// Viewer panels: their windows are normal windows.
|
||||
@@ -121,6 +123,9 @@ impl DockContent<u32> for Content {
|
||||
}
|
||||
|
||||
fn permissions(&self, panel: &u32) -> Permissions {
|
||||
if self.canvas == Some(*panel) {
|
||||
return Permissions { accepts_splits: true, ..Permissions::PROTECTED };
|
||||
}
|
||||
Permissions { closable: !self.fixed.contains(panel), ..Permissions::default() }
|
||||
}
|
||||
|
||||
@@ -982,6 +987,55 @@ fn a_native_windows_bar_docks_it_at_frame_edges_and_dock_lines_but_not_a_body_ce
|
||||
assert!(in_root(&d, 5) && in_root(&d, 6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn over_a_canvas_body_a_window_stays_floating_and_a_tab_floats_while_the_edge_bands_dock() {
|
||||
// PhotoCraft: a dock column of [2] above [3, 4] beside canvas 0, which takes splits, and
|
||||
// window [5, 6] floating.
|
||||
let accent = Color32::from_rgb(250, 10, 200);
|
||||
let dock = Node::Dock {
|
||||
side: super::Side::Start,
|
||||
columns: vec![super::Column::new(vec![super::Group::new(vec![2], 0), super::Group::new(vec![3, 4], 0)], 240.0)],
|
||||
};
|
||||
let root = Node::Split { axis: SplitAxis::Horizontal, size: SplitSize::FixedFirst(240.0), first: Box::new(dock), second: Box::new(tabs(&[0])) };
|
||||
let layout = Layout { root: Some(root), floating: vec![Floating::single(vec![5, 6], 0, [300.0, 80.0, 240.0, 200.0])] };
|
||||
let start = || {
|
||||
DockDriver::new(layout.clone(), Content { canvas: Some(0), ..Content::default() })
|
||||
.with_style(move |s| {
|
||||
s.accent = accent;
|
||||
s.float_hint = false;
|
||||
})
|
||||
.with_native_host(ORIGIN)
|
||||
};
|
||||
// The canvas spans x = 244 to 800 and the whole height: no tab strip.
|
||||
for at in [pos2(520.0, 150.0), pos2(520.0, 450.0)] {
|
||||
let mut d = start();
|
||||
let before = accented(d.shapes(), accent);
|
||||
let id = window(&d, 5).id;
|
||||
d.window_step(id, vec![Event::PointerMoved(pos2(40.0, 9.0)), button(pos2(40.0, 9.0), true)]);
|
||||
d.window_step(id, vec![Event::PointerMoved(pos2(60.0, 19.0))]);
|
||||
let local = d.window_local(id, ORIGIN + at.to_vec2()).unwrap();
|
||||
d.window_step(id, vec![Event::PointerMoved(local)]);
|
||||
d.step(vec![]);
|
||||
assert_eq!(accented(d.shapes(), accent), before, "{at:?}: no preview");
|
||||
let out = d.window_step(id, vec![button(pos2(40.0, 9.0), false)]);
|
||||
assert_eq!(window_move(&out), None, "{at:?}: {:?}", out.actions);
|
||||
assert!(window(&d, 5).contains(&6), "{at:?}");
|
||||
}
|
||||
// A docked tab dropped there floats.
|
||||
let mut d = start();
|
||||
let tab = d.hit(&Hit::Tab(3)).unwrap().center();
|
||||
let out = d.drag(tab, pos2(520.0, 450.0));
|
||||
assert!(matches!(out.actions.as_slice(), [Action::Float { panel: 3, .. }]), "{:?}", out.actions);
|
||||
// The window's right edge, where no dock is yet, and the bottom edge band beside the canvas
|
||||
// still dock the window.
|
||||
let mut d = start();
|
||||
let out = drag_bar_to(&mut d, 5, pos2(795.0, 300.0));
|
||||
assert_eq!(window_move(&out), Some(&Target::NewDock { side: super::Side::End }), "{:?}", out.actions);
|
||||
let mut d = start();
|
||||
let out = drag_bar_to(&mut d, 5, pos2(520.0, 590.0));
|
||||
assert!(matches!(window_move(&out), Some(Target::At { placement: Placement::Edge(super::Zone::Bottom), .. })), "{:?}", out.actions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_during_a_bar_drag_puts_the_window_back() {
|
||||
let mut d = driver(layout(true));
|
||||
|
||||
@@ -198,11 +198,11 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
|
||||
/// [`crate::docking::resolve_drop_by`] with [`crate::docking::DockStyle::zone_rule`] and the
|
||||
/// canvas sides of [`crate::docking::DockStyle::drop_edges`], where a split that would leave
|
||||
/// a group without room becomes a join of the target group, or no target when the group
|
||||
/// takes no tabs. A drop that would change only sizes is a no-op ([`Self::settled`]).
|
||||
/// canvas sides of [`crate::docking::DockStyle::drop_edges`] (none in the docking profile),
|
||||
/// where a split that would leave a group without room becomes a join of the target group,
|
||||
/// or no target when the group takes no tabs. A drop that would change only sizes is a no-op ([`Self::settled`]).
|
||||
fn resolve_roomy(&self, panel: &P, pointer: Pos2, targets: &[DropTarget<P>], action: impl Fn(Target<P>) -> Action<P>) -> Option<DropProposal<P>> {
|
||||
let proposal = resolve_drop_on(panel, pointer, targets, self.style.zone_rule(), self.style.drop_sides())?;
|
||||
let proposal = resolve_drop_on(panel, pointer, targets, self.style.zone_rule(), self.style.canvas_sides())?;
|
||||
if !matches!(proposal.placement, Placement::Split(zone) if zone != Zone::Center) {
|
||||
return Some(self.settled(proposal, action));
|
||||
}
|
||||
@@ -220,7 +220,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
pointer,
|
||||
std::slice::from_ref(&DropTarget { allow_splits: false, ..target.clone() }),
|
||||
self.style.zone_rule(),
|
||||
self.style.drop_sides(),
|
||||
self.style.canvas_sides(),
|
||||
)?;
|
||||
Some(self.settled(proposal, action))
|
||||
}
|
||||
|
||||
@@ -64,11 +64,13 @@ 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,
|
||||
/// 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.
|
||||
/// Which sides of the layout's outer edge, and of a group that takes no tabs (a canvas)
|
||||
/// without [`Self::new_dock_zones`], take a drop (all four). With [`Self::new_dock_zones`]
|
||||
/// a canvas body takes no drop, and 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
|
||||
@@ -81,8 +83,10 @@ pub struct DockStyle {
|
||||
/// zero). Other drops beside a group, column, dock or the layout's edge preview the rect the
|
||||
/// moved panels take. Border bands reach 4 pt past this line into a tab strip.
|
||||
pub drop_line_width: f32,
|
||||
/// [D] The docked area's left and right edges, where no dock is yet, take a drop as a new
|
||||
/// dock (see [`Self::new_dock_band`]).
|
||||
/// [D] The docking profile. The docked area's left and right edges, where no dock is yet,
|
||||
/// take a drop as a new dock (see [`Self::new_dock_band`]). Without
|
||||
/// [`Self::side_edge_zones`], the body of a frame or floating group that takes no tabs (a
|
||||
/// canvas) takes no drop: a release there floats as over no target.
|
||||
pub new_dock_zones: bool,
|
||||
/// [D] Height of the bar above each dock column. It drags the column and holds its »/« button.
|
||||
pub column_bar_height: f32,
|
||||
@@ -237,12 +241,20 @@ impl DockStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// The canvas sides [`Self::drop_edges`] gives in this profile: all four with
|
||||
/// The 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 sides of a frame or floating group that takes no tabs (a canvas) whose body takes a
|
||||
/// drop: none in the docking profile ([`Self::new_dock_zones`] without
|
||||
/// [`Self::side_edge_zones`]), where only the bands along borders dock beside it, else
|
||||
/// [`Self::drop_sides`].
|
||||
pub(super) fn canvas_sides(&self) -> super::Sides {
|
||||
if self.side_edge_zones || !self.new_dock_zones { self.drop_sides() } else { super::Sides::NONE }
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
||||
@@ -4,6 +4,8 @@ use super::{Placement, Side, Target, Zone};
|
||||
|
||||
/// One visible group, supplied in back-to-front order to [`resolve_drop`]. An empty tab strip
|
||||
/// or a group that rejects tabs may still accept side splits, e.g. a protected document canvas.
|
||||
/// The dock area's docking profile ([`crate::docking::DockStyle::new_dock_zones`]) gives the
|
||||
/// body of a frame or floating group that rejects tabs no target at all.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DropTarget<P> {
|
||||
pub anchor: P,
|
||||
@@ -75,9 +77,10 @@ pub fn drop_zone(rect: Rect, tab_height: f32, depth: f32, pointer: Pos2) -> Opti
|
||||
/// In the body below it, the outer `fraction` of its width from the left or right edge, or of
|
||||
/// its height from the top or bottom edge, is that side's zone; where two sides' zones meet, the
|
||||
/// side the pointer is relatively nearer wins. The rest is the center. Without `center` (a group
|
||||
/// that takes no tabs) the whole body goes to the relatively nearest side. A `fraction` of zero
|
||||
/// or less leaves only the center; more than one half acts as one half. Outside, non-finite,
|
||||
/// empty and inverted rectangles have no target.
|
||||
/// that takes no tabs) the whole body goes to the relatively nearest side, as the dock area has
|
||||
/// it without [`crate::docking::DockStyle::new_dock_zones`]; with it, such a body is no target.
|
||||
/// A `fraction` of zero or less leaves only the center; more than one half acts as one half.
|
||||
/// Outside, non-finite, empty and inverted rectangles have no target.
|
||||
pub fn body_zone(rect: Rect, tab_height: f32, fraction: f32, center: bool, pointer: Pos2) -> Option<Zone> {
|
||||
body_zone_on(rect, tab_height, fraction, center, Sides::ALL, pointer)
|
||||
}
|
||||
@@ -271,7 +274,7 @@ pub fn resolve_drop_by<P: Clone + Eq>(panel: &P, pointer: Pos2, targets: &[DropT
|
||||
}
|
||||
|
||||
/// [`resolve_drop_by`] where a group that takes no tabs splits only on `sides` with
|
||||
/// [`ZoneRule::Fraction`].
|
||||
/// [`ZoneRule::Fraction`], and with none is no target.
|
||||
pub(super) fn resolve_drop_on<P: Clone + Eq>(
|
||||
panel: &P,
|
||||
pointer: Pos2,
|
||||
|
||||
+14
-7
@@ -239,8 +239,14 @@ A flyout fits its content the same way, without the title bar.
|
||||
beside the group on that side. Where two sides meet, at a corner, the side the pointer is
|
||||
relatively nearer wins.
|
||||
- The middle third joins the group's tabs.
|
||||
- A group that takes no tabs, such as a protected canvas, has no middle: every point of its
|
||||
body docks beside it on the relatively nearest side. Its strip takes nothing.
|
||||
- A group that takes no tabs, such as a protected canvas, takes nothing on its strip, and its
|
||||
body has no middle: every point docks beside it on the relatively nearest side.
|
||||
- In the docking profile (`new_dock_zones` without `side_edge_zones`), the body of a frame or
|
||||
floating group that takes no tabs takes nothing, whatever its `accepts_splits`: a release
|
||||
there is a release over no target, so a floating window stays floating and a docked panel
|
||||
or group floats as `float_on_empty_drop` says. Only the bands along borders (below) dock
|
||||
beside it, as in Adobe apps, whose document area is no drop zone. A dock column's group
|
||||
keeps its sides.
|
||||
- Beside a frame is a split of that frame. Beside a group in a dock column, the top and bottom
|
||||
stack a new group above or below it, and the left and right make a new column on that side.
|
||||
- A frame too narrow or short for two halves of `DockStyle::min_pane` still splits when the
|
||||
@@ -276,11 +282,12 @@ A flyout fits its content the same way, without the title bar.
|
||||
`window.panel.dock` to an outer edge picks its anchor the same way, from the permissions
|
||||
`apply` (or `to_actions`) gets and the area's `FloatSpace` (`with_side_edge_zones` before it
|
||||
has drawn).
|
||||
- `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
|
||||
- `DockStyle::drop_edges` (a `Sides`, all four by default) lists which sides of the layout's
|
||||
outer edge, and of a group that takes no tabs (a canvas) without `new_dock_zones`, take a
|
||||
drop. There a canvas's whole body goes to the nearest listed side; with none listed it takes
|
||||
nothing. In the docking profile the canvas body takes no drop and the outer left and right
|
||||
edges are new docks, so only the outer top and bottom apply. 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
|
||||
|
||||
Reference in new issue
Block a user