mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-31 15:49:54 +00:00
Merge commit 'e8b4dde5' into docking-native-ime
This commit is contained in:
commit
cd25951cd8
5 files changed
+72
-25
No files matched your search
@@ -326,17 +326,25 @@ pub(super) fn split_frame<P>(root: &mut Node<P>, path: &[bool], new: Node<P>, zo
|
||||
true
|
||||
}
|
||||
|
||||
/// [`split_frame`] with the sizes of [`Placement::SplitSized`]: the new split takes `size`, and
|
||||
/// the nearest enclosing split whose divider runs along the frame's `zone` side takes `outer`.
|
||||
/// False when there is no such split, or a dock is beyond it.
|
||||
pub(super) fn split_frame_sized<P>(root: &mut Node<P>, path: &[bool], new: Node<P>, zone: Zone, size: SplitSize, outer: SplitSize) -> bool {
|
||||
let axis = if matches!(zone, Zone::Top | Zone::Bottom) { SplitAxis::Vertical } else { SplitAxis::Horizontal };
|
||||
/// The depth along the frame `path` of the split whose size a [`Placement::SplitSized`] on
|
||||
/// `zone` replaces: the nearest enclosing split along the zone's axis (`on_axis` at a depth)
|
||||
/// whose divider runs along the frame's `zone` side, else, for a frame at the edge of its
|
||||
/// area on that side, the nearest one along its opposite side.
|
||||
pub(super) fn sized_split_depth(path: &[bool], zone: Zone, on_axis: impl Fn(usize) -> bool) -> Option<usize> {
|
||||
// A divider on the frame's start side (left or top) belongs to a split holding the frame
|
||||
// in its second child.
|
||||
let start = matches!(zone, Zone::Left | Zone::Top);
|
||||
let enclosing = (0..path.len()).rev().find(|&depth| {
|
||||
path.get(depth) == Some(&start) && matches!(node_at(root, &path[..depth]), Some(Node::Split { axis: own, .. }) if *own == axis)
|
||||
});
|
||||
let nearest = |second: bool| (0..path.len()).rev().find(|&depth| path.get(depth) == Some(&second) && on_axis(depth));
|
||||
nearest(start).or_else(|| nearest(!start))
|
||||
}
|
||||
|
||||
/// [`split_frame`] with the sizes of [`Placement::SplitSized`]: the new split takes `size`, and
|
||||
/// the split [`sized_split_depth`] finds takes `outer`. False when there is no such split, or a
|
||||
/// dock is beyond it.
|
||||
pub(super) fn split_frame_sized<P>(root: &mut Node<P>, path: &[bool], new: Node<P>, zone: Zone, size: SplitSize, outer: SplitSize) -> bool {
|
||||
let axis = if matches!(zone, Zone::Top | Zone::Bottom) { SplitAxis::Vertical } else { SplitAxis::Horizontal };
|
||||
let enclosing =
|
||||
sized_split_depth(path, zone, |depth| matches!(node_at(root, &path[..depth]), Some(Node::Split { axis: own, .. }) if *own == axis));
|
||||
let Some(depth) = enclosing else { return false };
|
||||
let Some(Node::Split { size: own, first, second, .. }) = node_at_mut(root, &path[..depth]) else { return false };
|
||||
if zone == Zone::Center || matches!(**first, Node::Dock { .. }) || matches!(**second, Node::Dock { .. }) {
|
||||
|
||||
@@ -310,9 +310,10 @@ pub enum Placement<P> {
|
||||
/// A side [`Placement::Split`] that gives the new frame its own room instead of half the
|
||||
/// anchor's frame: the new split takes `size`, and `outer` replaces the size of the nearest
|
||||
/// enclosing split whose divider runs along the anchor frame's `zone` side, so the frames
|
||||
/// beyond that divider give the room. The renderer offers it beside a frame too small to
|
||||
/// halve. `Center`, and an anchor without such a divider or with a dock beyond it, are
|
||||
/// invalid.
|
||||
/// beyond that divider give the room. An anchor at the edge of its area on that side takes
|
||||
/// the nearest such split along its opposite side instead. The renderer offers it beside a
|
||||
/// frame too small to halve. `Center`, and an anchor without either divider or with a dock
|
||||
/// beyond it, are invalid.
|
||||
SplitSized { zone: Zone, size: SplitSize, outer: SplitSize },
|
||||
/// A new group above or below the anchor's group in a dock column. On an anchor in a
|
||||
/// floating window, a split of the anchor's group above or below.
|
||||
|
||||
@@ -786,8 +786,13 @@ fn a_sized_split_sets_the_new_split_and_the_enclosing_one_beside_the_anchor() {
|
||||
moved.apply(Action::Move { panel: 4, anchor: 3, placement: sized(Zone::Left) }).unwrap();
|
||||
let inner = hsplit(SplitSize::FixedSecond(164.0), tabs(&[2], 0), hsplit(SplitSize::FixedFirst(100.0), tabs(&[4], 0), tabs(&[3], 0)));
|
||||
assert_eq!(moved.root, Some(hsplit(SplitSize::Ratio(0.5), tabs(&[1], 0), inner)));
|
||||
// 1 has no divider on its left, and a center is no side.
|
||||
for (anchor, zone) in [(1, Zone::Left), (3, Zone::Center)] {
|
||||
// 1 has no divider on its left: the split on its right gives the room.
|
||||
let mut moved = layout.clone();
|
||||
moved.apply(Action::Move { panel: 4, anchor: 1, placement: sized(Zone::Left) }).unwrap();
|
||||
let rest = hsplit(SplitSize::FixedSecond(60.0), tabs(&[2], 0), tabs(&[3], 0));
|
||||
assert_eq!(moved.root, Some(hsplit(SplitSize::FixedSecond(164.0), hsplit(SplitSize::FixedFirst(100.0), tabs(&[4], 0), tabs(&[1], 0)), rest)));
|
||||
// 1 has no divider above or below it, and a center is no side.
|
||||
for (anchor, zone) in [(1, Zone::Top), (3, Zone::Center)] {
|
||||
let mut moved = layout.clone();
|
||||
assert_eq!(moved.apply(Action::Move { panel: 4, anchor, placement: sized(zone) }), Err(DockError::InvalidPlacement), "{anchor} {zone:?}");
|
||||
assert_eq!(moved, layout);
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{DROP_MARGIN, Drawn, Renderer, clamp_float, dock::group_action};
|
||||
use crate::docking::chrome::drop_preview;
|
||||
use crate::docking::{
|
||||
Action, Drag, DropProposal, DropRequest, DropTarget, GroupDrag, GroupGesture, Layout, Node, Placement, Target, Zone, cancel_drag,
|
||||
dock_model::Place,
|
||||
dock_model::{Place, sized_split_depth},
|
||||
surface::TAB_PADDING,
|
||||
targets::{DockDrop, SIDE_EDGE_ZONE_DEPTH, resolve_drop_on},
|
||||
};
|
||||
@@ -235,7 +235,8 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
|
||||
/// The side split of `proposal`'s frame when halving it leaves no room
|
||||
/// ([`Placement::SplitSized`]): the frame keeps its size, and the frames beyond the divider
|
||||
/// on that side give the new frame its minimum (with [`DROP_MARGIN`]). A floating panel
|
||||
/// on that side, or on the opposite side for a frame at the edge of its area, give the new
|
||||
/// frame its minimum (with [`DROP_MARGIN`]). A floating panel
|
||||
/// keeps its size along the split instead, and one in a dock column its column's width or
|
||||
/// its group's height, when that fits. `None` without such a divider, or when those frames
|
||||
/// cannot give the new frame its minimum.
|
||||
@@ -243,8 +244,6 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
let Placement::Split(zone) = proposal.placement else { return None };
|
||||
let axis = if matches!(zone, Zone::Top | Zone::Bottom) { SplitAxis::Vertical } else { SplitAxis::Horizontal };
|
||||
let along = |v: Vec2| if axis == SplitAxis::Horizontal { v.x } else { v.y };
|
||||
// The divider on the frame's start side (left or top) belongs to a split holding the
|
||||
// frame in its second child, as `split_frame_sized` finds it.
|
||||
let start = matches!(zone, Zone::Left | Zone::Top);
|
||||
let anchor = &proposal.anchor;
|
||||
let now = self.drawn_groups(self.layout);
|
||||
@@ -271,19 +270,20 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
let &(Node::Split { first, second, .. }, halved) = chain.last()? else { return None };
|
||||
let new = if start { first } else { second };
|
||||
let depth = (0..split_path.len()).rev().find(|&d| {
|
||||
split_path.get(d) == Some(&start) && chain.get(d).is_some_and(|(n, _)| matches!(n, Node::Split { axis: own, .. } if *own == axis))
|
||||
})?;
|
||||
let depth =
|
||||
sized_split_depth(split_path, zone, |d| chain.get(d).is_some_and(|(n, _)| matches!(n, Node::Split { axis: own, .. } if *own == axis)))?;
|
||||
// The frame is in the outer split's second child when the divider is on its start side.
|
||||
let in_second = *split_path.get(depth)?;
|
||||
let &(outer, outer_rect) = chain.get(depth)?;
|
||||
let Node::Split { size: outer_size, first: a, second: b, .. } = outer else { return None };
|
||||
if matches!(**a, Node::Dock { .. }) || matches!(**b, Node::Dock { .. }) {
|
||||
return None;
|
||||
}
|
||||
let [a, b] = sizer.children(outer, outer_rect)?;
|
||||
let far = if start { a } else { b };
|
||||
// The far side's edge at the divider is its end edge when the divider is on the start
|
||||
// side of the frame.
|
||||
let room = sizer.travel(far.0, far.1, axis, start).0 - DROP_MARGIN;
|
||||
let far = if in_second { a } else { b };
|
||||
// The far side's edge at the divider is its end edge when the frame is in the second
|
||||
// child.
|
||||
let room = sizer.travel(far.0, far.1, axis, in_second).0 - DROP_MARGIN;
|
||||
let least = along(super::node_limits(new, self.style, &*self.content).min) + DROP_MARGIN;
|
||||
let gap = self.style.gap;
|
||||
// A split frame's width is its share of the room, not one of its own.
|
||||
@@ -300,7 +300,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
let size = saved.filter(|width| (least..=most).contains(width)).unwrap_or(least);
|
||||
// The anchor's side of the outer split grows by what the halves did not take already.
|
||||
let grow = frame + gap + size - along(halved.size());
|
||||
let first = along(a.1.size()) + if start { -grow } else { grow };
|
||||
let first = along(a.1.size()) + if in_second { -grow } else { grow };
|
||||
let outer = super::sizes::resized(*outer_size, first, along(a.1.size()) + along(b.1.size()))?;
|
||||
let size = super::sizes::resized(SplitSize::Ratio(0.5), if start { size } else { frame }, frame + size)?;
|
||||
Some(Placement::SplitSized { zone, size, outer })
|
||||
|
||||
@@ -397,6 +397,39 @@ fn a_drop_beside_a_frame_too_narrow_to_halve_takes_room_from_its_neighbour() {
|
||||
assert!(new_properties.width() < properties.width() - 200.0, "Properties gave the room");
|
||||
}
|
||||
|
||||
/// Tools first in its row, at the area's left edge: its left side has no divider, so the room
|
||||
/// comes from beyond its right one.
|
||||
#[test]
|
||||
fn a_drop_on_the_outer_side_of_a_frame_too_narrow_to_halve_takes_room_from_the_other_side() {
|
||||
let root = Node::Split {
|
||||
axis: SplitAxis::Horizontal,
|
||||
size: SplitSize::FixedFirst(60.0),
|
||||
first: Box::new(tabs(&[TOOLS])),
|
||||
second: Box::new(tabs(&PROPERTIES)),
|
||||
};
|
||||
let layout = Layout { root: Some(root), floating: vec![Floating::single(vec![INFO], 0, [1100.0, 100.0, 280.0, 200.0])] };
|
||||
let mut d = driver(layout, Film::default());
|
||||
d.area = Rect::from_min_size(Pos2::ZERO, vec2(1000.0, SCREEN.y));
|
||||
d.settle();
|
||||
let (tools, properties, info) = (group(&d, TOOLS), group(&d, PROPERTIES[0]), group(&d, INFO));
|
||||
// Inside Tools' left zone, past the window edge band.
|
||||
let at = pos2(tools.left() + 12.0, tools.center().y + 13.0);
|
||||
d.hold(tab(&d, INFO), at);
|
||||
assert!(d.filled_polygon(&zone_shape(tools, 26.0, 40.0, Zone::Left).unwrap()), "Tools' left zone is a target");
|
||||
let out = d.release(at);
|
||||
assert!(
|
||||
matches!(&out.actions[..], [Action::Move { panel: INFO, anchor: TOOLS, placement: Placement::SplitSized { zone: Zone::Left, .. } }]),
|
||||
"{:?}",
|
||||
out.actions
|
||||
);
|
||||
d.settle();
|
||||
let (new_tools, new_info, new_properties) = (group(&d, TOOLS), group(&d, INFO), group(&d, PROPERTIES[0]));
|
||||
assert!((new_tools.width() - tools.width()).abs() < 1.0, "Tools keeps its width: {new_tools:?}");
|
||||
assert!((new_info.width() - info.width()).abs() < 1.0, "Info keeps its floating width: {new_info:?}");
|
||||
assert!(new_info.right() < new_tools.left() && new_tools.right() < new_properties.left());
|
||||
assert!(new_properties.width() < properties.width() - 200.0, "Properties gave the room");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_docked_panel_dropped_beside_a_frame_too_narrow_to_halve_takes_its_minimum_width() {
|
||||
// Info docked below Properties, as wide as it, beside a narrow Tools frame.
|
||||
|
||||
Reference in new issue
Block a user