Symbols: Delete Symbol asks first, and Alt-click deletes an unused symbol without asking

The Symbols panel's Delete Symbol button and menu item now ask before
deleting. A symbol no instance uses gets "Delete the selected symbol?"
with Yes and No. A symbol with placed instances gets Expand Instances,
which turns the instances into ordinary art, Delete Instances, which
removes them, and Cancel; either Instances button deletes the symbol in
one undo step. Alt-click on the button deletes an unused symbol at once
and still asks about one with instances.

The question is bound to the document it was asked in: once another
document is active, or the symbol is gone, Yes deletes nothing, and a
symbol that gained instances while the question was open gets the
in-use question instead. symbol.delete itself does not ask.

The shared dialog frame takes a Cancel label (DialogSpec::cancel) for
the No button. The new strings have rows in every catalog.
This commit is contained in:
dotCooCoo
2026-11-01 00:37:07 +07:00
committed by bflatastic
parent 034a327f5b
commit a8b3786f8c
30 changed files with 471 additions and 4 deletions
+8 -1
View File
@@ -106,6 +106,7 @@ pub mod spot_colors;
pub(crate) mod svg_options;
pub mod swatch_conflict;
pub mod swatch_options;
pub mod symbol_delete;
pub mod symbol_options;
pub mod symbolism_options;
mod text_export;
@@ -175,6 +176,8 @@ pub(crate) struct DialogSpec {
pub confirm: fn(&mut VectorcraftApp, &Dialog) -> DialogResult,
/// The OK button's label. None: no OK button, Enter does nothing and Cancel reads "Close".
pub ok: Option<&'static str>,
/// The Cancel button's label when it is not "Cancel" ("No"). Only with an `ok`.
pub cancel: Option<&'static str>,
/// An extra button left of Cancel that sets `discard: true` and confirms ("Don't Save").
pub discard: Option<&'static str>,
/// A button left of Cancel, as (label, field), that sets `field: true` and confirms, as OK
@@ -217,6 +220,7 @@ impl DialogSpec {
Ok(Value::Null)
},
ok: Some("OK"),
cancel: None,
discard: None,
alternate: None,
reset: None,
@@ -371,6 +375,8 @@ registry! {
ShapeBuilderOptions: [shape_builder_options::KIND] => shape_builder_options::SPEC,
FlareOptions: [flare_options::KIND] => flare_options::SPEC,
SymbolOptions: [symbol_options::KIND] => symbol_options::SPEC,
DeleteSymbol: [symbol_delete::KIND] => symbol_delete::SPEC,
DeleteUsedSymbol: [symbol_delete::USED] => symbol_delete::USED_SPEC,
SymbolismOptions: [symbolism_options::KIND] => symbolism_options::SPEC,
PerspectiveGridPresets: [perspective_presets::KIND] => perspective_presets::SPEC,
PerspectiveGridOptions: [perspective_options::KIND] => perspective_options::SPEC,
@@ -397,6 +403,7 @@ pub fn button_labels() -> Vec<&'static str> {
let mut v = vec!["Cancel", "Close"];
for spec in std::iter::once(&DialogSpec::FORM).chain(DialogKind::ALL.iter().map(|k| k.spec())) {
v.extend(spec.ok);
v.extend(spec.cancel);
v.extend(spec.discard);
v.extend(spec.alternate.map(|(label, _)| label));
v.extend(spec.reset.map(|_| "Reset"));
@@ -638,7 +645,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
let label = if resets {
"Reset"
} else if spec.ok.is_some() {
"Cancel"
spec.cancel.unwrap_or("Cancel")
} else {
"Close"
};
+100
View File
@@ -0,0 +1,100 @@
//! The Symbols panel's Delete Symbol question ([`ask`]). A symbol no instance uses gets "Delete the
//! selected symbol?" with Yes and No ([`KIND`]); a symbol with placed instances gets Expand
//! Instances, Delete Instances and Cancel ([`USED`]).
//!
//! Fields: `name` (the symbol) and `doc` (the [`vectorcraft_engine::DocState::uid`] of the document
//! it was asked in). Yes or either Instances button deletes the symbol only while that document is
//! the active one and still has the symbol; an unused symbol that has gained instances since asks
//! the in-use question instead.
use serde_json::{Value, json};
use super::{DialogResult, DialogSpec};
use crate::VectorcraftApp;
use crate::state::Dialog;
/// The dialog kind of the question for a symbol no instance uses.
pub const KIND: &str = "deleteSymbol";
/// The dialog kind of the question for a symbol with placed instances.
pub const USED: &str = "deleteUsedSymbol";
pub(super) const SPEC: DialogSpec = DialogSpec {
heading: |_| tl!("Delete the selected symbol?").into(),
body: |_, _, _| false,
confirm: confirm_unused,
ok: Some("Yes"),
cancel: Some("No"),
max_width: Some(420.0),
..DialogSpec::FORM
};
pub(super) const USED_SPEC: DialogSpec = DialogSpec {
heading: |_| tl!("One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted.").into(),
body: |_, _, _| false,
confirm: confirm_used,
ok: Some("Expand Instances"),
discard: Some("Delete Instances"),
max_width: Some(420.0),
..DialogSpec::FORM
};
/// Delete Symbol for symbol `name` of the active document. A symbol with instances always asks
/// what becomes of them; an unused one is deleted at once with `now` (Alt-click on the panel's
/// button) and asked about otherwise.
pub(crate) fn ask(app: &mut VectorcraftApp, name: &str, now: bool) {
let Some(uid) = app.session.active().map(|st| st.uid) else { return };
match instances(app, uid, name) {
None => {}
Some(0) if now => {
if let Err(e) = app.run("symbol.delete", json!({ "name": name })) {
app.status(e);
}
}
Some(n) => open(app, if n == 0 { KIND } else { USED }, name, uid),
}
}
fn open(app: &mut VectorcraftApp, kind: &str, name: &str, uid: u64) {
app.ui.dialog = Some(Dialog::new(kind, json!({ "name": name, "doc": uid })));
}
/// How many instances symbol `name` has, while the document `uid` is the active one and has the
/// symbol; `None` otherwise.
fn instances(app: &mut VectorcraftApp, uid: u64, name: &str) -> Option<u64> {
if app.session.active().map(|st| st.uid) != Some(uid) {
return None;
}
let list = app.run("symbol.list", json!({})).ok()?;
list["symbols"].as_array()?.iter().find(|s| s["name"].as_str() == Some(name)).map(|s| s["instances"].as_u64().unwrap_or(0))
}
/// The document and symbol `d` asks about.
fn target(d: &Dialog) -> (u64, String) {
(d.fields.get("doc").and_then(Value::as_u64).unwrap_or(0), d.str("name"))
}
/// Yes: deletes the symbol, or asks the in-use question when it has gained instances.
fn confirm_unused(app: &mut VectorcraftApp, d: &Dialog) -> DialogResult {
app.ui.dialog = None;
let (uid, name) = target(d);
match instances(app, uid, &name) {
None => Ok(Value::Null),
Some(0) => app.run("symbol.delete", json!({ "name": name })),
Some(_) => {
open(app, USED, &name, uid);
Ok(Value::Null)
}
}
}
/// Expand Instances (OK) turns the instances into ordinary art; Delete Instances (`discard`)
/// removes them. Either one deletes the symbol in one undo step.
fn confirm_used(app: &mut VectorcraftApp, d: &Dialog) -> DialogResult {
app.ui.dialog = None;
let (uid, name) = target(d);
if instances(app, uid, &name).is_none() {
return Ok(Value::Null);
}
app.run("symbol.delete", json!({ "name": name, "expandInstances": !d.bool("discard") }))
}
+6
View File
@@ -735,6 +735,12 @@ font-technology Variable Променливи
Delete Stop Изтриване на репер
Delete Swatch Изтриване на образец
Delete Symbol Изтриване на символ
Delete the selected symbol? Да се изтрие ли избраният символ?
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Един или повече от символите се използват и не могат да бъдат изтрити, докато екземплярите им не бъдат разширени или изтрити.
Expand Instances Разширяване на екземплярите
Delete Instances Изтриване на екземплярите
Yes Да
No Не
Delete Tab Изтриване на табулация
Delete the graphic style “{name}”? Изтриване на графичния стил „{name}“?
Delete Theme Изтриване на тема
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -3046,6 +3046,12 @@ theme Appearance Izgled
Current Symbol Trenutni simbol
Delete Brush Izbriši četkicu
Delete Symbol Izbriši simbol
Delete the selected symbol? Izbrisati odabrani simbol?
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
Yes Da
No Ne
Duplicate Brush Dupliciraj četkicu
Duplicate Symbol Dupliciraj simbol
Edit Symbol Uredi simbol
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -1052,6 +1052,12 @@
Delete Styles Suprimeix els estils
Delete Swatch Suprimeix la mostra
Delete Symbol Suprimeix el símbol
Delete the selected symbol? Voleu suprimir el símbol seleccionat?
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
Yes Sí
No No
Delete Tab Suprimeix la tabulació
Delete the graphic style “{name}”? Voleu suprimir l’estil gràfic “{name}”?
Delete the library “{name}”? Voleu suprimir la biblioteca “{name}”?
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -3078,6 +3078,12 @@ theme Appearance Motiv
Current Symbol Aktuální symbol
Delete Brush Odstranit štětec
Delete Symbol Odstranit symbol
Delete the selected symbol? Odstranit vybraný symbol?
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
Yes Ano
No Ne
Duplicate Brush Duplikovat štětec
Duplicate Symbol Duplikovat symbol
Edit Symbol Upravit symbol
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -2837,6 +2837,12 @@ theme Appearance Udseende
Current Symbol Aktuelt symbol
Delete Brush Slet pensel
Delete Symbol Slet symbol
Delete the selected symbol? Vil du slette det markerede symbol?
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
Yes Ja
No Nej
Duplicate Brush Duplikér pensel
Duplicate Symbol Duplikér symbol
Edit Symbol Redigér symbol
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -729,6 +729,12 @@ font-technology Variable Variabel
Delete Stop Farbstopp löschen
Delete Swatch Farbfeld löschen
Delete Symbol Symbol löschen
Delete the selected symbol? Ausgewähltes Symbol 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
Yes Ja
No Nein
Delete Tab Tabulator löschen
Delete the graphic style “{name}”? Grafikstil „{name}“ löschen?
Delete Theme Thema löschen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -709,6 +709,12 @@ font-technology Variable Μεταβλητές
Delete Stop Διαγραφή στάσης ντεγκραντέ
Delete Swatch Διαγραφή δείγματος χρώματος
Delete Symbol Διαγραφή συμβόλου
Delete the selected symbol? Να διαγραφεί το επιλεγμένο σύμβολο;
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Ένα ή περισσότερα από τα σύμβολα χρησιμοποιούνται και δεν μπορούν να διαγραφούν μέχρι να επεκταθούν ή να διαγραφούν τα στιγμιότυπά τους.
Expand Instances Επέκταση στιγμιοτύπων
Delete Instances Διαγραφή στιγμιοτύπων
Yes Ναι
No Όχι
Delete Tab Διαγραφή στηλοθέτη
Delete the graphic style “{name}”? Να διαγραφεί το στυλ γραφικών «{name}»;
Delete Theme Διαγραφή θέματος
Can't render this file because it contains an unexpected character in line 164 and column 5.
+6
View File
@@ -750,6 +750,12 @@ font-technology Variable Variables
Delete Stop Eliminar parada
Delete Swatch Eliminar muestra
Delete Symbol Eliminar símbolo
Delete the selected symbol? ¿Eliminar el símbolo seleccionado?
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
Yes Sí
No No
Delete Tab Eliminar tabulación
Delete the graphic style “{name}”? ¿Eliminar el estilo gráfico “{name}”?
Delete Theme Eliminar tema
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -3018,6 +3018,12 @@ theme Appearance Ulkoasu
Current Symbol Nykyinen symboli
Delete Brush Poista sivellin
Delete Symbol Poista symboli
Delete the selected symbol? Poistetaanko valittu symboli?
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
Yes Kyllä
No Ei
Duplicate Brush Monista sivellin
Duplicate Symbol Monista symboli
Edit Symbol Muokkaa symbolia
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -752,6 +752,12 @@ font-technology Variable Variables
Delete Stop Supprimer l’étape de dégradé
Delete Swatch Supprimer la nuance
Delete Symbol Supprimer le symbole
Delete the selected symbol? Supprimer le symbole sélectionné ?
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
Yes Oui
No Non
Delete Tab Supprimer la tabulation
Delete the graphic style “{name}”? Supprimer le style graphique « {name} » ?
Delete Theme Supprimer le thème
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -974,6 +974,12 @@ font-technology Variable Változó
Delete Stop Színátmenet-pont törlése
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?
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
Yes Igen
No Nem
Delete Tab Tabulátor törlése
Delete the graphic style “{name}”? Törlöd ezt a grafikai stílust: „{name}”?
Delete the library “{name}”? Törlöd ezt a könyvtárat: „{name}”?
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -2837,6 +2837,12 @@ theme Appearance Tampilan
Current Symbol Simbol saat ini
Delete Brush Hapus kuas
Delete Symbol Hapus simbol
Delete the selected symbol? Hapus simbol yang dipilih?
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
Yes Ya
No Tidak
Duplicate Brush Duplikatkan kuas
Duplicate Symbol Duplikatkan simbol
Edit Symbol Edit simbol
Can't render this file because it contains an unexpected character in line 2175 and column 5.
+6
View File
@@ -750,6 +750,12 @@ font-technology Variable Variabili
Delete Stop Elimina cursore colore
Delete Swatch Elimina campione
Delete Symbol Elimina simbolo
Delete the selected symbol? Eliminare il simbolo selezionato?
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
Yes Sì
No No
Delete Tab Elimina tabulazione
Delete the graphic style “{name}”? Eliminare lo stile grafico “{name}”?
Delete Theme Elimina tema
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -1297,6 +1297,12 @@ font-technology Variable バリアブル
Delete Stop 分岐点を削除
Delete Swatch スウォッチを削除
Delete Symbol シンボルを削除
Delete the selected symbol? 選択したシンボルを削除しますか?
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 インスタンスを削除
Yes はい
No いいえ
Delete Tab タブを削除
Delete Theme テーマを削除
Delete Transparency Flattener Preset 透明の分割・統合プリセットを削除
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -2650,6 +2650,12 @@ theme Appearance იერსახე
Current Symbol მიმდინარე სიმბოლო
Delete Brush ფუნჯის წაშლა
Delete Symbol სიმბოლოს წაშლა
Delete the selected symbol? წაიშალოს მონიშნული სიმბოლო?
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. ერთი ან მეტი სიმბოლო გამოიყენება და მისი წაშლა შეუძლებელია, სანამ მისი ეგზემპლარები არ გაიშლება ან არ წაიშლება.
Expand Instances ეგზემპლარების გაშლა
Delete Instances ეგზემპლარების წაშლა
Yes დიახ
No არა
Duplicate Brush ფუნჯის დუბლირება
Duplicate Symbol სიმბოლოს დუბლირება
Edit Symbol სიმბოლოს რედაქტირება
Can't render this file because it is too large.
+6
View File
@@ -1288,6 +1288,12 @@ font-technology Variable 가변
Delete Stop 정지점 삭제
Delete Swatch 견본 삭제
Delete Symbol 심볼 삭제
Delete the selected symbol? 선택한 심볼을 삭제하시겠습니까?
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. 하나 이상의 심볼이 사용 중이므로 인스턴스를 확장하거나 삭제하기 전에는 삭제할 수 없습니다.
Expand Instances 인스턴스 확장
Delete Instances 인스턴스 삭제
Yes 예
No 아니요
Delete Tab 탭 삭제
Delete Theme 테마 삭제
Delete Transparency Flattener Preset 투명도 병합 프리셋 삭제
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -2851,6 +2851,13 @@ theme Appearance Vormgeving
Current Symbol Huidig symbool
Delete Brush Penseel verwijderen
Delete Symbol Symbool verwijderen
Delete the selected symbol? Het geselecteerde symbool 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
Yes Ja
No Nee
Duplicate Brush Kwast dupliceren
Duplicate Brush Penseel dupliceren
Duplicate Symbol Symbool dupliceren
Edit Symbol Symbool bewerken
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -2958,6 +2958,12 @@ theme Appearance Wygląd
Current Symbol Bieżący symbol
Delete Brush Usuń pędzel
Delete Symbol Usuń symbol
Delete the selected symbol? Usunąć zaznaczony symbol?
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
Yes Tak
No Nie
Duplicate Brush Powiel pędzel
Duplicate Symbol Powiel symbol
Edit Symbol Edytuj symbol
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -663,6 +663,12 @@
Delete Stop Excluir Parada
Delete Swatch Excluir Amostra
Delete Symbol Excluir Símbolo
Delete the selected symbol? Excluir o símbolo selecionado?
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
Yes Sim
No Não
Delete Tab Excluir Parada de Tabulação
Delete Theme Excluir Tema
Delete Transparency Flattener Preset Excluir Predefinido de Achatamento de Transparência
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -748,6 +748,12 @@ font-technology Variable Вариативные
Delete Stop Удалить точку перехода
Delete Swatch Удалить образец
Delete Symbol Удалить символ
Delete the selected symbol? Удалить выбранный символ?
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Один или несколько символов используются, и их нельзя удалить, пока их экземпляры не будут разобраны или удалены.
Expand Instances Разобрать экземпляры
Delete Instances Удалить экземпляры
Yes Да
No Нет
Delete Tab Удалить табуляцию
Delete the graphic style “{name}”? Удалить графический стиль «{name}»?
Delete Theme Удалить тему
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -737,6 +737,12 @@ font-technology Variable Variabla
Delete Stop Radera stopp
Delete Swatch Radera färgruta
Delete Symbol Radera symbol
Delete the selected symbol? Vill du radera den markerade symbolen?
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
Yes Ja
No Nej
Delete Tab Radera tabb
Delete the graphic style “{name}”? Radera grafikstilen ”{name}”?
Delete Theme Radera tema
Can't render this file because it contains an unexpected character in line 5 and column 25.
+6
View File
@@ -729,6 +729,12 @@ font-technology Variable Değişken
Delete Stop Durdurmayı Sil
Delete Swatch Renk Örneğini Sil
Delete Symbol Sembolü Sil
Delete the selected symbol? Seçili sembol silinsin mi?
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
Yes Evet
No Hayır
Delete Tab Sekmeyi Sil
Delete the graphic style “{name}”? “{name}” grafik stili silinsin mi?
Delete Theme Temayı Sil
Can't render this file because it contains an unexpected character in line 5 and column 25.
+6
View File
@@ -744,6 +744,12 @@ font-technology Variable Варіативні
Delete Stop Видалити точку переходу
Delete Swatch Видалити зразок
Delete Symbol Видалити символ
Delete the selected symbol? Видалити вибраний символ?
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. Один або кілька символів використовуються, і їх не можна видалити, доки їхні екземпляри не буде розібрано або видалено.
Expand Instances Розібрати екземпляри
Delete Instances Видалити екземпляри
Yes Так
No Ні
Delete Tab Видалити табуляцію
Delete the graphic style “{name}”? Видалити графічний стиль «{name}»?
Delete Theme Видалити тему
Can't render this file because it contains an unexpected character in line 178 and column 5.
+6
View File
@@ -870,6 +870,12 @@ font-technology Variable Biến thiên
Delete Stop Xóa điểm dừng
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?
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ể
Yes Có
No Không
Delete Tab Xóa tab
Delete the graphic style “{name}”? Xóa kiểu đồ họa “{name}”?
Delete the library “{name}”? Xóa thư viện “{name}”?
Can't render this file because it contains an unexpected character in line 200 and column 5.
+6
View File
@@ -693,6 +693,12 @@
Delete Stop 删除色标
Delete Swatch 删除色板
Delete Symbol 删除符号
Delete the selected symbol? 要删除所选符号吗?
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. 一个或多个符号正在使用中,在扩展或删除其实例之前无法删除。
Expand Instances 扩展实例
Delete Instances 删除实例
Yes 是
No 否
Delete Tab 删除制表位
Delete the graphic style “{name}”? 要删除图形样式“{name}”吗?
Delete Theme 删除主题
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -712,6 +712,12 @@ font-technology Variable 可變
Delete Stop 刪除色標
Delete Swatch 刪除色票
Delete Symbol 刪除符號
Delete the selected symbol? 要刪除選取的符號嗎?
One or more of the symbols are in use and cannot be deleted until their instances are expanded or deleted. 一個或多個符號正在使用中,在展開或刪除其範例之前無法刪除。
Expand Instances 展開範例
Delete Instances 刪除範例
Yes 是
No 否
Delete Tab 刪除定位點
Delete the graphic style “{name}”? 要刪除繪圖樣式「{name}」嗎?
Delete Theme 刪除主題
Can't render this file because it contains an unexpected character in line 6 and column 47.
+205 -3
View File
@@ -273,10 +273,11 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
let cmd = if ui.input(|i| i.modifiers.alt) { "symbol.new" } else { "ui.symbolOptions" };
app.run(cmd, json!({})).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
{
app.run("symbol.delete", json!({"name": n})).ok();
crate::dialogs::symbol_delete::ask(app, n, super::alt_held(ui));
}
});
}
@@ -286,11 +287,23 @@ 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); 10] = [
let items: [(&str, bool, &str, Value); 3] = [
(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()),
];
for (label, enabled, cmd, params) in items {
if menu_item(ui, label, enabled, false) {
app.run(cmd, params).ok();
}
}
// 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);
}
let items: [(&str, bool, &str, Value); 6] = [
(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()),
@@ -738,6 +751,195 @@ mod tests {
assert!(orange(3, 3) && orange(36, 36), "{:?} {:?}", thumb.pixels[3 * 40 + 3], thumb.pixels[36 * 40 + 36]);
}
/// A document with symbol "Leaf", whose instance stands where its rectangle was, and symbol
/// "Dot", which no instance uses; "Dot" is current.
fn leaf_and_dot() -> VectorcraftApp {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
for (name, x) in [("Leaf", 10), ("Dot", 150)] {
app.run("shape.rectangle", json!({"x": x, "y": 10, "width": 30, "height": 30})).unwrap();
app.run("symbol.new", json!({"name": name})).unwrap();
}
// Dot's instance goes: no art uses Dot.
app.run("edit.clear", json!({})).unwrap();
app.run("symbol.setCurrent", json!({"name": "Dot"})).unwrap();
app
}
/// The instances of symbol `name` in the active document, and the ordinary paths in it.
fn art(app: &VectorcraftApp, name: &str) -> (usize, usize) {
let (mut instances, mut paths) = (0, 0);
for l in &app.session.active().unwrap().doc.layers {
l.walk(&mut |n| match &n.kind {
NodeKind::SymbolInstance { symbol, .. } if symbol == name => instances += 1,
NodeKind::Path { .. } => paths += 1,
_ => {}
});
}
(instances, paths)
}
fn dialog_kind(app: &VectorcraftApp) -> Option<&str> {
app.ui.dialog.as_ref().map(|d| d.kind.as_str())
}
/// One frame of the panel with `modifiers` held, its output kept.
fn frame_held(ctx: &egui::Context, app: &mut VectorcraftApp, events: Vec<egui::Event>, modifiers: egui::Modifiers) -> egui::FullOutput {
let screen_rect = Some(Rect::from_min_size(Pos2::ZERO, vec2(236.0, 400.0)));
let events = std::iter::once(egui::Event::ModifiersChanged(modifiers)).chain(events).collect();
let mut out = ctx.run_ui(egui::RawInput { screen_rect, events, ..Default::default() }, |ui| show(app, ui));
out.textures_delta.clear();
out
}
/// The labels of the clickable accessibility nodes of `out`, with their centers.
fn clickable(out: &egui::FullOutput) -> Vec<(String, Pos2)> {
let nodes = &out.platform_output.accesskit_update.as_ref().expect("an accessibility tree").nodes;
nodes
.iter()
.filter(|(_, n)| n.supports_action(egui::accesskit::Action::Click))
.filter_map(|(_, n)| {
let b = n.bounds()?;
Some((n.label()?.trim().to_string(), pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32)))
})
.collect()
}
/// Clicks the panel's Delete Symbol button with `modifiers` held.
fn click_delete(app: &mut VectorcraftApp, modifiers: egui::Modifiers) {
let ctx = egui::Context::default();
ctx.enable_accesskit();
let out = frame_held(&ctx, app, vec![], modifiers);
let at = clickable(&out).into_iter().find(|(l, _)| l == "Delete Symbol").expect("a Delete Symbol button").1;
let press = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers };
frame_held(&ctx, app, vec![egui::Event::PointerMoved(at), press(true), press(false)], modifiers);
}
/// Clicks the panel menu's Delete Symbol item.
fn menu_delete(app: &mut VectorcraftApp) {
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let labels = menu_frame(&ctx, app, vec![]);
let item = labels.iter().find(|(t, _)| t.trim() == "Delete Symbol").map(|(_, r)| r.center()).expect("Delete Symbol in the menu");
menu_frame(&ctx, app, vec![egui::Event::PointerMoved(item), button(item, true)]);
menu_frame(&ctx, app, vec![button(item, false)]);
}
/// Delete Symbol, from the panel's button or its menu, asks "Delete the selected symbol?" for a
/// symbol no instance uses, and the in-use question for one with instances, before changing
/// the document; No leaves it as it was and Yes deletes the symbol. Alt-click on the button
/// deletes an unused symbol without asking, and still asks for one with instances.
#[test]
fn delete_symbol_asks_first_and_alt_click_deletes_an_unused_symbol_at_once() {
let mut app = leaf_and_dot();
let before = app.session.active().unwrap().doc.clone();
let unchanged = |app: &VectorcraftApp| Arc::ptr_eq(&before, &app.session.active().unwrap().doc);
click_delete(&mut app, egui::Modifiers::NONE);
assert_eq!(dialog_kind(&app), Some(crate::dialogs::symbol_delete::KIND));
assert!(unchanged(&app), "nothing changes while it asks");
crate::dialogs::cancel(&mut app);
assert!(app.ui.dialog.is_none());
assert!(unchanged(&app), "No changes nothing");
menu_delete(&mut app);
assert_eq!(dialog_kind(&app), Some(crate::dialogs::symbol_delete::KIND), "the menu item asks too");
assert!(unchanged(&app));
crate::dialogs::confirm(&mut app).unwrap();
assert!(app.ui.dialog.is_none());
assert_eq!(symbols(&mut app), ["Leaf"], "Yes deletes it");
// Alt-click on the button: an unused symbol goes without a question.
app.run("edit.undo", json!({})).unwrap();
app.run("symbol.setCurrent", json!({"name": "Dot"})).unwrap();
click_delete(&mut app, egui::Modifiers::ALT);
assert!(app.ui.dialog.is_none());
assert_eq!(symbols(&mut app), ["Leaf"]);
// A symbol with an instance asks, with Alt held too, from the button and from the menu.
app.run("symbol.setCurrent", json!({"name": "Leaf"})).unwrap();
let before = app.session.active().unwrap().doc.clone();
for alt in [egui::Modifiers::NONE, egui::Modifiers::ALT] {
click_delete(&mut app, alt);
assert_eq!(dialog_kind(&app), Some(crate::dialogs::symbol_delete::USED), "{alt:?}");
crate::dialogs::cancel(&mut app);
}
menu_delete(&mut app);
assert_eq!(dialog_kind(&app), Some(crate::dialogs::symbol_delete::USED));
crate::dialogs::cancel(&mut app);
assert!(Arc::ptr_eq(&before, &app.session.active().unwrap().doc), "Cancel changes nothing");
assert_eq!(art(&app, "Leaf"), (1, 0));
}
/// The in-use question: Expand Instances deletes the symbol and turns its instance into
/// ordinary art, Delete Instances deletes the symbol and its instance, each in one undo step.
#[test]
fn the_in_use_question_expands_or_deletes_the_instances() {
for (discard, left) in [(false, (0, 1)), (true, (0, 0))] {
let mut app = leaf_and_dot();
crate::dialogs::symbol_delete::ask(&mut app, "Leaf", false);
assert_eq!(dialog_kind(&app), Some(crate::dialogs::symbol_delete::USED));
if discard {
app.ui.dialog.as_mut().unwrap().fields.insert("discard".into(), json!(true));
}
crate::dialogs::confirm(&mut app).unwrap();
assert!(app.ui.dialog.is_none());
assert_eq!(symbols(&mut app), ["Dot"], "discard {discard}");
assert_eq!(art(&app, "Leaf"), left, "discard {discard}");
app.run("edit.undo", json!({})).unwrap();
assert_eq!((symbols(&mut app), art(&app, "Leaf")), (vec!["Leaf".to_string(), "Dot".to_string()], (1, 0)), "one undo step");
}
}
/// A question asked in one document deletes nothing once another is active, and Yes for a
/// symbol that has gained an instance since asks the in-use question instead.
#[test]
fn a_stale_delete_symbol_question_deletes_nothing() {
let mut app = leaf_and_dot();
crate::dialogs::symbol_delete::ask(&mut app, "Dot", false);
let asked = app.ui.dialog.clone();
app.run("file.new", json!({"width": 100, "height": 100})).unwrap();
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 30})).unwrap();
app.run("symbol.new", json!({"name": "Dot"})).unwrap();
app.run("edit.clear", json!({})).unwrap();
app.ui.dialog = asked;
crate::dialogs::confirm(&mut app).unwrap();
assert!(app.ui.dialog.is_none());
assert_eq!(symbols(&mut app), ["Dot"], "the other document keeps its symbol");
// Back in the first document: Dot gains an instance while the question is open.
app.run("document.activate", json!({"index": 0})).unwrap();
crate::dialogs::symbol_delete::ask(&mut app, "Dot", false);
assert_eq!(dialog_kind(&app), Some(crate::dialogs::symbol_delete::KIND));
app.run("symbol.place", json!({"name": "Dot"})).unwrap();
crate::dialogs::confirm(&mut app).unwrap();
assert_eq!(dialog_kind(&app), Some(crate::dialogs::symbol_delete::USED));
assert_eq!(symbols(&mut app), ["Leaf", "Dot"]);
assert_eq!(art(&app, "Dot"), (1, 0));
}
/// The questions' buttons: Yes and No for an unused symbol; Expand Instances, Delete Instances
/// and Cancel for one with instances.
#[test]
fn the_delete_symbol_questions_show_their_buttons() {
let mut app = leaf_and_dot();
for (name, want) in [("Dot", &["Yes", "No"][..]), ("Leaf", &["Expand Instances", "Delete Instances", "Cancel"][..])] {
crate::dialogs::symbol_delete::ask(&mut app, name, false);
let ctx = egui::Context::default();
ctx.enable_accesskit();
// The window measures itself in its first frame and draws its buttons in the next.
let mut out = None;
for _ in 0..2 {
let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), ..Default::default() };
let mut o = ctx.run_ui(raw, |ui| crate::dialogs::show(&mut app, ui.ctx()));
o.textures_delta.clear();
out = Some(o);
}
let out = out.expect("a frame");
let labels: Vec<String> = clickable(&out).into_iter().map(|(l, _)| l).collect();
for w in want {
assert!(labels.iter().any(|l| l == w), "{name}: {w} in {labels:?}");
}
assert!(!labels.iter().any(|l| l == "OK"), "{name}: {labels:?}");
crate::dialogs::cancel(&mut app);
}
}
/// #4121: with symbol instances selected, the Control bar's Replace menu lists the document's
/// symbols and shows the instance's own; picking another replaces it.
#[test]
+1
View File
@@ -597,6 +597,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- macOS: Finder opens every format File › Open reads with VectorCraft (double-click, Open With, a drop on the Dock icon, `open -a`), at launch or while it runs; the release and `cargo xtask bundle` apps share one `Info.plist` that declares those document types, owning only VectorCraft's own formats. The DMG opens on a designed Finder window, VectorCraft.app beside the `Applications` link over a background with the app icon (#493).
- 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`.
- **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.