G5: Symbol Options Export Type (Graphic / Movie Clip)

Store Export Type on symbols for Animate handoff; New Symbol and Symbol Options set it, and symbol.list reports it.
This commit is contained in:
Fabio
2026-10-16 15:27:52 +07:00
committed by bflatastic
parent 21b4e70150
commit 38dd835af6
30 changed files with 445 additions and 36 deletions
+2 -1
View File
@@ -80,7 +80,7 @@ The dimension hours overlap (feature rows hold some UI, format and AI work), so
| 3D and Materials | ~25% | 30–55 |
| Type core — [type-parity.md](docs/type-parity.md) | 80% | 14–19 |
| Type advanced (CJK, spelling, Touch Type) | 50% | 20–28 |
| Symbols, blends, envelopes, Repeat, perspective | 85% | 6–10 |
| Symbols, blends, envelopes, Repeat, perspective | 87% | 5–9 |
| Image Trace, graphs, image tools | 75% | 5–8 |
| Layers, artboards, document setup | 78% | 5–8 |
| View & navigation | 70% | 9–14 |
@@ -134,6 +134,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap
| Date | What landed |
|---|---|
| 2026-10-16 | Symbol Options › Export Type (Graphic or Movie Clip, default Movie Clip) with Registration; `symbol.options` / `symbol.list` (G5).
| 2026-10-16 | About 160 community PRs landed, each through the full gate. Crashes and hostile files: damaged PDF dictionaries and arrays no longer crash import (#2133, #2240, #2244, #2271), area type with huge row or column counts opens (#2109), hostile mesh edges are bounded (#2051). New: Dimension tool (#1788), legacy version 7 `.ai` save (#1794), Object › Intertwine (#2095), Apple Pencil and touch input on the web (#1740), Sample Color from Screen (#2257), Drop Shadow Darkness (#2252), Smart Blur edge modes (#2154), Scribble variations (#2180), Free Distort corners in its dialog (#2134), Rotate View presets (#2106), tritanopia proofing (#2234), Layers panel filters (#2087), Hide Template (#2117), File › Automate Batch through the Actions panel (#1938), dataset and graph CSV (#1929, #1941), Reset Tracking and Kerning (#2274), Alt+Enter copies in Transform fields (#2236), Shift+Tab panels (#2254), Control bar isolation, Recolor and image Replace (#2258, #2079, #2081). Revolve renders smooth with Cap on by default (#1193); Image Trace keeps tiny parts, thin outlines and shared corners, and Grayscale gradients are grey (#2012, #2025, #2263, #2264, #2265, #1879). Import: many `.ai`, EPS and PDF fidelity fixes (#2014–#2016, #2029, #2030, #2044, #2046, #2050–#2052, #2058, #2129, #2199, #2202, #2218), images up to 1 GB decoded (#2060), `.ai`/`.ait`/`.svgz` files paste from a file manager (#2148, #2201). Fixes across fields (arithmetic #2069, #2209, #2022; caret #1403), New Document (#2158, #2176), menus (#2232), shortcuts (#2235, #2146), Transform Each axes (#2227), Paste in Place (#1926), Color Picker tab order (#1937, #1939) and more. Languages: Bulgarian, Greek, Swedish and Finnish complete under the tests, pt-br completed. Details per PR in the PRs; headline numbers unchanged (estimated). |
| 2026-10-14 | Brazilian Portuguese (`pt-br`) covers the whole interface and status/error messages under the completeness tests (#1914); Localization 71% → 74% (measured), hours 55–90 → 50–85. |
| 2026-10-14 | Direct Selection: deleting an interior anchor of an open path leaves separate surviving runs. Delete/Backspace and history regressions cover the gap; explicit Remove Anchors remains connected. Headline numbers unchanged. |
+48
View File
@@ -20,9 +20,11 @@ pub mod inks;
mod inline;
pub mod intertwine;
pub mod shared;
mod symbol_export_type;
mod symbol_registration;
pub use inline::{SYMBOL_HALF, SYMBOL_SIZES};
pub use shared::Shared;
pub use symbol_export_type::{SYMBOL_EXPORT_TYPE, SymbolExportType};
pub use symbol_registration::{SYMBOL_REGISTRATION, SymbolRegistration};
pub mod links;
pub mod live;
@@ -1578,6 +1580,52 @@ impl Document {
o.insert(name.into(), serde_json::json!(reg.0));
}
}
/// Move the stored registration when a symbol is renamed.
pub fn rename_symbol_registration(&mut self, from: &str, to: &str) {
if from == to {
return;
}
let reg = self.symbol_registration(from);
self.set_symbol_registration(from, SymbolRegistration::CENTER);
self.set_symbol_registration(to, reg);
}
/// Symbol Options › Export Type for `name` (default: Movie Clip).
pub fn symbol_export_type(&self, name: &str) -> SymbolExportType {
self.unknown.get(SYMBOL_EXPORT_TYPE).and_then(|m| m.get(name)).and_then(SymbolExportType::from_value).unwrap_or_default()
}
/// Set or clear (when Movie Clip, the default) the export type stored for `name`.
pub fn set_symbol_export_type(&mut self, name: &str, kind: SymbolExportType) {
if kind.is_default() {
self.clear_symbol_export_type(name);
return;
}
let m = self.unknown.entry(SYMBOL_EXPORT_TYPE.to_string()).or_insert_with(|| serde_json::json!({}));
let Some(o) = m.as_object_mut() else { return };
o.insert(name.into(), serde_json::json!(kind.id()));
}
/// Drop any stored export type for `name` (Movie Clip default).
pub fn clear_symbol_export_type(&mut self, name: &str) {
let Some(m) = self.unknown.get_mut(SYMBOL_EXPORT_TYPE) else { return };
let Some(o) = m.as_object_mut() else { return };
o.remove(name);
if o.is_empty() {
self.unknown.remove(SYMBOL_EXPORT_TYPE);
}
}
/// Move the stored export type when a symbol is renamed.
pub fn rename_symbol_export_type(&mut self, from: &str, to: &str) {
if from == to {
return;
}
let kind = self.symbol_export_type(from);
self.clear_symbol_export_type(from);
self.set_symbol_export_type(to, kind);
}
}
fn default_graphic_styles() -> Vec<GraphicStyle> {
+49
View File
@@ -0,0 +1,49 @@
//! Symbol Options › Export Type: Graphic or Movie Clip
//! ([helpx Symbol Options](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/create-and-place-symbols.html)).
//!
//! For Illustrator-only work the choice has no canvas effect; it is kept for Animate / Flash /
//! HTML5 handoff. Illustrator's default is Movie Clip. Stored under [`SYMBOL_EXPORT_TYPE`] keyed
//! by symbol name; the default (Movie Clip) is omitted from the map.
use serde_json::Value;
/// Document unknown key: `{ symbolName: "graphic"|"movieClip" }`.
pub const SYMBOL_EXPORT_TYPE: &str = "symbolExportType";
/// Symbol Options › Export Type.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum SymbolExportType {
/// Static / graphic symbol instance (Animate Graphic).
Graphic,
/// Default: Movie Clip for Animate / game handoff; instances still place the same on the canvas.
#[default]
MovieClip,
}
impl SymbolExportType {
pub const IDS: [&'static str; 2] = ["movieClip", "graphic"];
pub fn id(self) -> &'static str {
match self {
Self::Graphic => "graphic",
Self::MovieClip => "movieClip",
}
}
pub fn from_id(s: &str) -> Option<Self> {
match s {
"graphic" => Some(Self::Graphic),
"movieClip" => Some(Self::MovieClip),
_ => None,
}
}
/// True when this is the document default (nothing stored for the symbol).
pub fn is_default(self) -> bool {
matches!(self, Self::MovieClip)
}
pub fn from_value(v: &Value) -> Option<Self> {
v.as_str().and_then(Self::from_id)
}
}
+96 -9
View File
@@ -11,7 +11,7 @@ use std::sync::Arc;
use serde_json::{Value, json};
use vectorcraft_brush::{self as brush, Brush};
use vectorcraft_color::{Color, Paint};
use vectorcraft_doc::{AppearanceItem, Document, FillLayer, Node, NodeId, NodeKind, Symbol, SymbolRegistration};
use vectorcraft_doc::{AppearanceItem, Document, FillLayer, Node, NodeId, NodeKind, Symbol, SymbolExportType, SymbolRegistration};
use vectorcraft_geom::{Affine, Point, Vec2};
use super::edit::{outermost, selected_roots};
@@ -95,16 +95,25 @@ pub fn specs() -> Vec<CommandSpec> {
brush_freehand
),
// ----- symbols -----
cmd!(query "symbol.list", "Symbols", [], None, "{} → {symbols: [{name, instances, size: [w,h], registration: 0..8}], current}", has_doc, symbol_list),
cmd!(query "symbol.list", "Symbols", [], None, "{} → {symbols: [{name, instances, size: [w,h], registration: 0..8, exportType: movieClip|graphic}], current}", has_doc, symbol_list),
cmd!(
"symbol.new",
"New Symbol…",
[],
None,
"{name?, ids?|id?, registration?: 0..8 (9-point grid, 4 = centre; Symbol Options › Registration)} make a symbol from the selection and replace it with an instance → {name, id, registration}",
"{name?, ids?|id?, registration?: 0..8 (9-point grid, 4 = centre; Symbol Options › Registration), exportType?: movieClip (default)|graphic} make a symbol from the selection and replace it with an instance → {name, id, registration, exportType}",
has_selection,
symbol_new
),
cmd!(
"symbol.options",
"Symbol Options…",
[],
None,
"{name?, newName?, registration?: 0..8, exportType?: movieClip|graphic} rename a symbol and/or set Symbol Options › Registration and Export Type (default name: current) → {name, registration, exportType}",
has_doc,
symbol_options
),
cmd!(
"symbol.place",
"Place Symbol Instance",
@@ -517,10 +526,30 @@ fn place_xf(at: Point, (w, h): (f64, f64), reg: SymbolRegistration) -> Affine {
Affine::translate(c.to_vec2()) * Affine::scale_non_uniform(w / (2.0 * HALF), h / (2.0 * HALF))
}
fn registration_param(p: &Value, cmd: &str) -> Result<SymbolRegistration> {
fn registration_param(p: &Value, cmd: &str) -> Result<Option<SymbolRegistration>> {
match p.get("registration") {
None => Ok(SymbolRegistration::CENTER),
Some(v) => v.as_u64().and_then(SymbolRegistration::from_u64).ok_or_else(|| bad(cmd, "registration must be 0..8")),
None => Ok(None),
Some(v) => v.as_u64().and_then(SymbolRegistration::from_u64).map(Some).ok_or_else(|| bad(cmd, "registration must be 0..8")),
}
}
fn export_type_param(p: &Value, cmd: &str) -> Result<Option<SymbolExportType>> {
match str_param(p, "exportType") {
None => Ok(None),
Some(s) => SymbolExportType::from_id(s).map(Some).ok_or_else(|| bad(cmd, format!("exportType must be movieClip or graphic, not `{s}`"))),
}
}
fn rename_natural_size(d: &mut Document, from: &str, to: &str) {
if from == to {
return;
}
let size = d.unknown.get(SIZES_KEY).and_then(|m| m.get(from)).cloned();
set_natural_size(d, from, None);
if let Some(v) = size
&& let (Some(w), Some(h)) = (v.get(0).and_then(Value::as_f64), v.get(1).and_then(Value::as_f64))
{
set_natural_size(d, to, Some((w, h)));
}
}
@@ -566,6 +595,7 @@ fn symbol_list(s: &mut Session, _: &Value) -> Result<Value> {
"instances": instances_of(d, Some(&sym.name)).len(),
"size": [w, h],
"registration": d.symbol_registration(&sym.name).0,
"exportType": d.symbol_export_type(&sym.name).id(),
})
})
.collect();
@@ -589,11 +619,13 @@ fn replace_with_instance(d: &mut Document, ids: &[NodeId], name: &str, xf: Affin
}
fn symbol_new(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "symbol.new";
let ids = roots(s, p)?;
let art = selection_art(s, p)?.ok_or_else(|| EngineError::Other("nothing selected".into()))?;
let (art, size, c) = normalize(art)?;
let name = s.doc()?.doc.unique_symbol_name(str_param(p, "name").filter(|n| !n.trim().is_empty()).unwrap_or("New Symbol"));
let reg = registration_param(p, "symbol.new")?;
let reg = registration_param(p, C)?.unwrap_or(SymbolRegistration::CENTER);
let export = export_type_param(p, C)?.unwrap_or_default();
// Keep the art where it was: the geometric centre stays at `c`, whatever the registration is.
let place = place_xf(c + reg.offset_from_center(size.0, size.1), size, reg);
let id = s.edit("New Symbol", |d, sel| {
@@ -601,12 +633,64 @@ fn symbol_new(s: &mut Session, p: &Value) -> Result<Value> {
d.symbols.push(Symbol { name: name.clone(), art: Arc::new(art) });
set_natural_size(d, &name, Some(size));
d.set_symbol_registration(&name, reg);
d.set_symbol_export_type(&name, export);
d.unknown.insert(CURRENT_SYMBOL.into(), json!(name));
let id = replace_with_instance(d, &ids, &name, place)?;
sel.set([id]);
Ok(id)
})?;
Ok(json!({ "name": name, "id": id.0, "registration": reg.0 }))
Ok(json!({ "name": name, "id": id.0, "registration": reg.0, "exportType": export.id() }))
}
fn symbol_options(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "symbol.options";
let name = resolve_symbol(&s.doc()?.doc, p, C)?;
let export = export_type_param(p, C)?;
let reg = registration_param(p, C)?;
let new_name = str_param(p, "newName").map(str::trim).filter(|n| !n.is_empty()).map(str::to_string);
if new_name.is_none() && export.is_none() && reg.is_none() {
return Err(bad(C, "pass newName, registration and/or exportType"));
}
if let Some(ref n) = new_name
&& n != &name
&& s.doc()?.doc.symbols.iter().any(|x| x.name == *n)
{
return Err(bad(C, format!("a symbol named `{n}` already exists")));
}
let final_name = new_name.clone().unwrap_or_else(|| name.clone());
s.edit("Symbol Options", |d, _| {
if let Some(ref n) = new_name
&& n != &name
{
if let Some(sym) = d.symbols.iter_mut().find(|x| x.name == name) {
sym.name = n.clone();
}
for id in instances_of(d, Some(&name)) {
if let Some(NodeKind::SymbolInstance { symbol, .. }) = d.node_mut(id).map(|node| &mut node.kind) {
*symbol = n.clone();
}
}
rename_natural_size(d, &name, n);
d.rename_symbol_registration(&name, n);
d.rename_symbol_export_type(&name, n);
if d.unknown.get(CURRENT_SYMBOL).and_then(Value::as_str) == Some(name.as_str()) {
d.unknown.insert(CURRENT_SYMBOL.into(), json!(n));
}
}
if let Some(r) = reg {
d.set_symbol_registration(&final_name, r);
}
if let Some(kind) = export {
d.set_symbol_export_type(&final_name, kind);
}
Ok(())
})?;
let d = &s.doc()?.doc;
Ok(json!({
"name": final_name,
"registration": d.symbol_registration(&final_name).0,
"exportType": d.symbol_export_type(&final_name).id(),
}))
}
fn resolve_symbol(d: &Document, p: &Value, cmd: &str) -> Result<String> {
@@ -763,6 +847,7 @@ fn symbol_delete(s: &mut Session, p: &Value) -> Result<Value> {
d.symbols.retain(|x| x.name != name);
set_natural_size(d, &name, None);
d.set_symbol_registration(&name, SymbolRegistration::CENTER);
d.clear_symbol_export_type(&name);
if d.unknown.get(CURRENT_SYMBOL).and_then(Value::as_str) == Some(name.as_str()) {
d.unknown.remove(CURRENT_SYMBOL);
}
@@ -779,14 +864,16 @@ fn symbol_duplicate(s: &mut Session, p: &Value) -> Result<Value> {
let new = d.unique_symbol_name(str_param(p, "newName").unwrap_or(&format!("{name} copy")));
let size = natural_size(d, &name);
let reg = d.symbol_registration(&name);
let export = d.symbol_export_type(&name);
s.edit("Duplicate Symbol", |d, _| {
let art = d.reid(&sym.art);
d.symbols.push(Symbol { name: new.clone(), art: Arc::new(art) });
set_natural_size(d, &new, Some(size));
d.set_symbol_registration(&new, reg);
d.set_symbol_export_type(&new, export);
Ok(())
})?;
Ok(json!({ "name": new }))
Ok(json!({ "name": new, "registration": reg.0, "exportType": export.id() }))
}
fn symbol_replace(s: &mut Session, p: &Value) -> Result<Value> {
+27
View File
@@ -505,6 +505,33 @@ fn delete_duplicate_and_replace_symbols() {
assert!(s.execute("symbol.delete", &json!({"name": "Small"})).is_err());
}
#[test]
fn symbol_export_type_defaults_to_movie_clip_and_round_trips() {
let mut s = session();
let (_, name, _) = make_symbol(&mut s);
let list = s.execute("symbol.list", &json!({})).unwrap();
assert_eq!(list["symbols"][0]["exportType"], "movieClip");
assert!(s.doc().unwrap().doc.unknown.get(vectorcraft_doc::SYMBOL_EXPORT_TYPE).is_none());
s.execute("symbol.options", &json!({"name": name, "exportType": "graphic"})).unwrap();
assert_eq!(s.execute("symbol.list", &json!({})).unwrap()["symbols"][0]["exportType"], "graphic");
assert_eq!(s.doc().unwrap().doc.unknown[vectorcraft_doc::SYMBOL_EXPORT_TYPE][&name], json!("graphic"));
let copy = s.execute("symbol.duplicate", &json!({"name": name})).unwrap();
assert_eq!(copy["exportType"], "graphic");
s.execute("symbol.options", &json!({"name": name, "newName": "Renamed", "exportType": "movieClip"})).unwrap();
let list = s.execute("symbol.list", &json!({})).unwrap();
let renamed = list["symbols"].as_array().unwrap().iter().find(|x| x["name"] == "Renamed").unwrap();
assert_eq!(renamed["exportType"], "movieClip");
assert!(s.doc().unwrap().doc.unknown.get(vectorcraft_doc::SYMBOL_EXPORT_TYPE).and_then(|m| m.get("Renamed")).is_none());
assert_eq!(s.doc().unwrap().doc.unknown[vectorcraft_doc::SYMBOL_EXPORT_TYPE]["Box copy"], json!("graphic"));
s.execute("symbol.delete", &json!({"name": "Box copy"})).unwrap();
assert!(s.doc().unwrap().doc.unknown.get(vectorcraft_doc::SYMBOL_EXPORT_TYPE).is_none());
rect(&mut s, 0.0, 0.0, 10.0, 10.0);
let r = s.execute("symbol.new", &json!({"name": "Icon", "exportType": "graphic"})).unwrap();
assert_eq!(r["exportType"], "graphic");
assert!(s.execute("symbol.new", &json!({"exportType": "bogus"})).is_err());
assert!(s.execute("symbol.options", &json!({"name": "Renamed"})).is_err());
}
#[test]
fn sprayer_command_creates_a_symbol_set_and_alt_removes() {
let mut s = session();
+68 -12
View File
@@ -1,7 +1,10 @@
//! Symbol Options: the name and registration point of the symbol New Symbol makes from the
//! selection (`symbol.new`), as the Symbols panel's New Symbol button and menu item do.
//! Symbol Options: name, Export Type (Graphic or Movie Clip) and Registration for a new symbol
//! from the selection (`symbol.new`), or for the current symbol in the Symbols panel
//! (`symbol.options`).
//!
//! Fields: `name`, `registration` (0..8, the 9-point grid; 4 = centre).
//! Fields: `name`, `exportType` (`movieClip` | `graphic`), `registration` (0..8, the 9-point grid;
//! 4 = centre), `__edit` (true when editing an existing symbol). Help:
//! <https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/create-and-place-symbols.html>.
use serde_json::{Value, json};
@@ -14,6 +17,9 @@ use crate::widgets;
/// The dialog kind of Symbol Options.
pub const KIND: &str = "symbolOptions";
/// Export Type choices: (value, label). Illustrator's default is Movie Clip.
const EXPORT_TYPES: [(&str, &str); 2] = [("movieClip", "Movie Clip"), ("graphic", "Graphic")];
pub(super) const SPEC: DialogSpec = DialogSpec { heading: |_| tl!("Symbol Options").into(), body, confirm, min_width: 320.0, ..DialogSpec::FORM };
/// Open Symbol Options to name a new symbol made from the selection.
@@ -23,7 +29,38 @@ pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
return Err("nothing selected".into());
}
let name = st.doc.unique_symbol_name("New Symbol");
app.ui.dialog = Some(Dialog::new(KIND, json!({ "name": name, "registration": 4 })));
app.ui.dialog = Some(Dialog::new(KIND, json!({ "name": name, "exportType": "movieClip", "registration": 4, "__edit": false })));
Ok(Value::Null)
}
/// Open Symbol Options on an existing symbol (`name`, else the Symbols panel's current).
pub fn open_edit(app: &mut VectorcraftApp, name: Option<&str>) -> Result<Value, String> {
let st = app.session.active().ok_or("no document open")?;
let name = match name.filter(|n| !n.is_empty()) {
Some(n) if st.doc.symbols.iter().any(|s| s.name == n) => n.to_string(),
Some(n) => return Err(format!("no symbol named `{n}`")),
None => st
.doc
.unknown
.get("currentSymbol")
.and_then(|v| v.as_str())
.filter(|n| st.doc.symbols.iter().any(|s| s.name == *n))
.map(str::to_string)
.or_else(|| st.doc.symbols.first().map(|s| s.name.clone()))
.ok_or_else(|| "the document has no symbols".to_string())?,
};
let export = st.doc.symbol_export_type(&name).id();
let registration = st.doc.symbol_registration(&name).0;
app.ui.dialog = Some(Dialog::new(
KIND,
json!({
"name": name,
"exportType": export,
"registration": registration,
"__edit": true,
"__from": name,
}),
));
Ok(Value::Null)
}
@@ -32,6 +69,14 @@ fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
label(ui, tl!("Name:"));
form::text(ui, d, "name", 190.0);
ui.end_row();
label(ui, tl!("Export Type:"));
let cur = d.str("exportType");
let shown = EXPORT_TYPES.iter().find(|(v, _)| *v == cur).map_or(EXPORT_TYPES[0].1, |(_, l)| *l);
let labels = EXPORT_TYPES.map(|(_, l)| l);
if let Some((value, _)) = widgets::dropdown(ui, "symbol-export-type", shown, &labels, 190.0).and_then(|i| EXPORT_TYPES.get(i)) {
d.fields.insert("exportType".into(), json!(value));
}
ui.end_row();
label(ui, tl!("Registration:"));
let cur = d.fields.get("registration").and_then(Value::as_u64).unwrap_or(4) as usize;
if let Some(i) = widgets::reference_point(ui, cur) {
@@ -42,15 +87,26 @@ fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
false
}
/// OK: make the symbol from the selection with the name and registration chosen.
/// OK: make a new symbol, or apply name / Export Type / Registration on an existing one.
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
let r = app.run(
"symbol.new",
json!({
"name": d.str("name"),
"registration": d.fields.get("registration").and_then(Value::as_u64).unwrap_or(4),
}),
);
let name = d.str("name");
let name = name.trim();
if name.is_empty() {
return Err("name is empty".into());
}
let export = d.str("exportType");
let registration = d.fields.get("registration").and_then(Value::as_u64).unwrap_or(4);
let edit = d.fields.get("__edit").and_then(Value::as_bool).unwrap_or(false);
let r = if edit {
let from = d.fields.get("__from").and_then(Value::as_str).unwrap_or(name);
let mut params = json!({ "name": from, "exportType": export, "registration": registration });
if from != name {
params["newName"] = json!(name);
}
app.run("symbol.options", params)
} else {
app.run("symbol.new", json!({ "name": name, "exportType": export, "registration": registration }))
};
if r.is_ok() {
app.ui.dialog = None;
}
+6
View File
@@ -4928,3 +4928,9 @@ theme Appearance Външен вид
# User Interface › Color Scheme
Color Scheme Цветова схема
Modern Модерна
# Symbol Options › Export Type
Export Type: Тип на експортиране:
Movie Clip Филмов клип
@msg name is empty името е празно
@msg no symbol named `{n}` няма символ с име `{n}`
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4946,3 +4946,9 @@ mark-style Japanese Japonské
# User Interface › Color Scheme
Color Scheme Barevné schéma
Modern Moderní
# Symbol Options › Export Type
Export Type: Typ exportu:
Movie Clip Filmový klip
@msg name is empty název je prázdný
@msg no symbol named `{n}` žádný symbol s názvem `{n}`
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4483,3 +4483,9 @@ theme Appearance Erscheinungsbild
# User Interface › Color Scheme
Color Scheme Farbschema
Modern Modern
# Symbol Options › Export Type
Export Type: Exporttyp:
Movie Clip Movieclip
@msg name is empty Name ist leer
@msg no symbol named `{n}` kein Symbol namens `{n}`
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4898,3 +4898,9 @@ effect Points Σημεία
# User Interface › Color Scheme
Color Scheme Χρωματικός συνδυασμός
Modern Σύγχρονο
# Symbol Options › Export Type
Export Type: Τύπος εξαγωγής:
Movie Clip Κλιπ ταινίας
@msg name is empty το όνομα είναι κενό
@msg no symbol named `{n}` κανένα σύμβολο με όνομα `{n}`
Can't render this file because it contains an unexpected character in line 1859 and column 19.
+6
View File
@@ -4888,3 +4888,9 @@ mark-style Japanese Japonesas
# User Interface › Color Scheme
Color Scheme Esquema de color
Modern Moderno
# Symbol Options › Export Type
Export Type: Tipo de exportación:
Movie Clip Clip de película
@msg name is empty el nombre está vacío
@msg no symbol named `{n}` no hay ningún símbolo llamado `{n}`
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4910,3 +4910,9 @@ mark-style Japanese Japanilainen
# User Interface › Color Scheme
Color Scheme Värimalli
Modern Moderni
# Symbol Options › Export Type
Export Type: Vientityyppi:
Movie Clip Elokuvaleike
@msg name is empty nimi on tyhjä
@msg no symbol named `{n}` symbolia nimeltä `{n}` ei ole
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4945,3 +4945,9 @@ theme Appearance Apparence
# User Interface › Color Scheme
Color Scheme Jeu de couleurs
Modern Moderne
# Symbol Options › Export Type
Export Type: Type d'exportation :
Movie Clip Clip
@msg name is empty le nom est vide
@msg no symbol named `{n}` aucun symbole nommé `{n}`
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4913,3 +4913,9 @@ imageTrace Outline Körvonal
# User Interface › Color Scheme
Color Scheme Színséma
Modern Modern
# Symbol Options › Export Type
Export Type: Exportálás típusa:
Movie Clip Mozgóklip
@msg name is empty a név üres
@msg no symbol named `{n}` nincs `{n}` nevű szimbólum
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4523,3 +4523,9 @@ lighting Light Luce
# User Interface › Color Scheme
Color Scheme Combinazione colori
Modern Moderno
# Symbol Options › Export Type
Export Type: Tipo di esportazione:
Movie Clip Clip filmato
@msg name is empty il nome è vuoto
@msg no symbol named `{n}` nessun simbolo denominato `{n}`
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4564,3 +4564,9 @@ lighting Light ライト
# User Interface › Color Scheme
Color Scheme 配色
Modern モダン
# Symbol Options › Export Type
Export Type: 書き出しタイプ:
Movie Clip ムービークリップ
@msg name is empty 名前が空です
@msg no symbol named `{n}` `{n}` という名前のシンボルはありません
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4487,3 +4487,9 @@ lighting Light 광원
# User Interface › Color Scheme
Color Scheme 색 구성표
Modern 모던
# Symbol Options › Export Type
Export Type: 내보내기 유형:
Movie Clip 무비 클립
@msg name is empty 이름이 비어 있습니다
@msg no symbol named `{n}` 이름이 `{n}`인 심볼이 없습니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4791,3 +4791,9 @@ mark-style Japanese Japońskie
# User Interface › Color Scheme
Color Scheme Schemat kolorów
Modern Nowoczesny
# Symbol Options › Export Type
Export Type: Typ eksportu:
Movie Clip Klip filmowy
@msg name is empty nazwa jest pusta
@msg no symbol named `{n}` brak symbolu o nazwie `{n}`
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7 -1
View File
@@ -1046,6 +1046,9 @@
Graphic Style Options… Opções de Estilo Gráfico…
Symbol Options Opções de Símbolo
Symbol Options… Opções de Símbolo…
Export Type: Tipo de exportação:
Movie Clip Clipe de filme
Graphic Gráfico
Graphic Styles Estilos Gráficos
Graphic Styles Options Opções de Estilos Gráficos
Graphics Processor Processador Gráfico
@@ -4211,7 +4214,6 @@ axis Both Ambos
Delete Group Excluir Grupo
Import Library… Importar Biblioteca…
Export Library… Exportar Biblioteca…
Graphic Gráfico
System Layout Layout do Sistema
Tabular Lining Alinhado Tabular
Tabular Oldstyle Antigo Tabular
@@ -4863,3 +4865,7 @@ mark-style Japanese Japonesas
# User Interface › Color Scheme
Color Scheme Esquema de cores
Modern Moderno
# Symbol Options › Export Type
@msg name is empty o nome está vazio
@msg no symbol named `{n}` nenhum símbolo chamado `{n}`
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -4519,3 +4519,9 @@ lighting Light Свет
# User Interface › Color Scheme
Color Scheme Цветовая схема
Modern Современная
# Symbol Options › Export Type
Export Type: Тип экспорта:
Movie Clip Видеоклип
@msg name is empty имя пустое
@msg no symbol named `{n}` нет символа с именем `{n}`
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4902,3 +4902,9 @@ theme Appearance Utseende
# User Interface › Color Scheme
Color Scheme Färgschema
Modern Modern
# Symbol Options › Export Type
Export Type: Exporttyp:
Movie Clip Filmklipp
@msg name is empty namnet är tomt
@msg no symbol named `{n}` ingen symbol med namnet `{n}`
Can't render this file because it contains an unexpected character in line 5 and column 25.
+6
View File
@@ -4503,3 +4503,9 @@ lighting Light Світло
# User Interface › Color Scheme
Color Scheme Колірна схема
Modern Сучасна
# Symbol Options › Export Type
Export Type: Тип експорту:
Movie Clip Кліп
@msg name is empty ім’я порожнє
@msg no symbol named `{n}` немає символу з назвою `{n}`
Can't render this file because it contains an unexpected character in line 718 and column 40.
+6
View File
@@ -4503,3 +4503,9 @@ theme Appearance Giao diện
# User Interface › Color Scheme
Color Scheme Bảng phối màu
Modern Hiện đại
# Symbol Options › Export Type
Export Type: Kiểu xuất:
Movie Clip Clip phim
@msg name is empty tên trống
@msg no symbol named `{n}` không có biểu tượng nào tên `{n}`
Can't render this file because it contains an unexpected character in line 3733 and column 10.
+6
View File
@@ -4054,3 +4054,9 @@ lighting Light 光源
# User Interface › Color Scheme
Color Scheme 配色方案
Modern 现代
# Symbol Options › Export Type
Export Type: 导出类型:
Movie Clip 影片剪辑
@msg name is empty 名称为空
@msg no symbol named `{n}` 没有名为 `{n}` 的符号
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -3842,3 +3842,9 @@ lighting Light 光源
# User Interface › Color Scheme
Color Scheme 配色方案
Modern 現代
# Symbol Options › Export Type
Export Type: 轉存類型:
Movie Clip 影片片段
@msg name is empty 名稱為空
@msg no symbol named `{n}` 沒有名為 `{n}` 的符號
Can't render this file because it contains an unexpected character in line 6 and column 47.
+10 -2
View File
@@ -465,7 +465,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"ui.symbolOptions",
"Symbol Options…",
"",
"{} open Symbol Options for a new symbol made from the selection, what the Symbols panel's New Symbol opens (dialog `symbolOptions`, fields `name` and `registration` 0..8 on the 9-point grid, default centre; default name the first free \"New Symbol\"/\"New Symbol N\"); OK runs symbol.new {name, registration}",
"{name?} open Symbol Options (dialog `symbolOptions`, fields `name`, `exportType`: movieClip|graphic, `registration` 0..8). Without `name` and with a selection: New Symbol (default name the first free \"New Symbol\"/\"New Symbol N\", Export Type Movie Clip, Registration centre); OK runs symbol.new {name, exportType, registration}. With `edit: true` or `name`: edit that symbol; OK runs symbol.options {name, newName?, exportType, registration}",
),
(
"tool.options",
@@ -1460,7 +1460,15 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
"ui.colorPicker" => crate::dialogs::open_color_picker(app, p),
"ui.sampleScreenColor" => crate::screen_color::start(app, p),
"ui.graphicStyleOptions" => crate::dialogs::graphic_style_options::open(app, s("name").as_deref()),
"ui.symbolOptions" => crate::dialogs::symbol_options::open(app),
"ui.symbolOptions" => {
let name = s("name");
let edit = p.get("edit").and_then(Value::as_bool).unwrap_or(false);
if edit || name.is_some() {
crate::dialogs::symbol_options::open_edit(app, name.as_deref())
} else {
crate::dialogs::symbol_options::open(app)
}
}
"ui.brushOptions" => crate::dialogs::brush_options::open(app, s("name").as_deref()),
"tool.options" => crate::toolbar::open_options(app, &s("tool").unwrap_or_default()),
"ui.colorGuideOptions" => {
+16 -2
View File
@@ -172,11 +172,12 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
let has_sel = app.session.active().is_some_and(|d| !d.selection.is_empty());
let inst = has_instances_selected(app);
let name = json!({"name": sel});
let items: [(&str, bool, &str, Value); 9] = [
let items: [(&str, bool, &str, Value); 10] = [
(tl!("New Symbol…"), has_sel, "ui.symbolOptions", json!({})),
(tl!("Redefine Symbol"), has_sel && sel.is_some(), "symbol.update", name.clone()),
(tl!("Duplicate Symbol"), sel.is_some(), "symbol.duplicate", name.clone()),
(tl!("Delete Symbol"), sel.is_some(), "symbol.delete", name.clone()),
(tl!("Symbol Options…"), sel.is_some(), "ui.symbolOptions", json!({"edit": true, "name": sel})),
(tl!("Edit Symbol"), inst, "symbol.edit", json!({})),
(tl!("Place Symbol Instance"), sel.is_some(), "symbol.place", name.clone()),
(tl!("Replace Symbol"), inst && sel.is_some(), "symbol.replace", name.clone()),
@@ -270,15 +271,28 @@ mod tests {
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some("symbolOptions"));
assert!(symbols(&mut app).is_empty(), "no symbol before OK");
assert_eq!(app.ui.dialog.as_ref().unwrap().str("name"), "New Symbol");
assert_eq!(app.ui.dialog.as_ref().unwrap().str("exportType"), "movieClip");
crate::dialogs::confirm(&mut app).unwrap();
assert!(app.ui.dialog.is_none());
// The next one is offered a name no symbol has, and OK keeps the name typed.
let list = app.run("symbol.list", json!({})).unwrap();
assert_eq!(list["symbols"][0]["exportType"], "movieClip");
// The next one is offered a name no symbol has, and OK keeps the name typed and Export Type.
app.run("shape.rectangle", json!({"x": 60, "y": 10, "width": 30, "height": 20})).unwrap();
app.run("ui.symbolOptions", json!({})).unwrap();
assert_eq!(app.ui.dialog.as_ref().unwrap().str("name"), "New Symbol 2");
app.ui.dialog.as_mut().unwrap().fields.insert("name".into(), json!("Badge"));
app.ui.dialog.as_mut().unwrap().fields.insert("exportType".into(), json!("graphic"));
crate::dialogs::confirm(&mut app).unwrap();
assert_eq!(symbols(&mut app), ["New Symbol", "Badge"]);
let list = app.run("symbol.list", json!({})).unwrap();
assert_eq!(list["symbols"][1]["exportType"], "graphic");
// Symbol Options on the current symbol renames and keeps Export Type.
app.run("ui.symbolOptions", json!({"edit": true, "name": "Badge"})).unwrap();
assert_eq!(app.ui.dialog.as_ref().unwrap().str("exportType"), "graphic");
app.ui.dialog.as_mut().unwrap().fields.insert("name".into(), json!("Mark"));
crate::dialogs::confirm(&mut app).unwrap();
assert_eq!(symbols(&mut app), ["New Symbol", "Mark"]);
assert_eq!(app.run("symbol.list", json!({})).unwrap()["symbols"][1]["exportType"], "graphic");
}
#[test]
+6 -4
View File
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -987,8 +987,10 @@ Guides) can't be picked. An artboard guide is picked, drawn and snapped to acros
These families are in the live catalog (`command_list` / `vectorcraft://commands`); the prompt `trace-and-style`
already walks Image Trace. Short agent entry points:
- Symbols: `symbol.list`, `symbol.new {name?, ids?}`, `symbol.place {name?, x?, y?}`, `symbol.breakLink {ids?}`,
`symbol.replace`, `symbol.edit`, `symbol.duplicate`, `symbol.selectInstances {name}`.
- Symbols: `symbol.list` (each row includes `registration` 0..8 and `exportType`: `movieClip`|`graphic`),
`symbol.new {name?, registration?, exportType?, ids?}`, `symbol.options {name?, newName?, registration?, exportType?}`,
`symbol.place {name?, x?, y?}`, `symbol.breakLink {ids?}`, `symbol.replace`, `symbol.edit`, `symbol.duplicate`,
`symbol.selectInstances {name}`.
- Brushes: `brush.list`, `brush.get {name}`, `brush.apply {name, ids?}`, `brush.new {type, name?, params?, ids?}`,
`brush.setCurrent {name}`, `brush.options {name, params?}`, `brush.freehand`, `object.expandBrush {ids?}`.
- Variables: `variable.list`, `variable.define {name, kind: text|visibility|image|graph}`, `variable.bind {variable, ids?}`,
File diff suppressed because one or more lines are too long