From 442430ff12c54dc50a6f152f3236cdb46718f053 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 14:57:49 -0800 Subject: [PATCH] Tabs: Above Text puts the panel below text near the window's top With no room above the text, the panel went to the top of the main window's content and could cover the text. It now goes just below the text when it fits there in the canvas, and at the window's top only when it does not. --- crates/ui-egui/src/panels/tabs.rs | 16 +++++-- crates/ui-egui/src/panels/tabs/tests.rs | 57 ++++++++++++++----------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/crates/ui-egui/src/panels/tabs.rs b/crates/ui-egui/src/panels/tabs.rs index e68ae9b54..ae49d2048 100644 --- a/crates/ui-egui/src/panels/tabs.rs +++ b/crates/ui-egui/src/panels/tabs.rs @@ -265,9 +265,19 @@ 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]); - // 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)); + // Text near the top of the window: the panel goes just below the text when it fits there in + // the canvas, else at the window's top, so its bar stays where it can be grabbed rather than + // above the screen. + let above = on_screen.top() - height - 6.0; + let below = on_screen.bottom() + 6.0; + let top = if above >= 0.0 { + above + } else if below + height <= app.canvas_rect.map_or(f32::NEG_INFINITY, |c| c.bottom()) { + below + } else { + 0.0 + }; + let pos = egui::pos2(on_screen.left() - RULER_LEFT, top); 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 8ab728afe..8a6d2394e 100644 --- a/crates/ui-egui/src/panels/tabs/tests.rs +++ b/crates/ui-egui/src/panels/tabs/tests.rs @@ -1046,33 +1046,38 @@ fn above_text_moves_a_panel_floating_in_its_own_window() { 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. +/// Text near the top of the window: Position Panel Above Text puts the panel just below the text, +/// so neither the panel nor its OS window goes above the screen and the text stays in view. Text +/// with no room below it either gets the panel at the window's top. #[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; + for (text_height, below) in [(100.0, true), (760.0, false)] { + 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": text_height}})).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()); + let frame = xf.rect_to_screen(node.geometric_bounds().unwrap()); + assert!(frame.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(); + let top = if below { frame.bottom() + 6.0 } else { 0.0 }; + assert_eq!(window.rect[1], top, "text {text_height} high"); + 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 + top, "text {text_height} high: the OS window"); } - 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"); }