From 48e2bc65086428ae2d56a783752d7fdbd277d30d Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 14:27:13 -0800 Subject: [PATCH] Tabs: Above Text keeps the panel's bar inside the window With text near the top of the window, Position Panel Above Text put the panel, and its OS window, above the main window's content and could put the bar above every screen. The panel now goes no higher than the top of the main window's content. --- crates/ui-egui/src/panels/tabs.rs | 4 ++- crates/ui-egui/src/panels/tabs/tests.rs | 34 ++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panels/tabs.rs b/crates/ui-egui/src/panels/tabs.rs index f8a2d758e..e68ae9b54 100644 --- a/crates/ui-egui/src/panels/tabs.rs +++ b/crates/ui-egui/src/panels/tabs.rs @@ -265,7 +265,9 @@ 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); + // Text near the top of the window: the panel's bar stays inside the window, where it can be + // grabbed, not above the screen. + let pos = egui::pos2(on_screen.left() - RULER_LEFT, (on_screen.top() - height - 6.0).max(0.0)); 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 5fd7b17c4..8ab728afe 100644 --- a/crates/ui-egui/src/panels/tabs/tests.rs +++ b/crates/ui-egui/src/panels/tabs/tests.rs @@ -991,7 +991,8 @@ fn above_text_puts_the_ruler_over_the_text_at_the_zoom() { 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))); + // The canvas starts low enough in the window for the panel to fit above the text at 200%. + app.canvas_rect = Some(egui::Rect::from_min_size(egui::pos2(50.0, 440.0), vec2(1000.0, 700.0))); for zoom in [1.0, 2.0] { if let Some(v) = app.view_mut() { v.zoom = zoom; @@ -1044,3 +1045,34 @@ fn above_text_moves_a_panel_floating_in_its_own_window() { 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]]); } + +/// Text near the top of the window: Position Panel Above Text keeps the panel's bar inside the +/// window, so neither the panel nor its OS window goes above the screen. +#[test] +fn above_text_keeps_the_panel_bar_in_the_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": 10, "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 xf = crate::canvas::Xf::new(app.canvas_rect.unwrap(), app.view().unwrap()); + assert!(xf.rect_to_screen(node.geometric_bounds().unwrap()).top() < 240.0, "no room above the text"); + let ctx = egui::Context::default(); + above_text(&mut app, &ctx, &node, span).unwrap(); + let mut layout = app.ui.docking.clone().unwrap(); + let window = layout.floating.iter_mut().find(|w| w.contains(&"tabs".to_string())).unwrap(); + assert_eq!(window.rect[1], 0.0, "the panel's bar at the window's top"); + window.native = Some([1500.0, 900.0, window.rect[2], window.rect[3]]); + app.ui.docking = Some(layout); + 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(); + assert_eq!(g.native.unwrap()[1], 100.0, "the OS window's bar at the main window's content top"); +}