mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
Merge pull request #5007 from mockydevs/feat/symbols-select-unused-4918
Fix #4918: Symbols panel Select All Unused
This commit is contained in:
@@ -38,7 +38,7 @@ use symbols::selected_instances;
|
||||
pub(crate) use symbols::{instance_in_place, natural_size, normalize, place_xf, set_natural_size, store_symbol};
|
||||
use symbols::{
|
||||
symbol_adjust, symbol_break_link, symbol_delete, symbol_duplicate, symbol_edit, symbol_list, symbol_new, symbol_options, symbol_place,
|
||||
symbol_replace, symbol_select_instances, symbol_set_current, symbol_spray, symbol_update,
|
||||
symbol_replace, symbol_select_instances, symbol_set_current, symbol_spray, symbol_unused, symbol_update,
|
||||
};
|
||||
|
||||
const SIZES_KEY: &str = vectorcraft_doc::SYMBOL_SIZES;
|
||||
@@ -228,7 +228,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Delete Symbol",
|
||||
[],
|
||||
None,
|
||||
"{name, expandInstances?: true} delete a symbol; its instances are expanded (or deleted with false)",
|
||||
"{name | names: [name…], expandInstances?: true} delete a symbol (or several, in one undo step); its instances are expanded (or deleted with false)",
|
||||
has_doc,
|
||||
symbol_delete
|
||||
),
|
||||
@@ -269,6 +269,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
symbol_set_current
|
||||
),
|
||||
cmd!(
|
||||
query "symbol.unused",
|
||||
"Select All Unused",
|
||||
[],
|
||||
None,
|
||||
"{} → {names: [the symbols nothing places, in panel order]} (the Symbols panel selects them): instances in the art (opacity masks included), graph designs, pattern tiles (with the tiles that pattern editing's Cancel puts back) and brush art count, and so do instances in the art of a symbol that is used",
|
||||
has_doc,
|
||||
symbol_unused
|
||||
),
|
||||
cmd!(query "symbol.selectInstances", "Select All Instances", [], None, "{name} select every instance of a symbol → {count}", has_doc, symbol_select_instances),
|
||||
cmd!(
|
||||
"symbol.spray",
|
||||
|
||||
@@ -107,6 +107,37 @@ fn instances_of(d: &Document, name: Option<&str>) -> Vec<NodeId> {
|
||||
v
|
||||
}
|
||||
|
||||
/// The symbols an instance in `n` (its opacity masks included) places, pushed onto `out`.
|
||||
fn art_symbols(n: &Node, out: &mut Vec<String>) {
|
||||
vectorcraft_doc::swatches::walk_with_masks(n, &mut |m| {
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &m.kind {
|
||||
out.push(symbol.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Select All Unused (#4918): the symbols nothing places. Instances in the art (opacity masks
|
||||
/// included), graph designs, pattern tiles and the tiles that pattern editing's Cancel puts back,
|
||||
/// and the art of the document's brushes count, and so do the instances in the art of every symbol
|
||||
/// a counted instance places. A symbol placed only inside an unused symbol is unused too.
|
||||
pub(super) fn symbol_unused(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let d = &s.doc()?.doc;
|
||||
let mut todo = vec![];
|
||||
let lib = brush::library(d);
|
||||
let patterns = d.patterns.iter().chain(d.pattern_edit.iter().filter_map(|pe| pe.original.as_ref()));
|
||||
let trees = d.layers.iter().map(|l| &**l).chain(d.graph_designs.iter().map(|g| &*g.art));
|
||||
trees.chain(patterns.flat_map(|p| p.art.iter().map(|n| &**n))).chain(lib.iter().flat_map(|b| b.art())).for_each(|n| art_symbols(n, &mut todo));
|
||||
let mut used = std::collections::BTreeSet::new();
|
||||
while let Some(name) = todo.pop() {
|
||||
if !used.contains(&name) {
|
||||
d.symbols.iter().filter(|sym| sym.name == name).for_each(|sym| art_symbols(&sym.art, &mut todo));
|
||||
used.insert(name);
|
||||
}
|
||||
}
|
||||
let names: Vec<&str> = d.symbols.iter().map(|sym| sym.name.as_str()).filter(|n| !used.contains(*n)).collect();
|
||||
Ok(json!({ "names": names }))
|
||||
}
|
||||
|
||||
fn symbol_names(d: &Document) -> Vec<String> {
|
||||
d.symbols.iter().map(|s| s.name.clone()).collect()
|
||||
}
|
||||
@@ -417,27 +448,74 @@ pub(super) fn symbol_update(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({ "name": name, "id": id.0 }))
|
||||
}
|
||||
|
||||
/// How many levels of nested instances Delete Symbol expands before it gives up.
|
||||
const MAX_DELETE_ROUNDS: usize = 256;
|
||||
|
||||
pub(super) fn symbol_delete(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "symbol.delete";
|
||||
let name = name_param(p, C)?.to_string();
|
||||
if !s.doc()?.doc.symbols.iter().any(|x| x.name == name) {
|
||||
// `names` deletes several symbols in one undo step (the Symbols panel's Select All Unused, #4918).
|
||||
let names: Vec<String> = match p.get("names") {
|
||||
Some(v) => {
|
||||
let a = v.as_array().ok_or_else(|| bad(C, "names must be an array of symbol names"))?;
|
||||
let mut names = Vec::with_capacity(a.len());
|
||||
for n in a {
|
||||
let n = n.as_str().ok_or_else(|| bad(C, "names must be an array of symbol names"))?;
|
||||
if !names.iter().any(|x: &String| x == n) {
|
||||
names.push(n.to_string());
|
||||
}
|
||||
}
|
||||
if names.is_empty() {
|
||||
return Err(bad(C, "names is empty"));
|
||||
}
|
||||
names
|
||||
}
|
||||
None => vec![name_param(p, C)?.to_string()],
|
||||
};
|
||||
let d = &s.doc()?.doc;
|
||||
if let Some(name) = names.iter().find(|n| !d.symbols.iter().any(|x| &x.name == *n)) {
|
||||
return Err(bad(C, format!("no symbol named `{name}`")));
|
||||
}
|
||||
let expand = switch_or(p, "expandInstances", true, C)?;
|
||||
s.edit("Delete Symbol", |d, _| {
|
||||
let inst = instances_of(d, Some(&name));
|
||||
if expand {
|
||||
break_links(d, &inst, true)?;
|
||||
} else {
|
||||
remove_each(d, &inst)?;
|
||||
let label = if names.len() == 1 { "Delete Symbol" } else { "Delete Symbols" };
|
||||
s.edit(label, |d, _| {
|
||||
// Every instance goes before any definition, whatever the order of `names`: expanding an
|
||||
// instance of one symbol can bring out instances of another being deleted (nested in its
|
||||
// art), which expand in turn while their definitions are still there. Each round expands
|
||||
// one level of nesting, so the rounds are capped.
|
||||
let of_names = |d: &Document| {
|
||||
let mut v = vec![];
|
||||
d.walk(|n| {
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &n.kind
|
||||
&& names.contains(symbol)
|
||||
{
|
||||
v.push(n.id);
|
||||
}
|
||||
});
|
||||
v
|
||||
};
|
||||
for _ in 0..=MAX_DELETE_ROUNDS {
|
||||
let inst = of_names(d);
|
||||
if inst.is_empty() {
|
||||
break;
|
||||
}
|
||||
if expand {
|
||||
break_links(d, &inst, true)?;
|
||||
} else {
|
||||
remove_each(d, &inst)?;
|
||||
}
|
||||
}
|
||||
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);
|
||||
d.set_symbol_nine_slice(&name, None);
|
||||
if d.unknown.get(CURRENT_SYMBOL).and_then(Value::as_str) == Some(name.as_str()) {
|
||||
d.unknown.remove(CURRENT_SYMBOL);
|
||||
if !of_names(d).is_empty() {
|
||||
return Err(EngineError::Other("the symbols are nested too deeply to expand their instances".into()));
|
||||
}
|
||||
for name in &names {
|
||||
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);
|
||||
d.set_symbol_nine_slice(name, None);
|
||||
if d.unknown.get(CURRENT_SYMBOL).and_then(Value::as_str) == Some(name.as_str()) {
|
||||
d.unknown.remove(CURRENT_SYMBOL);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
@@ -1432,3 +1432,72 @@ fn symbol_nine_slice_guides_round_trip() {
|
||||
s.execute("symbol.delete", &json!({"name": "Box copy"})).unwrap();
|
||||
assert!(s.doc().unwrap().doc.unknown.get(vectorcraft_doc::SYMBOL_NINE_SLICE).is_none());
|
||||
}
|
||||
|
||||
/// Symbols panel › Select All Unused (#4918): a symbol counts as used when an instance in the art,
|
||||
/// a pattern tile, the art of a brush or a used symbol places it; one placed only inside an unused
|
||||
/// symbol is unused too. Delete Symbol with `names` deletes several in one undo step.
|
||||
#[test]
|
||||
fn symbol_unused_lists_the_symbols_nothing_places_and_delete_takes_several() {
|
||||
let mut s = session();
|
||||
// A symbol of the selection (a new square at `x` when `x` is given), left placed.
|
||||
let symbol = |s: &mut Session, name: &str, x: Option<f64>| {
|
||||
if let Some(x) = x {
|
||||
rect(s, x, 0.0, 10.0, 10.0);
|
||||
}
|
||||
NodeId(s.execute("symbol.new", &json!({"name": name})).unwrap()["id"].as_u64().unwrap())
|
||||
};
|
||||
let unused =
|
||||
|s: &mut Session| -> Vec<String> { serde_json::from_value(s.execute("symbol.unused", &json!({})).unwrap()["names"].clone()).unwrap() };
|
||||
// Placed: its instance stays on the artboard.
|
||||
symbol(&mut s, "Placed", Some(0.0));
|
||||
// Inner is placed only inside Outer, whose instance stays: both are used.
|
||||
let inner = symbol(&mut s, "Inner", Some(20.0));
|
||||
s.execute("select.set", &json!({"ids": [inner.0]})).unwrap();
|
||||
symbol(&mut s, "Outer", None);
|
||||
// Lonely's and Nested's instances go; Nested is placed only inside Lonely.
|
||||
let nested = symbol(&mut s, "Nested", Some(40.0));
|
||||
s.execute("select.set", &json!({"ids": [nested.0]})).unwrap();
|
||||
let lonely = symbol(&mut s, "Lonely", None);
|
||||
s.execute("edit.clear", &json!({"ids": [lonely.0]})).unwrap();
|
||||
// In a brush's art only.
|
||||
let in_brush = symbol(&mut s, "Brushed", Some(60.0));
|
||||
s.execute("brush.new", &json!({"type": "art", "name": "B", "ids": [in_brush.0]})).unwrap();
|
||||
s.execute("edit.clear", &json!({"ids": [in_brush.0]})).unwrap();
|
||||
assert_eq!(unused(&mut s), ["Nested", "Lonely"], "in panel order");
|
||||
// A placed one becomes unused when its last instance goes.
|
||||
let placed = s.doc().unwrap().doc.layers[0]
|
||||
.children()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|n| matches!(&n.kind, NodeKind::SymbolInstance { symbol, .. } if symbol == "Placed"))
|
||||
.unwrap()
|
||||
.id;
|
||||
s.execute("edit.clear", &json!({"ids": [placed.0]})).unwrap();
|
||||
assert_eq!(unused(&mut s), ["Placed", "Nested", "Lonely"]);
|
||||
// Delete Symbol takes them all in one undo step, and Undo brings them back.
|
||||
let before = s.doc().unwrap().history.undo.len();
|
||||
s.execute("symbol.delete", &json!({"names": ["Placed", "Nested", "Lonely"]})).unwrap();
|
||||
let names = |s: &Session| s.doc().unwrap().doc.symbols.iter().map(|x| x.name.clone()).collect::<Vec<_>>();
|
||||
assert_eq!(names(&s), ["Inner", "Outer", "Brushed"]);
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), before + 1);
|
||||
assert!(unused(&mut s).is_empty());
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(names(&s), ["Placed", "Inner", "Outer", "Nested", "Lonely", "Brushed"]);
|
||||
// Deleting a symbol and the one placed inside it, in either order, expands every instance
|
||||
// while both definitions are still there: no instance is left without its symbol.
|
||||
for order in [["Inner", "Outer"], ["Outer", "Inner"]] {
|
||||
s.execute("symbol.delete", &json!({"names": order})).unwrap();
|
||||
let mut dangling = 0;
|
||||
s.doc()
|
||||
.unwrap()
|
||||
.doc
|
||||
.walk(|n| dangling += usize::from(matches!(&n.kind, NodeKind::SymbolInstance { symbol, .. } if symbol == "Inner" || symbol == "Outer")));
|
||||
assert_eq!(dangling, 0, "{order:?}");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
}
|
||||
// Bad `names` are refused and change nothing.
|
||||
for bad in [json!({"names": []}), json!({"names": "Placed"}), json!({"names": [1]}), json!({"names": ["Placed", "Nope"]})] {
|
||||
assert!(s.execute("symbol.delete", &bad).is_err(), "{bad}");
|
||||
}
|
||||
assert_eq!(names(&s).len(), 6);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,14 @@ pub const KIND: &str = "deleteSymbol";
|
||||
pub const USED: &str = "deleteUsedSymbol";
|
||||
|
||||
pub(super) const SPEC: DialogSpec = DialogSpec {
|
||||
heading: |_| tl!("Delete the selected symbol?").into(),
|
||||
heading: |d| {
|
||||
if d.fields.get("names").and_then(Value::as_array).is_some_and(|n| n.len() > 1) {
|
||||
tl!("Delete the selected symbols?")
|
||||
} else {
|
||||
tl!("Delete the selected symbol?")
|
||||
}
|
||||
.into()
|
||||
},
|
||||
body: |_, _, _| false,
|
||||
confirm: confirm_unused,
|
||||
ok: Some("Yes"),
|
||||
@@ -55,6 +62,37 @@ pub(crate) fn ask(app: &mut VectorcraftApp, name: &str, now: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete Symbol for the unused symbols `names` that Select All Unused picked (#4918): asked once
|
||||
/// ("Delete the selected symbols?", or the singular for one), or at once with `now`. Only those still
|
||||
/// unused then (`symbol.unused`: brush art, patterns and nested instances count) go, in one undo step.
|
||||
pub(crate) fn ask_many(app: &mut VectorcraftApp, names: &[String], now: bool) {
|
||||
let Some(uid) = app.session.active().map(|st| st.uid) else { return };
|
||||
if now {
|
||||
// An error already went to the status bar.
|
||||
let _ = delete_unused(app, uid, names);
|
||||
} else {
|
||||
app.ui.dialog = Some(Dialog::new(KIND, json!({ "names": names, "doc": uid })));
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes those of `names` that are still unused in document `uid` while it is the active one.
|
||||
fn delete_unused(app: &mut VectorcraftApp, uid: u64, names: &[String]) -> DialogResult {
|
||||
if app.session.active().map(|st| st.uid) != Some(uid) {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
let unused: Vec<String> =
|
||||
app.run("symbol.unused", json!({})).ok().and_then(|r| serde_json::from_value(r["names"].clone()).ok()).unwrap_or_default();
|
||||
let names: Vec<&String> = names.iter().filter(|n| unused.contains(n)).collect();
|
||||
if names.is_empty() {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
let r = app.run("symbol.delete", json!({ "names": names }));
|
||||
if let Err(e) = &r {
|
||||
app.status(e.clone());
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
fn open(app: &mut VectorcraftApp, kind: &str, name: &str, uid: u64) {
|
||||
app.ui.dialog = Some(Dialog::new(kind, json!({ "name": name, "doc": uid })));
|
||||
}
|
||||
@@ -78,6 +116,9 @@ fn target(d: &Dialog) -> (u64, String) {
|
||||
fn confirm_unused(app: &mut VectorcraftApp, d: &Dialog) -> DialogResult {
|
||||
app.ui.dialog = None;
|
||||
let (uid, name) = target(d);
|
||||
if let Some(names) = d.fields.get("names").and_then(|v| serde_json::from_value::<Vec<String>>(v.clone()).ok()) {
|
||||
return delete_unused(app, uid, &names);
|
||||
}
|
||||
match instances(app, uid, &name) {
|
||||
None => Ok(Value::Null),
|
||||
Some(0) => app.run("symbol.delete", json!({ "name": name })),
|
||||
@@ -98,3 +139,19 @@ fn confirm_used(app: &mut VectorcraftApp, d: &Dialog) -> DialogResult {
|
||||
}
|
||||
app.run("symbol.delete", json!({ "name": name, "expandInstances": !d.bool("discard") }))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The question for the symbols Select All Unused picked is plural (#4918).
|
||||
#[test]
|
||||
fn several_symbols_get_the_plural_question() {
|
||||
let one = Dialog::new(KIND, json!({"name": "A", "doc": 1}));
|
||||
let many = Dialog::new(KIND, json!({"names": ["A", "B"], "doc": 1}));
|
||||
assert_eq!((SPEC.heading)(&one), "Delete the selected symbol?");
|
||||
assert_eq!((SPEC.heading)(&many), "Delete the selected symbols?");
|
||||
let picked_one = Dialog::new(KIND, json!({"names": ["A"], "doc": 1}));
|
||||
assert_eq!((SPEC.heading)(&picked_one), "Delete the selected symbol?");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,6 +736,9 @@ font-technology Variable Променливи
|
||||
Delete Swatch Изтриване на образец
|
||||
Delete Symbol Изтриване на символ
|
||||
Delete the selected symbol? Да се изтрие ли избраният символ?
|
||||
Delete the selected symbols? Да се изтрият ли избраните символи?
|
||||
No unused symbols Няма неизползвани символи
|
||||
Delete Symbols Изтриване на символи
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Един или повече от символите се използват и не могат да бъдат изтрити, докато екземплярите им не бъдат разширени или изтрити.
|
||||
Expand Instances Разширяване на екземплярите
|
||||
Delete Instances Изтриване на екземплярите
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3068,6 +3068,9 @@ theme Appearance Izgled
|
||||
Delete Brush Izbriši četkicu
|
||||
Delete Symbol Izbriši simbol
|
||||
Delete the selected symbol? Izbrisati odabrani simbol?
|
||||
Delete the selected symbols? Izbrisati odabrane simbole?
|
||||
No unused symbols Nema nekorištenih simbola
|
||||
Delete Symbols Brisanje simbola
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Jedan ili više simbola se koriste i ne mogu se izbrisati dok se njihove instance ne prošire ili izbrišu.
|
||||
Expand Instances Proširi instance
|
||||
Delete Instances Izbriši instance
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1053,6 +1053,9 @@
|
||||
Delete Swatch Suprimeix la mostra
|
||||
Delete Symbol Suprimeix el símbol
|
||||
Delete the selected symbol? Voleu suprimir el símbol seleccionat?
|
||||
Delete the selected symbols? Voleu suprimir els símbols seleccionats?
|
||||
No unused symbols No hi ha símbols sense utilitzar
|
||||
Delete Symbols Suprimeix els símbols
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Un o més dels símbols estan en ús i no es poden suprimir fins que se n’expandeixin o se’n suprimeixin les instàncies.
|
||||
Expand Instances Expandeix les instàncies
|
||||
Delete Instances Suprimeix les instàncies
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3099,6 +3099,9 @@ theme Appearance Motiv
|
||||
Delete Brush Odstranit štětec
|
||||
Delete Symbol Odstranit symbol
|
||||
Delete the selected symbol? Odstranit vybraný symbol?
|
||||
Delete the selected symbols? Odstranit vybrané symboly?
|
||||
No unused symbols Žádné nepoužité symboly
|
||||
Delete Symbols Odstranit symboly
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Jeden nebo více symbolů se používá a nelze je odstranit, dokud jejich instance nerozložíte nebo neodstraníte.
|
||||
Expand Instances Rozložit instance
|
||||
Delete Instances Odstranit instance
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2855,6 +2855,9 @@ theme Appearance Udseende
|
||||
Delete Brush Slet pensel
|
||||
Delete Symbol Slet symbol
|
||||
Delete the selected symbol? Vil du slette det markerede symbol?
|
||||
Delete the selected symbols? Vil du slette de markerede symboler?
|
||||
No unused symbols Ingen ubrugte symboler
|
||||
Delete Symbols Slet symboler
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Et eller flere af symbolerne er i brug og kan ikke slettes, før deres forekomster er udvidet eller slettet.
|
||||
Expand Instances Udvid forekomster
|
||||
Delete Instances Slet forekomster
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -730,6 +730,9 @@ font-technology Variable Variabel
|
||||
Delete Swatch Farbfeld löschen
|
||||
Delete Symbol Symbol löschen
|
||||
Delete the selected symbol? Ausgewähltes Symbol löschen?
|
||||
Delete the selected symbols? Ausgewählte Symbole löschen?
|
||||
No unused symbols Keine nicht verwendeten Symbole
|
||||
Delete Symbols Symbole löschen
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Mindestens eines der Symbole wird verwendet und kann erst gelöscht werden, wenn seine Instanzen umgewandelt oder gelöscht wurden.
|
||||
Expand Instances Instanzen umwandeln
|
||||
Delete Instances Instanzen löschen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -710,6 +710,9 @@ font-technology Variable Μεταβλητές
|
||||
Delete Swatch Διαγραφή δείγματος χρώματος
|
||||
Delete Symbol Διαγραφή συμβόλου
|
||||
Delete the selected symbol? Να διαγραφεί το επιλεγμένο σύμβολο;
|
||||
Delete the selected symbols? Να διαγραφούν τα επιλεγμένα σύμβολα;
|
||||
No unused symbols Δεν υπάρχουν αχρησιμοποίητα σύμβολα
|
||||
Delete Symbols Διαγραφή συμβόλων
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Ένα ή περισσότερα από τα σύμβολα χρησιμοποιούνται και δεν μπορούν να διαγραφούν μέχρι να επεκταθούν ή να διαγραφούν τα στιγμιότυπά τους.
|
||||
Expand Instances Επέκταση στιγμιοτύπων
|
||||
Delete Instances Διαγραφή στιγμιοτύπων
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 164 and column 5.
|
@@ -751,6 +751,9 @@ font-technology Variable Variables
|
||||
Delete Swatch Eliminar muestra
|
||||
Delete Symbol Eliminar símbolo
|
||||
Delete the selected symbol? ¿Eliminar el símbolo seleccionado?
|
||||
Delete the selected symbols? ¿Eliminar los símbolos seleccionados?
|
||||
No unused symbols No hay símbolos sin usar
|
||||
Delete Symbols Eliminar símbolos
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Uno o más de los símbolos están en uso y no se pueden eliminar hasta que se expandan o eliminen sus instancias.
|
||||
Expand Instances Expandir instancias
|
||||
Delete Instances Eliminar instancias
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3038,6 +3038,9 @@ theme Appearance Ulkoasu
|
||||
Delete Brush Poista sivellin
|
||||
Delete Symbol Poista symboli
|
||||
Delete the selected symbol? Poistetaanko valittu symboli?
|
||||
Delete the selected symbols? Poistetaanko valitut symbolit?
|
||||
No unused symbols Ei käyttämättömiä symboleja
|
||||
Delete Symbols Poista symbolit
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Yksi tai useampi symboleista on käytössä, eikä niitä voi poistaa ennen kuin niiden ilmentymät on laajennettu tai poistettu.
|
||||
Expand Instances Laajenna ilmentymät
|
||||
Delete Instances Poista ilmentymät
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -756,6 +756,9 @@ font-technology Variable Variables
|
||||
Delete Swatch Supprimer la nuance
|
||||
Delete Symbol Supprimer le symbole
|
||||
Delete the selected symbol? Supprimer le symbole sélectionné ?
|
||||
Delete the selected symbols? Supprimer les symboles sélectionnés ?
|
||||
No unused symbols Aucun symbole inutilisé
|
||||
Delete Symbols Supprimer les symboles
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Un ou plusieurs symboles sont utilisés et ne peuvent pas être supprimés tant que leurs instances ne sont pas décomposées ou supprimées.
|
||||
Expand Instances Décomposer les instances
|
||||
Delete Instances Supprimer les instances
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -975,6 +975,9 @@ font-technology Variable Változó
|
||||
Delete Swatch Színminta törlése
|
||||
Delete Symbol Szimbólum törlése
|
||||
Delete the selected symbol? Törli a kijelölt szimbólumot?
|
||||
Delete the selected symbols? Törli a kijelölt szimbólumokat?
|
||||
No unused symbols Nincsenek nem használt szimbólumok
|
||||
Delete Symbols Szimbólumok törlése
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Egy vagy több szimbólum használatban van, és nem törölhető, amíg a példányait ki nem bontja vagy nem törli.
|
||||
Expand Instances Példányok kibontása
|
||||
Delete Instances Példányok törlése
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2855,6 +2855,9 @@ theme Appearance Tampilan
|
||||
Delete Brush Hapus kuas
|
||||
Delete Symbol Hapus simbol
|
||||
Delete the selected symbol? Hapus simbol yang dipilih?
|
||||
Delete the selected symbols? Hapus simbol yang dipilih?
|
||||
No unused symbols Tidak ada simbol yang tidak digunakan
|
||||
Delete Symbols Hapus Simbol
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Satu atau beberapa simbol sedang digunakan dan tidak dapat dihapus sampai instansinya diperluas atau dihapus.
|
||||
Expand Instances Perluas instansi
|
||||
Delete Instances Hapus instansi
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2190 and column 5.
|
@@ -751,6 +751,9 @@ font-technology Variable Variabili
|
||||
Delete Swatch Elimina campione
|
||||
Delete Symbol Elimina simbolo
|
||||
Delete the selected symbol? Eliminare il simbolo selezionato?
|
||||
Delete the selected symbols? Eliminare i simboli selezionati?
|
||||
No unused symbols Nessun simbolo inutilizzato
|
||||
Delete Symbols Elimina simboli
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Uno o più simboli sono in uso e non possono essere eliminati finché le loro istanze non vengono espanse o eliminate.
|
||||
Expand Instances Espandi istanze
|
||||
Delete Instances Elimina istanze
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1308,6 +1308,9 @@ digits Malayalam マラヤーラム数字
|
||||
Delete Swatch スウォッチを削除
|
||||
Delete Symbol シンボルを削除
|
||||
Delete the selected symbol? 選択したシンボルを削除しますか?
|
||||
Delete the selected symbols? 選択したシンボルを削除しますか?
|
||||
No unused symbols 未使用のシンボルはありません
|
||||
Delete Symbols シンボルを削除
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. 1 つ以上のシンボルが使用中のため、インスタンスを拡張または削除するまで削除できません。
|
||||
Expand Instances インスタンスを拡張
|
||||
Delete Instances インスタンスを削除
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2668,6 +2668,9 @@ theme Appearance იერსახე
|
||||
Delete Brush ფუნჯის წაშლა
|
||||
Delete Symbol სიმბოლოს წაშლა
|
||||
Delete the selected symbol? წაიშალოს მონიშნული სიმბოლო?
|
||||
Delete the selected symbols? წაიშალოს მონიშნული სიმბოლოები?
|
||||
No unused symbols გამოუყენებელი სიმბოლოები არ არის
|
||||
Delete Symbols სიმბოლოების წაშლა
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. ერთი ან მეტი სიმბოლო გამოიყენება და მისი წაშლა შეუძლებელია, სანამ მისი ეგზემპლარები არ გაიშლება ან არ წაიშლება.
|
||||
Expand Instances ეგზემპლარების გაშლა
|
||||
Delete Instances ეგზემპლარების წაშლა
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1298,6 +1298,9 @@ digits Malayalam 말라얄람어
|
||||
Delete Swatch 견본 삭제
|
||||
Delete Symbol 심볼 삭제
|
||||
Delete the selected symbol? 선택한 심볼을 삭제하시겠습니까?
|
||||
Delete the selected symbols? 선택한 심볼을 모두 삭제하시겠습니까?
|
||||
No unused symbols 사용하지 않는 심볼이 없습니다
|
||||
Delete Symbols 심볼 삭제
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. 하나 이상의 심볼이 사용 중이므로 인스턴스를 확장하거나 삭제하기 전에는 삭제할 수 없습니다.
|
||||
Expand Instances 인스턴스 확장
|
||||
Delete Instances 인스턴스 삭제
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2869,6 +2869,9 @@ theme Appearance Vormgeving
|
||||
Delete Brush Penseel verwijderen
|
||||
Delete Symbol Symbool verwijderen
|
||||
Delete the selected symbol? Het geselecteerde symbool verwijderen?
|
||||
Delete the selected symbols? De geselecteerde symbolen verwijderen?
|
||||
No unused symbols Geen ongebruikte symbolen
|
||||
Delete Symbols Symbolen verwijderen
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Een of meer van de symbolen zijn in gebruik en kunnen pas worden verwijderd als hun exemplaren zijn uitgebreid of verwijderd.
|
||||
Expand Instances Exemplaren uitbreiden
|
||||
Delete Instances Exemplaren verwijderen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2978,6 +2978,9 @@ theme Appearance Wygląd
|
||||
Delete Brush Usuń pędzel
|
||||
Delete Symbol Usuń symbol
|
||||
Delete the selected symbol? Usunąć zaznaczony symbol?
|
||||
Delete the selected symbols? Usunąć zaznaczone symbole?
|
||||
No unused symbols Brak nieużywanych symboli
|
||||
Delete Symbols Usuń symbole
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Co najmniej jeden z symboli jest używany i nie można go usunąć, dopóki jego wystąpienia nie zostaną rozszerzone lub usunięte.
|
||||
Expand Instances Rozszerz wystąpienia
|
||||
Delete Instances Usuń wystąpienia
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -664,6 +664,9 @@
|
||||
Delete Swatch Excluir Amostra
|
||||
Delete Symbol Excluir Símbolo
|
||||
Delete the selected symbol? Excluir o símbolo selecionado?
|
||||
Delete the selected symbols? Excluir os símbolos selecionados?
|
||||
No unused symbols Nenhum símbolo não utilizado
|
||||
Delete Symbols Excluir símbolos
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Um ou mais símbolos estão em uso e não podem ser excluídos até que suas instâncias sejam expandidas ou excluídas.
|
||||
Expand Instances Expandir Instâncias
|
||||
Delete Instances Excluir Instâncias
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -749,6 +749,9 @@ font-technology Variable Вариативные
|
||||
Delete Swatch Удалить образец
|
||||
Delete Symbol Удалить символ
|
||||
Delete the selected symbol? Удалить выбранный символ?
|
||||
Delete the selected symbols? Удалить выбранные символы?
|
||||
No unused symbols Нет неиспользуемых символов
|
||||
Delete Symbols Удалить символы
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Один или несколько символов используются, и их нельзя удалить, пока их экземпляры не будут разобраны или удалены.
|
||||
Expand Instances Разобрать экземпляры
|
||||
Delete Instances Удалить экземпляры
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -738,6 +738,9 @@ font-technology Variable Variabla
|
||||
Delete Swatch Radera färgruta
|
||||
Delete Symbol Radera symbol
|
||||
Delete the selected symbol? Vill du radera den markerade symbolen?
|
||||
Delete the selected symbols? Vill du radera de markerade symbolerna?
|
||||
No unused symbols Inga oanvända symboler
|
||||
Delete Symbols Radera symboler
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. En eller flera av symbolerna används och kan inte raderas förrän deras instanser har expanderats eller raderats.
|
||||
Expand Instances Expandera instanser
|
||||
Delete Instances Radera instanser
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -730,6 +730,9 @@ font-technology Variable Değişken
|
||||
Delete Swatch Renk Örneğini Sil
|
||||
Delete Symbol Sembolü Sil
|
||||
Delete the selected symbol? Seçili sembol silinsin mi?
|
||||
Delete the selected symbols? Seçili semboller silinsin mi?
|
||||
No unused symbols Kullanılmayan sembol yok
|
||||
Delete Symbols Sembolleri Sil
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Sembollerden biri veya birkaçı kullanımda ve örnekleri genişletilene ya da silinene kadar silinemez.
|
||||
Expand Instances Örnekleri Genişlet
|
||||
Delete Instances Örnekleri Sil
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -745,6 +745,9 @@ font-technology Variable Варіативні
|
||||
Delete Swatch Видалити зразок
|
||||
Delete Symbol Видалити символ
|
||||
Delete the selected symbol? Видалити вибраний символ?
|
||||
Delete the selected symbols? Видалити вибрані символи?
|
||||
No unused symbols Немає невикористаних символів
|
||||
Delete Symbols Видалити символи
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Один або кілька символів використовуються, і їх не можна видалити, доки їхні екземпляри не буде розібрано або видалено.
|
||||
Expand Instances Розібрати екземпляри
|
||||
Delete Instances Видалити екземпляри
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 178 and column 5.
|
@@ -871,6 +871,9 @@ font-technology Variable Biến thiên
|
||||
Delete Swatch Xóa mẫu màu
|
||||
Delete Symbol Xóa ký hiệu
|
||||
Delete the selected symbol? Xóa ký hiệu đã chọn?
|
||||
Delete the selected symbols? Xóa các ký hiệu đã chọn?
|
||||
No unused symbols Không có ký hiệu nào chưa dùng
|
||||
Delete Symbols Xóa các ký hiệu
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Một hoặc nhiều ký hiệu đang được sử dụng và không thể xóa cho đến khi các thực thể của chúng được mở rộng hoặc xóa.
|
||||
Expand Instances Mở rộng thực thể
|
||||
Delete Instances Xóa thực thể
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 200 and column 5.
|
@@ -694,6 +694,9 @@
|
||||
Delete Swatch 删除色板
|
||||
Delete Symbol 删除符号
|
||||
Delete the selected symbol? 要删除所选符号吗?
|
||||
Delete the selected symbols? 要删除所选的多个符号吗?
|
||||
No unused symbols 没有未使用的符号
|
||||
Delete Symbols 删除符号
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. 一个或多个符号正在使用中,在扩展或删除其实例之前无法删除。
|
||||
Expand Instances 扩展实例
|
||||
Delete Instances 删除实例
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -713,6 +713,9 @@ font-technology Variable 可變
|
||||
Delete Swatch 刪除色票
|
||||
Delete Symbol 刪除符號
|
||||
Delete the selected symbol? 要刪除選取的符號嗎?
|
||||
Delete the selected symbols? 要刪除選取的多個符號嗎?
|
||||
No unused symbols 沒有未使用的符號
|
||||
Delete Symbols 刪除符號
|
||||
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. 一個或多個符號正在使用中,在展開或刪除其範例之前無法刪除。
|
||||
Expand Instances 展開範例
|
||||
Delete Instances 刪除範例
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -201,6 +201,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let names: Vec<String> = doc.symbols.iter().map(|s| s.name.clone()).collect();
|
||||
let list: bool = pstate(ui.ctx(), "sym-list");
|
||||
let sel = current(app);
|
||||
let uid = app.session.active().map(|d| d.uid);
|
||||
let marked = marked(ui.ctx(), uid, &names);
|
||||
// The symbols drawn selected: the ones Select All Unused picked, else the current one.
|
||||
let on = |n: &str| if marked.is_empty() { sel.as_deref() == Some(n) } else { marked.iter().any(|m| m == n) };
|
||||
let mut clicked: Option<(String, bool)> = None;
|
||||
let list_rect = widgets::list_box(ui, |ui| {
|
||||
ui.set_min_height(110.0);
|
||||
@@ -214,7 +218,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 26.0), Sense::click_and_drag());
|
||||
// Dragged onto the canvas, the symbol is placed there.
|
||||
widgets::drag_source(ui, &resp, || PanelDrag::Symbol(n.clone()));
|
||||
if sel.as_deref() == Some(n.as_str()) {
|
||||
if on(n) {
|
||||
ui.painter().rect_filled(r, 0.0, t.row_selected);
|
||||
} else if resp.hovered() {
|
||||
ui.painter().rect_filled(r, 0.0, t.hover);
|
||||
@@ -233,7 +237,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(44.0, 44.0), Sense::click_and_drag());
|
||||
widgets::drag_source(ui, &resp, || PanelDrag::Symbol(n.clone()));
|
||||
chip(ui, &doc, &keys, r.shrink(2.0), n);
|
||||
let on = sel.as_deref() == Some(n.as_str());
|
||||
let on = on(n);
|
||||
ui.painter().rect_stroke(
|
||||
r,
|
||||
0.0,
|
||||
@@ -258,6 +262,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
app.status(e);
|
||||
}
|
||||
if let Some((n, place)) = clicked {
|
||||
// A click picks one symbol again.
|
||||
set_pstate(ui.ctx(), MARKED, (uid, Vec::<String>::new()));
|
||||
app.run("symbol.setCurrent", json!({"name": n})).ok();
|
||||
if place {
|
||||
app.run("symbol.place", json!({"name": n})).ok();
|
||||
@@ -286,16 +292,17 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
app.run(cmd, p).ok();
|
||||
}
|
||||
// Alt deletes an unused symbol without asking; a symbol with instances still asks.
|
||||
if widgets::icon_button_enabled(ui, "trash-2", tl!("Delete Symbol"), false, sel.is_some(), 24.0).clicked()
|
||||
&& let Some(n) = &sel
|
||||
{
|
||||
crate::dialogs::symbol_delete::ask(app, n, super::alt_held(ui));
|
||||
if widgets::icon_button_enabled(ui, "trash-2", tl!("Delete Symbol"), false, sel.is_some() || !marked.is_empty(), 24.0).clicked() {
|
||||
delete(app, &marked, sel.as_deref(), super::alt_held(ui));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let sel = current(app);
|
||||
let names: Vec<String> = app.session.active().map(|d| d.doc.symbols.iter().map(|s| s.name.clone()).collect()).unwrap_or_default();
|
||||
let uid = app.session.active().map(|d| d.uid);
|
||||
let marked = marked(ui.ctx(), uid, &names);
|
||||
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});
|
||||
@@ -310,10 +317,8 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
// Asks first, as the panel's button does; the menu item has no Alt shortcut.
|
||||
if menu_item(ui, tl!("Delete Symbol"), sel.is_some(), false)
|
||||
&& let Some(n) = &sel
|
||||
{
|
||||
crate::dialogs::symbol_delete::ask(app, n, false);
|
||||
if menu_item(ui, tl!("Delete Symbol"), sel.is_some() || !marked.is_empty(), false) {
|
||||
delete(app, &marked, sel.as_deref(), false);
|
||||
}
|
||||
let items: [(&str, bool, &str, Value); 7] = [
|
||||
(tl!("Symbol Options…"), sel.is_some(), "ui.symbolOptions", json!({"edit": true, "name": sel})),
|
||||
@@ -330,6 +335,18 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
// Select All Unused (#4918): the symbols nothing places show selected, and Delete Symbol
|
||||
// deletes them all.
|
||||
if menu_item(ui, tl!("Select All Unused"), !names.is_empty(), false)
|
||||
&& let Ok(r) = app.run("symbol.unused", json!({}))
|
||||
{
|
||||
let unused: Vec<String> = serde_json::from_value(r["names"].clone()).unwrap_or_default();
|
||||
if unused.is_empty() {
|
||||
app.status(tl!("No unused symbols"));
|
||||
}
|
||||
set_pstate(ui.ctx(), MARKED, (uid, unused));
|
||||
}
|
||||
ui.separator();
|
||||
let list: bool = pstate(ui.ctx(), "sym-list");
|
||||
if menu_item(ui, tl!("Thumbnail View"), true, !list) {
|
||||
set_pstate(ui.ctx(), "sym-list", false);
|
||||
@@ -345,6 +362,31 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The panel state key of the symbols Select All Unused picked.
|
||||
const MARKED: &str = "sym-unused";
|
||||
|
||||
/// The symbols Select All Unused picked in the active document (`uid`) that it still has (`names`);
|
||||
/// none once another document is active.
|
||||
fn marked(ctx: &egui::Context, uid: Option<u64>, names: &[String]) -> Vec<String> {
|
||||
let (of, mut marked): (Option<u64>, Vec<String>) = pstate(ctx, MARKED);
|
||||
if of != uid {
|
||||
return vec![];
|
||||
}
|
||||
marked.retain(|m| names.contains(m));
|
||||
marked
|
||||
}
|
||||
|
||||
/// Delete Symbol: the symbols Select All Unused picked (`marked`) when there are any, asked about
|
||||
/// once unless `now` (Alt), else the current symbol `sel`.
|
||||
fn delete(app: &mut VectorcraftApp, marked: &[String], sel: Option<&str>, now: bool) {
|
||||
match (marked, sel) {
|
||||
([], Some(n)) => crate::dialogs::symbol_delete::ask(app, n, now),
|
||||
([], None) => {}
|
||||
// Picked by Select All Unused, even one: deleted only while still unused.
|
||||
(names, _) => crate::dialogs::symbol_delete::ask_many(app, names, now),
|
||||
}
|
||||
}
|
||||
|
||||
/// Save Symbol Library… for the document's symbols (`names`: the one selected in the panel).
|
||||
fn save_library(app: &mut VectorcraftApp, names: Vec<String>) {
|
||||
if let Err(e) = app.run("ui.saveSymbolLibrary", json!({ "names": names })) {
|
||||
@@ -598,6 +640,90 @@ mod tests {
|
||||
crate::tests_removeanchors::shapes_text(&out.shapes.iter().map(|c| c.shape.clone()).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
/// Clicks the menu item labelled `label`.
|
||||
fn click_menu(ctx: &egui::Context, app: &mut VectorcraftApp, label: &str) {
|
||||
let labels = menu_frame(ctx, app, vec![]);
|
||||
let item = labels.iter().find(|(t, _)| t.trim() == label).map(|(_, r)| r.center()).unwrap_or_else(|| panic!("{label} in the menu"));
|
||||
menu_frame(ctx, app, vec![egui::Event::PointerMoved(item), button(item, true)]);
|
||||
menu_frame(ctx, app, vec![button(item, false)]);
|
||||
}
|
||||
|
||||
/// #4918: Select All Unused picks the symbols nothing places, Delete Symbol asks once and
|
||||
/// deletes them all in one undo step, and a click on a symbol picks one again.
|
||||
#[test]
|
||||
fn select_all_unused_picks_the_unused_symbols_and_delete_deletes_them() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
|
||||
for (i, name) in ["A", "B", "C"].into_iter().enumerate() {
|
||||
app.run("shape.rectangle", json!({"x": 10.0 + 40.0 * i as f64, "y": 10, "width": 30, "height": 20})).unwrap();
|
||||
let id = app.run("symbol.new", json!({"name": name})).unwrap()["id"].as_u64().unwrap();
|
||||
// A and C lose their instances; B keeps its own.
|
||||
if name != "B" {
|
||||
app.run("edit.clear", json!({"ids": [id]})).unwrap();
|
||||
}
|
||||
}
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
click_menu(&ctx, &mut app, "Select All Unused");
|
||||
let names = symbols(&mut app);
|
||||
assert_eq!(marked(&ctx, uid(&app), &names), ["A", "C"]);
|
||||
let undo = app.session.active().unwrap().history.undo.len();
|
||||
click_menu(&ctx, &mut app, "Delete Symbol");
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(crate::dialogs::symbol_delete::KIND));
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().fields["names"], json!(["A", "C"]), "asked once, about both");
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert_eq!(symbols(&mut app), ["B"]);
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), undo + 1, "one undo step");
|
||||
assert!(marked(&ctx, uid(&app), &symbols(&mut app)).is_empty(), "the deleted ones are no longer picked");
|
||||
// Undone, they are back; Select All Unused again, then a click on a symbol picks just it.
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
click_menu(&ctx, &mut app, "Select All Unused");
|
||||
assert_eq!(marked(&ctx, uid(&app), &symbols(&mut app)).len(), 2);
|
||||
frame(&ctx, &mut app, vec![]);
|
||||
let tile = pos2(26.0, 26.0);
|
||||
frame(&ctx, &mut app, vec![egui::Event::PointerMoved(tile), button(tile, true)]);
|
||||
frame(&ctx, &mut app, vec![button(tile, false)]);
|
||||
assert!(marked(&ctx, uid(&app), &symbols(&mut app)).is_empty());
|
||||
// With nothing unused, the item reports it and picks nothing.
|
||||
app.run("symbol.delete", json!({"names": ["A", "C"]})).unwrap();
|
||||
click_menu(&ctx, &mut app, "Select All Unused");
|
||||
assert!(marked(&ctx, uid(&app), &symbols(&mut app)).is_empty());
|
||||
}
|
||||
|
||||
fn uid(app: &VectorcraftApp) -> Option<u64> {
|
||||
app.session.active().map(|d| d.uid)
|
||||
}
|
||||
|
||||
/// Picks made in one document don't carry to another with symbols of the same names, and a
|
||||
/// picked symbol that gained a use since (here in a brush's art) isn't deleted (#4918 review).
|
||||
#[test]
|
||||
fn picked_symbols_stay_with_their_document_and_are_rechecked_before_deleting() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
let make = |app: &mut VectorcraftApp| {
|
||||
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
|
||||
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap();
|
||||
let id = app.run("symbol.new", json!({"name": "A"})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("edit.clear", json!({"ids": [id]})).unwrap();
|
||||
};
|
||||
make(&mut app);
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
click_menu(&ctx, &mut app, "Select All Unused");
|
||||
assert_eq!(marked(&ctx, uid(&app), &symbols(&mut app)), ["A"]);
|
||||
make(&mut app);
|
||||
assert!(marked(&ctx, uid(&app), &symbols(&mut app)).is_empty(), "not in the other document");
|
||||
// Back in the first one: A gains a use in a brush's art, then Delete (Alt: no question).
|
||||
click_menu(&ctx, &mut app, "Select All Unused");
|
||||
let id = app.run("symbol.place", json!({"name": "A"})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("brush.new", json!({"type": "art", "name": "B", "ids": [id]})).unwrap();
|
||||
app.run("edit.clear", json!({"ids": [id]})).unwrap();
|
||||
let names = symbols(&mut app);
|
||||
let picked = marked(&ctx, uid(&app), &names);
|
||||
assert_eq!(picked, ["A"]);
|
||||
delete(&mut app, &picked, None, true);
|
||||
assert_eq!(symbols(&mut app), ["A"], "still used by the brush, so kept");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_symbol_asks_for_symbol_options_before_making_the_symbol() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
|
||||
+7
-1
@@ -1561,7 +1561,8 @@ already walks Image Trace. Short agent entry points:
|
||||
of `symbol.list` carry `nineSlice`: the guides or `null`; `type` is Symbol Type, `static` by default for the command
|
||||
and `dynamic` by default in the Symbol Options dialog),
|
||||
`symbol.place {name?, x?, y?}`, `symbol.breakLink {ids?}`, `symbol.replace`, `symbol.edit`, `symbol.duplicate`,
|
||||
`symbol.selectInstances {name}`. `symbol.new` and `symbol.duplicate {name, newName?}` trim the white space around the
|
||||
`symbol.selectInstances {name}`, `symbol.delete {name | names, expandInstances?}` (`names` deletes several in one
|
||||
undo step). `symbol.new` and `symbol.duplicate {name, newName?}` trim the white space around the
|
||||
name they are given, as `symbol.options` trims `newName`; a blank one gives "New Symbol" or "<name> copy".
|
||||
- Dynamic symbols: an instance of a symbol whose `type` is `dynamic` changes the appearance of objects in the symbol's
|
||||
art for itself only and stays linked. `symbol.instance.setAppearance {id?, path, fill?, stroke?, opacity?, blend?}`
|
||||
@@ -1577,6 +1578,11 @@ already walks Image Trace. Short agent entry points:
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"symbol.instance.setAppearance","params":{"id":42,"path":[1],"fill":"#d0021b","opacity":80}}}
|
||||
```
|
||||
- The Symbols panel menu's Select All Unused: `symbol.unused` (a query) lists the symbols nothing places, in panel
|
||||
order, and the panel shows them selected; its Delete Symbol then deletes them all, asked once, in one undo step.
|
||||
Instances in the art (opacity masks included), graph designs, pattern tiles (with the tiles that pattern editing's
|
||||
Cancel puts back) and the art of the document's brushes count, and so do the instances in the art of a symbol that
|
||||
is used; a symbol placed only inside an unused symbol is unused too.
|
||||
- The Brushes panel menu's Select All Unused: `brush.unused` (a query) lists the brushes no stroke paints with, in
|
||||
library order, and the panel selects them. Strokes in the art (opacity masks included), symbols, graph designs,
|
||||
pattern tiles (with the tiles that pattern editing's Cancel puts back) and graphic styles count, and so do the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Parity with Adobe Illustrator
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Area Type Options › Text Flow, #4978; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Symbols panel: Select All Unused, #4918) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -607,6 +607,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Swatch and graphic style libraries, Libraries panel files, presets and plug-ins open wherever they are dropped on the window, as File › Open opens them.
|
||||
- **Symbol libraries** ([public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/create-or-import-symbol-libraries.html)): Window › Symbol Libraries and the Symbols panel's Symbol Libraries Menu open each library in a library panel of its own, as swatch and graphic style libraries do. The built-in library, Basic Shapes, is original art generated in code (`crates/engine/src/cmd/symbollib.rs`). A click on a symbol adds it to the document's Symbols as one undo step, with its Symbol Options (size, registration, export type, 9-slice guides) and every resource it uses (the symbols it nests, patterns, swatches, brushes, graphic styles, images), and makes it the current symbol; a symbol the document already has is not added again and another symbol of the same name keeps its name, the library's comes in numbered. Thumbnails, in the library panels and the Symbols panel, draw a symbol with the symbols it nests and the patterns and images it uses. Dragging a library symbol onto the canvas also places an instance there, in the same step ([placing from a library](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/create-and-place-symbols.html)). Save Symbols (the library menu) and Save Symbol Library… (the Symbols panel menu) write the document's symbols as a `.vectorcraft` document holding only them, into the user library folder (listed under User Defined) or a file; Other Library… reads the symbols of any document VectorCraft opens; a file it can't read is an error and opens no panel. Tests: `crates/engine/src/tests/symbollib.rs`, `crates/ui-egui/src/panels/tests_symbol_libraries.rs`, `panels::symbols::tests::thumbnails_show_the_symbols_a_symbol_nests`, `canvas::tests::a_library_symbol_dropped_on_the_canvas_is_added_and_placed_there_in_one_step`.
|
||||
- **Delete Symbol asks first** ([Symbols panel](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/symbols-panel-options.html), [symbol libraries](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/create-or-import-symbol-libraries.html), [Alt-click Delete](https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html)): the Symbols panel's Delete Symbol button and menu item ask "Delete the selected symbol?" with Yes and No for a symbol no instance uses. For a symbol with placed instances they ask with Expand Instances (the instances become ordinary art), Delete Instances and Cancel; either Instances button deletes the symbol in one undo step. Alt-click on the button deletes an unused symbol without asking and still asks about one with instances. A question left open while another document became active deletes nothing. `symbol.delete` itself does not ask. Tests: `panels::symbols::tests::delete_symbol_asks_first_and_alt_click_deletes_an_unused_symbol_at_once`, `the_in_use_question_expands_or_deletes_the_instances`, `a_stale_delete_symbol_question_deletes_nothing`, `the_delete_symbol_questions_show_their_buttons`.
|
||||
- **Symbols › Select All Unused** ([Symbols panel](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/symbols-panel-options.html), #4918): the Symbols panel menu's Select All Unused shows the symbols nothing places selected (`symbol.unused`): instances in the art (opacity masks included), graph designs, pattern tiles and brush art count, and so do instances in a used symbol's art. Delete Symbol, the button or the menu item, then asks "Delete the selected symbols?" once and deletes those still unused in one undo step (`symbol.delete {names}`); Alt-click on the button deletes them without asking, and a click on a symbol picks just it again. Tests: `tests::brushsym::symbol_unused_lists_the_symbols_nothing_places_and_delete_takes_several`, `panels::symbols::tests::select_all_unused_picks_the_unused_symbols_and_delete_deletes_them`, `dialogs::symbol_delete::tests::several_symbols_get_the_plural_question`.
|
||||
- **Brush libraries** ([public source](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/apply-and-edit-strokes/create-new-brush-libraries.html), [import](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/apply-and-edit-strokes/import-brushes-from-brush-libraries-or-other-files.html), [Brushes panel](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/apply-and-edit-strokes/brushes-panel-overview.html)): Window › Brush Libraries, the Brushes panel's Brush Libraries Menu and its menu's Open Brush Library open each library in a library panel of its own, as swatch, graphic style and symbol libraries do. The built-in libraries (Art Ribbons, Calligraphic Nibs, Pattern Borders, Scatter Shapes) are original brushes generated in code (`crates/engine/src/cmd/brushlib.rs`); Striped Ribbon's art is filled with the library's own pattern and Sparkles scatters the library's own symbol. A click on a brush adds it to the document's Brushes as one undo step with every resource its art uses (patterns, symbols, swatches, graphic styles, images), makes it the current brush and, with paths selected, applies it to them in the same step; a brush the document already has is not added again and a different brush of the same name comes in numbered ("Wave 2"). Shift/Cmd-clicks select brushes for Add to Brushes; a brush dragged onto a path is added and applied to it, and one dragged onto the Brushes panel is added. Save Brushes… / the Brushes panel's Save Brush Library… write the document's brushes (or the selected ones) as a `.vectorcraft` library into the `Brushes` folder next to the preferences, listed under User Defined, or to a file; Other Library… reads the brushes of any document VectorCraft opens. Thumbnails, in the library panels, the Brushes panel and Brush Options, render brush art with the patterns and symbols it uses. Copying brushed art now carries the patterns and symbols its brush's art uses too. Tests: `crates/engine/src/tests/brushlib.rs`, `crates/ui-egui/src/panels/tests_brush_libraries.rs`, `panels::brushes::tests::library_brush_previews_show_the_patterns_their_art_uses`, `canvas::tests::a_library_brush_dropped_on_a_path_is_added_and_applied_in_one_step`.
|
||||
- A canvas context menu: right-click selects the object under the pointer and lists what applies to the selection (Undo/Redo, clipboard, group, isolation, join, Remove Anchor Points when anchors are selected, masks, compound paths, guides, Transform, Arrange, Select, Export Selection), or the view commands on empty canvas.
|
||||
- A right-click over locked objects adds an Unlock submenu to the canvas context menu. The submenu lists up to 20 locked objects under the pointer, topmost first, by the name the Layers panel shows, and choosing one unlocks it (`layer.setProps`). A locked group is listed as one item. Objects on hidden, locked or template layers are not listed.
|
||||
@@ -1432,6 +1433,7 @@ Symbols area (#4938), from the public symbol documentation ([create and place sy
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-01 | minor | Symbols panel: Select All Unused (`symbol.unused`) shows the symbols nothing places selected, and Delete Symbol deletes them all, asked once, in one undo step (`symbol.delete {names}`, #4918); scores and hours unchanged |
|
||||
| 2026-11-01 | minor | Type core: Area Type Options › Text Flow (#4978): area type with several rows and columns fills them by columns (the default) or by rows, from the dialog's radio buttons or `text.areaOptions {flow}`; evidence updated; scores unchanged. |
|
||||
| 2026-11-01 | minor | Symbols: dynamic symbols (#4938): Symbol Options › Symbol Type, per-instance appearance for objects of a dynamic symbol's art that stays linked through redefinition, Break Link baking, Reset and Static dropping it; evidence added. Score unchanged at 87% (estimated), hours 5–9 → 4–8. |
|
||||
| 2026-11-01 | minor | Artboard background colors in output (#4937): raster exports (a transparent background too), PDF, SVG and EPS pages, `.ai` files' PDF pages, Export for Screens and print (and its preview) draw each artboard's color behind its art, inside the artboard and in the color's own model; Export for Screens has Include Artboard Fill beside Include Bleed (on by default, remembered; `includeArtboardFill`, also on `document.export` and `file.print`); editing data keeps the color as the artboard's, and Export for Screens' document rows keep it too; evidence updated; scores unchanged. |
|
||||
|
||||
Reference in New Issue
Block a user