mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Merge pull request #2093 from dotCooCoo/feat/presentation-mode-artboard-keys
View: in Presentation Mode, the left and right arrow keys and a click go between artboards
This commit is contained in:
@@ -89,6 +89,8 @@ enum Drag {
|
||||
Art,
|
||||
/// Fingers making a touch gesture ([`crate::touch`]): the first one's press does nothing.
|
||||
Gesture,
|
||||
/// A press in Presentation Mode: released as a click, it goes to the next artboard.
|
||||
Present,
|
||||
}
|
||||
|
||||
fn drag_id() -> egui::Id {
|
||||
@@ -633,6 +635,27 @@ fn handle_input(app: &mut VectorcraftApp, ui: &Ui, resp: &egui::Response, rect:
|
||||
}
|
||||
}
|
||||
|
||||
// In Presentation Mode a click anywhere goes to the next artboard. The tool receives no press,
|
||||
// drag or hover, and a drag does not pan the view. A drag that began before the mode was turned
|
||||
// on ends as usual.
|
||||
if app.ui.screen_mode == 3 && matches!(drag, None | Some(Drag::Present | Drag::Gesture)) {
|
||||
let pressed = drag.is_none() && pointer.primary_pressed() && resp.hovered() && at.is_some();
|
||||
// `press` is this frame's new press, or else the press held so far. A quick tap can be
|
||||
// pressed and released in one frame.
|
||||
let press = if pressed { Some(Drag::Present) } else { drag };
|
||||
if press.is_some() && !pointer.primary_down() {
|
||||
ui.data_mut(|dd| dd.remove::<Drag>(drag_id()));
|
||||
// A tap of several fingers leaves `Drag::Gesture` (`touch_gestures`), and its release
|
||||
// does not move the view to the next artboard.
|
||||
if press == Some(Drag::Present) && resp.clicked() {
|
||||
crate::shortcuts::presentation_step(app, true);
|
||||
}
|
||||
} else if pressed {
|
||||
ui.data_mut(|dd| dd.insert_temp(drag_id(), Drag::Present));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let tool = app.session.tool_id();
|
||||
// Held, Space is the Hand tool for the moment, and Cmd+Space the Zoom tool (with Alt, zooming
|
||||
// out), whatever the tool.
|
||||
@@ -718,7 +741,7 @@ fn handle_input(app: &mut VectorcraftApp, ui: &Ui, resp: &egui::Response, rect:
|
||||
}
|
||||
ui.data_mut(|dd| dd.insert_temp(drag_id(), Drag::ZoomScrub { start, held, moved }));
|
||||
}
|
||||
Drag::Art | Drag::Gesture => {}
|
||||
Drag::Art | Drag::Gesture | Drag::Present => {}
|
||||
Drag::Tool if drag_art_out(app, ui, resp, p, view) => {
|
||||
ui.data_mut(|dd| dd.insert_temp(drag_id(), Drag::Art));
|
||||
}
|
||||
@@ -772,7 +795,7 @@ fn handle_input(app: &mut VectorcraftApp, ui: &Ui, resp: &egui::Response, rect:
|
||||
let ev = PointerEvent { kind: PointerKind::Up, pos: xf.to_doc(p), mods: mods(m, space), pressure: pen_pressure(ui, false) };
|
||||
dispatch(app, &ev, view);
|
||||
}
|
||||
Drag::Art | Drag::Gesture => {}
|
||||
Drag::Art | Drag::Gesture | Drag::Present => {}
|
||||
Drag::Pan { .. } | Drag::RotateView { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +297,28 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
if ctx.input(|i| i.key_pressed(Key::Tab)) {
|
||||
ctx.memory_mut(|m| m.move_focus(egui::FocusDirection::None));
|
||||
}
|
||||
// In Presentation Mode, ← and → go to the previous and the next artboard. Every arrow key press
|
||||
// is removed from the input here, so no arrow key moves the selection or is sent to the tool.
|
||||
if app.ui.screen_mode == 3 {
|
||||
let mut steps = vec![];
|
||||
ctx.input_mut(|i| {
|
||||
i.events.retain(|e| match e {
|
||||
egui::Event::Key { key: Key::ArrowLeft, pressed: true, .. } => {
|
||||
steps.push(false);
|
||||
false
|
||||
}
|
||||
egui::Event::Key { key: Key::ArrowRight, pressed: true, .. } => {
|
||||
steps.push(true);
|
||||
false
|
||||
}
|
||||
egui::Event::Key { key: Key::ArrowUp | Key::ArrowDown, pressed: true, .. } => false,
|
||||
_ => true,
|
||||
})
|
||||
});
|
||||
for next in steps {
|
||||
presentation_step(app, next);
|
||||
}
|
||||
}
|
||||
// Type tool editing: text, IME composition and editing keys go to the tool.
|
||||
if app.session.tool_wants_text() {
|
||||
type_text(app, ctx);
|
||||
@@ -516,6 +538,21 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Go to the next artboard (`next`) or the previous one with `view.goToArtboard`, as the status bar
|
||||
/// navigator's Next and Previous buttons do, for the arrow keys and a click in Presentation Mode.
|
||||
/// On the last artboard a step forward leaves the view as it is, and so does a step back on the
|
||||
/// first.
|
||||
pub(crate) fn presentation_step(app: &mut VectorcraftApp, next: bool) {
|
||||
let n = app.session.active().map_or(0, |d| d.doc.artboards.len());
|
||||
let at = crate::panels::artboards::selected(app, n);
|
||||
let to = if next { (at + 1 < n).then_some("next") } else { (at > 0).then_some("previous") };
|
||||
if let Some(to) = to
|
||||
&& let Err(e) = app.run("view.goToArtboard", json!({ "index": to }))
|
||||
{
|
||||
app.status(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// What a single-key shortcut reads for `text`, typed by the key at `physical`: the text itself,
|
||||
/// or, when it holds no ASCII character (a layout without Latin letters), what that key types on a
|
||||
/// US layout, so V selects the Selection tool whatever the V key types.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Screen modes and Presentation Mode (#472): Escape leaves Presentation Mode, F cycles the three
|
||||
//! screen modes, and no screen mode is saved with the preferences.
|
||||
//! screen modes, and no screen mode is saved with the preferences. In Presentation Mode, ← and →
|
||||
//! and a click go between the artboards.
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
@@ -23,6 +24,48 @@ fn escape() -> Vec<egui::Event> {
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
/// A press and release of `key`.
|
||||
fn key(key: egui::Key) -> Vec<egui::Event> {
|
||||
[true, false].map(|pressed| egui::Event::Key { key, physical_key: None, pressed, repeat: false, modifiers: Default::default() }).to_vec()
|
||||
}
|
||||
|
||||
/// A click of the primary button at `pos`, one event a frame.
|
||||
fn click(app: &mut VectorcraftApp, ctx: &egui::Context, pos: egui::Pos2) {
|
||||
let button = |pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
for e in [egui::Event::PointerMoved(pos), button(true), button(false)] {
|
||||
frame(app, ctx, vec![e]);
|
||||
}
|
||||
}
|
||||
|
||||
/// A document with three 200 × 100 artboards and a rectangle in the middle of the first, in
|
||||
/// Presentation Mode with the Selection tool, after a frame laid the window out.
|
||||
fn presenting(ctx: &egui::Context) -> VectorcraftApp {
|
||||
let mut app = VectorcraftApp::new(Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 100, "artboards": 3})).unwrap();
|
||||
app.run("shape.rectangle", json!({"x": 90, "y": 40, "width": 20, "height": 20})).unwrap();
|
||||
app.select_tool("selection");
|
||||
frame(&mut app, ctx, vec![]);
|
||||
app.run("view.presentation", json!({})).unwrap();
|
||||
frame(&mut app, ctx, vec![]);
|
||||
app
|
||||
}
|
||||
|
||||
/// The artboard the view is on, its zoom and the document point at its center.
|
||||
fn view(app: &VectorcraftApp) -> (usize, f64, vectorcraft_geom::Point) {
|
||||
let v = app.view().unwrap();
|
||||
(v.artboard, v.zoom, v.center)
|
||||
}
|
||||
|
||||
/// A check that the document, the selection and the undo steps are still as they are now.
|
||||
fn snapshot(app: &VectorcraftApp) -> impl Fn(&VectorcraftApp) -> bool + use<> {
|
||||
let st = app.session.active().unwrap();
|
||||
let (doc, selection, undo) = (st.doc.clone(), st.selection.clone(), st.history.undo.len());
|
||||
move |app| {
|
||||
let st = app.session.active().unwrap();
|
||||
st.doc == doc && st.selection == selection && st.history.undo.len() == undo
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_leaves_presentation_mode() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), crate::Services::default());
|
||||
@@ -39,6 +82,119 @@ fn escape_leaves_presentation_mode() {
|
||||
assert_eq!(app.ui.screen_mode, 0);
|
||||
}
|
||||
|
||||
/// In Presentation Mode → and ← go to the next and the previous artboard and fit it in the
|
||||
/// window, as the status bar navigator's Next and Previous do. On the last artboard → leaves the
|
||||
/// view as it is, and on the first ←, ↑ and ↓ leave it as it is. No arrow key moves the selection.
|
||||
#[test]
|
||||
fn arrow_keys_go_between_artboards_in_presentation_mode() {
|
||||
use egui::Key::{ArrowDown, ArrowLeft, ArrowRight, ArrowUp};
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = presenting(&ctx);
|
||||
app.run("select.all", json!({})).unwrap();
|
||||
let unchanged = snapshot(&app);
|
||||
let centers: Vec<_> = app.session.active().unwrap().doc.artboards.iter().map(|a| a.rect.center()).collect();
|
||||
// All three artboards are 200 × 100, so each fits at the zoom that fits the first.
|
||||
app.run("view.fitArtboard", json!({})).unwrap();
|
||||
let fit = view(&app).1;
|
||||
for (k, to) in [(ArrowRight, 1), (ArrowRight, 2), (ArrowLeft, 1), (ArrowLeft, 0)] {
|
||||
app.run("view.setZoom", json!({"zoom": 300, "center": [5, 5]})).unwrap();
|
||||
frame(&mut app, &ctx, key(k));
|
||||
assert_eq!(view(&app), (to, fit, centers[to]), "{k:?}");
|
||||
}
|
||||
app.run("view.setZoom", json!({"zoom": 300, "center": [5, 5]})).unwrap();
|
||||
let first = view(&app);
|
||||
for k in [ArrowLeft, ArrowUp, ArrowDown] {
|
||||
frame(&mut app, &ctx, key(k));
|
||||
assert_eq!(view(&app), first, "{k:?} on the first artboard");
|
||||
}
|
||||
app.run("view.goToArtboard", json!({"index": "last"})).unwrap();
|
||||
app.run("view.setZoom", json!({"zoom": 300, "center": [5, 5]})).unwrap();
|
||||
let last = view(&app);
|
||||
frame(&mut app, &ctx, key(ArrowRight));
|
||||
assert_eq!(view(&app), last, "→ on the last artboard");
|
||||
assert!(unchanged(&app), "no arrow key moved the selected rectangle");
|
||||
}
|
||||
|
||||
/// In Presentation Mode a click anywhere goes to the next artboard, as the status bar navigator's
|
||||
/// Next does, and the tool gets no press: a click on the rectangle with the Selection tool selects
|
||||
/// nothing, and with the Rectangle tool a drag draws nothing and a click pressed and released in
|
||||
/// one frame opens no dialog. A drag, also with the middle button or with Space held, leaves the
|
||||
/// view as it is, the press that closes the context menu goes to no other artboard, and on the last
|
||||
/// artboard a click leaves the view as it is.
|
||||
#[test]
|
||||
fn a_click_goes_to_the_next_artboard_in_presentation_mode() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = presenting(&ctx);
|
||||
app.run("select.none", json!({})).unwrap();
|
||||
let unchanged = snapshot(&app);
|
||||
// The middle of the window shows the middle of the first artboard, where the rectangle is.
|
||||
let middle = app.canvas_rect.unwrap().center();
|
||||
click(&mut app, &ctx, middle);
|
||||
assert_eq!(view(&app).0, 1, "the click on the rectangle went to the second artboard");
|
||||
assert!(unchanged(&app), "and selected nothing");
|
||||
let right = |pressed| egui::Event::PointerButton { pos: middle, button: egui::PointerButton::Secondary, pressed, modifiers: Default::default() };
|
||||
for e in [vec![right(true)], vec![right(false)], vec![]] {
|
||||
frame(&mut app, &ctx, e);
|
||||
}
|
||||
assert!(egui::Popup::is_any_open(&ctx), "a right click opens the context menu");
|
||||
click(&mut app, &ctx, middle - egui::vec2(150.0, 0.0));
|
||||
assert!(!egui::Popup::is_any_open(&ctx), "a click closes it");
|
||||
assert_eq!(view(&app).0, 1, "the click that closes the context menu");
|
||||
app.select_tool("rectangle");
|
||||
let button = |button, pos, pressed| egui::Event::PointerButton { pos, button, pressed, modifiers: Default::default() };
|
||||
let to = middle + egui::vec2(80.0, 60.0);
|
||||
let second = view(&app);
|
||||
let space = |pressed| egui::Event::Key { key: egui::Key::Space, physical_key: None, pressed, repeat: false, modifiers: Default::default() };
|
||||
for (what, b, with_space) in [
|
||||
("a drag", egui::PointerButton::Primary, false),
|
||||
("a middle-button drag", egui::PointerButton::Middle, false),
|
||||
("a Space drag", egui::PointerButton::Primary, true),
|
||||
] {
|
||||
let mut events = vec![egui::Event::PointerMoved(middle), button(b, middle, true), egui::Event::PointerMoved(to), button(b, to, false)];
|
||||
if with_space {
|
||||
events.insert(0, space(true));
|
||||
events.push(space(false));
|
||||
}
|
||||
for e in events {
|
||||
frame(&mut app, &ctx, vec![e]);
|
||||
}
|
||||
assert_eq!(view(&app), second, "{what}");
|
||||
}
|
||||
let left = |pos, pressed| button(egui::PointerButton::Primary, pos, pressed);
|
||||
frame(&mut app, &ctx, vec![egui::Event::PointerMoved(middle), left(middle, true), left(middle, false)]);
|
||||
assert_eq!(view(&app).0, 2, "a click with the Rectangle tool, pressed and released in one frame");
|
||||
assert!(app.ui.dialog.is_none(), "opens no Rectangle dialog");
|
||||
app.run("view.setZoom", json!({"zoom": 300, "center": [5, 5]})).unwrap();
|
||||
let last = view(&app);
|
||||
click(&mut app, &ctx, middle);
|
||||
assert_eq!(view(&app), last, "a click on the last artboard");
|
||||
assert!(unchanged(&app), "nothing was selected or drawn");
|
||||
}
|
||||
|
||||
/// In Presentation Mode a tap goes to the next artboard. A tap of two fingers undoes (Enable Touch
|
||||
/// Gestures) and goes to no other artboard.
|
||||
#[test]
|
||||
fn a_tap_goes_to_the_next_artboard_in_presentation_mode() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = presenting(&ctx);
|
||||
let objects = |app: &VectorcraftApp| app.session.active().unwrap().doc.layers[0].children().unwrap().len();
|
||||
let c = app.canvas_rect.unwrap().center();
|
||||
let touch = |n: u64, phase, pos| egui::Event::Touch { device_id: egui::TouchDeviceId(1), id: egui::TouchId(n), phase, pos, force: None };
|
||||
let button = |pressed| egui::Event::PointerButton { pos: c, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
// `fingers` fingers tap 50 points apart, the first also pressing and releasing as the pointer.
|
||||
let tap = |app: &mut VectorcraftApp, fingers: u64| {
|
||||
let at = |n: u64| c + egui::vec2(n as f32 * 50.0, 0.0);
|
||||
frame(app, &ctx, vec![touch(0, egui::TouchPhase::Start, c), egui::Event::PointerMoved(c), button(true)]);
|
||||
frame(app, &ctx, (1..fingers).map(|n| touch(n, egui::TouchPhase::Start, at(n))).collect());
|
||||
frame(app, &ctx, vec![touch(0, egui::TouchPhase::End, c), button(false), egui::Event::PointerGone]);
|
||||
frame(app, &ctx, (1..fingers).map(|n| touch(n, egui::TouchPhase::End, at(n))).collect());
|
||||
};
|
||||
tap(&mut app, 1);
|
||||
assert_eq!((view(&app).0, objects(&app)), (1, 1), "a tap");
|
||||
tap(&mut app, 2);
|
||||
assert_eq!((view(&app).0, objects(&app)), (1, 0), "two fingers undo the rectangle");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f_cycles_the_screen_modes_and_leaves_presentation_mode() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), crate::Services::default());
|
||||
|
||||
@@ -490,6 +490,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Drawing aids:** Smart Guides and snapping (moving, with the Selection, Group Selection or Direct Selection tool, and resizing by the bounding-box handles with the Selection or Free Transform tool, Shift and Alt too: the dragged corner or side lands on the other objects' anchors and centres ("anchor", "center") or lines up with their edges and centres (a line and "align"); with Smart Guides › Spacing Guides, a moved selection that lines up with nothing along an axis lands as far from its neighbour as two other objects of its row or column are apart, or halfway between its two neighbours, the equal gaps marked; with View › Snap to Grid on, a moved selection lands on the grid instead, by whichever of its edges or its centre is nearest a gridline on each axis, #740; the Rotate, Scale, Shear and Reflect tools drag from the anchor they grab and snap the pointer the same way, so a corner dragged onto another object's anchor scales exactly onto it, #482; anchors dragged with Direct Selection: the one pressed on lands on other anchors, its own path's that stay put among them, on path segments or in line with them, the others following, Shift keeps 45° steps, and with Smart Guides off Snap to Point pulls it onto anchors; the Artboard tool snaps artboards, and their bleed, to other artboards, bleeds and art, the grid or whole pixels while moving, copying and resizing them, and the Transform panel shows and edits the active artboard's X/Y/W/H; drawn points, #506: hovering shows where the press goes, the point lands on the other objects' anchors, centres and paths ("anchor", "center", "path") or lines up with their anchors, edges and centres ("align"), the Pen's on the path being drawn too, near a construction guide through the anchor before it on that guide (Preferences › Smart Guides › Construction Guides and Angles), and with Shift kept to its 45° step or a square's diagonal it slides along that way into line; the targets are gathered once per document state, from the art in the window, and the anchors, centres and lines near the pointer are found by bisection, so hovering never rescans the document), guides dragged out of the rulers (#451: they snap as moved guides do, Shift to the ruler's ticks; with the Artboard tool they are artboard guides, which run across their artboard only and move, copy and delete with it, `guide.add {artboard}`) that the Selection, Direct Selection and Group Selection tools then pick and drag (#414: the selected guides highlight and move together, Alt copies them, Shift snaps the dragged one to the ticks, otherwise it snaps to pixels, the grid or the edges, centres (so the sides' midpoints) and anchors of the art and the artboards, or with Smart Guides off Snap to Point pulls it onto anchors; Shift-clicked they join the selected art, and Shift-added art keeps them, so guides and art are selected together in either order, Delete then deleting both in one step; the arrow keys nudge them, Delete deletes them, dragging one back onto its ruler deletes it, Lock Guides and Hide Guides leave them alone; `guide.list`, `guide.select`, `guide.move` and `guide.remove` reach agents), a right-click on either ruler or the origin box to pick the document's units (the current one checked; `document.setUnits`, one undo step, #528), and Draw Normal / Behind / Inside modes. Smart Guides: a point drawn with the Pen or a shape tool, and an anchor dragged with Direct Selection, snaps to where paths and ruler guides cross, labelled "intersect" (#1658, part of #1602; a moved selection's own anchors don't).
|
||||
- **Artboard click-to-fit (#1227):** a plain click fits the topmost editable object or group to a new artboard, including its stroke like Fit to Selected Art, and makes the new artboard active. Art and its selection stay unchanged; creation is one undo step. Handles, modified clicks, drags, Escape and tool changes retain their meaning. Tested inside an artboard and on the pasteboard (`crates/engine/tests/artboard_click.rs`); the public [CS6 Printing Guide](https://helpx.adobe.com/content/dam/help/en/indesign/pdf/CS6_PrintGuide.pdf) documents the object-click gesture. This legacy gesture adds evidence without regrading the 2026 canvas controls.
|
||||
- **Views:** the status bar's artboard navigator (first, previous, next, last; `view.goToArtboard`) goes to an artboard, fits it and synchronizes the Artboard tool selection when that tool is active; ordinary object selection is preserved. Actual Next-button regressions cover keyboard/Properties deletion and Undo in `chrome.rs`. Fit Artboard in Window and Actual Size show the navigator's artboard. The Artboards panel highlights that active artboard: clicking a row makes it active (the Artboard tool's too, while it is chosen) as does clicking an artboard with the Artboard tool, double-clicking its number also fits it in the window and double-clicking its name renames it. Duplicate Artboards, or a row dragged onto New Artboard, copies the artboard with its art (#446). Escape leaves Presentation Mode, F from it goes back to Normal Screen Mode, and the screen mode isn't saved with the preferences: the app always starts in Normal Screen Mode with its menus and panels (#472). View › Pixel Preview shows the art as the document pixels it rasterizes to, one per point and hard-edged when magnified, turning with a rotated view (it used to draw the smooth vectors, with only the pixel grid over them). In Outline view a template layer keeps drawing in Preview, dimmed, so a picture placed to be traced stays visible under the outlines (#1505, fixes #1486).
|
||||
In Presentation Mode, → and ← go to the next and the previous artboard, and a click anywhere goes to the next one, as the status bar navigator's Next and Previous do (`view.goToArtboard`): on the last artboard → and a click leave the view as it is, and so does ← on the first. The arrow keys do not move the selection there, and the tool does not receive a click, so a click selects and draws nothing (behavior reference: [View artwork](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)).
|
||||
Align to Artboard uses the active artboard, the one the Artboards panel highlights (`object.align {artboard}`). A press with a selection tool inside an artboard makes that artboard active; with the Selection tool, a Shift-click on an empty spot inside an artboard makes it active and keeps the selection. One selected object is aligned to the active artboard too, whatever Align To says (a lone object is never the key object). When an action plays, its Align to Artboard, All on Active Artboard, Paste in Place, Paste in Front and Paste in Back steps that name no artboard use the artboard that is active then.
|
||||
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.
|
||||
Choosing the Artboard tool (Shift+O, its toolbar button, Edit Artboards in Properties or Document Setup, or the Artboards panel menu's Artboard Options…) starts it on the active artboard: the canvas outlines that artboard, and Delete, Cut, Copy, the Transform panel and Artboard Options act on it.
|
||||
|
||||
Reference in New Issue
Block a user