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.
This commit is contained in:
dotCooCoo
2026-11-02 14:32:49 +07:00
committed by bflatastic
parent 6be88a24b4
commit d8394d5d87
3 changed files with 43 additions and 5 deletions
+9 -3
View File
@@ -787,8 +787,9 @@ fn float_space(app: &VectorcraftApp) -> FloatSpace<String> {
}
/// 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<egui::Pos2>) -> 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(())
}
+2 -2
View File
@@ -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(())
}
+32
View File
@@ -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]]);
}