mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-31 07:39:23 +00:00
docking: hidden floating windows do not cover dock targets
Dock targets check the floating windows drawn this frame instead of every window's last egui area rect. floating_layer says that an undrawn window keeps its last area rect.
This commit is contained in:
1 parent
a55cbe870e
commit
ae0c19b49b
6 files changed
+38
-19
No files matched your search
@@ -592,3 +592,20 @@ fn hidden_panels_leave_their_split_and_resizing_names_the_layout_path() {
|
||||
let Some(Node::Split { size: SplitSize::FixedFirst(200.0), second, .. }) = &d.layout.root else { panic!("{:?}", d.layout) };
|
||||
assert!(matches!(**second, Node::Split { size, .. } if size != SplitSize::Ratio(0.5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hidden_floating_window_does_not_cover_dock_targets() {
|
||||
// Window 5 covers the column's seam at x 150..240 until it is hidden.
|
||||
let mut layout = workspace();
|
||||
layout.floating = vec![Floating::single(vec![5], 0, [150.0, 250.0, 200.0, 150.0]), Floating::single(vec![8], 0, [560.0, 300.0, 200.0, 200.0])];
|
||||
let mut d = Driver::new(layout, Profile::Dock);
|
||||
d.hide = Hide::Keep;
|
||||
d.content.keep = vec![2, 3, 4, 8];
|
||||
d.step(vec![]);
|
||||
d.step(vec![]);
|
||||
d.drag(d.title(8).center(), pos2(200.0, 309.0));
|
||||
let Some(Node::Split { first, .. }) = &d.layout.root else { panic!("{:?}", d.layout.root) };
|
||||
let Node::Dock { columns, .. } = &**first else { panic!("{first:?}") };
|
||||
let groups: Vec<Vec<u32>> = columns[0].groups.iter().map(|g| g.panels.clone()).collect();
|
||||
assert_eq!(groups, vec![vec![2, 3], vec![8], vec![4]]);
|
||||
}
|
||||
@@ -112,7 +112,9 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> DockArea<'_, P> {
|
||||
|
||||
/// The egui layer of a floating window in the area `id`; `None` for an empty window. Of two
|
||||
/// layers, the later in `ctx.memory(|m| m.layer_ids())` is drawn on top, and
|
||||
/// `ctx.memory(|m| m.area_rect(layer.id))` is the window's rect.
|
||||
/// `ctx.memory(|m| m.area_rect(layer.id))` is the window's rect. A window the area does not
|
||||
/// draw (all its panels hidden, or a frame maximized) keeps its last rect there: check
|
||||
/// `ctx.memory(|m| m.areas().is_visible(&layer))` before treating it as covering anything.
|
||||
pub fn floating_layer(id: Id, window: &Floating<P>) -> Option<LayerId> {
|
||||
window.panels().next().map(|first| LayerId::new(Order::Middle, id.with(("floating", first))))
|
||||
}
|
||||
|
||||
@@ -412,15 +412,13 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Whether a floating window (other than the one following the pointer) or a host overlay
|
||||
/// covers `pointer`. Covered dock targets take nothing.
|
||||
fn dock_covered(&self, ctx: &Context, pointer: Pos2, moving: Option<&P>) -> bool {
|
||||
/// Whether a floating window drawn this frame (other than the one following the pointer)
|
||||
/// or a host overlay covers `pointer`. Covered dock targets take nothing.
|
||||
fn dock_covered(&self, pointer: Pos2, moving: Option<&P>) -> bool {
|
||||
self.blockers.iter().any(|r| r.contains(pointer))
|
||||
|| self.iconic.covers(pointer)
|
||||
|| self.layout.floating.iter().any(|window| {
|
||||
let Some(first) = window.panels().next() else { return false };
|
||||
!moving.is_some_and(|p| window.contains(p))
|
||||
&& ctx.memory(|m| m.area_rect(self.id.with(("floating", first)))).is_some_and(|r| r.contains(pointer))
|
||||
|| self.frame.stacks.iter().any(|window| {
|
||||
window.window.contains(pointer) && !moving.is_some_and(|p| window.groups.iter().any(|(panels, ..)| panels.contains(p)))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -451,9 +449,9 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
|
||||
/// The dock target a whole group or column drag would take at `pointer`, with the action
|
||||
/// and its line. A target that leaves the layout unchanged is a no-op: `None` action.
|
||||
pub(super) fn dock_group_proposal(&self, ctx: &Context, drag: &GroupDrag<P>, pointer: Pos2) -> Option<(Option<Action<P>>, Rect)> {
|
||||
pub(super) fn dock_group_proposal(&self, drag: &GroupDrag<P>, pointer: Pos2) -> Option<(Option<Action<P>>, Rect)> {
|
||||
let moving = (drag.gesture == GroupGesture::Window).then_some(&drag.panel);
|
||||
if self.dock_covered(ctx, pointer, moving) {
|
||||
if self.dock_covered(pointer, moving) {
|
||||
return None;
|
||||
}
|
||||
let candidates = self.dock_candidates();
|
||||
@@ -465,8 +463,8 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
/// The dock target a single tab drag would take: a new group, column or dock of that panel.
|
||||
/// A panel alone in its group takes the group's state to a new dock; one that shares its
|
||||
/// group leaves the other tabs behind (P6).
|
||||
pub(super) fn dock_tab_proposal(&self, ctx: &Context, panel: &P, pointer: Pos2) -> Option<(Option<Action<P>>, Rect)> {
|
||||
if self.dock_covered(ctx, pointer, None) {
|
||||
pub(super) fn dock_tab_proposal(&self, panel: &P, pointer: Pos2) -> Option<(Option<Action<P>>, Rect)> {
|
||||
if self.dock_covered(pointer, None) {
|
||||
return None;
|
||||
}
|
||||
let candidates = self.dock_candidates();
|
||||
|
||||
@@ -310,8 +310,8 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
/// Release of a title bar drag. The window already sits where it was dragged; a dock or
|
||||
/// group target docks or merges every group in it, else hosts get the release in
|
||||
/// `group_drop`.
|
||||
pub(super) fn drop_window(&mut self, ctx: &Context, drag: &GroupDrag<P>, pointer: Pos2, float_only: bool) {
|
||||
if !float_only && let Some((action, _)) = self.dock_group_proposal(ctx, drag, pointer) {
|
||||
pub(super) fn drop_window(&mut self, drag: &GroupDrag<P>, pointer: Pos2, float_only: bool) {
|
||||
if !float_only && let Some((action, _)) = self.dock_group_proposal(drag, pointer) {
|
||||
self.output.actions.extend(action);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
let Some(pointer) = pointer.filter(|p| !self.blockers.iter().any(|r| r.contains(*p))) else { return };
|
||||
match drag.gesture {
|
||||
GroupGesture::Window => self.drop_window(ctx, &drag, pointer, float_only),
|
||||
GroupGesture::Window => self.drop_window(&drag, pointer, float_only),
|
||||
GroupGesture::Group | GroupGesture::Column => self.drop_group(ctx, &drag, pointer, float_only),
|
||||
}
|
||||
} else if let Some(pointer) = pointer.filter(|p| !self.blockers.iter().any(|r| r.contains(*p))) {
|
||||
@@ -113,7 +113,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
if pointer.distance(drag.origin) < 2.0 {
|
||||
return;
|
||||
}
|
||||
if !float_only && let Some((action, _)) = self.dock_group_proposal(ctx, drag, pointer) {
|
||||
if !float_only && let Some((action, _)) = self.dock_group_proposal(drag, pointer) {
|
||||
self.output.actions.extend(action);
|
||||
return;
|
||||
}
|
||||
@@ -162,7 +162,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
if float_only {
|
||||
return;
|
||||
}
|
||||
if let Some((action, line)) = self.dock_group_proposal(ctx, drag, pointer) {
|
||||
if let Some((action, line)) = self.dock_group_proposal(drag, pointer) {
|
||||
if action.is_some() {
|
||||
self.paint_dock_line(ctx, line);
|
||||
}
|
||||
@@ -214,7 +214,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
return;
|
||||
}
|
||||
let dock = if float_only || gone { None } else { self.dock_tab_proposal(ctx, &drag.panel, pointer) };
|
||||
let dock = if float_only || gone { None } else { self.dock_tab_proposal(&drag.panel, pointer) };
|
||||
if let Some((action, line)) = dock {
|
||||
if released {
|
||||
cancel_drag::<P>(ctx, self.id);
|
||||
|
||||
+3
-1
@@ -274,7 +274,9 @@ grips, resize edges and dividers. It is always compiled, so an app can return it
|
||||
`DockArea::hit_id(id, &hit)` is the egui widget id of a control, for tests and automation that
|
||||
focus it or read its response. `DockArea::floating_layer(id, &window)` is a floating window's
|
||||
egui layer: compare its place in `memory.layer_ids()` with another layer to tell whether the
|
||||
window covers it, and read its rect with `memory.area_rect(layer.id)`.
|
||||
window covers it, and read its rect with `memory.area_rect(layer.id)`. A window the area does not
|
||||
draw (its panels hidden, or a frame maximized) keeps its last rect there, so check
|
||||
`memory.areas().is_visible(&layer)` first.
|
||||
|
||||
The `testing` feature adds `docking::testing::DockDriver`. It runs a `DockArea` in a headless
|
||||
egui context with real pointer and key events, and applies the actions each frame emits.
|
||||
|
||||
Reference in new issue
Block a user