Merge pull request #4561 from adambelis/fork/feat/control-bar-distribute

feat: show six Distribute Objects buttons in Control bar when 2+ objects selected (Illustrator parity)
This commit is contained in:
BFlat
2026-10-30 20:17:28 +07:00
committed by GitHub
3 changed files with 69 additions and 0 deletions
+6
View File
@@ -244,6 +244,12 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
return;
}
crate::panels::align::align_buttons(app, ui, 24.0);
// Two or more objects get the six Distribute Objects buttons too; a single
// object keeps the bar as it was.
if sel.len() > 1 {
ui.add_space(4.0);
crate::panels::align::distribute_buttons(app, ui, 24.0);
}
ui.separator();
// The Transform link opens the whole Transform panel (reference point, rotate,
// shear, options) in a popover; X/Y/W/H follow inline while the bar has room.
+53
View File
@@ -1633,3 +1633,56 @@ fn full_screen_modes_fill_the_screen_and_normal_mode_restores_the_window() {
app.ui.screen_mode = 0;
assert!(sent(&mut app, true).is_empty(), "still full screen, as the user had it");
}
/// Two or more selected objects show all six Distribute Objects buttons in the Control bar;
/// a single object keeps the bar as it was. A click distributes as the Align panel's button does.
#[test]
fn the_control_bar_shows_the_distribute_buttons_for_two_or_more_objects() {
crate::i18n::set_current(crate::i18n::Lang::EN);
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 200})).unwrap();
let rect = |app: &mut crate::VectorcraftApp, x: f64| {
app.run("shape.rectangle", json!({"x": x, "y": 10, "width": 30, "height": 20})).unwrap()["id"].clone()
};
let (a, b, c) = (rect(&mut app, 10.0), rect(&mut app, 100.0), rect(&mut app, 200.0));
let ctx = egui::Context::default();
ctx.enable_accesskit();
crate::theme::install_fonts(&ctx);
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1600.0, 300.0));
let frame = |app: &mut crate::VectorcraftApp, events: Vec<egui::Event>| {
let mut out = ctx.run_ui(egui::RawInput { events, screen_rect: Some(screen), ..Default::default() }, |ui| super::control_bar(app, ui));
out.textures_delta.clear();
out.platform_output.accesskit_update.map(|u| u.nodes).unwrap_or_default()
};
const DISTRIBUTE: [&str; 6] = [
"Vertical Distribute Top",
"Vertical Distribute Center",
"Vertical Distribute Bottom",
"Horizontal Distribute Left",
"Horizontal Distribute Center",
"Horizontal Distribute Right",
];
let rects = |app: &mut crate::VectorcraftApp| {
frame(app, vec![]);
let nodes = frame(app, vec![]);
DISTRIBUTE.map(|name| {
let n = nodes.iter().find(|(_, n)| n.label() == Some(name)).map(|(_, n)| n)?;
n.bounds().map(|b| egui::Rect::from_min_max(egui::pos2(b.x0 as f32, b.y0 as f32), egui::pos2(b.x1 as f32, b.y1 as f32)))
})
};
app.run("select.set", json!({"ids": [a]})).unwrap();
assert!(rects(&mut app).iter().all(Option::is_none), "one object: no distribute buttons");
app.run("select.set", json!({"ids": [a, b, c]})).unwrap();
let rects = rects(&mut app);
assert!(rects.iter().all(Option::is_some), "three objects: {rects:?}");
let r = rects[3].expect("Horizontal Distribute Left");
let press =
|pressed| egui::Event::PointerButton { pos: r.center(), button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
for events in [vec![egui::Event::PointerMoved(r.center()), press(true)], vec![press(false)]] {
frame(&mut app, events);
}
let left = |app: &crate::VectorcraftApp, id: &serde_json::Value| {
app.session.active().unwrap().doc.node(vectorcraft_doc::NodeId(id.as_u64().unwrap())).unwrap().geometric_bounds().unwrap().x0
};
assert_eq!((left(&app, &a), left(&app, &b), left(&app, &c)), (10.0, 105.0, 200.0), "{}", app.ui.status);
}
+10
View File
@@ -192,6 +192,16 @@ pub fn align_buttons(app: &mut VectorcraftApp, ui: &mut Ui, size: f32) {
}
}
/// The six Distribute Objects buttons of the Control bar, `size` points square. They distribute
/// what the Align panel's buttons do, to what its Align To says.
pub(crate) fn distribute_buttons(app: &mut VectorcraftApp, ui: &mut Ui, size: f32) {
for (icon, tip, axis, v) in DISTRIBUTE {
if widgets::icon_button(ui, icon, tl!(tip), false, size).clicked() {
run_button(app, Button::Distribute(axis, v)).ok();
}
}
}
/// Whether the selection has a key object or a key guide.
fn has_key(app: &VectorcraftApp) -> bool {
app.session.active().is_some_and(|st| st.selection.has_key())