diff --git a/crates/ui/src/docking/native_tests.rs b/crates/ui/src/docking/native_tests.rs index 4c8bb3b9..164c7cec 100644 --- a/crates/ui/src/docking/native_tests.rs +++ b/crates/ui/src/docking/native_tests.rs @@ -859,6 +859,43 @@ fn the_only_tab_of_a_native_window_docks_it_with_title_bar_docks_off() { assert!(!in_root(&d, 7)); } +#[test] +fn the_only_tab_of_a_native_window_fills_and_joins_a_frame_bodys_center() { + let mut layout = layout(false); + layout.floating.push(Floating::single(vec![7], 0, [300.0, 80.0, 240.0, 200.0])); + let style = |s: &mut super::DockStyle| { + s.side_edge_zones = true; + s.title_bar_docks = false; + s.float_hint = false; + }; + let mut d = DockDriver::new(layout, Content::default()).with_style(style).with_native_host(ORIGIN); + let id = window(&d, 7).id; + // The middle of frame [3, 4], in the main window. + let middle = pos2(600.0, 300.0); + let from = pos2(15.0, TAB_ROW); + d.window_step(id, vec![Event::PointerMoved(from), button(from, true)]); + d.window_step(id, vec![Event::PointerMoved(from + vec2(8.0, 6.0))]); + for _ in 0..2 { + let local = d.window_local(id, ORIGIN + middle.to_vec2()).unwrap(); + d.window_step(id, vec![Event::PointerMoved(local)]); + } + // The center rectangle is the only zone around the pointer: outlined and filled. + let zone = |filled: bool| { + d.shapes().iter().any(|clipped| match &clipped.shape { + egui::Shape::Path(path) => { + path.closed && (path.fill != Color32::TRANSPARENT) == filled && Rect::from_points(&path.points).contains(middle) + } + _ => false, + }) + }; + assert!(zone(false), "outlined"); + assert!(zone(true), "filled"); + let local = d.window_local(id, ORIGIN + middle.to_vec2()).unwrap(); + let out = d.window_step(id, vec![button(local, false)]); + assert!(matches!(window_move(&out), Some(Target::At { anchor: 3 | 4, placement: Placement::Split(super::Zone::Center) })), "{:?}", out.actions); + assert!(in_root(&d, 7)); +} + #[test] fn a_native_window_forwards_no_keys_while_one_of_its_widgets_has_focus() { let mut d = driver(layout(true)); diff --git a/crates/ui/src/docking/render/preview.rs b/crates/ui/src/docking/render/preview.rs index b746c8e6..60d38b89 100644 --- a/crates/ui/src/docking/render/preview.rs +++ b/crates/ui/src/docking/render/preview.rs @@ -116,7 +116,8 @@ impl Renderer<'_, P> { /// The valid target under the pointer for a whole group. A target holding the group /// itself is a no-op proposal; a target the model would reject is no target. A window - /// moving live looks through itself and does not split frames or join a body's center. + /// moving live looks through itself and does not split frames or, moved by its bar, join + /// a body's center. pub(super) fn group_proposal(&self, drag: &GroupDrag

, pointer: Pos2) -> Option> { let window = drag.gesture == GroupGesture::Window; if let Some(proposal) = self.extra_proposal(&drag.members, pointer, window) { @@ -148,14 +149,16 @@ impl Renderer<'_, P> { } let proposal = self.resolve_roomy(&drag.panel, pointer, std::slice::from_ref(target), |to| group_action(drag, to))?; // A window moving live splits only groups of other floating windows. A native - // window's bar also takes the main window's frame edges, but not a body's center. + // window's bar also takes the main window's frame edges, but not a body's center. A + // native window moved by its only tab or group joins a body's center too, as that tab + // or group drag would. let into_window = matches!(self.place(&proposal.anchor), Some(Place::Floating { .. })); #[cfg(feature = "native-windows")] - let edges = self.cross.bar; + let (edges, joins) = (self.cross.bar, self.cross.docks); #[cfg(not(feature = "native-windows"))] - let edges = false; + let (edges, joins) = (false, false); let split = match proposal.placement { - Placement::Split(Zone::Center) => true, + Placement::Split(Zone::Center) => !joins, Placement::Split(_) => !edges, _ => false, }; diff --git a/docs/ui-docking.md b/docs/ui-docking.md index 1af22a93..4ebf7b93 100644 --- a/docs/ui-docking.md +++ b/docs/ui-docking.md @@ -439,7 +439,8 @@ that is not floatable, stays in-window. Floating windows get no taskbar or Alt-T `DockStyle::title_bar_docks`; the only tab or group docks either way, since it is the way back in with that flag off. Targets: in the main window a dock line, a frame's tab row or edge zone, or the layout's outer edge; in a - floating window a frame zone or tab row. A frame body's center takes no whole window. Over + floating window a frame zone or tab row. A frame body's center takes a window moved by its + only tab or group, as that tab or group drag would, but not one moved by its bar. Over the desktop, or with Ctrl/Cmd held, it stays where it was moved, and the layout takes its place once, at the release. Escape puts it back. An in-window floating window's title bar still splits only groups of other floating windows.