From cdd656bd480c5b3a0324972e08ab82a4430037ec Mon Sep 17 00:00:00 2001 From: "Ayberk A." <105879357+AyberkA0@users.noreply.github.com> Date: Tue, 13 Oct 2026 17:04:57 +0300 Subject: [PATCH] Part of #1736: two toolbar columns run on without group labels, and the drawer stays under the title bar - Without group labels, the double-column toolbar fills every row: a group no longer starts a half-empty row, as the reference's Basic toolbar runs on in two columns. One column keeps its faint dashes, and with the labels each group still starts its own row under its name. - The All Tools drawer, now a tall single column, never goes above the toolbar's top: on macOS it ran under the window's buttons. Its scroll height is what is left from there to the window's bottom. --- crates/ui-egui/src/edit_toolbar.rs | 8 ++++-- crates/ui-egui/src/toolbar.rs | 42 +++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/edit_toolbar.rs b/crates/ui-egui/src/edit_toolbar.rs index cad227ac..1138e37b 100644 --- a/crates/ui-egui/src/edit_toolbar.rs +++ b/crates/ui-egui/src/edit_toolbar.rs @@ -408,7 +408,10 @@ pub(crate) fn drawer(app: &mut VectorcraftApp, ctx: &egui::Context) { let id = drawer_id(); let size = ctx.memory(|m| m.area_rect(id)).map_or(vec2(WIDTH, 200.0), |r| r.size()); let x = (bar.right() + GAP).min(screen.right() - size.x - GAP).max(screen.left()); - let y = button.top().min(screen.bottom() - size.y - GAP).max(screen.top() + GAP); + // Never above the toolbar's top: higher up are the window's title bar (and on macOS its + // buttons) and the bars under it. + let top = bar.top().max(screen.top() + GAP); + let y = button.top().min(screen.bottom() - size.y - GAP).max(top); let in_toolbar: BTreeSet<&str> = crate::toolbar::slots(app).into_iter().flat_map(|(_, s)| s).collect(); let active = app.session.tool_id(); let dragged = DragAndDrop::payload::(ctx).map(|d| *d); @@ -448,7 +451,7 @@ pub(crate) fn drawer(app: &mut VectorcraftApp, ctx: &egui::Context) { ui.add_space(4.0); // As tall as its tools, up to the window's height (it scrolls then); the Area's size // from the last frame mustn't cap it. - let room = (screen.height() - 2.0 * GAP - 104.0).max(120.0); + let room = (screen.bottom() - top - GAP - 104.0).max(120.0); egui::ScrollArea::vertical().max_height(room).min_scrolled_height(room).auto_shrink([false, true]).show(ui, |ui| { ui.spacing_mut().item_spacing = vec2(0.0, 0.0); let inner = ui.available_width() - 10.0; @@ -885,6 +888,7 @@ mod tests { let bar = ctx.data(|d| d.get_temp::(toolbar_id())).unwrap(); assert!((drawer.left() - bar.right() - GAP).abs() < 1.0, "beside the toolbar: {drawer:?} {bar:?}"); assert!((drawer.top() - edit.top()).abs() < 1.0 || drawer.bottom() >= SCREEN.y - GAP - 1.0, "by the button: {drawer:?} {edit:?}"); + assert!(drawer.top() >= bar.top() - 0.5, "never above the toolbar (the title bar's there): {drawer:?} {bar:?}"); assert!(drawer.y_range().contains(edit.center().y), "by the button: {drawer:?} {edit:?}"); assert!(drawer.width() < WIDTH + 24.0, "one row of tools wide: {drawer:?}"); // A click on a toolbar tool picks it and leaves the drawer open. diff --git a/crates/ui-egui/src/toolbar.rs b/crates/ui-egui/src/toolbar.rs index f8fd5f77..ae91e998 100644 --- a/crates/ui-egui/src/toolbar.rs +++ b/crates/ui-egui/src/toolbar.rs @@ -182,9 +182,13 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, cols: usize, floating: Option, egui::Rect)> = None; // A group torn off into a floating flyout is raised instead of opening its flyout. let mut raise: Option<&'static str> = None; + let labels = app.session.prefs.tool_group_labels; let mut i = 0; while i < all.len() { - if all[i].0.is_some() && !app.session.prefs.tool_group_labels { + if all[i].0.is_some() && !labels && cols == 2 { + // Without labels, two columns run on from group to group, as the reference's + // Basic toolbar: no half-empty rows, no dashes between rows. + } else if all[i].0.is_some() && !labels { // User Interface › Show Tool Group Labels off: a faint dash between the groups. let (r, _) = ui.allocate_exact_size(vec2(ui.available_width(), 9.0), Sense::hover()); ui.painter().line_segment([r.center() - vec2(6.0, 0.0), r.center() + vec2(6.0, 0.0)], egui::Stroke::new(1.0, t.divider)); @@ -196,9 +200,10 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, cols: usize, floating: Option 6 { format!("{}...", cat.chars().take(4).collect::()) } else { cat.to_string() }; ui.painter().text(r.center() + vec2(0.0, 2.0), egui::Align2::CENTER_CENTER, label, egui::FontId::proportional(11.0), t.text); } - // One row = `cols` slots (a category label always starts a new row). + // One row = `cols` slots (a category label always starts a new row; without labels, a + // new group does only in one column). let mut row = vec![i]; - while row.len() < cols && i + row.len() < all.len() && all[i + row.len()].0.is_none() { + while row.len() < cols && i + row.len() < all.len() && (all[i + row.len()].0.is_none() || !labels) { row.push(i + row.len()); } ui.horizontal(|ui| { @@ -1259,6 +1264,37 @@ pub(crate) mod tests { lefts.len() } + /// Rows of tool buttons by their tops: how many buttons each row holds, top to bottom. + fn row_sizes(rects: &[egui::Rect]) -> Vec { + let mut rows: Vec<(i32, usize)> = vec![]; + for r in rects { + let top = r.top().round() as i32; + match rows.last_mut() { + Some((t, n)) if *t == top => *n += 1, + _ => rows.push((top, 1)), + } + } + rows.into_iter().map(|(_, n)| n).collect() + } + + /// #1736: without group labels (as it ships), two columns run on from group to group, every + /// row full but maybe the last; with the labels, each group starts its own row under its name. + #[test] + fn two_columns_run_on_without_group_labels() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 200})).unwrap(); + app.ui.toolbar_double = true; + let ctx = egui::Context::default(); + let rows = row_sizes(&frame_in(&mut app, &ctx, 0.0, vec![], 1600.0)); + let slots = slots(&app).len(); + assert_eq!(rows.iter().sum::(), slots); + assert!(rows[..rows.len() - 1].iter().all(|n| *n == 2), "{rows:?}"); + assert_eq!(rows.len(), slots.div_ceil(2)); + app.run("prefs.set", json!({"key": "toolGroupLabels", "value": true})).unwrap(); + let labelled = row_sizes(&frame_in(&mut app, &ctx, 1.0, vec![], 1600.0)); + assert!(labelled.len() > rows.len() && labelled.contains(&1), "{labelled:?}"); + } + #[test] fn the_double_arrow_toggles_one_and_two_columns_in_a_short_window() { let mut app = VectorcraftApp::new(Session::new(), Default::default());