Merge pull request #2761 from dotCooCoo/feat/artboards-select-all

Artboards: Ctrl/Cmd+A in the Artboards panel selects every artboard with the Artboard tool
This commit is contained in:
BFlat
2026-10-18 01:53:16 +07:00
committed by GitHub
5 changed files with 105 additions and 6 deletions
+4 -2
View File
@@ -3359,11 +3359,13 @@ fn field_event(app: &mut VectorcraftApp, id: &str) -> Option<egui::Event> {
/// Invoke an item clicked in the system menu bar (macOS; its key equivalents come back to egui as
/// key presses, see [`crate::native_menu`]). While a text field has the keyboard, Select All, Cut,
/// Copy and Paste act on the field's text, as their keys do; everything else (and those commands
/// with no field focused) goes to [`invoke`].
/// Copy and Paste act on the field's text, as their keys do. While its keys select every artboard
/// ([`crate::panels::artboards::selects_all`]), Select All does too. Everything else (and those
/// commands with no field focused) goes to [`invoke`].
pub fn invoke_from_system_menu(app: &mut VectorcraftApp, ctx: &egui::Context, id: &str, p: Value) {
match focused_text_field(ctx).and_then(|f| Some((f, field_event(app, id)?))) {
Some((field, e)) => to_field(ctx, field, e),
None if id == "select.all" && crate::panels::artboards::selects_all(app, ctx) => crate::panels::artboards::select_all(app),
None => invoke(app, id, p),
}
}
+36 -2
View File
@@ -203,6 +203,32 @@ fn toggle(app: &mut VectorcraftApp, i: usize, n: usize) {
app.session.set_tool_option("selected", &json!(all));
}
/// Whether the panel has the keyboard: one of its rows was clicked, and in every frame since then
/// the panel was drawn and the pointer was not pressed outside its list.
fn has_keyboard(app: &VectorcraftApp, ctx: &egui::Context) -> bool {
pstate::<Option<u64>>(ctx, "ab-keys").is_some_and(|f| f + 1 >= app.frame)
}
/// Whether Select All's shortcut selects every artboard ([`select_all`]) instead of the art: the
/// panel has the keyboard and the Artboard tool is in use. [`crate::shortcuts::handle`] and
/// [`crate::menus::invoke_from_system_menu`] read it before they run `select.all`, which they reach
/// only while no text field has the keyboard.
pub(crate) fn selects_all(app: &VectorcraftApp, ctx: &egui::Context) -> bool {
app.session.tool_id() == "artboard" && has_keyboard(app, ctx)
}
/// Ctrl/Cmd+A while [`selects_all`]: the Artboard tool selects every artboard, and the active
/// artboard stays active. The art's selection stays as it is.
pub(crate) fn select_all(app: &mut VectorcraftApp) {
let n = app.session.active().map_or(0, |d| d.doc.artboards.len());
if n == 0 {
return;
}
let active = selected(app, n);
let all: Vec<usize> = std::iter::once(active).chain((0..n).filter(|i| *i != active)).collect();
app.session.set_tool_option("selected", &json!(all));
}
/// Make artboard `i` the active one, leaving the view where it is: the navigator's and, while it
/// is the tool, the Artboard tool's.
pub(crate) fn select(app: &mut VectorcraftApp, i: usize) {
@@ -254,10 +280,11 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
let sel = selected(app, abs.len());
let all = picked(app, abs.len());
let editing: Option<usize> = pstate(ui.ctx(), "ab-edit");
widgets::list_box(ui, |ui| {
let mut row_clicked = false;
let list_rect = widgets::list_box(ui, |ui| {
ui.set_min_height(110.0);
ui.spacing_mut().item_spacing.y = 0.0;
egui::ScrollArea::vertical().id_salt("ab-scroll").max_height(220.0).show(ui, |ui| {
let scrolled = egui::ScrollArea::vertical().id_salt("ab-scroll").max_height(220.0).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() {
@@ -308,6 +335,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
}
let m = ui.input(|inp| inp.modifiers);
let tool = app.session.tool_id() == "artboard";
row_clicked |= resp.clicked();
if resp.clicked() && tool && m.shift {
let from = select_range(app, pstate(ui.ctx(), "ab-anchor"), i, abs.len());
set_pstate(ui.ctx(), "ab-anchor", Some(from));
@@ -329,7 +357,13 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
}
}
});
scrolled.inner_rect
});
// Keyboard: the panel takes it with a click on a row and gives it up when the pointer is
// pressed outside the list or the panel is not drawn (`ab-keys`: the frame it last had it in).
let pressed_outside = ui.input(|i| i.pointer.any_pressed() && i.pointer.interact_pos().is_some_and(|p| !list_rect.contains(p)));
let keys = row_clicked || (has_keyboard(app, ui.ctx()) && !pressed_outside);
set_pstate(ui.ctx(), "ab-keys", keys.then_some(app.frame));
let n = abs.len();
widgets::bottom_bar(ui, |ui| {
if widgets::icon_button_enabled(ui, "dc-rearrange", tl!("Rearrange All Artboards"), false, n > 1, 24.0).clicked() {
+7 -1
View File
@@ -520,7 +520,13 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
}
}
if let Some((id, p)) = fire {
crate::menus::invoke(app, id, p);
// With the Artboard tool, Select All's keys after a click on an Artboards panel row select
// every artboard instead of the art.
if id == "select.all" && crate::panels::artboards::selects_all(app, ctx) {
crate::panels::artboards::select_all(app);
} else {
crate::menus::invoke(app, id, p);
}
return;
}
// Arrow nudges (Shift = ×10, Alt = copy).
+57 -1
View File
@@ -1,6 +1,7 @@
//! Select All (Cmd+A) follows the keyboard, from the window and from the system menu bar (macOS):
//! a focused text field selects its own text, the Type tool editing selects the text being edited,
//! and only otherwise are all the objects selected.
//! the Artboards panel selects every artboard after a click on one of its rows with the Artboard
//! tool, and only otherwise are all the objects selected.
use egui::{Event, Key, Modifiers, PointerButton, Pos2, Rect, vec2};
use serde_json::json;
@@ -172,6 +173,61 @@ fn cmd_a_on_the_canvas_selects_all_objects() {
assert_eq!(a.selected(), 2, "so does the menu's Select All");
}
/// With the Artboard tool, Cmd+A after a click on an Artboards panel row selects every artboard
/// and leaves the active artboard and the art as they are; so does the macOS menu bar's Select All.
/// After a press on the canvas, with the panel closed, or with the Selection tool, Cmd+A selects
/// every object.
#[test]
fn cmd_a_after_a_click_in_the_artboards_panel_selects_every_artboard() {
let mut a = App::new();
for x in [500, 1000] {
a.app.run("artboard.new", json!({"x": x, "y": 0, "width": 400, "height": 300})).unwrap();
}
a.app.run("window.panel", json!({"panel": "artboards"})).unwrap();
a.app.select_tool("artboard");
let picked = |a: &App| (crate::panels::artboards::picked(&a.app, 3), crate::panels::artboards::selected(&a.app, 3));
// Artboard 2's row, while Artboard 1 is active (the Control bar and the navigator show its name).
let row = |a: &mut App| {
crate::panels::artboards::select(&mut a.app, 0);
a.frame(vec![]);
text_at(&a.frame(vec![]), "Artboard 2", false)
};
let at = row(&mut a);
click_at(&mut a, at);
assert_eq!(picked(&a), (vec![1], 1));
let art = a.selected();
a.frame(vec![press(Key::A, Modifiers::COMMAND)]);
assert_eq!(picked(&a), (vec![0, 1, 2], 1), "Cmd+A selects every artboard, and Artboard 2 stays active");
assert_eq!(a.selected(), art, "and not the art");
// So does Select All in the macOS menu bar.
click_at(&mut a, at);
assert_eq!(picked(&a), (vec![1], 1));
a.menu("select.all");
assert_eq!(picked(&a), (vec![0, 1, 2], 1), "the menu's Select All selects every artboard");
assert_eq!(a.selected(), art, "and not the art");
// A press on the canvas gives the keys back to the art.
let canvas = a.app.canvas_rect.unwrap();
click_at(&mut a, canvas.left_top() + vec2(20.0, 20.0));
a.frame(vec![press(Key::A, Modifiers::COMMAND)]);
assert_eq!(a.selected(), 2, "after a press on the canvas, Cmd+A selects every object");
// So does closing the panel after the click on a row.
a.app.run("select.none", json!({})).unwrap();
let at = row(&mut a);
click_at(&mut a, at);
a.app.run("window.panel", json!({"panel": "artboards", "toggle": true})).unwrap();
a.frame(vec![]);
a.frame(vec![press(Key::A, Modifiers::COMMAND)]);
assert_eq!(a.selected(), 2, "with the panel closed, Cmd+A selects every object");
// With the Selection tool, Cmd+A after a click on a row selects every object.
a.app.run("select.none", json!({})).unwrap();
a.app.run("window.panel", json!({"panel": "artboards"})).unwrap();
a.app.select_tool("selection");
let at = row(&mut a);
click_at(&mut a, at);
a.frame(vec![press(Key::A, Modifiers::COMMAND)]);
assert_eq!(a.selected(), 2, "with the Selection tool, Cmd+A selects every object");
}
/// The copied texts of a frame's output.
fn copied(out: &egui::FullOutput) -> Vec<String> {
out.platform_output.commands.iter().filter_map(|c| if let egui::OutputCommand::CopyText(t) = c { Some(t.clone()) } else { None }).collect()
+1
View File
@@ -483,6 +483,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Shift+Tab (`window.panels`) shows and hides the dock and the floating panels and leaves the toolbar and the Control bar as they are; hiding the panels with Tab or Shift+Tab also closes a panel popped out of the icon column.
- The second Tab (`window.dock`) shows again only the bars the first Tab hid, after a restart too: a Control bar that was off stays off, a toolbar hidden with Window › Tools stays hidden, and panels shown with Shift+Tab in between stay shown.
- Cmd/Ctrl+A follows the keyboard: in a panel, dialog or search field it selects the field's text, while the Type tool edits it selects all of that text (also `select.all` over MCP), and only otherwise every object. On macOS the menu bar's Select All, Cut, Copy and Paste (keys or clicks) act on a focused field too, and elsewhere the in-window menus' do: the field that had the keyboard when the menu opened gets them, with its selection, and the keyboard back (#516). Not yet: Select All across threaded frames (one frame's text is selected).
- With the Artboard tool, Cmd/Ctrl+A after a click on a row of the Artboards panel selects every artboard, as the macOS menu bar's Select All does then; the active artboard stays active, and the art's selection does not change. After a press outside the panel's list, Cmd/Ctrl+A selects every object again. It used to select every object after a click in the panel too. Public reference: [Select artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/select-artboards.html).
- Cmd/Ctrl+Alt+Shift+. and , (`type.size.increaseByFive`, `type.size.decreaseByFive`) step the font size of the selected type, or of the text the Type tool has selected, by five times the Size/Leading increment (Preferences › Type); Cmd/Ctrl+Shift+. and , still step it by one increment.
- Cmd/Ctrl+Alt+Q (`type.resetTracking`) sets the tracking of the selected type, or of the text the Type tool has selected, to 0 and its kerning to Auto (0 in the Character panel), in one undo step; at a caret it resets the kerning of the character before it.
- Cmd/Ctrl+Shift+X (`type.resetHorizontalScale`) sets the horizontal scale of the selected type, or of the text the Type tool has selected, to 100% as the Character panel shows it (the object's own stretch included), in one undo step; at a caret it leaves the text as it is.