feat(ui): add horizontal and vertical center align button

# Conflicts:
#	crates/engine/src/tests.rs
This commit is contained in:
Murat Vural authored and bflatastic committed 2026-10-12 06:59:49 +07:00
1 parent 365e7f2896
commit a672bd70dd
6 files changed
+41 -5

No files matched your search

+1
View File
@@ -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 | |
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="butt" stroke-linejoin="miter"><path d="M12 3V21M3 12H21" stroke-width="1.5" /><rect x="6.5" y="6.5" width="11" height="11" fill="currentColor" fill-opacity="1.0" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 336 B

+13
View File
@@ -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.
+1
View File
@@ -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")),
+5 -3
View File
@@ -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<R
&& let Some(g) = crate::floating::group_of(&app.ui, p).and_then(|i| app.ui.floating_panels.get_mut(i))
{
g.active = g.panels.iter().position(|q| q == p).unwrap_or(g.active);
g.collapsed = false;
let out = json!({ "floating": g.panels });
app.ui.dock = true;
return Some(Ok(out));
@@ -2151,6 +2150,9 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec<Item>)> {
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"})),
+20 -2
View File
@@ -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]