From 367c092fee5c63955cc8e69cf2d13f982a8a7bca Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 20:43:51 -0800 Subject: [PATCH] Layers: deleting layers that hold art asks first The Layers panel's trash button, rows dropped on it, the panel menu's Delete Selection, and Delete or Backspace on highlighted rows now run ui.layerDelete. When a layer or sublayer among the rows holds art (hidden, locked and nested objects count; guides do not), a confirm dialog asks first. OK deletes the rows it asked about, by id, in the document it was asked in, as one undo step; Cancel keeps them. Empty layers, guide-only layers and object or group rows alone are deleted at once. layer.delete itself is unchanged and never asks. --- crates/mcp/src/ui_catalog.rs | 6 + crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/bs.tsv | 2 + crates/ui-egui/src/i18n/ca.tsv | 3 + crates/ui-egui/src/i18n/cs.tsv | 3 + crates/ui-egui/src/i18n/da.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 3 + crates/ui-egui/src/i18n/el.tsv | 3 + crates/ui-egui/src/i18n/es.tsv | 3 + crates/ui-egui/src/i18n/fi.tsv | 3 + crates/ui-egui/src/i18n/fr.tsv | 3 + crates/ui-egui/src/i18n/hi.tsv | 3 + crates/ui-egui/src/i18n/hu.tsv | 3 + crates/ui-egui/src/i18n/id.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 3 + crates/ui-egui/src/i18n/ja.tsv | 3 + crates/ui-egui/src/i18n/ka.tsv | 3 + crates/ui-egui/src/i18n/ko.tsv | 3 + crates/ui-egui/src/i18n/nl.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 3 + crates/ui-egui/src/i18n/pt-br.tsv | 3 + crates/ui-egui/src/i18n/ru.tsv | 3 + crates/ui-egui/src/i18n/sv.tsv | 3 + crates/ui-egui/src/i18n/tr.tsv | 3 + crates/ui-egui/src/i18n/uk.tsv | 3 + crates/ui-egui/src/i18n/vi.tsv | 3 + crates/ui-egui/src/i18n/zh-hans.tsv | 3 + crates/ui-egui/src/i18n/zh-hant.tsv | 3 + crates/ui-egui/src/menus/run.rs | 1 + crates/ui-egui/src/menus/ui_commands.rs | 6 + crates/ui-egui/src/panels/layers.rs | 6 +- crates/ui-egui/src/panels/layers/controls.rs | 76 ++++++++++- crates/ui-egui/src/panels/layers/tests.rs | 125 ++++++++++++++++++- docs/control-protocol.md | 3 + docs/mcp.md | 7 +- docs/target-app-parity.md | 1 + 36 files changed, 298 insertions(+), 9 deletions(-) diff --git a/crates/mcp/src/ui_catalog.rs b/crates/mcp/src/ui_catalog.rs index d66ea18445..9e8310b984 100644 --- a/crates/mcp/src/ui_catalog.rs +++ b/crates/mcp/src/ui_catalog.rs @@ -945,6 +945,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{ids?|id?} open Layer Options for Layers panel rows (default: the highlighted rows, else the current layer), filled in from the first: dialog `layerOptions`, fields name, color (a preset name such as \"Light Blue\" or #rrggbb), template, locked, visible, printable, preview, dimImages (bool), dimPercent (0–100); an object's row has name, visible and locked. OK runs layer.setProps (one undo step); for several rows it sends only the fields set in the dialog", ), + ( + "ui.layerDelete", + "Delete Selection", + "", + "{ids?|id?, doc?, confirmed?: bool} Delete Selection as the Layers panel runs it (its trash button, rows dropped on the trash, its menu, Delete and Backspace), on the rows layer.delete takes (default: the highlighted rows, else the current layer). When a layer or sublayer among them holds art (an object other than a guide; hidden, locked and nested objects count) it asks first: dialog `confirm` with `message`; OK deletes those rows by id with layer.delete (one undo step), Cancel keeps them. OK runs ui.layerDelete again with the rows, `doc` (the document's uid) and `confirmed: true`: with another document active then, nothing is deleted. Other rows (an empty layer, groups and objects alone) and rows layer.delete refuses go to layer.delete at once → layer.delete's {deleted}, or null while the question is open", + ), ( "ui.rename", "Rename", diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 8edde76eb3..6bc15b60fb 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -797,6 +797,8 @@ font-technology Variable Променливи Delete Width Point Изтриване на точка на ширината Delete Workspace Изтриване на работно пространство Delete {kind} Style Изтриване на стил ({kind}) + Delete the selected items and their artwork? Да се изтрият ли избраните елементи заедно с графиката им? + Delete this layer and its artwork? Да се изтрие ли този слой заедно с графиката му? Delete “{name}”? Изтриване на „{name}“? Depth: Дълбочина: Description Описание diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index c9c207eaa2..3bb5858ff5 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -2141,6 +2141,8 @@ digits Malayalam Malajalam Weight: Debljina: Art using a deleted global swatch keeps its colour. Crtež koji koristi izbrisani globalni uzorak boje zadržava svoju boju. Color Group: {name} Grupa boja: {name} + Delete the selected items and their artwork? Izbrisati odabrane stavke zajedno s njihovim crtežom? + Delete this layer and its artwork? Izbrisati ovaj sloj zajedno s njegovim crtežom? Delete “{name}”? Izbrisati „{name}”? Document Swatches Uzorci boja dokumenta Edit or Apply Color Group Uredi ili primijeni grupu boja diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 0399e6c04a..df78d8dd0a 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1163,6 +1163,8 @@ antiAlias Sharp Nítid Delete Width Point Suprimeix el punt d’amplada Delete Workspace Suprimeix l’espai de treball Delete {kind} Style Suprimeix l’estil de {kind} + Delete the selected items and their artwork? Voleu suprimir els elements seleccionats i el seu art? + Delete this layer and its artwork? Voleu suprimir aquesta capa i el seu art? Delete “{name}”? Voleu suprimir “{name}”? Depth Profunditat Depth: Profunditat: @@ -6027,6 +6029,7 @@ digits Malayalam Malaiàlam @msg t, dx and dy must be finite t, dx i dy han de ser finits @msg Targa files are RGB: colorModel rgb Els fitxers Targa són RGB: colorModel rgb @msg Text Wrap: select a wrap object Ajustament de text: selecciona un objecte d’ajustament +@msg the active document changed while the question was open; nothing was deleted el document actiu ha canviat mentre la pregunta era oberta; no s’ha suprimit res @msg that document is no longer open aquest document ja no està obert @msg The 3D surface is too complex; simplify the shape La superfície 3D és massa complexa; simplifica la forma @msg the action has no steps l’acció no té cap pas diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index e62070329c..795e5dc4b7 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2172,6 +2172,8 @@ digits Malayalam Malajálamské Weight: Tloušťka: Art using a deleted global swatch keeps its colour. Kresba, která používá odstraněnou globální položku vzorníku, si barvu ponechá. Color Group: {name} Skupina barev: {name} + Delete the selected items and their artwork? Odstranit vybrané položky i s kresbou? + Delete this layer and its artwork? Odstranit tuto vrstvu i s kresbou? Delete “{name}”? Odstranit „{name}“? Document Swatches Vzorník dokumentu Edit or Apply Color Group Upravit nebo použít skupinu barev @@ -5396,6 +5398,7 @@ imageTrace Outline Obrys @msg choose the font files to add vyberte soubory písem, které chcete přidat @msg not a file nejde o soubor @msg not a font file nejde o soubor písma +@msg the active document changed while the question was open; nothing was deleted aktivní dokument se změnil, zatímco byla otázka otevřená; nic nebylo odstraněno @msg that document is no longer open tento dokument už není otevřen @msg larger than 256 MB větší než 256 MB @msg the files come to more than 1 GB: add fewer at a time soubory mají dohromady více než 1 GB: přidávejte jich méně najednou diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 47a78ef612..ad31980485 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1974,6 +1974,8 @@ digits Malayalam Malayalam Weight: Vægt: Art using a deleted global swatch keeps its colour. Grafik, der bruger en slettet global farveprøve, bevarer sin farve. Color Group: {name} Farvegruppe: {name} + Delete the selected items and their artwork? Slet de valgte elementer og deres grafik? + Delete this layer and its artwork? Slet dette lag og dets grafik? Delete “{name}”? Slet »{name}«? Document Swatches Dokumentfarveprøver Edit or Apply Color Group Redigér eller anvend farvegruppe diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index af4489a637..0c65b788cf 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -791,6 +791,8 @@ font-technology Variable Variabel Delete Width Point Breitenpunkt löschen Delete Workspace Arbeitsbereich löschen Delete {kind} Style {kind}format löschen + Delete the selected items and their artwork? Die ausgewählten Elemente und ihr Bildmaterial löschen? + Delete this layer and its artwork? Diese Ebene und ihr Bildmaterial löschen? Delete “{name}”? „{name}“ löschen? Depth: Tiefe: Description Beschreibung @@ -5013,6 +5015,7 @@ theme Appearance Erscheinungsbild @msg choose the font files to add wählen Sie die Schriftdateien aus, die hinzugefügt werden sollen @msg not a file keine Datei @msg not a font file keine Schriftdatei +@msg the active document changed while the question was open; nothing was deleted das aktive Dokument hat sich geändert, während die Frage offen war; es wurde nichts gelöscht @msg that document is no longer open dieses Dokument ist nicht mehr geöffnet @msg larger than 256 MB größer als 256 MB @msg the files come to more than 1 GB: add fewer at a time die Dateien sind zusammen größer als 1 GB: Fügen Sie weniger auf einmal hinzu diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 6ad6ff11c0..cbd58074f9 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -771,6 +771,8 @@ font-technology Variable Μεταβλητές Delete Width Point Διαγραφή σημείου πλάτους Delete Workspace Διαγραφή χώρου εργασίας Delete {kind} Style Διαγραφή στυλ {kind} + Delete the selected items and their artwork? Να διαγραφούν τα επιλεγμένα στοιχεία και τα γραφικά τους; + Delete this layer and its artwork? Να διαγραφεί αυτό το επίπεδο και τα γραφικά του; Delete “{name}”? Να διαγραφεί το «{name}»; Depth: Βάθος: Description Περιγραφή @@ -5035,6 +5037,7 @@ digits Malayalam Μαλαγιάλαμ @msg choose the font files to add επιλέξτε τα αρχεία γραμματοσειρών που θέλετε να προσθέσετε @msg not a file δεν είναι αρχείο @msg not a font file δεν είναι αρχείο γραμματοσειράς +@msg the active document changed while the question was open; nothing was deleted το ενεργό έγγραφο άλλαξε ενώ η ερώτηση ήταν ανοιχτή· δεν διαγράφηκε τίποτα @msg that document is no longer open αυτό το έγγραφο δεν είναι πλέον ανοιχτό @msg larger than 256 MB μεγαλύτερο από 256 MB @msg the files come to more than 1 GB: add fewer at a time τα αρχεία ξεπερνούν το 1 GB: προσθέστε λιγότερα κάθε φορά diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 41f92e14f7..b0fbeee7d9 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -812,6 +812,8 @@ font-technology Variable Variables Delete Width Point Eliminar punto de anchura Delete Workspace Eliminar espacio de trabajo Delete {kind} Style Eliminar estilo de {kind} + Delete the selected items and their artwork? ¿Eliminar los elementos seleccionados y su ilustración? + Delete this layer and its artwork? ¿Eliminar esta capa y su ilustración? Delete “{name}”? ¿Eliminar “{name}”? Depth: Profundidad: Description Descripción @@ -5088,6 +5090,7 @@ axis Both Ambos @msg choose the font files to add elige los archivos de fuentes que quieres agregar @msg not a file no es un archivo @msg not a font file no es un archivo de fuente +@msg the active document changed while the question was open; nothing was deleted el documento activo cambió mientras la pregunta estaba abierta; no se eliminó nada @msg that document is no longer open ese documento ya no está abierto @msg larger than 256 MB mayor de 256 MB @msg the files come to more than 1 GB: add fewer at a time los archivos suman más de 1 GB: agrega menos a la vez diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index fd38382f63..264401e9cc 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -2133,6 +2133,8 @@ digits Malayalam Malajalam Weight: Paksuus: Art using a deleted global swatch keeps its colour. Poistettua globaalia värimallia käyttävä kuvitus säilyttää värinsä. Color Group: {name} Väriryhmä: {name} + Delete the selected items and their artwork? Poistetaanko valitut kohteet ja niiden kuvitus? + Delete this layer and its artwork? Poistetaanko tämä taso ja sen kuvitus? Delete “{name}”? Poistetaanko ”{name}”? Document Swatches Asiakirjan värimallit Edit or Apply Color Group Muokkaa tai käytä väriryhmää @@ -5296,6 +5298,7 @@ imageTrace Outline Ääriviiva @msg choose the font files to add valitse lisättävät fonttitiedostot @msg not a file ei tiedosto @msg not a font file ei fonttitiedosto +@msg the active document changed while the question was open; nothing was deleted aktiivinen asiakirja vaihtui kysymyksen ollessa auki; mitään ei poistettu @msg that document is no longer open kyseinen asiakirja ei ole enää auki @msg larger than 256 MB suurempi kuin 256 MB @msg the files come to more than 1 GB: add fewer at a time tiedostojen koko on yli 1 GB: lisää vähemmän kerrallaan diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index dcbe436fc3..824fb8e8ce 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -815,6 +815,8 @@ font-technology Variable Variables Delete Width Point Supprimer le point de largeur Delete Workspace Supprimer l’espace de travail Delete {kind} Style Supprimer le style de {kind} + Delete the selected items and their artwork? Supprimer les éléments sélectionnés et leur illustration ? + Delete this layer and its artwork? Supprimer ce calque et son illustration ? Delete “{name}”? Supprimer « {name} » ? Depth: Profondeur : Description Description @@ -5075,6 +5077,7 @@ axis Both Les deux @msg choose the font files to add choisissez les fichiers de polices à ajouter @msg not a file n’est pas un fichier @msg not a font file n’est pas un fichier de police +@msg the active document changed while the question was open; nothing was deleted le document actif a changé pendant que la question était ouverte ; rien n’a été supprimé @msg that document is no longer open ce document n’est plus ouvert @msg larger than 256 MB plus de 256 Mo @msg the files come to more than 1 GB: add fewer at a time les fichiers dépassent 1 Go : ajoutez-en moins à la fois diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 9b7a15bbad..d745a05103 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -1222,6 +1222,8 @@ antiAlias Sharp शार्प Define Perspective Grid… पर्सपेक्टिव ग्रिड डिफ़ाइन करें… Definition परिभाषा Delete हटाएँ + Delete the selected items and their artwork? चुने गए आइटम और उनका आर्टवर्क हटाएँ? + Delete this layer and its artwork? यह लेयर और इसका आर्टवर्क हटाएँ? Delete “{name}”? "{name}" को हटाएं? Delete {kind} Style Delete {kind} Style Delete Action Sets एक्शन सेट हटाएं @@ -7821,6 +7823,7 @@ antiAlias Sharp शार्प @msg textureImage: {e} textureImage: {e} @msg textureImage: give {path} or {name, dataBase64} of an image file, or a texture as effect.list gives it textureImage: किसी इमेज फ़ाइल का {path} या {name, dataBase64} दें, या effect.list द्वारा दिया गया टेक्सचर दें @msg that anchor can't slide: an end of an open path has nowhere to go कि एंकर स्लाइड नहीं कर सकते: एक खुले रास्ते का अंत कहीं नहीं जाना है +@msg the active document changed while the question was open; nothing was deleted प्रश्न खुला रहते हुए सक्रिय डॉक्यूमेंट बदल गया; कुछ भी नहीं हटाया गया @msg that document is no longer open यह डॉक्यूमेंट अब खुला नहीं है @msg that makes more than {MAX_PIECES} slices जो {MAX_PIECES} स्लाइस से अधिक बनाता है @msg that makes too many pieces (at most 100000): make the dashes longer जो बहुत सारे टुकड़े बनाता है (अधिकांश 100000 में): डैश को लंबे समय तक बनाते हैं diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 6832a86707..c46fa17243 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1042,6 +1042,8 @@ font-technology Variable Változó Delete Width Point Szélességi pont törlése Delete Workspace Munkaterület törlése Delete {kind} Style Stílus törlése: {kind} + Delete the selected items and their artwork? Törlöd a kijelölt elemeket a grafikájukkal együtt? + Delete this layer and its artwork? Törlöd ezt a réteget a grafikájával együtt? Delete “{name}”? Törlöd ezt: „{name}”? Depth: Színmélység: Descender Lenyúló szár @@ -5598,6 +5600,7 @@ imageTrace Outline Körvonal @msg SVG parse error: {0} SVG-elemzési hiba: {0} @msg Targa files are RGB: colorModel rgb a Targa-fájlok RGB-k: colorModel rgb @msg Text Wrap: select a wrap object Szöveg körbefuttatása: jelölj ki egy körbefuttató objektumot +@msg the active document changed while the question was open; nothing was deleted az aktív dokumentum megváltozott, amíg a kérdés nyitva volt; semmi sem lett törölve @msg that document is no longer open az a dokumentum már nincs megnyitva @msg the art could not be sampled a grafikából nem sikerült mintát venni @msg the art has no bounds a grafikának nincsenek határai diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 45e05b9622..23fbfcd03a 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1974,6 +1974,8 @@ digits Malayalam Malayalam Weight: Ketebalan: Art using a deleted global swatch keeps its colour. Karya seni yang memakai swatch global terhapus tetap mempertahankan warnanya. Color Group: {name} Grup warna: {name} + Delete the selected items and their artwork? Hapus item yang dipilih beserta karya seninya? + Delete this layer and its artwork? Hapus lapisan ini beserta karya seninya? Delete “{name}”? Hapus “{name}”? Document Swatches Swatch dokumen Edit or Apply Color Group Edit atau terapkan grup warna diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 57f141f880..c343b68c04 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -812,6 +812,8 @@ font-technology Variable Variabili Delete Width Point Elimina punto di larghezza Delete Workspace Elimina area di lavoro Delete {kind} Style Elimina stile {kind} + Delete the selected items and their artwork? Eliminare gli elementi selezionati e la loro grafica? + Delete this layer and its artwork? Eliminare questo livello e la sua grafica? Delete “{name}”? Eliminare “{name}”? Depth: Profondità: Description Descrizione @@ -5139,6 +5141,7 @@ axis Both Entrambi @msg choose the font files to add scegli i file dei font da aggiungere @msg not a file non è un file @msg not a font file non è un file di font +@msg the active document changed while the question was open; nothing was deleted il documento attivo è cambiato mentre la domanda era aperta; non è stato eliminato nulla @msg that document is no longer open quel documento non è più aperto @msg larger than 256 MB più grande di 256 MB @msg the files come to more than 1 GB: add fewer at a time i file superano 1 GB: aggiungine meno alla volta diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 69b55c9121..4ac83d7bf9 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1471,6 +1471,8 @@ antiAlias Sharp シャープ Delete theme テーマを削除 Delete this saved preset この保存済みプリセットを削除 Delete {kind} Style {kind}スタイルを削除 + Delete the selected items and their artwork? 選択した項目とそのアートワークを削除しますか? + Delete this layer and its artwork? このレイヤーとそのアートワークを削除しますか? Delete “{name}”? 「{name}」を削除しますか? Depth: 深さ: Description 説明 @@ -5206,6 +5208,7 @@ swatch Bright Violet ビビッドバイオレット @msg choose the font files to add 追加するフォントファイルを選択してください @msg not a file ファイルではありません @msg not a font file フォントファイルではありません +@msg the active document changed while the question was open; nothing was deleted 質問が開いている間にアクティブなドキュメントが変わったため、何も削除されませんでした @msg that document is no longer open そのドキュメントはもう開かれていません @msg larger than 256 MB 256 MB を超えています @msg the files come to more than 1 GB: add fewer at a time ファイルの合計が 1 GB を超えています: 一度に追加するファイルを減らしてください diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 1cbb2d1bf1..b8d3b37aed 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1903,6 +1903,8 @@ digits Malayalam მალაიალამური Weight: სისქე: Art using a deleted global swatch keeps its colour. წაშლილი გლობალური ფერის ნიმუშის მქონე ნახატი თავის ფერს ინარჩუნებს. Color Group: {name} ფერთა ჯგუფი: {name} + Delete the selected items and their artwork? წაიშალოს მონიშნული ელემენტები და მათი ნახატი? + Delete this layer and its artwork? წაიშალოს ეს შრე და მისი ნახატი? Delete “{name}”? წაიშალოს „{name}“? Document Swatches დოკუმენტის ფერის ნიმუშები Edit or Apply Color Group ფერთა ჯგუფის რედაქტირება ან გამოყენება @@ -4733,6 +4735,7 @@ font-technology Variable ცვლადი @msg choose the font files to add აირჩიეთ დასამატებელი შრიფტის ფაილები @msg not a file ფაილი არ არის @msg not a font file შრიფტის ფაილი არ არის +@msg the active document changed while the question was open; nothing was deleted აქტიური დოკუმენტი შეიცვალა, სანამ კითხვა ღია იყო; არაფერი წაშლილა @msg that document is no longer open ის დოკუმენტი აღარ არის გახსნილი @msg larger than 256 MB 256 MB-ზე დიდია @msg the files come to more than 1 GB: add fewer at a time ფაილები 1 GB-ს აჭარბებს: დაამატეთ ნაკლები ერთდროულად diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 6135f8d5ec..c0a03bd75a 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1461,6 +1461,8 @@ antiAlias Sharp 날카롭게 Delete theme 테마 삭제 Delete this saved preset 저장된 이 프리셋 삭제 Delete {kind} Style {kind} 스타일 삭제 + Delete the selected items and their artwork? 선택한 항목과 해당 아트워크를 삭제하시겠습니까? + Delete this layer and its artwork? 이 레이어와 해당 아트워크를 삭제하시겠습니까? Delete “{name}”? “{name}”을(를) 삭제하시겠습니까? Depth: 깊이: Description 설명 @@ -5133,6 +5135,7 @@ theme Appearance 외관 @msg choose the font files to add 추가할 글꼴 파일을 선택하세요 @msg not a file 파일이 아닙니다 @msg not a font file 글꼴 파일이 아닙니다 +@msg the active document changed while the question was open; nothing was deleted 질문이 열려 있는 동안 활성 문서가 바뀌어 아무것도 삭제되지 않았습니다 @msg that document is no longer open 해당 문서는 더 이상 열려 있지 않습니다 @msg larger than 256 MB 256MB보다 큽니다 @msg the files come to more than 1 GB: add fewer at a time 파일의 합계가 1GB를 넘습니다: 한 번에 적게 추가하세요 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 327aee902c..2ba217c459 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1988,6 +1988,8 @@ digits Malayalam Malayalam Weight: Dikte: Art using a deleted global swatch keeps its colour. Artwork met een verwijderd globaal staal behoudt zijn kleur. Color Group: {name} Kleurgroep: {name} + Delete the selected items and their artwork? De geselecteerde items en hun artwork verwijderen? + Delete this layer and its artwork? Deze laag en het artwork erin verwijderen? Delete “{name}”? „{name}” verwijderen? Document Swatches Documentstalen Edit or Apply Color Group Kleurgroep bewerken of toepassen diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index f277d51d65..7ebd9b904a 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2090,6 +2090,8 @@ digits Malayalam Malajalam Weight: Grubość: Art using a deleted global swatch keeps its colour. Kompozycja używająca usuniętej próbki globalnej zachowa swój kolor. Color Group: {name} Grupa kolorów: {name} + Delete the selected items and their artwork? Usunąć zaznaczone elementy razem z grafiką? + Delete this layer and its artwork? Usunąć tę warstwę razem z grafiką? Delete “{name}”? Usunąć „{name}”? Document Swatches Próbki dokumentu Edit or Apply Color Group Edytuj lub zastosuj grupę kolorów @@ -5222,6 +5224,7 @@ imageTrace Outline Kontur @msg choose the font files to add wybierz pliki czcionek do dodania @msg not a file to nie jest plik @msg not a font file to nie jest plik czcionki +@msg the active document changed while the question was open; nothing was deleted aktywny dokument zmienił się, gdy pytanie było otwarte; nic nie zostało usunięte @msg that document is no longer open ten dokument nie jest już otwarty @msg larger than 256 MB większy niż 256 MB @msg the files come to more than 1 GB: add fewer at a time pliki przekraczają łącznie 1 GB: dodawaj mniej naraz diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index afd10a5fbc..8de1c2a03d 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -725,6 +725,8 @@ antiAlias Sharp Nítido Delete theme Excluir tema Delete this saved preset Excluir este predefinido salvo Delete {kind} Style Excluir Estilo de {kind} + Delete the selected items and their artwork? Excluir os itens selecionados e suas ilustrações? + Delete this layer and its artwork? Excluir esta camada e sua ilustração? Delete “{name}”? Excluir “{name}”? Depth: Profundidade: Description Descrição @@ -4537,6 +4539,7 @@ axis Both Ambos @msg choose the font files to add escolha os arquivos de fonte a adicionar @msg not a file não é um arquivo @msg not a font file não é um arquivo de fonte +@msg the active document changed while the question was open; nothing was deleted o documento ativo mudou enquanto a pergunta estava aberta; nada foi excluído @msg that document is no longer open esse documento não está mais aberto @msg larger than 256 MB maior que 256 MB @msg the files come to more than 1 GB: add fewer at a time os arquivos somam mais de 1 GB: adicione menos por vez diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 2dca232079..705a0d20f5 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -810,6 +810,8 @@ font-technology Variable Вариативные Delete Width Point Удалить точку ширины Delete Workspace Удалить рабочую среду Delete {kind} Style Удалить стиль {kind} + Delete the selected items and their artwork? Удалить выбранные элементы вместе с графикой? + Delete this layer and its artwork? Удалить этот слой вместе с графикой? Delete “{name}”? Удалить «{name}»? Depth: Глубина: Description Описание @@ -5125,6 +5127,7 @@ axis Both Оба @msg choose the font files to add выберите файлы шрифтов для добавления @msg not a file не файл @msg not a font file не файл шрифта +@msg the active document changed while the question was open; nothing was deleted активный документ сменился, пока был открыт вопрос; ничего не удалено @msg that document is no longer open этот документ больше не открыт @msg larger than 256 MB больше 256 МБ @msg the files come to more than 1 GB: add fewer at a time файлы занимают больше 1 ГБ: добавляйте меньше за раз diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index e47c4e8662..e7c57a2f6b 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -799,6 +799,8 @@ font-technology Variable Variabla Delete Width Point Radera breddpunkt Delete Workspace Radera arbetslayout Delete {kind} Style Radera stil ({kind}) + Delete the selected items and their artwork? Radera de markerade objekten och deras bildmaterial? + Delete this layer and its artwork? Radera det här lagret och dess bildmaterial? Delete “{name}”? Radera ”{name}”? Depth: Djup: Description Beskrivning @@ -4983,6 +4985,7 @@ axis Both Båda @msg choose the font files to add välj de typsnittsfiler som ska läggas till @msg not a file inte en fil @msg not a font file inte en typsnittsfil +@msg the active document changed while the question was open; nothing was deleted det aktiva dokumentet ändrades medan frågan var öppen; inget raderades @msg that document is no longer open det dokumentet är inte längre öppet @msg larger than 256 MB större än 256 MB @msg the files come to more than 1 GB: add fewer at a time filerna blir större än 1 GB: lägg till färre åt gången diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 5983ec4bfe..f86ce47c4c 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -791,6 +791,8 @@ font-technology Variable Değişken Delete Width Point Genişlik Noktasını Sil Delete Workspace Çalışma Alanını Sil Delete {kind} Style {kind} Stilini Sil + Delete the selected items and their artwork? Seçili öğeler ve içlerindeki çizim silinsin mi? + Delete this layer and its artwork? Bu katman ve içindeki çizim silinsin mi? Delete “{name}”? “{name}” silinsin mi? Depth: Derinlik: Description Açıklama @@ -4821,6 +4823,7 @@ axis Both Her İkisi @msg choose the font files to add eklenecek yazı tipi dosyalarını seçin @msg not a file dosya değil @msg not a font file yazı tipi dosyası değil +@msg the active document changed while the question was open; nothing was deleted soru açıkken etkin belge değişti; hiçbir şey silinmedi @msg that document is no longer open bu belge artık açık değil @msg larger than 256 MB 256 MB'tan büyük @msg the files come to more than 1 GB: add fewer at a time dosyalar 1 GB'tan fazla geliyor: bir kerede daha azını ekleyin diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 4c9326af72..1b54a5fc9c 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -806,6 +806,8 @@ font-technology Variable Варіативні Delete Width Point Видалити точку ширини Delete Workspace Видалити робоче середовище Delete {kind} Style Видалити стиль {kind} + Delete the selected items and their artwork? Видалити вибрані елементи разом із графікою? + Delete this layer and its artwork? Видалити цей шар разом із графікою? Delete “{name}”? Видалити "{name}"? Depth: Глибина: Description Опис @@ -5123,6 +5125,7 @@ axis Both Обидві @msg choose the font files to add виберіть файли шрифтів для додавання @msg not a file не файл @msg not a font file не файл шрифту +@msg the active document changed while the question was open; nothing was deleted активний документ змінився, поки питання було відкрите; нічого не видалено @msg that document is no longer open цей документ більше не відкрито @msg larger than 256 MB більше 256 МБ @msg the files come to more than 1 GB: add fewer at a time файли займають понад 1 ГБ: додавайте менше за раз diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index fef0b8ff90..72ff0d4718 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -938,6 +938,8 @@ font-technology Variable Biến thiên Delete Width Point Xóa điểm chiều rộng Delete Workspace Xóa không gian làm việc Delete {kind} Style Xóa kiểu {kind} + Delete the selected items and their artwork? Xóa các mục đã chọn và tác phẩm của chúng? + Delete this layer and its artwork? Xóa lớp này và tác phẩm của nó? Delete “{name}”? Xóa “{name}”? Depth Độ sâu Depth: Độ sâu: @@ -5261,6 +5263,7 @@ theme Appearance Giao diện @msg t, dx and dy must be finite t, dx và dy phải là số hữu hạn @msg Targa files are RGB: colorModel rgb Tệp Targa là RGB: colorModel rgb @msg Text Wrap: select a wrap object Bọc chữ: hãy chọn đối tượng bọc +@msg the active document changed while the question was open; nothing was deleted tài liệu đang hoạt động đã thay đổi khi câu hỏi đang mở; không có gì bị xóa @msg that document is no longer open tài liệu đó không còn mở @msg The 3D surface is too complex; simplify the shape Bề mặt 3D quá phức tạp; hãy đơn giản hóa hình dạng @msg the art could not be sampled không thể lấy mẫu tác phẩm diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index a0d941ada8..0825efcfdc 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -755,6 +755,8 @@ antiAlias Sharp 锐利 Delete Width Point 删除宽度点 Delete Workspace 删除工作区 Delete {kind} Style 删除{kind}样式 + Delete the selected items and their artwork? 要删除所选项目及其图稿吗? + Delete this layer and its artwork? 要删除此图层及其图稿吗? Delete “{name}”? 要删除“{name}”吗? Depth: 深度: Description 描述 @@ -4895,6 +4897,7 @@ imageTrace Outline 轮廓 @msg choose the font files to add 请选择要添加的字体文件 @msg not a file 不是文件 @msg not a font file 不是字体文件 +@msg the active document changed while the question was open; nothing was deleted 问题打开期间活动文档已更改;未删除任何内容 @msg that document is no longer open 该文档已不再打开 @msg larger than 256 MB 大于 256 MB @msg the files come to more than 1 GB: add fewer at a time 文件总计超过 1 GB:请每次少添加一些 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index b724e57351..e517aace75 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -774,6 +774,8 @@ font-technology Variable 可變 Delete Width Point 刪除寬度點 Delete Workspace 刪除工作區 Delete {kind} Style 刪除{kind}樣式 + Delete the selected items and their artwork? 要刪除所選項目及其圖稿嗎? + Delete this layer and its artwork? 要刪除此圖層及其圖稿嗎? Delete “{name}”? 要刪除「{name}」嗎? Depth: 深度: Description 描述 @@ -2662,6 +2664,7 @@ file-filter Templates 範本 @msg choose the font files to add 請選擇要加入的字體檔案 @msg not a file 不是檔案 @msg not a font file 不是字體檔案 +@msg the active document changed while the question was open; nothing was deleted 問題開啟期間作用中文件已變更;未刪除任何內容 @msg that document is no longer open 該文件已不再開啟 @msg larger than 256 MB 大於 256 MB @msg the files come to more than 1 GB: add fewer at a time 檔案總計超過 1 GB:請每次少加入一些 diff --git a/crates/ui-egui/src/menus/run.rs b/crates/ui-egui/src/menus/run.rs index 964be00d61..3fd9a831c4 100644 --- a/crates/ui-egui/src/menus/run.rs +++ b/crates/ui-egui/src/menus/run.rs @@ -829,6 +829,7 @@ fn run_ui_part8(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option crate::dialogs::tile_edge_color::open(app), "ui.layerOptions" => crate::dialogs::layer_options::open(app, p), + "ui.layerDelete" => crate::panels::layers::delete(app, p), "ui.rename" => crate::panels::layers::rename(app, p), "ui.newLayer" => crate::dialogs::layer_options::open_new(app, p), "ui.newArtboard" => crate::dialogs::artboard_options::open_new(app), diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index 08c1bbb4c6..3b7ef8d853 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -946,6 +946,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{ids?|id?} open Layer Options for Layers panel rows (default: the highlighted rows, else the current layer), filled in from the first: dialog `layerOptions`, fields name, color (a preset name such as \"Light Blue\" or #rrggbb), template, locked, visible, printable, preview, dimImages (bool), dimPercent (0–100); an object's row has name, visible and locked. OK runs layer.setProps (one undo step); for several rows it sends only the fields set in the dialog", ), + ( + "ui.layerDelete", + "Delete Selection", + "", + "{ids?|id?, doc?, confirmed?: bool} Delete Selection as the Layers panel runs it (its trash button, rows dropped on the trash, its menu, Delete and Backspace), on the rows layer.delete takes (default: the highlighted rows, else the current layer). When a layer or sublayer among them holds art (an object other than a guide; hidden, locked and nested objects count) it asks first: dialog `confirm` with `message`; OK deletes those rows by id with layer.delete (one undo step), Cancel keeps them. OK runs ui.layerDelete again with the rows, `doc` (the document's uid) and `confirmed: true`: with another document active then, nothing is deleted. Other rows (an empty layer, groups and objects alone) and rows layer.delete refuses go to layer.delete at once → layer.delete's {deleted}, or null while the question is open", + ), ( "ui.rename", "Rename", diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index f027f2c717..0ac14d243d 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -22,6 +22,8 @@ //! a box shows where); Alt copies them. A dimmed copy of the row follows the pointer. Layers //! dropped in the empty space under the last row go below the bottom layer. Dropped on the trash //! they are deleted, on New Layer duplicated. +//! - Deleting rows from the panel (the trash, rows dropped on it, Delete Selection, Delete and +//! Backspace) asks first when a layer or sublayer among them holds art ([`delete`]). //! - The triangle opens or closes a row; Alt-click does the same to everything inside it. //! - Double-clicking a name renames it; double-clicking elsewhere on a row opens its options. //! - A target circle targets its layer, group or object (`layer.target`); dragging it onto another @@ -60,7 +62,7 @@ mod rows; mod thumbs; use controls::{bottom_bar, filter_button}; -pub use controls::{expand, menu, set_filter}; +pub use controls::{delete, expand, menu, set_filter}; pub(crate) use model::shown_name; pub use model::{Filter, ObjectKind, RowState}; #[cfg(test)] @@ -381,7 +383,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { && ui.ctx().memory(|m| m.focused().is_none()) && ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Delete) || i.consume_key(egui::Modifiers::NONE, egui::Key::Backspace)) { - out.actions.push(("layer.delete".into(), json!({}))); + out.actions.push(("ui.layerDelete".into(), json!({}))); } bottom_bar(app, ui, &view, &doc, &mut out.actions); for (c, p) in out.actions { diff --git a/crates/ui-egui/src/panels/layers/controls.rs b/crates/ui-egui/src/panels/layers/controls.rs index ca0e9f99e7..a9ee00462b 100644 --- a/crates/ui-egui/src/panels/layers/controls.rs +++ b/crates/ui-egui/src/panels/layers/controls.rs @@ -1,6 +1,6 @@ use super::model::{ObjectKind, RowState, names, parse_names, toggled}; use super::{ - Document, HashSet, LayersDrag, Node, NodeId, NodeKind, PanelDrag, Sense, Stroke, Ui, Value, VectorcraftApp, View, json, last_selected, + Arc, Document, HashSet, LayersDrag, Node, NodeId, NodeKind, PanelDrag, Sense, Stroke, Ui, Value, VectorcraftApp, View, json, last_selected, see_selection, vec2, widgets, }; @@ -66,7 +66,7 @@ pub(super) fn bottom_bar(app: &mut VectorcraftApp, ui: &mut Ui, view: &View, doc if view.rows.is_empty() && app.session.active().is_some_and(|d| !d.selection.is_empty()) { actions.push(("edit.clear".into(), json!({}))); } else { - actions.push(("layer.delete".into(), json!({}))); + actions.push(("ui.layerDelete".into(), json!({}))); } } // Create New Layer: Alt asks for its options first, Ctrl/Cmd puts it on top of every layer, @@ -83,14 +83,14 @@ pub(super) fn bottom_bar(app: &mut VectorcraftApp, ui: &mut Ui, view: &View, doc } // Rows dropped on the trash are deleted, on New Layer duplicated, as in Illustrator. Both are // outlined while rows (or, on the trash, a target circle) hang over them. - for (button, cmd) in [(&trash, "layer.delete"), (&new_layer, "layer.duplicate")] { + for (button, cmd) in [(&trash, "ui.layerDelete"), (&new_layer, "layer.duplicate")] { if let Some(d) = button.dnd_release_payload::() && let LayersDrag::Rows(ids) = &*d { actions.push((cmd.into(), json!({"ids": ids.iter().map(|i| i.0).collect::>()}))); } let over = button.dnd_hover_payload::().is_some_and(|d| matches!(*d, LayersDrag::Rows(_))) - || (cmd == "layer.delete" && button.dnd_hover_payload::().is_some_and(|d| matches!(*d, PanelDrag::Appearance(_)))); + || (cmd == "ui.layerDelete" && button.dnd_hover_payload::().is_some_and(|d| matches!(*d, PanelDrag::Appearance(_)))); if over { widgets::drop_target_outline(&child, button.rect); } @@ -143,7 +143,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { Some(("New Layer…", "ui.newLayer", json!({}), true, false)), Some(("New Sublayer…", "ui.newLayer", json!({"sublayer": true}), current.is_some(), false)), Some(("Duplicate Selection", "layer.duplicate", json!({}), !targets.is_empty(), false)), - Some(("Delete Selection", "layer.delete", json!({}), !targets.is_empty(), false)), + Some(("Delete Selection", "ui.layerDelete", json!({}), !targets.is_empty(), false)), Some(("Options for Selection…", "ui.layerOptions", json!({}), !targets.is_empty(), false)), None, Some(("Make/Release Clipping Mask", "layer.clippingMask.toggle", json!({}), true, false)), @@ -272,6 +272,72 @@ fn save_selection_menu(app: &mut VectorcraftApp, ui: &mut Ui) { }); } +/// `ui.layerDelete {ids?|id?, doc?, confirmed?}`: Delete Selection as the Layers panel runs it (the +/// trash button, rows dropped on it, the panel menu, Delete and Backspace), on the rows +/// `layer.delete` takes. When a layer or sublayer among them holds art (an object other than a +/// guide, hidden, locked and nested ones included), it asks first: OK deletes those rows, by id, in +/// the same document (`doc`, its uid, with `confirmed`); with another document active then, nothing +/// is deleted. Other rows, and rows `layer.delete` refuses (an unknown id, every top-level layer), +/// go to `layer.delete` at once. → `layer.delete`'s result, or null while the question is open +pub fn delete(app: &mut VectorcraftApp, p: &Value) -> Result { + let confirmed = crate::menus::opt_bool(p, "confirmed")?.unwrap_or(false); + let key = "doc"; + let doc = match p.get(key) { + None | Some(Value::Null) => None, + Some(v) => Some(v.as_u64().ok_or_else(|| format!("`{key}` must be a whole number of 0 or more"))?), + }; + let st = app.session.active().ok_or("no document open")?; + if doc.is_some_and(|d| d != st.uid) { + return Err("the active document changed while the question was open; nothing was deleted".into()); + } + let rows: serde_json::Map = ["ids", "id"].iter().filter_map(|k| Some((k.to_string(), p.get(*k)?.clone()))).collect(); + // An id the engine refuses: `layer.delete` gives the error, and deletes nothing. + let row = |v: &Value| v.as_u64().map(NodeId).filter(|id| st.doc.node(*id).is_some_and(|n| !n.is_ruler_guide())); + let ids: Option> = match (p.get("ids"), p.get("id")) { + (Some(v), _) => v.as_array().and_then(|a| a.iter().map(row).collect()), + (None, Some(v)) => row(v).map(|id| vec![id]), + (None, None) => { + let rows = st.highlighted_rows(); + Some(if rows.is_empty() { st.current_layer().into_iter().collect() } else { rows }) + } + }; + let Some(ids) = ids.filter(|_| !confirmed) else { return app.run("layer.delete", Value::Object(rows)) }; + let listed: HashSet = ids.iter().copied().collect(); + let mut roots = vec![]; + row_roots(&st.doc.layers, &listed, &mut roots); + let every_layer = st.doc.layers.iter().all(|l| listed.contains(&l.id)); + if every_layer || !roots.iter().any(|n| n.is_layer() && holds_art(n)) { + return app.run("layer.delete", json!({ "ids": ids.iter().map(|i| i.0).collect::>() })); + } + let message = match roots.as_slice() { + [n] if n.is_layer() => tl!("Delete this layer and its artwork?"), + _ => tl!("Delete the selected items and their artwork?"), + }; + let params = json!({"ids": ids.iter().map(|i| i.0).collect::>(), "doc": st.uid, "confirmed": true}); + crate::dialogs::confirm::ask(app, message, "", "ui.layerDelete", params); + Ok(Value::Null) +} + +/// The rows among `nodes` (and the rows inside them) that are in `listed` and not inside another +/// listed row, top-level first. +fn row_roots<'a>(nodes: &'a [Arc], listed: &HashSet, out: &mut Vec<&'a Node>) { + for n in nodes { + if out.len() == listed.len() { + return; + } + if listed.contains(&n.id) { + out.push(n); + } else if let Some(children) = n.children() { + row_roots(children, listed, out); + } + } +} + +/// Whether a layer holds art: an object other than a guide, in it or in one of its sublayers. +fn holds_art(layer: &Node) -> bool { + layer.children().into_iter().flatten().any(|c| if c.is_layer() { holds_art(c) } else { !c.is_guide() }) +} + /// `ui.layersExpand {ids?, open?}`: open (or close) rows of the active document's Layers panel; /// without `ids`, every row that holds others. pub fn expand(app: &mut VectorcraftApp, p: &Value) -> Result { diff --git a/crates/ui-egui/src/panels/layers/tests.rs b/crates/ui-egui/src/panels/layers/tests.rs index 0fd8586409..2143bd1ee3 100644 --- a/crates/ui-egui/src/panels/layers/tests.rs +++ b/crates/ui-egui/src/panels/layers/tests.rs @@ -452,6 +452,127 @@ fn dragging_rows_onto_the_trash_deletes_them() { assert!(doc.node(top).is_none()); } +/// The message of the confirmation open now, if one is. +fn asked(app: &VectorcraftApp) -> Option { + app.ui.dialog.as_ref().filter(|d| d.kind == crate::dialogs::confirm::KIND).map(|d| d.str("message")) +} + +/// The trash, the Delete key and rows dropped on the trash ask before deleting a layer that holds +/// art, hidden and locked art included: Cancel keeps it, and OK deletes the rows it asked about in +/// one undo step, even after another row was highlighted. +#[test] +fn deleting_a_layer_that_holds_art_asks_first() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + let run = |app: &mut VectorcraftApp, id: &str, p: serde_json::Value| app.session.execute(id, &p).unwrap(); + run(&mut app, "file.new", json!({"width": 200, "height": 200})); + let rect = NodeId(run(&mut app, "shape.rectangle", json!({"x": 0, "y": 0, "width": 20, "height": 20}))["id"].as_u64().unwrap()); + run(&mut app, "select.none", json!({})); + run(&mut app, "layer.setProps", json!({"ids": [rect.0], "visible": false, "locked": true})); + let art = app.session.active().unwrap().doc.layers[0].id; + run(&mut app, "layer.new", json!({})); + let there = |app: &VectorcraftApp| app.session.active().unwrap().doc.node(art).is_some(); + let ctx = egui::Context::default(); + // Rows top first: Layer 2, Layer 1, its rectangle. + let (c, _) = frame(&mut app, &ctx, vec![], false); + let name = |i: usize| egui::pos2(150.0, c[i].y); + let question = Some("Delete this layer and its artwork?".to_string()); + click_with(&mut app, &ctx, name(1), egui::Modifiers::NONE); + let trash = trash_pos(&mut app, &ctx); + click_with(&mut app, &ctx, trash, egui::Modifiers::NONE); + assert_eq!(asked(&app), question, "the trash asks"); + assert!(there(&app), "nothing is deleted before OK"); + crate::dialogs::cancel(&mut app); + assert!(app.ui.dialog.is_none() && there(&app), "Cancel keeps the layer"); + click_with(&mut app, &ctx, name(1), egui::Modifiers::NONE); + let del = egui::Event::Key { key: egui::Key::Delete, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE }; + frame(&mut app, &ctx, vec![del], false); + assert_eq!(asked(&app), question, "Delete asks"); + assert!(there(&app)); + crate::dialogs::cancel(&mut app); + let undo = app.session.active().unwrap().history.undo.len(); + drag(&mut app, &ctx, c[1] - vec2(100.0, 0.0), trash, false); + assert_eq!(asked(&app), question, "a drop on the trash asks"); + assert!(there(&app)); + // Highlighting Layer 2 now leaves OK deleting the row that was dropped. + click_with(&mut app, &ctx, name(0), egui::Modifiers::NONE); + crate::dialogs::confirm(&mut app).unwrap(); + let st = app.session.active().unwrap(); + assert!(app.ui.dialog.is_none() && st.doc.node(art).is_none() && st.doc.node(rect).is_none()); + assert_eq!((st.doc.layers.len(), st.history.undo.len()), (1, undo + 1), "one undo step"); + run(&mut app, "edit.undo", json!({})); + assert!(there(&app)); +} + +/// The panel menu's Delete Selection (`ui.layerDelete`) asks once for several rows when one of +/// them is a layer with art, art in a sublayer included; an empty layer, a layer with only a +/// guide, and objects alone go at once, and `layer.delete` itself never asks. +#[test] +fn delete_selection_asks_only_for_layers_with_art() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + let run = |app: &mut VectorcraftApp, id: &str, p: serde_json::Value| app.session.execute(id, &p).unwrap(); + let id = |v: serde_json::Value| v["id"].as_u64().unwrap(); + run(&mut app, "file.new", json!({"width": 200, "height": 200})); + let bottom = app.session.active().unwrap().doc.layers[0].id.0; + let loose = id(run(&mut app, "shape.rectangle", json!({"x": 0, "y": 0, "width": 20, "height": 20}))); + let outer = id(run(&mut app, "layer.new", json!({}))); + let sub = id(run(&mut app, "layer.newSublayer", json!({}))); + let nested = id(run(&mut app, "shape.ellipse", json!({"x": 50, "y": 50, "width": 20, "height": 20}))); + run(&mut app, "select.none", json!({})); + let empty = id(run(&mut app, "layer.new", json!({"top": true}))); + let guided = id(run(&mut app, "layer.new", json!({"top": true}))); + run(&mut app, "guide.add", json!({"vertical": true, "pos": 50, "layer": guided})); + run(&mut app, "select.none", json!({})); + let exists = |app: &VectorcraftApp, id: u64| app.session.active().unwrap().doc.node(NodeId(id)).is_some(); + for (rows, what) in [(json!([empty]), "an empty layer"), (json!([guided]), "a layer with only a guide"), (json!([loose]), "an object")] { + app.run("ui.layerDelete", json!({ "ids": rows })).unwrap(); + assert!(app.ui.dialog.is_none(), "{what} goes at once"); + assert!(!exists(&app, rows[0].as_u64().unwrap()), "{what} is deleted"); + } + run(&mut app, "edit.undo", json!({})); + // Art in a sublayer counts; the highlighted rows are the default, and a mixed selection asks once. + run(&mut app, "layer.highlight", json!({"ids": [outer, loose]})); + app.run("ui.layerDelete", json!({})).unwrap(); + assert_eq!(asked(&app).as_deref(), Some("Delete the selected items and their artwork?")); + assert!(exists(&app, nested) && exists(&app, loose)); + crate::dialogs::confirm(&mut app).unwrap(); + assert!(!exists(&app, outer) && !exists(&app, sub) && !exists(&app, nested) && !exists(&app, loose)); + assert!(app.ui.dialog.is_none(), "asked once"); + // A sublayer with art asks as a layer does. + run(&mut app, "edit.undo", json!({})); + app.run("ui.layerDelete", json!({ "id": sub })).unwrap(); + assert_eq!(asked(&app).as_deref(), Some("Delete this layer and its artwork?")); + crate::dialogs::cancel(&mut app); + // Scripts deleting with layer.delete are not asked. + app.run("layer.delete", json!({ "ids": [outer] })).unwrap(); + assert!(app.ui.dialog.is_none() && !exists(&app, outer)); + assert!(exists(&app, bottom)); +} + +/// Rows `layer.delete` refuses are refused without a question, and OK deletes nothing once another +/// document is active. +#[test] +fn delete_selection_refusals_and_a_document_change() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + let run = |app: &mut VectorcraftApp, id: &str, p: serde_json::Value| app.session.execute(id, &p).unwrap(); + run(&mut app, "file.new", json!({"width": 200, "height": 200})); + run(&mut app, "shape.rectangle", json!({"x": 0, "y": 0, "width": 20, "height": 20})); + let only = app.session.active().unwrap().doc.layers[0].id.0; + let e = app.run("ui.layerDelete", json!({ "ids": [only] })).unwrap_err(); + assert!(e.contains("at least one layer") && app.ui.dialog.is_none(), "{e}"); + for bad in [json!({"ids": [999_999]}), json!({"id": "x"}), json!({"ids": 3}), json!({"confirmed": "yes"}), json!({"doc": -1})] { + assert!(app.run("ui.layerDelete", bad.clone()).is_err() && app.ui.dialog.is_none(), "{bad}"); + } + let art = run(&mut app, "layer.new", json!({}))["id"].as_u64().unwrap(); + run(&mut app, "shape.rectangle", json!({"x": 0, "y": 0, "width": 20, "height": 20})); + app.run("ui.layerDelete", json!({ "ids": [art] })).unwrap(); + assert!(asked(&app).is_some()); + run(&mut app, "file.new", json!({"width": 100, "height": 100})); + let e = crate::dialogs::confirm(&mut app).unwrap_err(); + assert!(e.contains("nothing was deleted"), "{e}"); + let first = &app.session.documents()[0].doc; + assert!(first.node(NodeId(art)).is_some(), "the first document keeps the layer"); +} + #[test] fn renaming_a_layer_commits_on_enter_and_escape_cancels() { let mut app = VectorcraftApp::new(Session::new(), Default::default()); @@ -665,9 +786,11 @@ fn rows_at_every_depth_are_clickable() { assert_eq!(rows_of(&app), vec![sub, b, a]); click_with(&mut app, &ctx, name(5), egui::Modifiers::COMMAND); assert_eq!(rows_of(&app), vec![sub, b, a, c]); - // Delete with the panel's keyboard: the highlighted rows go. + // Delete with the panel's keyboard: the highlighted rows go once OK answers the question the + // sublayer's art asks. let del = egui::Event::Key { key: egui::Key::Delete, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE }; frame(&mut app, &ctx, vec![del], false); + crate::dialogs::confirm(&mut app).unwrap(); let d = &app.session.active().unwrap().doc; assert!(d.node(sub).is_none() && d.node(c).is_none()); assert_eq!(d.layers.len(), 1); diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 19f245175d..84f018dc83 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -140,6 +140,9 @@ of another kind, it moves. Confirmations: deleting swatches from the Swatches panel opens a `confirm` dialog (fields `message`, `detail`); `ui.dialog.confirm` runs the command it asks about (here `swatch.delete`) and `ui.dialog.cancel` drops it. +Deleting Layers panel rows that include a layer or sublayer with art (`engine.execute` of `ui.layerDelete`, which the +panel's trash, its menu's Delete Selection and Delete or Backspace run) opens a `confirm` dialog too; its OK deletes +those rows with `layer.delete` in the document the question was asked in. `layer.delete` itself deletes at once. New swatches and colour groups: `ui.newSwatch {spot?, global?, group?}` opens the `newSwatch` dialog prefilled from the active fill or stroke (fields as in `swatchOptions` plus `group`; a gradient or pattern only takes `name`) and diff --git a/docs/mcp.md b/docs/mcp.md index 20e37222c6..f99bab92b1 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4236,7 +4236,12 @@ and `layerRows`, the rows highlighted in the panel. `layer.newSublayer {parent?, bottom?, name?, …options}` (on top of the parent's contents; `bottom`, under them but above a clipping layer's clipping path, with Ctrl/Cmd+Alt; both refused while an opacity mask is edited), `layer.delete {ids?}` (rows with what they hold; the - last layer stays), `layer.locate {id?}` (highlights the selected object's row; the panel opens the rows around it) + last layer stays; it does not ask), `ui.layerDelete {ids?|id?}` (the panel's trash, rows dropped on it, Delete + Selection, and Delete or Backspace on highlighted rows: when a layer or sublayer among the rows holds art, hidden, + locked or nested art included, dialog `confirm` asks "Delete this layer and its artwork?" for one layer, else "Delete + the selected items and their artwork?"; OK deletes the rows it asked about in that document, one undo step, and + Cancel keeps them; an empty layer, a layer with only guides, and groups or objects alone are deleted at once), + `layer.locate {id?}` (highlights the selected object's row; the panel opens the rows around it) and `layer.clippingMask.toggle`. - **Panel menu**: `layer.duplicate {ids?}`, `layer.merge {ids?}` (into the layer highlighted last, keeping the stacking order), `layer.flatten {id?}` (every other visible layer's art into one layer; hidden layers are deleted, diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 30b1e883d7..845375e6e6 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1094,6 +1094,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Layers panel:** every layer, sublayer, group and object is a clickable row at any depth: a click highlights it and makes its layer current (new art goes there), Shift-click a range, Ctrl/Cmd-click toggles. Sublayers are no longer treated as objects: clicks, marquees, Select All and the Select menu take the art inside them, and each sublayer selects in its own colour. Selection squares on rows with selected art (click selects a row's art, Shift adds or removes; dragging the square moves the selection to another row, Alt copies), the eye and lock on every row with drag-down-the-column (one undo step), Ctrl/Cmd-click an eye for per-layer Outline, Alt-click an eye or lock to hide or lock the other layers (again: show or unlock them all), triangles with Alt to open or close everything inside, drag and drop rows above, below or into layers and groups with a drop line (Alt copies) and a dimmed copy of the row under the pointer (layers dropped in the empty space under the last row go below the bottom layer, #2260; dropped on the trash rows are deleted, on New Layer duplicated, as in Illustrator), inline rename, Delete key on highlighted rows. Buttons and the full panel menu: New Layer/Sublayer (Alt: options, Ctrl: on top), Duplicate, Delete, Options for Selection, clipping mask, isolation, Locate Object, Merge Selected, Flatten Artwork, Collect in New Layer, Release to Layers (Sequence and Build), Reverse Order, Template, Hide/Outline/Lock Others and Show/Preview/Unlock All, Paste Remembers Layers, Panel Options (row size, thumbnails per kind, Show Layers Only). Layer Options: name, colour (presets, each with a chip of its colour, or custom), Template, Lock, Show, Print, Preview (outline drawing and clicking) and Dim Images to %. Every operation is a command (`layer.move`, `layer.highlight`, `layer.merge`…) with validation against cycles and invalid nesting. A document opens with only its top-level layers open; the rows left open are saved in the native file (nothing is written for the default) and reopen that way. Only the rows scrolled into view are laid out, from a row list made once per change: a layer of 5,000 groups of 10 paths, all open, draws the panel in 0.4 ms a frame instead of about 29 s (#638). The Filter button at the right of Search All keeps the rows that are of an object kind ticked under By Object (paths, shapes, text, images, effects, groups, linked files, guides) and in a state ticked under By Layer (locked, hidden), with the rows that hold them; a section with nothing ticked takes every row. The filter combines with the search, the button is lit while a filter is on, and Clear All in its popup turns the filter off (`ui.layersFilter`). The filter lasts the session. The panel menu's Save Selection submenu has Save Selection…, Update Selection and Edit Selection…, enabled and run as in the Select menu, and lists the document's saved selections: choosing one selects its objects again (`select.recall`). + Deleting rows from the Layers panel (the trash button, rows dragged onto it, the panel menu's Delete Selection, and Delete or Backspace on highlighted rows) asks "Delete this layer and its artwork?" when a layer or sublayer among them holds art, hidden, locked and nested art included, and "Delete the selected items and their artwork?" when several rows are deleted; OK deletes the rows it asked about in one undo step, and Cancel keeps them (`ui.layerDelete`). The help page's shortcut list names the Layers panel as the place where deleting asks for confirmation ([Default keyboard shortcuts](https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html)). An empty layer, a layer with only guides, and groups or objects alone are deleted without a question, and `layer.delete` itself does not ask (`panels::layers` tests `deleting_a_layer_that_holds_art_asks_first`, `delete_selection_asks_only_for_layers_with_art`). `object.setProps` with `visible: false` or `locked: true` deselects every selected object that is then hidden or locked, the key object included, as the eye and lock columns do. Ctrl/Cmd+Alt-clicking an eye switches the other layers to Outline and the row's layer to Preview, or switches all layers back to Preview when the others are in Outline (`layer.outlineOthers`, `layer.previewAll`). Ctrl/Cmd+Alt-clicking New Layer adds a layer directly below the current layer, at its level (`layer.new {below: true}`), and Ctrl/Cmd+Alt-clicking New Sublayer adds a sublayer under the contents of the layer it goes into (`layer.newSublayer {bottom: true}`), each in one undo step. The reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) page lists these clicks for placing a layer below the selected layer and a new sublayer at the bottom of the layer list.