diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index d601e76d1..db736fb6f 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -542,6 +542,9 @@ impl VectorcraftApp { // Control bar docked at the bottom has set it to the bar's top. widgets::set_menu_floor(&ctx, ctx.content_rect().bottom()); } + if self.ui.toolbar && self.ui.screen_mode < 3 { + tools_float::set_window_dock_zone(&ctx, ui.available_rect_before_wrap()); + } if self.ui.toolbar && self.ui.screen_mode < 3 && self.ui.toolbar_pos.is_some() { toolbar::show(self, ui); } else if !self.ui.toolbar && self.ui.screen_mode < 3 && self.ui.tab_hid.is_none() { diff --git a/crates/ui-egui/src/panel_docking/tests.rs b/crates/ui-egui/src/panel_docking/tests.rs index 75b47e089..419bd2ff5 100644 --- a/crates/ui-egui/src/panel_docking/tests.rs +++ b/crates/ui-egui/src/panel_docking/tests.rs @@ -344,6 +344,38 @@ fn tools_follow_left_panels_when_resized_hidden_and_restored() { } } +#[test] +fn tools_redock_at_window_edge_above_and_below_canvas() { + for above in [true, false] { + let mut h = Harness::new(); + let tabs = |panel: &str| Box::new(Node::Tabs { panels: vec![panel.to_string()], active: 0 }); + h.app.ui.docking = Some(Layout { + root: Some(Node::Split { + axis: craft_ui::layout::SplitAxis::Vertical, + size: craft_ui::layout::SplitSize::FixedFirst(180.0), + first: tabs("layers"), + second: Box::new(Node::Split { + axis: craft_ui::layout::SplitAxis::Vertical, + size: craft_ui::layout::SplitSize::FixedSecond(180.0), + first: tabs(CANVAS), + second: tabs("properties"), + }), + }), + floating: vec![], + }); + assert!(valid(&h.layout())); + h.app.run("window.panel.float", json!({"panel": "tools", "x": 600, "y": 300})).unwrap(); + for _ in 0..3 { + h.frame(vec![]); + } + let panel = h.tab(if above { "layers" } else { "properties" }).unwrap(); + let canvas = h.app.canvas_rect.unwrap(); + assert!(panel.center().y < canvas.top() || panel.center().y > canvas.bottom()); + h.drag(pos2(620.0, 307.0), pos2(12.0, panel.center().y)); + assert_eq!(h.app.ui.toolbar_pos, None, "window-edge drop above canvas: {above}"); + } +} + #[test] #[ignore = "manual visual check; set LEFT_TOOLS_SCREENSHOTS to an existing harness directory"] fn capture_tools_beside_left_panels() { diff --git a/crates/ui-egui/src/tools_float.rs b/crates/ui-egui/src/tools_float.rs index ebb748ffa..c6b4422ba 100644 --- a/crates/ui-egui/src/tools_float.rs +++ b/crates/ui-egui/src/tools_float.rs @@ -51,15 +51,19 @@ fn window_zone_id() -> Id { Id::unique("tools-float-window-zone") } -/// Keep the window-edge gesture while also accepting a drop next to panels docked left. +/// Accept a drop next to panels docked left, independently of the window-edge gesture. pub(crate) fn set_dock_zones(ctx: &egui::Context, canvas: Rect, width: f32) { - let window = pos2(ctx.content_rect().left(), canvas.top()); ctx.data_mut(|d| { d.insert_temp(tools_zone_id(), Rect::from_min_size(canvas.min, vec2(width, canvas.height()))); - d.insert_temp(window_zone_id(), Rect::from_min_size(window, vec2(width, canvas.height()))); }); } +/// The legacy 48-point window-edge target spans the full layout below the bars, including +/// panels above and below the canvas. Its width does not follow the toolbar's column count. +pub(crate) fn set_window_dock_zone(ctx: &egui::Context, window: Rect) { + ctx.data_mut(|d| d.insert_temp(window_zone_id(), Rect::from_min_size(window.min, vec2(48.0, window.height())))); +} + /// `pos` moved so a panel of `size` stays inside `screen` (a position saved on a bigger window). pub fn clamp(pos: [f32; 2], size: Vec2, screen: Rect) -> Pos2 { let [x, y] = pos;