mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 18:56:55 -06:00
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:
@@ -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