From 49290fa8e6bd32b1f841718a28e73a7f2b6ebdd1 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 30 Oct 2026 06:32:34 -0700 Subject: [PATCH] Brushes: Delete Brush asks first, and Alt-click deletes without asking The Brushes panel's Delete Brush button and its panel menu item now ask before deleting the selected brushes, as Swatches and Graphic Styles do. OK deletes them in one undo step and strokes that used them become plain strokes; Cancel leaves the document unchanged. Alt-click (Option-click on macOS) on the button deletes without asking. The brush.delete command still deletes at once. --- crates/ui-egui/src/i18n/bg.tsv | 3 + crates/ui-egui/src/i18n/ca.tsv | 3 + crates/ui-egui/src/i18n/cs.tsv | 3 + crates/ui-egui/src/i18n/da.tsv | 3 + 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/hu.tsv | 3 + crates/ui-egui/src/i18n/id.tsv | 3 + 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 | 3 + 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/panels/brushes.rs | 21 +++++- crates/ui-egui/src/panels/brushes/tests.rs | 83 ++++++++++++++++++++-- docs/target-app-parity.md | 1 + 28 files changed, 171 insertions(+), 9 deletions(-) diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index dabb1c8e9..702fb78d2 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -742,6 +742,9 @@ font-technology Variable Променливи Yes Да No Не Delete Tab Изтриване на табулация + Delete the brush “{name}”? Да се изтрие четката „{name}“? + Delete these {count} brushes? Да се изтрият тези {count} четки? + Strokes using deleted brushes become plain strokes. Щрихите, които използват изтритите четки, стават обикновени щрихи. Delete the graphic style “{name}”? Изтриване на графичния стил „{name}“? Delete Theme Изтриване на тема Delete theme Изтриване на тема diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 59984a519..4269d2b7e 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1059,6 +1059,9 @@ Yes Sí No No Delete Tab Suprimeix la tabulació + Delete the brush “{name}”? Voleu suprimir el pinzell “{name}”? + Delete these {count} brushes? Voleu suprimir aquests {count} pinzells? + Strokes using deleted brushes become plain strokes. Els traços que utilitzen pinzells suprimits esdevenen traços normals. Delete the graphic style “{name}”? Voleu suprimir l’estil gràfic “{name}”? Delete the library “{name}”? Voleu suprimir la biblioteca “{name}”? Delete Theme Suprimeix el tema diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 11d38443b..39c5e2be1 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1558,6 +1558,9 @@ lighting Light Světlo Reverse Gradient Obrátit přechod Save to Swatches Uložit do vzorníku Delete Graphic Style (Alt-click: without asking) Odstranit grafický styl (Alt+kliknutí: bez dotazu) + Delete the brush “{name}”? Odstranit štětec „{name}“? + Delete these {count} brushes? Odstranit těchto {count} štětců? + Strokes using deleted brushes become plain strokes. Tahy používající odstraněné štětce se změní na běžné tahy. Delete the graphic style “{name}”? Odstranit grafický styl „{name}“? Delete these {n} graphic styles? Odstranit tyto grafické styly ({n})? Delete this {n} graphic style? Odstranit tento grafický styl ({n})? diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 1203b3d36..f7021936c 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1472,6 +1472,9 @@ lighting Light Lys Reverse Gradient Omvend gradient Save to Swatches Gem i farveprøver Delete Graphic Style (Alt-click: without asking) Slet grafisk typografi (Alt-klik: uden at spørge) + Delete the brush “{name}”? Slet penslen »{name}«? + Delete these {count} brushes? Slet disse {count} pensler? + Strokes using deleted brushes become plain strokes. Streger, der bruger slettede pensler, bliver almindelige streger. Delete the graphic style “{name}”? Slet den grafiske typografi »{name}«? Delete these {n} graphic styles? Slet disse {n} grafiske typografier? Delete this {n} graphic style? Slet denne {n} grafiske typografi? diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index e832311b0..dc9c67f8b 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -736,6 +736,9 @@ font-technology Variable Variabel Yes Ja No Nein Delete Tab Tabulator löschen + Delete the brush “{name}”? Pinsel „{name}“ löschen? + Delete these {count} brushes? Diese {count} Pinsel löschen? + Strokes using deleted brushes become plain strokes. Konturen mit gelöschten Pinseln werden zu einfachen Konturen. Delete the graphic style “{name}”? Grafikstil „{name}“ löschen? Delete Theme Thema löschen Delete theme Thema löschen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 90709397a..150002aa4 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -716,6 +716,9 @@ font-technology Variable Μεταβλητές Yes Ναι No Όχι Delete Tab Διαγραφή στηλοθέτη + Delete the brush “{name}”? Να διαγραφεί το πινέλο «{name}»; + Delete these {count} brushes? Να διαγραφούν αυτά τα {count} πινέλα; + Strokes using deleted brushes become plain strokes. Οι πινελιές που χρησιμοποιούν διαγραμμένα πινέλα γίνονται απλές πινελιές. Delete the graphic style “{name}”? Να διαγραφεί το στυλ γραφικών «{name}»; Delete Theme Διαγραφή θέματος Delete theme Διαγραφή θέματος diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index f90a4b047..2bbb31702 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -757,6 +757,9 @@ font-technology Variable Variables Yes Sí No No Delete Tab Eliminar tabulación + Delete the brush “{name}”? ¿Eliminar el pincel “{name}”? + Delete these {count} brushes? ¿Eliminar estos {count} pinceles? + Strokes using deleted brushes become plain strokes. Los trazos que usan pinceles eliminados se convierten en trazos simples. Delete the graphic style “{name}”? ¿Eliminar el estilo gráfico “{name}”? Delete Theme Eliminar tema Delete theme Eliminar tema diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 8c4bc1fc1..1a276e0e7 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1532,6 +1532,9 @@ lighting Light Valo Reverse Gradient Käännä liukuväri Save to Swatches Tallenna värimalleihin Delete Graphic Style (Alt-click: without asking) Poista grafiikkatyyli (Alt-napsautus: kysymättä) + Delete the brush “{name}”? Poistetaanko sivellin ”{name}”? + Delete these {count} brushes? Poistetaanko nämä {count} sivellintä? + Strokes using deleted brushes become plain strokes. Poistettuja siveltimiä käyttävät viivat muuttuvat tavallisiksi viivoiksi. Delete the graphic style “{name}”? Poistetaanko grafiikkatyyli ”{name}”? Delete these {n} graphic styles? Poistetaanko nämä {n} grafiikkatyyliä? Delete this {n} graphic style? Poistetaanko tämä {n} grafiikkatyyli? diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 7db8ee535..68a78da7d 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -759,6 +759,9 @@ font-technology Variable Variables Yes Oui No Non Delete Tab Supprimer la tabulation + Delete the brush “{name}”? Supprimer la forme « {name} » ? + Delete these {count} brushes? Supprimer ces {count} formes ? + Strokes using deleted brushes become plain strokes. Les contours utilisant des formes supprimées deviennent des contours simples. Delete the graphic style “{name}”? Supprimer le style graphique « {name} » ? Delete Theme Supprimer le thème Delete theme Supprimer le thème diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 0aceab5ab..322770fb8 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -981,6 +981,9 @@ font-technology Variable Változó Yes Igen No Nem Delete Tab Tabulátor törlése + Delete the brush “{name}”? Törlöd ezt az ecsetet: „{name}”? + Delete these {count} brushes? Törlöd ezt a {count} ecsetet? + Strokes using deleted brushes become plain strokes. A törölt ecseteket használó körvonalak egyszerű körvonalakká válnak. 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}”? Delete Theme Téma törlése diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 95a303633..25ee0213a 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1472,6 +1472,9 @@ lighting Light Cahaya Reverse Gradient Balikkan gradien Save to Swatches Simpan ke swatch Delete Graphic Style (Alt-click: without asking) Hapus gaya grafis (Alt+klik: tanpa konfirmasi) + Delete the brush “{name}”? Hapus kuas “{name}”? + Delete these {count} brushes? Hapus {count} kuas ini? + Strokes using deleted brushes become plain strokes. Goresan yang menggunakan kuas terhapus menjadi goresan biasa. Delete the graphic style “{name}”? Hapus gaya grafis “{name}”? Delete these {n} graphic styles? Hapus {n} gaya grafis ini? Delete this {n} graphic style? Hapus {n} gaya grafis ini? diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index a9c16d3ba..7685ad98a 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -757,6 +757,9 @@ font-technology Variable Variabili Yes Sì No No Delete Tab Elimina tabulazione + Delete the brush “{name}”? Eliminare il pennello “{name}”? + Delete these {count} brushes? Eliminare questi {count} pennelli? + Strokes using deleted brushes become plain strokes. I tracciati che usano pennelli eliminati diventano tracciati semplici. Delete the graphic style “{name}”? Eliminare lo stile grafico “{name}”? Delete Theme Elimina tema Delete theme Elimina tema diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 01c25917b..99376a0f1 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1308,6 +1308,9 @@ font-technology Variable バリアブル Delete Transparency Flattener Preset 透明の分割・統合プリセットを削除 Delete Width Point 幅ポイントを削除 Delete Workspace ワークスペースを削除 + Delete the brush “{name}”? ブラシ「{name}」を削除しますか? + Delete these {count} brushes? これら {count} 個のブラシを削除しますか? + Strokes using deleted brushes become plain strokes. 削除したブラシを使用する線は通常の線になります。 Delete the graphic style “{name}”? グラフィックスタイル「{name}」を削除しますか? Delete theme テーマを削除 Delete this saved preset この保存済みプリセットを削除 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index e0e1a1095..b55b3f2c9 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1389,6 +1389,9 @@ lighting Light სინათლე Reverse Gradient გრადიენტის შებრუნება Save to Swatches ფერის ნიმუშებში შენახვა Delete Graphic Style (Alt-click: without asking) გრაფიკული სტილის წაშლა (Alt-დაწკაპუნებით: დაუკითხავად) + Delete the brush “{name}”? წაიშალოს ფუნჯი „{name}“? + Delete these {count} brushes? წაიშალოს ეს {count} ფუნჯი? + Strokes using deleted brushes become plain strokes. წაშლილი ფუნჯებით დახატული შტრიხები ჩვეულებრივ შტრიხებად იქცევა. Delete the graphic style “{name}”? წაიშალოს გრაფიკული სტილი „{name}“? Delete these {n} graphic styles? წაიშალოს ეს გრაფიკული სტილები ({n})? Delete this {n} graphic style? წაიშალოს ეს გრაფიკული სტილი ({n})? diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 45ce73252..e5dee07ee 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1299,6 +1299,9 @@ font-technology Variable 가변 Delete Transparency Flattener Preset 투명도 병합 프리셋 삭제 Delete Width Point 너비 포인트 삭제 Delete Workspace 작업 영역 삭제 + Delete the brush “{name}”? 브러시 “{name}”을(를) 삭제하시겠습니까? + Delete these {count} brushes? 이 브러시 {count}개를 삭제하시겠습니까? + Strokes using deleted brushes become plain strokes. 삭제된 브러시를 사용한 선은 일반 선이 됩니다. Delete the graphic style “{name}”? 그래픽 스타일 “{name}”을(를) 삭제하시겠습니까? Delete theme 테마 삭제 Delete this saved preset 저장된 이 프리셋 삭제 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index e4cca253f..bbee0d063 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1486,6 +1486,9 @@ lighting Light Licht Reverse Gradient Verloop omkeren Save to Swatches Opslaan in stalen Delete Graphic Style (Alt-click: without asking) Grafische stijl verwijderen (Alt-klik: zonder vragen) + Delete the brush “{name}”? Kwast „{name}” verwijderen? + Delete these {count} brushes? Deze {count} kwasten verwijderen? + Strokes using deleted brushes become plain strokes. Lijnen die verwijderde kwasten gebruiken, worden gewone lijnen. Delete the graphic style “{name}”? De grafische stijl „{name}” verwijderen? Delete these {n} graphic styles? Deze {n} grafische stijlen verwijderen? Delete this {n} graphic style? Deze {n} grafische stijl verwijderen? diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 027f7198e..95d62b9a0 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1472,6 +1472,9 @@ lighting Light Światło Reverse Gradient Odwróć gradient Save to Swatches Zapisz w próbkach Delete Graphic Style (Alt-click: without asking) Usuń styl graficzny (kliknięcie z Alt: bez pytania) + Delete the brush “{name}”? Usunąć pędzel „{name}”? + Delete these {count} brushes? Usunąć te {count} pędzle? + Strokes using deleted brushes become plain strokes. Obrysy używające usuniętych pędzli stają się zwykłymi obrysami. Delete the graphic style “{name}”? Usunąć styl graficzny „{name}”? Delete these {n} graphic styles? Usunąć te style graficzne ({n})? Delete this {n} graphic style? Usunąć ten styl graficzny ({n})? diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index e9ee07994..7f4d8287a 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -674,6 +674,9 @@ Delete Transparency Flattener Preset Excluir Predefinido de Achatamento de Transparência Delete Width Point Excluir Ponto de Largura Delete Workspace Excluir Espaço de Trabalho + Delete the brush “{name}”? Excluir o pincel “{name}”? + Delete these {count} brushes? Excluir estes {count} pincéis? + Strokes using deleted brushes become plain strokes. Traços que usam pincéis excluídos tornam-se traços simples. Delete the graphic style “{name}”? Excluir o estilo gráfico “{name}”? Delete theme Excluir tema Delete this saved preset Excluir este predefinido salvo diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index dea1bee84..583a3f09a 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -755,6 +755,9 @@ font-technology Variable Вариативные Yes Да No Нет Delete Tab Удалить табуляцию + Delete the brush “{name}”? Удалить кисть «{name}»? + Delete these {count} brushes? Удалить эти кисти ({count})? + Strokes using deleted brushes become plain strokes. Обводки, использующие удалённые кисти, становятся обычными обводками. Delete the graphic style “{name}”? Удалить графический стиль «{name}»? Delete Theme Удалить тему Delete theme Удалить тему diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index fcd0427a7..1b6882662 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -744,6 +744,9 @@ font-technology Variable Variabla Yes Ja No Nej Delete Tab Radera tabb + Delete the brush “{name}”? Radera penseln ”{name}”? + Delete these {count} brushes? Radera dessa {count} penslar? + Strokes using deleted brushes become plain strokes. Linjer som använder borttagna penslar blir vanliga linjer. Delete the graphic style “{name}”? Radera grafikstilen ”{name}”? Delete Theme Radera tema Delete theme Radera tema diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 7a1ea3647..d318f8a75 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -736,6 +736,9 @@ font-technology Variable Değişken Yes Evet No Hayır Delete Tab Sekmeyi Sil + Delete the brush “{name}”? “{name}” fırçası silinsin mi? + Delete these {count} brushes? Bu {count} fırça silinsin mi? + Strokes using deleted brushes become plain strokes. Silinen fırçaları kullanan konturlar düz konturlara dönüşür. Delete the graphic style “{name}”? “{name}” grafik stili silinsin mi? Delete Theme Temayı Sil Delete theme Temayı sil diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 58693ddfd..5dd9b8979 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -751,6 +751,9 @@ font-technology Variable Варіативні Yes Так No Ні Delete Tab Видалити табуляцію + Delete the brush “{name}”? Видалити пензель «{name}»? + Delete these {count} brushes? Видалити ці пензлі ({count})? + Strokes using deleted brushes become plain strokes. Обведення, що використовують видалені пензлі, стають звичайними обведеннями. Delete the graphic style “{name}”? Видалити графічний стиль «{name}»? Delete Theme Видалити тему Delete theme Видалити тему diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index aaa74d0e7..6136b2ddd 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -877,6 +877,9 @@ font-technology Variable Biến thiên Yes Có No Không Delete Tab Xóa tab + Delete the brush “{name}”? Xóa cọ “{name}”? + Delete these {count} brushes? Xóa {count} cọ này? + Strokes using deleted brushes become plain strokes. Các nét dùng cọ đã xóa trở thành nét thông thường. Delete the graphic style “{name}”? Xóa kiểu đồ họa “{name}”? Delete the library “{name}”? Xóa thư viện “{name}”? Delete Theme Xóa bộ màu diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 71b1fead1..7fbdf119b 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -700,6 +700,9 @@ Yes 是 No 否 Delete Tab 删除制表位 + Delete the brush “{name}”? 要删除画笔“{name}”吗? + Delete these {count} brushes? 要删除这 {count} 个画笔吗? + Strokes using deleted brushes become plain strokes. 使用已删除画笔的描边会变成普通描边。 Delete the graphic style “{name}”? 要删除图形样式“{name}”吗? Delete Theme 删除主题 Delete theme 删除主题 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index a89fd28cc..3cb07c146 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -719,6 +719,9 @@ font-technology Variable 可變 Yes 是 No 否 Delete Tab 刪除定位點 + Delete the brush “{name}”? 要刪除筆刷「{name}」嗎? + Delete these {count} brushes? 要刪除這 {count} 個筆刷嗎? + Strokes using deleted brushes become plain strokes. 使用已刪除筆刷的筆畫會變成一般筆畫。 Delete the graphic style “{name}”? 要刪除繪圖樣式「{name}」嗎? Delete Theme 刪除主題 Delete theme 刪除主題 diff --git a/crates/ui-egui/src/panels/brushes.rs b/crates/ui-egui/src/panels/brushes.rs index cf95c5a9f..29acddf37 100644 --- a/crates/ui-egui/src/panels/brushes.rs +++ b/crates/ui-egui/src/panels/brushes.rs @@ -204,6 +204,23 @@ fn run_on(app: &mut VectorcraftApp, id: &str, names: &[String]) { app.run(id, p).ok(); } +/// Delete `names` after asking, or immediately when the bottom button was Alt-clicked. +fn delete(app: &mut VectorcraftApp, names: &[String], now: bool) { + let params = match names { + [name] => json!({ "name": name }), + _ => json!({ "names": names }), + }; + if now { + app.run("brush.delete", params).ok(); + return; + } + let message = match names { + [name] => crate::i18n::fmt(tl!("Delete the brush “{name}”?"), &[("name", name)]), + _ => crate::i18n::fmt(tl!("Delete these {count} brushes?"), &[("count", &names.len().to_string())]), + }; + crate::dialogs::confirm::ask(app, &message, tl!("Strokes using deleted brushes become plain strokes."), "brush.delete", params); +} + /// The names in `list` in the order the panel shows them: by type in [`KINDS`] order, without the /// hidden types. fn shown<'a>(list: &'a [(String, String)], hidden: &[String]) -> Vec<&'a str> { @@ -417,7 +434,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { app.status(e); } if widgets::icon_button_enabled(ui, "trash-2", tl!("Delete Brush"), false, !targets.is_empty(), 24.0).clicked() { - run_on(app, "brush.delete", &targets); + delete(app, &targets, ui.input(|i| i.modifiers.alt)); } }); } @@ -452,7 +469,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { run_on(app, "brush.duplicate", &targets); } if menu_item(ui, tl!("Delete Brush"), !targets.is_empty(), false) { - run_on(app, "brush.delete", &targets); + delete(app, &targets, false); } if menu_item(ui, tl!("Remove Brush Stroke"), sel.is_some(), false) { app.run("brush.remove", json!({})).ok(); diff --git a/crates/ui-egui/src/panels/brushes/tests.rs b/crates/ui-egui/src/panels/brushes/tests.rs index 47fc962c6..711a60199 100644 --- a/crates/ui-egui/src/panels/brushes/tests.rs +++ b/crates/ui-egui/src/panels/brushes/tests.rs @@ -146,6 +146,23 @@ fn names(app: &mut VectorcraftApp) -> Vec { brushes(app).0.into_iter().map(|b| b.0).collect() } +fn undo_count(app: &VectorcraftApp) -> usize { + app.session.active().unwrap().history.undo.len() +} + +fn delete_button(ctx: &egui::Context, app: &mut VectorcraftApp, modifiers: egui::Modifiers, time: f64) { + let out = frame_with(ctx, app, vec![], egui::Modifiers::NONE, time, show); + let at = clickable(&out).into_iter().find(|(label, _)| label.as_deref() == Some("Delete Brush")).expect("Delete Brush button").1.center(); + click(ctx, app, at, modifiers, time + 0.5, show); +} + +fn assert_delete_dialog(app: &VectorcraftApp, message: &str) { + let d = app.ui.dialog.as_ref().expect("Delete Brush asks first"); + assert_eq!(d.kind, crate::dialogs::confirm::KIND); + assert_eq!(d.str("message"), message); + assert_eq!(d.str("detail"), "Strokes using deleted brushes become plain strokes."); +} + /// Click the panel menu's item `label` (frames at `time`, `time + 0.5`). fn menu_click(ctx: &egui::Context, app: &mut VectorcraftApp, label: &str, time: f64) { let out = frame_with(ctx, app, vec![], egui::Modifiers::NONE, time, menu); @@ -186,21 +203,23 @@ fn shift_and_command_clicks_select_brushes_for_delete_and_duplicate() { click(&ctx, &mut app, tiles[1].center(), egui::Modifiers::COMMAND, 3.0, show); let out = frame_with(&ctx, &mut app, vec![], egui::Modifiers::NONE, 3.5, show); assert_eq!(lit_tiles(&out), [0, 2]); - let undo = |app: &VectorcraftApp| app.session.active().unwrap().history.undo.len(); - let before = (names(&mut app), undo(&app)); + let before = (names(&mut app), undo_count(&app)); menu_click(&ctx, &mut app, "Duplicate Brush", 4.0); let copies = [format!("{} copy", three[0]), format!("{} copy", three[2])]; assert_eq!(names(&mut app)[before.0.len()..], copies); - assert_eq!(undo(&app), before.1 + 1); + assert_eq!(undo_count(&app), before.1 + 1); // The bottom bar's last button, Delete Brush. - let out = frame_with(&ctx, &mut app, vec![], egui::Modifiers::NONE, 5.0, show); - let trash = clickable(&out).last().unwrap().1.center(); - click(&ctx, &mut app, trash, egui::Modifiers::NONE, 6.0, show); + delete_button(&ctx, &mut app, egui::Modifiers::NONE, 5.0); + assert_delete_dialog(&app, "Delete these 2 brushes?"); + assert_eq!(names(&mut app), [before.0.clone(), copies.to_vec()].concat()); + assert_eq!(brush_of(&app, rect).as_deref(), Some(three[0].as_str())); + assert_eq!(undo_count(&app), before.1 + 1); + crate::dialogs::confirm(&mut app).unwrap(); let left = names(&mut app); assert!(!left.contains(&three[0]) && !left.contains(&three[2]) && left.contains(&three[1]), "{left:?}"); assert!(copies.iter().all(|c| left.contains(c)), "{left:?}"); assert_eq!(brush_of(&app, rect), None); - assert_eq!(undo(&app), before.1 + 2); + assert_eq!(undo_count(&app), before.1 + 2); } /// Selecting other art ends the multi-selection: selecting the first art again does not bring @@ -231,6 +250,9 @@ fn selecting_other_art_ends_the_brush_multi_selection() { let out = frame_with(&ctx, &mut app, vec![], egui::Modifiers::NONE, 4.0, show); assert_eq!(lit_tiles(&out), [1]); menu_click(&ctx, &mut app, "Delete Brush", 5.0); + assert_delete_dialog(&app, &format!("Delete the brush “{}”?", three[1])); + assert!(names(&mut app).contains(&three[1])); + crate::dialogs::confirm(&mut app).unwrap(); let left = names(&mut app); assert!(left.contains(&three[0]) && !left.contains(&three[1]) && left.contains(&three[2]), "{left:?}"); assert_eq!((brush_of(&app, a).as_deref(), brush_of(&app, c)), (Some(three[0].as_str()), None)); @@ -297,10 +319,57 @@ fn select_all_unused_selects_the_brushes_no_stroke_paints_with() { let on: Vec = (0..tiles.len()).filter(|i| lit(&ctx, &out).contains(&tiles[*i])).collect(); assert_eq!(on, (0..tiles.len()).filter(|i| *i != 1).collect::>()); menu_click(&ctx, &mut app, "Delete Brush", 3.0); + assert_delete_dialog(&app, &format!("Delete these {} brushes?", list.len() - 1)); + assert_eq!(names(&mut app).len(), list.len()); + crate::dialogs::confirm(&mut app).unwrap(); assert_eq!(names(&mut app), [list[1].0.clone()]); assert_eq!(brush_of(&app, a), Some(list[1].0.clone())); } +#[test] +fn delete_button_cancel_keeps_selected_brushes_strokes_and_history() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 100})).unwrap(); + let rect = NodeId(app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 40})).unwrap()["id"].as_u64().unwrap()); + let ctx = egui::Context::default(); + ctx.enable_accesskit(); + let (three, _) = select_three(&ctx, &mut app, rect); + let before = (names(&mut app), brush_of(&app, rect), undo_count(&app)); + delete_button(&ctx, &mut app, egui::Modifiers::NONE, 3.0); + assert_delete_dialog(&app, "Delete these 3 brushes?"); + assert_eq!((names(&mut app), brush_of(&app, rect), undo_count(&app)), before); + crate::dialogs::cancel(&mut app); + assert!(app.ui.dialog.is_none()); + assert_eq!((names(&mut app), brush_of(&app, rect), undo_count(&app)), before); + assert_eq!(before.1.as_deref(), Some(three[0].as_str())); + app.run("brush.delete", json!({"name": three[0]})).unwrap(); + assert!(app.ui.dialog.is_none()); + assert_eq!(undo_count(&app), before.2 + 1); + app.run("brush.delete", json!({"names": [three[1], three[2]]})).unwrap(); + assert!(app.ui.dialog.is_none()); + assert_eq!(undo_count(&app), before.2 + 2); +} + +#[test] +fn alt_delete_button_removes_selected_brushes_without_dialog() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 100})).unwrap(); + let rect = NodeId(app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 40})).unwrap()["id"].as_u64().unwrap()); + let ctx = egui::Context::default(); + ctx.enable_accesskit(); + let (three, _) = select_three(&ctx, &mut app, rect); + let before = undo_count(&app); + delete_button(&ctx, &mut app, egui::Modifiers::NONE, 3.0); + assert_delete_dialog(&app, "Delete these 3 brushes?"); + crate::dialogs::cancel(&mut app); + delete_button(&ctx, &mut app, egui::Modifiers::ALT, 4.0); + assert!(app.ui.dialog.is_none()); + let left = names(&mut app); + assert!(three.iter().all(|name| !left.contains(name))); + assert_eq!(brush_of(&app, rect), None); + assert_eq!(undo_count(&app), before + 1); +} + #[test] fn art_dropped_on_the_panel_becomes_an_art_brush_and_a_brush_drags_out() { let mut app = VectorcraftApp::new(Session::new(), Default::default()); diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 05985f9f1..ab9b96f4b 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -962,6 +962,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Hue Shift set by `brush.new` or `brush.options` without a key color takes the color the art's fills and strokes use most, as Brush Options already offers it, following the Brushes page, where the key color defaults to the color that stands out most in the art (). The page does not state how prominence is measured. Counting the fills and strokes that use each solid color, with the first one found winning a tie and black for art with no solid color, is VectorCraft's own rule. A `brush.options` edit that gives only the method keeps the brush's key color, and one that gives only an art brush's start guide keeps its end guide. Evidence: `tests_brushsym::hue_shift_without_a_key_takes_the_most_used_art_color`, `tests_brushsym::art_brush_options_keep_the_scale_fields_an_edit_leaves_out`. Bristle Brush Options and `brush.new` take a bristle brush name of at most 31 characters, as the Brushes page states for the Bristle Brush Options. The Name field stops taking characters at 31, and `brush.new` and `brush.options` refuse a longer new name with an error that names the limit. The page does not say how the limit counts characters or what happens to longer names already saved; counting characters and the number added to a taken name, keeping longer saved names, and leaving `brush.duplicate` unlimited are VectorCraft's own rules. A bristle brush that a document or a saved file already holds under a longer name keeps it, and the other brush types take a name of any length. Test: `bristle_brush_names_have_at_most_31_characters` in `tests/brushsym.rs`. Public reference: [Brushes](https://web.archive.org/web/20241230205225/https://helpx.adobe.com/illustrator/using/brushes.html) (archived copy). In the Brushes panel, Shift-click selects a range of brushes and Ctrl/Cmd-click adds or removes one. Delete Brush and Duplicate Brush then act on all of them (`brush.delete {names}`, `brush.duplicate {names}`). + The bottom Delete Brush button and the panel menu ask before deleting; Alt-click (Option-click on macOS) on the button deletes without asking. Document Info has a Brushes category after Graphic Styles. It lists the Brushes panel's brushes, or with Selection Only it lists the brushes that the selected objects' strokes use. The Document Info report and Package's report list them too (`document.info {category: "brushes"}`, `brushNames`). Document Info has a Repeat Objects category after Embedded Images. It lists each radial, grid and mirror Repeat object by name with its kind and the number of instances it makes. For a grid it gives the rows and columns instead, followed by the number of instances it draws when that is fewer. With Selection Only it lists the Repeat objects in the selection. The Document Info report and Package's report list them too (`document.info {category: "repeats"}`, `repeatObjects`). The Brushes panel menu's Select All Unused selects the brushes that are not used in the document, as the [Brushes](https://web.archive.org/web/20241230205225/https://helpx.adobe.com/illustrator/using/brushes.html) page (archived copy) describes. A brush counts as used when a stroke paints with it in the art (opacity masks included), in symbols, graph designs, pattern tiles or graphic styles, or in the art of a used brush (`brush.unused`).