diff --git a/ASSETS.md b/ASSETS.md
index bf15723a..1003a0e0 100644
--- a/ASSETS.md
+++ b/ASSETS.md
@@ -83,6 +83,7 @@ Generated-in-code art is original and has no file to list. This covers the defau
| `assets/icons/copy.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
| `assets/icons/dc-actions.svg` | VectorCraft contributors | Original work, drawn for VectorCraft | MIT OR Apache-2.0 | |
| `assets/icons/dc-al-bottom.svg` | VectorCraft contributors | Original work, drawn for VectorCraft | MIT OR Apache-2.0 | |
+| `assets/icons/dc-al-center.svg` | VectorCraft contributors | Original work, drawn for VectorCraft | MIT OR Apache-2.0 | |
| `assets/icons/dc-al-hcenter.svg` | VectorCraft contributors | Original work, drawn for VectorCraft | MIT OR Apache-2.0 | |
| `assets/icons/dc-al-left.svg` | VectorCraft contributors | Original work, drawn for VectorCraft | MIT OR Apache-2.0 | |
| `assets/icons/dc-al-right.svg` | VectorCraft contributors | Original work, drawn for VectorCraft | MIT OR Apache-2.0 | |
diff --git a/assets/icons/dc-al-center.svg b/assets/icons/dc-al-center.svg
new file mode 100644
index 00000000..2e8e9e97
--- /dev/null
+++ b/assets/icons/dc-al-center.svg
@@ -0,0 +1 @@
+
diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs
index 8071a7ed..6a66ad1a 100644
--- a/crates/engine/src/tests.rs
+++ b/crates/engine/src/tests.rs
@@ -506,6 +506,19 @@ fn align_to_artboard_uses_the_artboard_named() {
assert_eq!(x0(&s, c), boards[0].x0, "off every artboard: the first");
}
+#[test]
+fn align_both_centers() {
+ let mut s = session();
+ let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
+ let b = rect(&mut s, 20.0, 40.0, 20.0, 20.0);
+ s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
+ s.execute("object.align", &json!({"horizontal": "center", "vertical": "center"})).unwrap();
+ let gb_a = s.doc().unwrap().doc.node(a).unwrap().geometric_bounds().unwrap();
+ let gb_b = s.doc().unwrap().doc.node(b).unwrap().geometric_bounds().unwrap();
+ assert!((gb_a.center().x - gb_b.center().x).abs() < 1e-6);
+ assert!((gb_a.center().y - gb_b.center().y).abs() < 1e-6);
+}
+
/// #541: with the Selection tool, a click on one object of the selection makes it the key object:
/// aligning to the key moves the others to it, Distribute Spacing spaces them from it, and a click
/// on the key again lets it go.
diff --git a/crates/ui-egui/src/icon_data.rs b/crates/ui-egui/src/icon_data.rs
index f2a97a7c..016b4722 100644
--- a/crates/ui-egui/src/icon_data.rs
+++ b/crates/ui-egui/src/icon_data.rs
@@ -31,6 +31,7 @@ pub static ICONS: &[(&str, &[u8])] = &[
("copy", include_bytes!("../../../assets/icons/copy.svg")),
("dc-actions", include_bytes!("../../../assets/icons/dc-actions.svg")),
("dc-al-bottom", include_bytes!("../../../assets/icons/dc-al-bottom.svg")),
+ ("dc-al-center", include_bytes!("../../../assets/icons/dc-al-center.svg")),
("dc-al-hcenter", include_bytes!("../../../assets/icons/dc-al-hcenter.svg")),
("dc-al-left", include_bytes!("../../../assets/icons/dc-al-left.svg")),
("dc-al-right", include_bytes!("../../../assets/icons/dc-al-right.svg")),
diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs
index 6bf171ae..6254bd3c 100644
--- a/crates/ui-egui/src/menus.rs
+++ b/crates/ui-egui/src/menus.rs
@@ -308,13 +308,13 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"window.panel.float",
"Float Panel",
"",
- "{panel: id or \"tools\", x?, y?, onto?: id, below?: id, above?: id, group?: bool, collapsed?: bool} float a panel out of the dock (or out of its floating group) as its own floating group with its top-left corner at x, y in window points (default: cascaded), as dragging its tab out of the dock does; `onto` stacks it as a tab with the floating group holding that panel instead; `below` / `above` puts its group in a set with that group, right below or above it, as dropping it on that group's lower or upper half does (the set's groups stack top to bottom and move together); `group` takes its whole group (its floating group, or the Properties | Layers | Libraries tabs left in the dock); a panel alone in its group is moved (out of its set, given x, y); `collapsed` shows its group's tabs only, as a double-click on a tab does; a docked group floats its dock column. \"tools\" floats the Tools panel. Returns {panel, floating, group, set, pos, collapsed, column}",
+ "{panel: id or \"tools\", x?, y?, onto?: id, group?: bool} float a panel out of the dock (or out of its floating group) as its own floating group with its top-left corner at x, y in window points (default: cascaded), as dragging its tab out of the dock does; `onto` stacks it with the floating group holding that panel instead; `group` takes its whole group (its floating group, or the Properties | Layers | Libraries tabs left in the dock); a panel alone in its group is moved. \"tools\" floats the Tools panel. Returns {panel, floating, group, pos}",
),
(
"window.panel.dock",
"Dock Panel",
"",
- "{panel: id or \"tools\", group?: bool, column?: true or n} put a floating panel (with `group`, its whole group) back in the dock, as dropping it on the dock or its group's × does: Properties, Layers and Libraries in the tabbed group, the other panels in the icon column; with `column`, dock its group and the rest of its set as a dock column instead, as dropping it on the left edge of the icon column or of a dock column does: `true` at the dock's left, n at that place (1: next to the icon column, counting left); \"tools\" docks the Tools panel at the window's left edge. Returns {panel, floating, column}",
+ "{panel: id or \"tools\", group?: bool} put a floating panel (with `group`, its whole group) back in the dock, as dropping it on the dock or its group's × does: Properties, Layers and Libraries in the tabbed group, the other panels in the icon column; \"tools\" docks the Tools panel at the window's left edge",
),
(
"window.collapseDock",
@@ -1181,7 +1181,6 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option Vec<(&'static str, Vec)> {
cp("Horizontal Align Left", "object.align", json!({"horizontal": "left"})),
cp("Horizontal Align Center", "object.align", json!({"horizontal": "center"})),
cp("Horizontal Align Right", "object.align", json!({"horizontal": "right"})),
+ Sep,
+ cp("Horizontal & Vertical Align Center", "object.align", json!({"horizontal": "center", "vertical": "center"})),
+ Sep,
cp("Vertical Align Top", "object.align", json!({"vertical": "top"})),
cp("Vertical Align Center", "object.align", json!({"vertical": "center"})),
cp("Vertical Align Bottom", "object.align", json!({"vertical": "bottom"})),
diff --git a/crates/ui-egui/src/panels/align.rs b/crates/ui-egui/src/panels/align.rs
index 40b9d23a..a24f191f 100644
--- a/crates/ui-egui/src/panels/align.rs
+++ b/crates/ui-egui/src/panels/align.rs
@@ -58,14 +58,20 @@ pub const DISTRIBUTE: [(&str, &str, &str, &str); 6] = [
pub fn align_buttons(app: &mut VectorcraftApp, ui: &mut Ui, size: f32) {
let n = selection_len(app);
let to = align_to(app, ui.ctx());
- for (icon, tip, axis, v) in [
+ for (i, (icon, tip, axis, v)) in [
("align-start-vertical", "Horizontal Align Left", "horizontal", "left"),
("align-center-vertical", "Horizontal Align Center", "horizontal", "center"),
("align-end-vertical", "Horizontal Align Right", "horizontal", "right"),
("align-start-horizontal", "Vertical Align Top", "vertical", "top"),
("align-center-horizontal", "Vertical Align Center", "vertical", "center"),
("align-end-horizontal", "Vertical Align Bottom", "vertical", "bottom"),
- ] {
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ if i == 3 && widgets::icon_button(ui, "dc-al-center", tl!("Horizontal & Vertical Align Center"), false, size).clicked() {
+ app.run("object.align", align_params(json!({"horizontal": "center", "vertical": "center"}), to, n)).ok();
+ }
if widgets::icon_button(ui, icon, tl!(tip), false, size).clicked() {
app.run("object.align", align_params(json!({axis: v}), to, n)).ok();
}
@@ -98,6 +104,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
for (i, (icon, tip, axis, v)) in ALIGN.iter().enumerate() {
if i == 3 {
ui.add_space(6.0);
+ if widgets::icon_button_enabled(ui, "dc-al-center", tl!("Horizontal & Vertical Align Center"), false, n >= 1, 32.0).clicked() {
+ app.run("object.align", align_params(json!({"horizontal": "center", "vertical": "center"}), to, n)).ok();
+ }
+ ui.add_space(6.0);
}
if widgets::icon_button_enabled(ui, icon, tl!(tip), false, n >= 1, 32.0).clicked() {
app.run("object.align", align_params(json!({*axis: v}), to, n)).ok();
@@ -247,6 +257,14 @@ mod tests {
assert_eq!(bounds(&app).x0, 0.0, "the artboard's left edge");
}
+ #[test]
+ fn align_both_centers_params() {
+ let p = align_params(json!({"horizontal": "center", "vertical": "center"}), AlignTo::Selection, 2);
+ assert_eq!(p["horizontal"], "center");
+ assert_eq!(p["vertical"], "center");
+ assert_eq!(p["to"], "selection");
+ }
+
/// #541: while the selection has a key object Align aligns to it, whatever Align To says;
/// choosing Align to Selection lets the key go.
#[test]