mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 13:07:41 +00:00
Merge pull request #5162 from dotCooCoo/fix/issue-4995
Fix #4995: Above Text moves a Tabs panel that already floats
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
@@ -1231,7 +1237,7 @@ const DOCK_LABELS: &[(&str, &str)] = &[
|
||||
];
|
||||
|
||||
/// The size a panel floats at (tab strip included), whatever the size of the dock it left.
|
||||
fn floating_size(panel: &str) -> egui::Vec2 {
|
||||
pub(crate) fn floating_size(panel: &str) -> egui::Vec2 {
|
||||
match panel {
|
||||
"properties" | "layers" | "libraries" => egui::vec2(300.0, 420.0),
|
||||
// Tall enough to show its Corners and Shared Edges sections, last in it, which the Corner
|
||||
|
||||
@@ -264,10 +264,26 @@ fn above_text(app: &mut VectorcraftApp, ctx: &egui::Context, n: &Node, span: f64
|
||||
return None;
|
||||
}
|
||||
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 height = crate::panel_docking::layout_of(&app.ui)
|
||||
.floating
|
||||
.iter()
|
||||
.find(|w| w.contains(&"tabs".to_string()))
|
||||
.map_or(crate::panel_docking::floating_size("tabs").y, |w| w.rect[3]);
|
||||
// Text near the top of the window: the panel goes just below the text when that place is in
|
||||
// the window and the panel 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 >= 0.0 && 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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -1012,3 +1013,96 @@ 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]]);
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// Position Panel Above Text puts the panel at the window's top when the place below the text is
|
||||
/// above the window (text panned above the canvas), or when the panel at the height it floats at
|
||||
/// does not fit below the text in the canvas.
|
||||
#[test]
|
||||
fn above_text_puts_the_panel_at_the_top_when_below_the_text_does_not_fit() {
|
||||
// The view's centre and the text's height: panned so the text is above the canvas, and a text
|
||||
// whose bottom leaves room for 240 points of panel but not for the 360 it floats at.
|
||||
for (center_y, text_height) in [(600.0, 100.0), (350.0, 340.0)] {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 600, "height": 800})).unwrap();
|
||||
app.session.execute("text.create", &json!({"x": 40, "y": 20, "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;
|
||||
v.center = vectorcraft_geom::Point::new(300.0, center_y);
|
||||
}
|
||||
let (node, _, span) = current(&app).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
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.rect[1], 0.0, "view centered at y {center_y}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user