From d8394d5d87525fa756610b71ab4629d92e15cc7e Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 13:21:37 -0800 Subject: [PATCH] Fix #4995: Above Text moves a Tabs panel that already floats Position Panel Above Text moved a floating Tabs panel only in the layout's in-window rect. On desktop builds a floating panel is an OS window, which moves only when its desktop rect changes, so the panel stayed where it was and the layout followed it back on the next frame. A docked panel floated above the text as expected. place_floating now also sets the window's desktop rect, from the main window's content origin, when the panel's window already has one. --- crates/ui-egui/src/panel_docking.rs | 12 +++++++--- crates/ui-egui/src/panels/tabs.rs | 4 ++-- crates/ui-egui/src/panels/tabs/tests.rs | 32 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/panel_docking.rs b/crates/ui-egui/src/panel_docking.rs index cdec00eaa..83cad5282 100644 --- a/crates/ui-egui/src/panel_docking.rs +++ b/crates/ui-egui/src/panel_docking.rs @@ -787,8 +787,9 @@ fn float_space(app: &VectorcraftApp) -> FloatSpace { } /// Float `panel` (open or not) as a window of its own with its top-left corner at `at`, `width` -/// wide: the Tabs panel over the text it sets. -pub(crate) fn place_floating(app: &mut VectorcraftApp, panel: &str, at: egui::Pos2, width: f32) -> Result<(), String> { +/// wide: the Tabs panel over the text it sets. `origin`: the main window's content top-left on the +/// desktop. A panel already floating in an OS window of its own moves to `at` from there (#4995). +pub(crate) fn place_floating(app: &mut VectorcraftApp, panel: &str, at: egui::Pos2, width: f32, origin: Option) -> Result<(), String> { let panel = normalize(panel).ok_or("unknown panel")?.to_string(); if !(at.x.is_finite() && at.y.is_finite() && width.is_finite()) { return Err("panel position must be finite".into()); @@ -800,8 +801,13 @@ pub(crate) fn place_floating(app: &mut VectorcraftApp, panel: &str, at: egui::Po let height = floating_window(&txn.workspace.layout, &panel).map_or(floating_size(&panel).y, |w| w.rect[3]); let rect = [at.x, at.y, width.max(1.0), height]; let whole = floating_window(&txn.workspace.layout, &panel).is_some_and(|w| w.panels().count() == 1); - let action = if whole { Action::MoveFloating { panel, rect } } else { Action::Float { panel, rect } }; + let native = whole && floating_window(&txn.workspace.layout, &panel).is_some_and(|w| w.native.is_some()); + let action = if whole { Action::MoveFloating { panel: panel.clone(), rect } } else { Action::Float { panel: panel.clone(), rect } }; txn.apply(action)?; + // The OS window follows its desktop rect, not the in-window one. + if let Some(origin) = origin.filter(|o| native && o.is_finite()) { + txn.apply(Action::SetNative { panel, rect: Some([origin.x + at.x, origin.y + at.y, rect[2], rect[3]]) })?; + } txn.commit(app, Vec::new(), None); Ok(()) } diff --git a/crates/ui-egui/src/panels/tabs.rs b/crates/ui-egui/src/panels/tabs.rs index 1e0818454..f8a2d758e 100644 --- a/crates/ui-egui/src/panels/tabs.rs +++ b/crates/ui-egui/src/panels/tabs.rs @@ -266,8 +266,8 @@ fn above_text(app: &mut VectorcraftApp, ctx: &egui::Context, n: &Node, span: f64 let width = span as f32 * per + RULER_LEFT * 2.0; let height = crate::panel_docking::layout_of(&app.ui).floating.iter().find(|w| w.contains(&"tabs".to_string())).map_or(240.0, |w| w.rect[3]); let pos = egui::pos2(on_screen.left() - RULER_LEFT, on_screen.top() - height - 6.0); - let _ = ctx; - crate::panel_docking::place_floating(app, "tabs", pos, width).ok()?; + let origin = ctx.input(|i| i.raw.viewports.get(&egui::ViewportId::ROOT).and_then(|v| v.inner_rect)).map(|r| r.min); + crate::panel_docking::place_floating(app, "tabs", pos, width, origin).ok()?; Some(()) } diff --git a/crates/ui-egui/src/panels/tabs/tests.rs b/crates/ui-egui/src/panels/tabs/tests.rs index b598d0c2a..5fd7b17c4 100644 --- a/crates/ui-egui/src/panels/tabs/tests.rs +++ b/crates/ui-egui/src/panels/tabs/tests.rs @@ -1012,3 +1012,35 @@ fn above_text_puts_the_ruler_over_the_text_at_the_zoom() { } assert_eq!(app.ui.docking.unwrap().floating.len(), 1, "placed again, not floated twice"); } + +/// Position Panel Above Text moves the panel when it already floats in an OS window of its own: +/// the window's desktop rect goes above the text too, from the main window's top-left (#4995). +#[test] +fn above_text_moves_a_panel_floating_in_its_own_window() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 600, "height": 400})).unwrap(); + app.session.execute("text.create", &json!({"x": 40, "y": 200, "text": "a b", "area": {"width": 300, "height": 100}})).unwrap(); + app.canvas_rect = Some(egui::Rect::from_min_size(egui::pos2(50.0, 40.0), vec2(1000.0, 700.0))); + if let Some(v) = app.view_mut() { + v.zoom = 1.0; + } + let (node, _, span) = current(&app).unwrap(); + let ctx = egui::Context::default(); + above_text(&mut app, &ctx, &node, span).unwrap(); + // The panel's OS window sits elsewhere on the desktop, as after the user dragged it there. + let mut layout = app.ui.docking.clone().unwrap(); + let window = layout.floating.iter_mut().find(|w| w.contains(&"tabs".to_string())).unwrap(); + window.native = Some([1500.0, 900.0, window.rect[2], window.rect[3]]); + app.ui.docking = Some(layout); + // The main window's content starts at (200, 100) on the desktop. + let mut raw = egui::RawInput::default(); + raw.viewports.entry(egui::ViewportId::ROOT).or_default().inner_rect = + Some(egui::Rect::from_min_size(egui::pos2(200.0, 100.0), vec2(1200.0, 800.0))); + ctx.run_ui(raw, |_| {}).textures_delta.clear(); + above_text(&mut app, &ctx, &node, span).unwrap(); + let layout = app.ui.docking.clone().unwrap(); + let g = layout.floating.iter().find(|w| w.contains(&"tabs".to_string())).unwrap(); + let native = g.native.unwrap(); + assert_eq!([native[0], native[1]], [200.0 + g.rect[0], 100.0 + g.rect[1]], "the OS window goes above the text"); + assert_eq!([native[2], native[3]], [g.rect[2], g.rect[3]]); +}