mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Artboards: Ctrl/Cmd+A in the Artboards panel selects every artboard with the Artboard tool
With the Artboard tool in use, Ctrl/Cmd+A after a click on a row of the Artboards panel selected all the art. The panel now takes the keyboard when one of its rows is clicked, as the Layers panel does for Delete, and gives it up when the pointer is pressed outside its list or the panel is not drawn. While it has the keyboard and the Artboard tool is in use, Select All's keys, and Select All in the macOS menu bar, select every artboard: the active artboard stays active and the art's selection does not change. Otherwise Select All still selects the art. Docs: docs/target-app-parity.md.
This commit is contained in:
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user