mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 10:45:45 +00:00
Merge pull request #7005 from dotCooCoo/feat/context-menus-beyond-canvas
Artboards: a right-click on a panel row opens a menu with Rename
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
//! The Artboard tool's canvas menu › Rename: a name for the selected artboards. OK runs
|
||||
//! The Artboard tool's canvas menu › Rename, and the Artboards panel's row menu › Rename: a name
|
||||
//! for the selected artboards. OK runs
|
||||
//! `artboard.rename` as one undo step; with several artboards the name is numbered.
|
||||
//!
|
||||
//! Fields: `name` (the artboard's name with one artboard, empty with several) and `indices` (the
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Artboards panel: numbered list with inline rename (double-click the name, or the panel menu's
|
||||
//! Rename; while several artboards are selected, the name typed goes to all of them, numbered),
|
||||
//! Rename; while several artboards are selected, the name typed goes to all of them, numbered), a
|
||||
//! row menu (right-click) whose Rename opens the Rename dialog for the selected artboards,
|
||||
//! move up / down (or drag a row above or below another), new
|
||||
//! (Alt-click: Artboard Options for the new artboard first), delete; a row dragged onto New
|
||||
//! Artboard duplicates its artboard (as Duplicate Artboards does, with
|
||||
@@ -541,7 +542,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let scrolled = scroll.show(ui, |ui| {
|
||||
for (i, name) in abs.iter().enumerate() {
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 24.0), Sense::click_and_drag());
|
||||
if resp.drag_started() {
|
||||
// Only the primary button drags a row; a right-button drag reorders nothing.
|
||||
if resp.drag_started_by(egui::PointerButton::Primary) {
|
||||
egui::DragAndDrop::set_payload(ui.ctx(), RowDrag(i));
|
||||
}
|
||||
if all.contains(&i) {
|
||||
@@ -648,6 +650,14 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
} else if resp.double_clicked() {
|
||||
start_rename(ui.ctx(), app, i);
|
||||
}
|
||||
// A right-click on a row that isn't selected makes its artboard the active one and
|
||||
// the only selected one ([`select`] sets the Artboard tool's `active` option, which
|
||||
// clears the rest of its selection); on a selected row the selection stays. The row
|
||||
// menu then acts on the selection.
|
||||
if resp.secondary_clicked() && !picked(app, abs.len()).contains(&i) {
|
||||
select(app, i);
|
||||
}
|
||||
resp.context_menu(|ui| row_menu(app, ui));
|
||||
// Another row dragged over this one lands above or below it by the pointer's half,
|
||||
// a line showing where.
|
||||
if let Some(from) = resp.dnd_hover_payload::<RowDrag>().map(|d| d.0).filter(|from| *from != i) {
|
||||
@@ -707,6 +717,14 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
});
|
||||
}
|
||||
|
||||
/// A row's menu: Rename opens the Rename dialog for the selected artboards ([`picked`]), whose OK
|
||||
/// runs `artboard.rename` in one undo step, numbering the name when there are several.
|
||||
fn row_menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
if menu_item(ui, tl!("Rename"), true, false) {
|
||||
crate::menus::invoke(app, "artboard.rename", json!({}));
|
||||
}
|
||||
}
|
||||
|
||||
/// Open Rearrange All Artboards (#681).
|
||||
pub(crate) fn open_rearrange(app: &mut VectorcraftApp) {
|
||||
app.run("ui.menuDialog", json!({ "command": "artboard.rearrange" })).ok();
|
||||
|
||||
@@ -1122,6 +1122,132 @@ fn dragging_a_row_onto_another_row_reorders_the_artboards() {
|
||||
assert_eq!(names(&app), ["Artboard 1", "Artboard 2", "Artboard 3"]);
|
||||
}
|
||||
|
||||
/// The panel drawn in a 240 × 400 pt window, for the row menu's tests.
|
||||
struct RowMenuWindow {
|
||||
ctx: egui::Context,
|
||||
time: f64,
|
||||
}
|
||||
|
||||
impl RowMenuWindow {
|
||||
fn new() -> Self {
|
||||
RowMenuWindow { ctx: egui::Context::default(), time: 0.0 }
|
||||
}
|
||||
|
||||
/// One frame of the panel with `events`; the texts drawn and where.
|
||||
fn frame(&mut self, app: &mut VectorcraftApp, events: Vec<egui::Event>) -> Vec<(String, egui::Rect)> {
|
||||
self.time += 0.5;
|
||||
let screen = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(240.0, 400.0)));
|
||||
let raw = egui::RawInput { events, time: Some(self.time), screen_rect: screen, ..Default::default() };
|
||||
let mut out = self.ctx.run_ui(raw, |ui| show(app, ui));
|
||||
out.textures_delta.clear();
|
||||
out.shapes
|
||||
.iter()
|
||||
.filter_map(|c| match &c.shape {
|
||||
egui::Shape::Text(t) => Some((t.galley.text().trim_start().to_string(), t.visual_bounding_rect())),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Where the text `label` is drawn now, if it is.
|
||||
fn find(&mut self, app: &mut VectorcraftApp, label: &str) -> Option<egui::Pos2> {
|
||||
self.frame(app, vec![]).into_iter().find(|(t, _)| t == label).map(|(_, r)| r.center())
|
||||
}
|
||||
|
||||
/// A click with `button` at `at`.
|
||||
fn click(&mut self, app: &mut VectorcraftApp, at: egui::Pos2, button: egui::PointerButton) {
|
||||
let b = |pressed| egui::Event::PointerButton { pos: at, button, pressed, modifiers: Default::default() };
|
||||
self.frame(app, vec![egui::Event::PointerMoved(at), b(true), b(false)]);
|
||||
self.frame(app, vec![]);
|
||||
}
|
||||
|
||||
/// A right-click on the row named `row`, then a click on its menu's Rename.
|
||||
fn rename_from_row_menu(&mut self, app: &mut VectorcraftApp, row: &str) {
|
||||
let at = self.find(app, row).expect("the row");
|
||||
self.click(app, at, egui::PointerButton::Secondary);
|
||||
let item = self.find(app, "Rename").expect("the row menu's Rename");
|
||||
self.click(app, item, egui::PointerButton::Primary);
|
||||
}
|
||||
}
|
||||
|
||||
/// A right-click on an Artboards panel row opens a menu with Rename, which opens the Rename dialog
|
||||
/// and renames nothing until OK: on a row of the selection, the dialog takes every selected
|
||||
/// artboard and OK numbers the name in one undo step; on a row that isn't selected, that row's
|
||||
/// artboard becomes the only selected one and the dialog takes it alone, and Cancel keeps its name.
|
||||
#[test]
|
||||
fn the_row_menus_rename_opens_the_rename_dialog_for_the_selected_artboards() {
|
||||
let mut app = menu_app(4);
|
||||
select(&mut app, 2);
|
||||
app.session.set_tool_option("selected", &json!([2, 1]));
|
||||
let mut window = RowMenuWindow::new();
|
||||
let steps = undo_steps(&app);
|
||||
window.rename_from_row_menu(&mut app, "Artboard 2");
|
||||
let d = app.ui.dialog.as_ref().expect("the Rename dialog");
|
||||
assert_eq!(d.kind, crate::dialogs::rename_artboards::KIND);
|
||||
assert_eq!((d.str("name").as_str(), &d.fields["indices"]), ("", &json!([1, 2])), "the dialog takes both selected artboards");
|
||||
assert_eq!(picked(&app, 4), [1, 2], "the selection stays");
|
||||
assert_eq!(board_names(&app), ["Artboard 1", "Artboard 2", "Artboard 3", "Artboard 4"], "nothing is renamed before OK");
|
||||
assert_eq!(undo_steps(&app), steps);
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("name".into(), json!("Page"));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert_eq!(board_names(&app), ["Artboard 1", "Page-1", "Page-2", "Artboard 4"]);
|
||||
assert_eq!(undo_steps(&app), steps + 1, "one undo step");
|
||||
|
||||
window.rename_from_row_menu(&mut app, "Artboard 4");
|
||||
let d = app.ui.dialog.as_ref().expect("the Rename dialog");
|
||||
assert_eq!((d.str("name").as_str(), &d.fields["indices"]), ("Artboard 4", &json!([3])), "the dialog takes the row's artboard alone");
|
||||
assert_eq!((picked(&app, 4), selected(&app, 4)), (vec![3], 3), "the row's artboard is the only one selected");
|
||||
crate::dialogs::cancel(&mut app);
|
||||
assert!(app.ui.dialog.is_none());
|
||||
assert_eq!(board_names(&app), ["Artboard 1", "Page-1", "Page-2", "Artboard 4"]);
|
||||
assert_eq!(undo_steps(&app), steps + 1, "Cancel records no undo step");
|
||||
}
|
||||
|
||||
/// Without the Artboard tool the row menu's Rename takes the right-clicked row's artboard, which
|
||||
/// becomes the active one; a locked artboard can be renamed too. A menu closed without a choice
|
||||
/// opens no dialog.
|
||||
#[test]
|
||||
fn the_row_menu_renames_the_right_clicked_artboard_with_any_tool() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 100, "height": 50, "units": "Points", "artboards": 3})).unwrap();
|
||||
app.session.execute("artboard.setProps", &json!({"index": 2, "locked": true})).unwrap();
|
||||
select(&mut app, 0);
|
||||
let mut window = RowMenuWindow::new();
|
||||
let at = window.find(&mut app, "Artboard 2").expect("the row");
|
||||
window.click(&mut app, at, egui::PointerButton::Secondary);
|
||||
assert!(window.find(&mut app, "Rename").is_some(), "the row menu is open");
|
||||
let escape = egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() };
|
||||
window.frame(&mut app, vec![escape]);
|
||||
window.frame(&mut app, vec![]);
|
||||
assert!(window.find(&mut app, "Rename").is_none(), "Escape closes the menu");
|
||||
assert!(app.ui.dialog.is_none(), "and opens no dialog");
|
||||
assert_eq!(selected(&app, 3), 1, "the right-clicked artboard is active");
|
||||
|
||||
window.rename_from_row_menu(&mut app, "Artboard 3");
|
||||
let d = app.ui.dialog.as_ref().expect("the Rename dialog");
|
||||
assert_eq!((d.str("name").as_str(), &d.fields["indices"]), ("Artboard 3", &json!([2])));
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("name".into(), json!("Back"));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert_eq!(board_names(&app), ["Artboard 1", "Artboard 2", "Back"], "the locked artboard is renamed");
|
||||
}
|
||||
|
||||
/// A row dragged with the right button reorders nothing; the primary button still drags rows.
|
||||
#[test]
|
||||
fn a_right_button_drag_does_not_move_a_row() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 100, "height": 100, "artboards": 3})).unwrap();
|
||||
let mut window = RowMenuWindow::new();
|
||||
let (first, last) = (window.find(&mut app, "Artboard 1").unwrap(), window.find(&mut app, "Artboard 3").unwrap());
|
||||
let below = last + vec2(0.0, 8.0);
|
||||
let button = |at, pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Secondary, pressed, modifiers: Default::default() };
|
||||
window.frame(&mut app, vec![egui::Event::PointerMoved(first), button(first, true)]);
|
||||
window.frame(&mut app, vec![egui::Event::PointerMoved(first + vec2(0.0, 12.0))]);
|
||||
window.frame(&mut app, vec![egui::Event::PointerMoved(below)]);
|
||||
window.frame(&mut app, vec![button(below, false)]);
|
||||
window.frame(&mut app, vec![]);
|
||||
assert_eq!(board_names(&app), ["Artboard 1", "Artboard 2", "Artboard 3"]);
|
||||
}
|
||||
|
||||
/// A click in a row's lock column locks its artboard and shows the lock next to the name, a
|
||||
/// second click unlocks it, and each click is one undo step. Delete Artboard leaves a locked
|
||||
/// artboard.
|
||||
|
||||
@@ -1123,6 +1123,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
With the Artboard tool in use, a Shift-click on a row of the Artboards panel selects the artboards from the last row clicked without Shift to the clicked row, and the clicked row's artboard becomes the active one. When the artboard of the last row clicked without Shift is no longer selected, the range starts from the active artboard, and later Shift-clicks start from there too. A Ctrl/Cmd-click still adds or removes one artboard. A Shift-click used to add or remove one artboard as well. The public [Select artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/select-artboards.html) page lists Shift-click in the Artboards panel for selecting several artboards.
|
||||
In the Artboards panel, a click on the active artboard's options icon, or a double-click on another row's icon, opens Artboard Options for that row's artboard, and the panel menu's Artboard Options… opens it for the active artboard (part of #1876). Both used to choose the Artboard tool instead. Opened while another tool is in use, the dialog moves the art with a new X or Y when the Artboard tool's Move Artwork with Artboard option is on.
|
||||
With several artboards selected with the Artboard tool, the Artboards panel menu's Rename opens an empty name field on the active artboard's row (on the first selected row when the active artboard is not selected), and the name typed there goes to every selected artboard, numbered in panel order (abc-1, abc-2, abc-3), in one undo step (`artboard.setProps {artboards}`). A double-click on a name with Shift or Ctrl/Cmd held that leaves several artboards selected opens the same field; a Ctrl/Cmd double-click on an unselected row's name adds its artboard to the selection first. Both used to rename the active artboard only. With one artboard selected, Rename still gives it the name as typed. The public [Rename artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/rename-artboards.html) page numbers the name the same way for several selected artboards.
|
||||
A right-click on a row of the Artboards panel opens a menu with Rename, as the [help page](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/rename-artboards.html) describes, with any tool. Rename opens the Rename dialog for the selected artboards, and OK names them in one undo step, numbered in panel order when there are several (`artboard.rename`); Cancel or a menu closed without a choice renames nothing, and a locked artboard can be renamed. A right-click on a row that is not selected first makes its artboard the only selected one and the active one, and on a selected row the selection stays; this is VectorCraft's own rule. A drag with the right button no longer moves a row.
|
||||
Artboard Options has X and Y fields beside a reference point locator (#1876), so an artboard's position can be typed as well as its size. X and Y are where the reference point lies, counted from the ruler origin (#1212), as the Transform panel and the Control bar show an artboard's position, and the locator is the one they and Properties share. A typed X or Y moves the artboard so that its reference point lands there, and a new Width or Height keeps the reference point in place; OK applies everything in one undo step. The shape dialogs still keep their X and Y to themselves (the click position). Pointer and key tests in `dialogs/tests.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) (a reference point in a nine-point grid, then X and Y).
|
||||
Alt-clicking New Artboard in the Artboards panel opens Artboard Options for the new artboard, and OK adds it and makes it the active artboard (`ui.newArtboard`; behavior reference: [Default keyboard shortcuts](https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html), Use panels). A plain click still adds the artboard at once.
|
||||
With Align to Artboard chosen, the Align panel's Distribute Objects and Distribute Spacing buttons use the active artboard (`object.distribute` and `object.distributeSpacing` with `to: "artboard"` and `artboard`). The first object's left or top edge goes to the artboard's left or top edge, and the last object's right or bottom edge goes to the artboard's right or bottom edge. Distribute Objects spaces the other objects evenly by its button's line, and two objects are enough. Distribute Spacing makes the gaps between the objects equal. With anchor points selected, the buttons space the points among themselves and do not use the artboard. When an action plays, its Distribute Objects and Distribute Spacing steps with Align to Artboard use the artboard that is active then.
|
||||
|
||||
Reference in New Issue
Block a user