mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 11:17:01 -06:00
Merge pull request #2102 from dotCooCoo/feat/properties-artboard-name-options
Properties: Edit Artboards renames the active artboard and opens Artboard Options
This commit is contained in:
@@ -200,7 +200,8 @@ fn multi_color(app: &mut VectorcraftApp, ctx: &egui::Context) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Edit Artboards (the Artboard tool): the active artboard's name, position and size, its preset and
|
||||
/// Edit Artboards (the Artboard tool): the active artboard's name, which can be edited to rename
|
||||
/// it, and the Artboard Options icon; its position and size, its preset and
|
||||
/// orientation (#671) (with Scale Artwork with Artboard on, a new size scales its art, #602), Move
|
||||
/// and Scale Artwork with Artboard, New Artboard and Delete Artboard, and Quick Actions: Rearrange All (#681) and
|
||||
/// Exit back to the Selection tool (#530).
|
||||
@@ -212,8 +213,20 @@ fn artboard_sections(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let scale_art = super::artboards::scale_art(app);
|
||||
let move_art = super::artboards::move_art(app);
|
||||
if let Some(ab) = app.session.active().and_then(|d| d.doc.artboards.get(i)).cloned() {
|
||||
// Its name as it is (names are never translated).
|
||||
ui.label(egui::RichText::new(&ab.name).size(13.0).color(Tokens::get(ui.ctx()).text));
|
||||
// The field shows the artboard's name as it is (names are never translated). Typing a new
|
||||
// name and pressing Enter or clicking elsewhere renames the artboard as the Artboards panel's
|
||||
// rename does; pressing Escape or leaving the field empty keeps the old name. A click on
|
||||
// the icon opens the artboard's Artboard Options, as double-clicking the Artboard tool's
|
||||
// button does.
|
||||
ui.horizontal(|ui| {
|
||||
let w = (ui.available_width() - 26.0 - ui.spacing().item_spacing.x).max(80.0);
|
||||
if let Some(name) = widgets::text_field(ui, ("ab", "name"), Some(&ab.name), w, 1).filter(|n| !n.is_empty()) {
|
||||
app.run("artboard.setProps", json!({"index": i, "name": name})).ok();
|
||||
}
|
||||
if widgets::icon_button(ui, "dc-artboard-options", tl!("Artboard Options"), false, 26.0).clicked() {
|
||||
app.run("tool.options", json!({"tool": "artboard"})).ok();
|
||||
}
|
||||
});
|
||||
let fw = super::field_width(ui);
|
||||
egui::Grid::new("ab-grid").num_columns(4).spacing([4.0, 6.0]).min_col_width(0.0).show(ui, |ui| {
|
||||
let r = ab.rect;
|
||||
@@ -822,6 +835,98 @@ mod tests {
|
||||
assert_eq!(app.session.tool_id(), "selection");
|
||||
}
|
||||
|
||||
/// Typing a name into Edit Artboards' name field and pressing Enter renames the active artboard
|
||||
/// alone, trimmed as the Artboards panel's rename trims it, with one command and one undo step;
|
||||
/// a click elsewhere in the panel renames it too. Pressing Escape or leaving the field empty
|
||||
/// keeps the name and runs no command.
|
||||
#[test]
|
||||
fn edit_artboards_renames_the_active_artboard() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 100, "artboards": 3})).unwrap();
|
||||
app.select_tool("artboard");
|
||||
super::super::artboards::select(&mut app, 1);
|
||||
app.session.doc_mut().unwrap().mark_saved();
|
||||
let before = app.session.doc().unwrap().doc.artboards.clone();
|
||||
let undo = app.session.doc().unwrap().history.undo.len();
|
||||
let ctx = egui::Context::default();
|
||||
let key = |key, modifiers| vec![Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers }];
|
||||
// Click the field showing Artboard 2's name, select all of it, type `typed`, then send each
|
||||
// frame's events of `end` → the number of commands run.
|
||||
let rename = |app: &mut VectorcraftApp, typed: Vec<Event>, end: Vec<Vec<Event>>| {
|
||||
frame(app, &ctx, vec![]);
|
||||
let texts = frame(app, &ctx, vec![]);
|
||||
let name = app.session.doc().unwrap().doc.artboards[1].name.clone();
|
||||
click(app, &ctx, &texts, &name);
|
||||
frame(app, &ctx, key(egui::Key::A, egui::Modifiers::COMMAND));
|
||||
let commands = app.run_count;
|
||||
frame(app, &ctx, typed);
|
||||
for events in end {
|
||||
frame(app, &ctx, events);
|
||||
}
|
||||
frame(app, &ctx, vec![]);
|
||||
app.run_count.wrapping_sub(commands)
|
||||
};
|
||||
let enter = || vec![key(egui::Key::Enter, egui::Modifiers::NONE)];
|
||||
let text = |s: &str| vec![Event::Text(s.into())];
|
||||
assert_eq!(rename(&mut app, text("Cover"), vec![key(egui::Key::Escape, egui::Modifiers::NONE)]), 0, "Escape");
|
||||
assert_eq!(rename(&mut app, key(egui::Key::Backspace, egui::Modifiers::NONE), enter()), 0, "an empty field");
|
||||
assert_eq!(app.session.doc().unwrap().doc.artboards, before);
|
||||
assert_eq!(rename(&mut app, text(" Cover "), enter()), 1);
|
||||
let mut renamed = before.clone();
|
||||
renamed[1].name = "Cover".into();
|
||||
let st = app.session.doc().unwrap();
|
||||
assert_eq!(st.doc.artboards, renamed, "Artboard 2 alone is renamed");
|
||||
assert_eq!(st.history.undo.len(), undo + 1, "one undo step");
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
let st = app.session.doc().unwrap();
|
||||
assert_eq!(st.doc.artboards, before);
|
||||
assert!(!st.is_dirty());
|
||||
// The click elsewhere is a press and release on the panel's "Artboard" heading, outside
|
||||
// the field.
|
||||
let at = frame(&mut app, &ctx, vec![]).iter().find(|(t, _)| t == "Artboard").map(|(_, r)| r.center()).unwrap();
|
||||
let b = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
assert_eq!(rename(&mut app, text("Back"), vec![vec![Event::PointerMoved(at), b(true)], vec![b(false)]]), 1, "a click elsewhere");
|
||||
renamed[1].name = "Back".into();
|
||||
assert_eq!(app.session.doc().unwrap().doc.artboards, renamed);
|
||||
}
|
||||
|
||||
/// A click on Edit Artboards' Artboard Options icon opens Artboard Options for the active
|
||||
/// artboard, as double-clicking the Artboard tool's button does (`tool.options {tool: "artboard"}`).
|
||||
#[test]
|
||||
fn edit_artboards_opens_the_active_artboards_options() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 100, "artboards": 3})).unwrap();
|
||||
app.select_tool("artboard");
|
||||
super::super::artboards::select(&mut app, 1);
|
||||
app.run("tool.options", json!({"tool": "artboard"})).unwrap();
|
||||
let want = app.ui.dialog.take().unwrap().fields;
|
||||
let ctx = egui::Context::default();
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
let texts = frame(&mut app, &ctx, vec![]);
|
||||
// The icon is the rightmost clickable widget on the name's line.
|
||||
let name = texts.iter().find(|(t, _)| t == "Artboard 2").map(|(_, r)| *r).unwrap();
|
||||
let icon = ctx.viewport(|vp| {
|
||||
vp.prev_pass
|
||||
.widgets
|
||||
.layers()
|
||||
.flat_map(|(_, w)| w.iter())
|
||||
.filter(|w| w.sense.senses_click() && w.rect.y_range().contains(name.center().y))
|
||||
.map(|w| w.rect)
|
||||
.max_by(|a, b| a.left().total_cmp(&b.left()))
|
||||
.unwrap()
|
||||
.center()
|
||||
});
|
||||
let commands = app.run_count;
|
||||
let press = |pressed| Event::PointerButton { pos: icon, button: PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
frame(&mut app, &ctx, vec![Event::PointerMoved(icon), press(true)]);
|
||||
frame(&mut app, &ctx, vec![press(false)]);
|
||||
assert_eq!(app.run_count, commands.wrapping_add(1), "one command");
|
||||
let d = app.ui.dialog.take().expect("Artboard Options");
|
||||
assert_eq!((d.kind.as_str(), d.f64("index", 9.0), d.str("name").as_str()), (crate::dialogs::artboard_options::KIND, 1.0, "Artboard 2"));
|
||||
assert_eq!(d.fields, want, "the fields the tool's double-click shows");
|
||||
assert_eq!(app.session.tool_id(), "artboard");
|
||||
}
|
||||
|
||||
fn artboard_deletion_app(count: usize) -> VectorcraftApp {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 100, "artboards": count})).unwrap();
|
||||
|
||||
@@ -394,6 +394,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Rulers (#1212):** drag the intersection to preview and set a document zero point, or double-click it to reset to the active artboard's top-left. Transform, Properties and Control X/Y fields measure from it and convert entered positions back to absolute command coordinates. The origin survives every native format version and compression; geometry, guides and patterns stay in place. Global/artboard mode switching and video rulers remain separate. Public reference: [Use rulers](https://helpx.adobe.com/ca/illustrator/desktop/measure-and-align/grids-and-guides/use-rulers.html).
|
||||
|
||||
- **Document Setup modal handoff:** Document Setup → Edit Artboards applies valid settings, closes the dialog and activates the Artboard tool. Pointer-click tests cover successful handoff, invalid settings, OK, Cancel and Undo in `dialogs/document_setup.rs`.
|
||||
- **Edit Artboards in Properties:** the active artboard's name is a field. Typing a new name and pressing Enter or clicking elsewhere renames that artboard in one undo step, as the Artboards panel's rename does (`artboard.setProps {index, name}`); pressing Escape or leaving the field empty keeps the old name. A click on the Artboard Options icon beside the field opens Artboard Options for the active artboard, as double-clicking the Artboard tool's button does (`tool.options {tool: "artboard"}`). Pointer and key tests in `panels/properties.rs` cover both. Public references: [Resize artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/resize-artboards.html) and [Create and add new artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/add-new-artboards.html).
|
||||
- **Artboard Escape routing:** Escape leaves Artboard editing for Selection; during a drag it first cancels the gesture. Headless egui key events cover idle Escape, active-drag cancellation and a subsequent Escape in `tests_distortkeys.rs`.
|
||||
- **Artboard deletion keys:** Delete/Backspace delete selected boards in one Undo step, retaining the original first board in the current order when all are selected, as in the public [Delete artboards guide](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/delete-artboards.html). The last board and active drags stay protected; neither key clears selected artwork. Headless shortcut regressions in `tests_distortkeys.rs` cover reordered boards, artwork, saved state and Undo/Redo.
|
||||
- **Linux RPM packaging:** 64-bit runtime capabilities replace Fedora-specific required package names (#1275); glibc 2.35, libm and libgcc_s are declared alongside dynamically loaded xkbcommon and Vulkan/EGL. Generated x86_64/aarch64 metadata and isolated synthetic-provider resolution checked; real-distro install/start-up QA remains pending.
|
||||
|
||||
Reference in New Issue
Block a user