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.
This commit is contained in:
Ayberk A. authored and bflatastic committed 2026-10-14 07:37:54 +07:00
1 parent 03c0b8f355
commit cdd656bd48
2 files changed
+45 -5

No files matched your search

+6 -2
View File
@@ -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::<ToolDrag>(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::<Rect>(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.
+39 -3
View File
@@ -182,9 +182,13 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, cols: usize, floating: Option<egu
let mut open_flyout: Option<(Vec<&'static str>, 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<egu
if cols == 1 && cat.chars().count() > 6 { format!("{}...", cat.chars().take(4).collect::<String>()) } 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<usize> {
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::<usize>(), 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());