Files
vectorcraft/crates/engine/src/command_sets.rs
T

169 lines
7.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::{NodeId, Value, cmd, find_command};
/// The commands that act on the active artboard (#693), which the app passes as their `artboard`:
/// Paste in Place, in Front and in Back paste onto it, Align to Artboard aligns to it, Distribute
/// Objects and Distribute Spacing with Align to Artboard distribute objects across it, All on
/// Active Artboard selects on it, and Edit Symbol from the Symbols panel centers the symbol's art
/// on it. A `command.batch` given `artboard` passes it on to each of their steps that names none.
pub const ON_ACTIVE_ARTBOARD: [&str; 8] = [
"edit.pasteInPlace",
"edit.pasteInFront",
"edit.pasteInBack",
"object.align",
"object.distribute",
"object.distributeSpacing",
"select.allOnArtboard",
"symbol.edit",
];
/// The commands that resize the artboards the app passes (Object › Artboards): the app passes the
/// selected artboards to Fit to Artwork Bounds as its `indices`, and the active artboard to Fit to
/// Selected Art as its `index`. A `command.batch` given `artboard` passes it on, as `index`, to
/// each of their steps that names none.
pub const FIT_ACTIVE_ARTBOARD: [&str; 2] = ["artboard.fitToArt", "artboard.fitToSelection"];
/// `(command, param)` pairs the journal records only so a replay reproduces the original run
/// (dates from the clock: `cmd::clock_date`; the artboards the app passes to the
/// [`ON_ACTIVE_ARTBOARD`] and [`FIT_ACTIVE_ARTBOARD`] commands and to a batch; the artboards the
/// Artboard tool had selected for its commands; the artboards and art Cut and Copy took with that
/// tool chosen; the view's center the app passes to Edit Symbol); an action leaves them out
/// ([`Session::journal_for_action`]).
const REPLAY_ONLY: [(&str, &str); 26] = [
("file.new", "created"),
("edit.newDocumentFromState", "created"),
("document.save", "modified"),
("file.saveAs", "modified"),
("edit.pasteInPlace", "artboard"),
("edit.pasteInFront", "artboard"),
("edit.pasteInBack", "artboard"),
("object.align", "artboard"),
("object.distribute", "artboard"),
("object.distributeSpacing", "artboard"),
("artboard.fitToArt", "index"),
("artboard.fitToArt", "indices"),
("artboard.fitToSelection", "index"),
("artboard.duplicate", "selectedArtboards"),
("artboard.copy", "selectedArtboards"),
("artboard.cut", "selectedArtboards"),
("artboard.rename", "selectedArtboards"),
("artboard.switchOrientation", "selectedArtboards"),
("edit.copy", "selectedArtboards"),
("edit.copy", "art"),
("edit.cut", "selectedArtboards"),
("edit.cut", "art"),
("select.allOnArtboard", "artboard"),
("symbol.edit", "artboard"),
("symbol.edit", "viewCenter"),
("command.batch", "artboard"),
];
/// `p`, the params of command `id`, without its [`REPLAY_ONLY`] values (a batch: its steps').
pub(crate) fn strip_replay_only(id: &str, p: &mut Value) {
let strip = |id: &str, p: &mut Value| {
if let Value::Object(m) = p {
m.retain(|k, _| !REPLAY_ONLY.iter().any(|&(c, key)| c == id && key == k));
}
};
strip(id, p);
// Batches don't nest: one level of steps.
if id == "command.batch"
&& let Some(Value::Array(steps)) = p.get_mut("commands")
{
for step in steps {
let id = step.get("command").and_then(Value::as_str).unwrap_or_default().to_string();
if let Some(p) = step.get_mut("params") {
strip(&id, p);
}
}
}
}
/// Queries that apply the edit the active tool is computing before they run, as edits do
/// ([`Session::finish_tool_edit`]): a batch runs edits, Find Objects with `select`, Select All
/// Instances and Lock Guides change the selection, and the proof views, the preferences and added
/// fonts give the documents new revisions (they draw again, and type lays out again).
const WAIT_FOR_TOOL_EDIT: &[&str] = &[
"command.batch",
"document.find",
"symbol.selectInstances",
"view.guides.lock",
"view.proofSetup",
"view.proofColors",
"view.overprintPreview",
"view.separationsPreview",
"prefs.set",
"prefs.reset",
"text.addFontFiles",
];
/// Does command `id`, run on its own, first apply the edit the active tool is computing on
/// another thread and the input held for it ([`Session::finish_tool_edit`])? Every command that
/// can edit, select, save or give a document a new revision does; queries that only read do not
/// (`commands_that_do_not_wait_for_a_tool_edit_change_no_document` in the command sweep checks
/// them). A frontend that adds params from
/// its own state (the active artboard) applies them first itself.
pub fn waits_for_tool_edit(id: &str) -> bool {
find_command(id).is_some_and(|spec| spec.journal || WAIT_FOR_TOOL_EDIT.contains(&id))
}
/// Journaled commands that write a file and change nothing in the document: with an object of a
/// dynamic symbol instance's art selected, they read the instance as queries do
/// ([`crate::Session::execute`]).
pub(crate) const JOURNALED_READS: &[&str] = &["css.export"];
/// Commands that act on the selection and take `ids` by selecting them first (`select.set`, with
/// its checks), then running as on the selection: the result and the selection afterwards are the
/// menu command's (#2130).
pub(crate) const SELECTS_GIVEN_IDS: &[&str] = &[
"edit.duplicate",
"object.arrange.bringForward",
"object.arrange.bringToFront",
"object.arrange.sendBackward",
"object.arrange.sendToBack",
"object.arrange.sendToCurrentLayer",
"object.clippingMask.make",
"object.clippingMask.release",
"object.compoundPath.make",
"object.compoundPath.release",
"object.compoundShape.expand",
"object.compoundShape.make",
"object.compoundShape.release",
"object.compoundShape.setMode",
"object.group",
"object.hide",
"object.lock",
"object.move",
"object.pathfinder.crop",
"object.pathfinder.divide",
"object.pathfinder.exclude",
"object.pathfinder.intersect",
"object.pathfinder.merge",
"object.pathfinder.minusBack",
"object.pathfinder.minusFront",
"object.pathfinder.outline",
"object.pathfinder.trap",
"object.pathfinder.trim",
"object.pathfinder.unite",
"object.reflect",
"object.rotate",
"object.scale",
"object.shear",
"object.ungroup",
];
/// The non-empty `ids` given to a command whose params description names `ids` as a key (the
/// key callers outside the app may pass it, [`cmd::params::takes_key`]), or its `id` as `ids` of
/// one when no `ids` are given and the description takes `id` so
/// ([`cmd::params::id_stands_for_ids`]), as [`cmd::targets`] reads them. Entries of `ids` that are
/// not ids are left out of this list; `Session::execute` refuses them before it reads it.
pub(crate) fn given_ids(description: &'static str, params: &Value) -> Option<Vec<NodeId>> {
if !cmd::params::takes_key(description, "ids") {
return None;
}
match params.get("ids").filter(|v| !v.is_null()) {
Some(v) => v.as_array().map(|a| a.iter().filter_map(Value::as_u64).map(NodeId).collect()),
None => cmd::id_param(params, "id").filter(|_| cmd::params::id_stands_for_ids(description)).map(|id| vec![id]),
}
.filter(|ids| !ids.is_empty())
}