From 757eaf9afa5b6a06573de90fa5b914b5d80b6c60 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 21 Oct 2026 17:40:07 +0000 Subject: [PATCH] Effect Gallery: plain filter list and live stack preview Remove hash-coloured thumbnails; preview the selection with the applied filter stack on the canvas (Cancel rolls back). OK commits via batch. Co-authored-by: nrq6993-a11y --- crates/ui-egui/src/dialogs/effect_gallery.rs | 171 ++++++++++++------- crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/cs.tsv | 2 + crates/ui-egui/src/i18n/da.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 2 + crates/ui-egui/src/i18n/el.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 2 + crates/ui-egui/src/i18n/fi.tsv | 2 + crates/ui-egui/src/i18n/fr.tsv | 2 + crates/ui-egui/src/i18n/hu.tsv | 2 + crates/ui-egui/src/i18n/id.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 2 + crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/ka.tsv | 9 + crates/ui-egui/src/i18n/ko.tsv | 2 + crates/ui-egui/src/i18n/nl.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 2 + crates/ui-egui/src/i18n/pt-br.tsv | 2 + crates/ui-egui/src/i18n/ru.tsv | 2 + crates/ui-egui/src/i18n/sv.tsv | 2 + crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/vi.tsv | 2 + crates/ui-egui/src/i18n/zh-hans.tsv | 2 + crates/ui-egui/src/i18n/zh-hant.tsv | 2 + 24 files changed, 161 insertions(+), 63 deletions(-) diff --git a/crates/ui-egui/src/dialogs/effect_gallery.rs b/crates/ui-egui/src/dialogs/effect_gallery.rs index 263d6a54f..c2bfd76b4 100644 --- a/crates/ui-egui/src/dialogs/effect_gallery.rs +++ b/crates/ui-egui/src/dialogs/effect_gallery.rs @@ -1,7 +1,7 @@ //! Effect → Effect Gallery: browse Photoshop-style raster filters by category, build a stack, and //! apply it as separate appearance effects in one undo step. -use egui::{Color32, Rect, Sense, Stroke}; +use egui; use serde_json::{Map, Value, json}; use vectorcraft_effects::{EffectInfo, PIXEL_EFFECTS}; @@ -23,8 +23,15 @@ const CATEGORIES: [(&str, &[&str]); 6] = [ ("Texture", &["Effect", "Texture"]), ]; -pub(super) const SPEC: DialogSpec = - DialogSpec { heading: |_| tl!("Effect Gallery").into(), body, confirm, min_width: 720.0, max_width: Some(820.0), ..DialogSpec::FORM }; +pub(super) const SPEC: DialogSpec = DialogSpec { + heading: |_| tl!("Effect Gallery").into(), + body, + confirm, + preview: true, + min_width: 720.0, + max_width: Some(820.0), + ..DialogSpec::FORM +}; #[derive(Clone)] struct StackEntry { @@ -39,7 +46,10 @@ pub fn open(app: &mut VectorcraftApp, _p: &Value) -> Result { return Err(tl!("Select artwork to apply effects").into()); } let selected = effects_in_category(0).first().map(|e| e.id).unwrap_or(""); - app.ui.dialog = Some(Dialog::new(KIND, json!({ "category": 0, "selected": selected, "stack": [], "stackSelected": 0, "stackFocus": false }))); + app.ui.dialog = Some(Dialog::new( + KIND, + json!({ "category": 0, "selected": selected, "stack": [], "stackSelected": 0, "stackFocus": false, "preview": true }), + )); Ok(Value::Null) } @@ -171,31 +181,66 @@ fn effect_param_fields(app: &mut VectorcraftApp, ui: &mut egui::Ui, effect_id: & } } -/// A small procedural thumbnail from the effect id (no copied artwork). -fn thumb(ui: &mut egui::Ui, id: &str, size: f32) { - let (rect, _) = ui.allocate_exact_size(egui::vec2(size, size), Sense::hover()); - let hash = id.bytes().fold(0u32, |h, b| h.wrapping_mul(31).wrapping_add(b as u32)); - let c1 = Color32::from_rgb((hash & 0xFF) as u8 | 40, ((hash >> 8) & 0xFF) as u8 | 40, ((hash >> 16) & 0xFF) as u8 | 40); - let c2 = Color32::from_rgb(((hash >> 4) & 0xFF) as u8 | 20, ((hash >> 12) & 0xFF) as u8 | 20, ((hash >> 20) & 0xFF) as u8 | 20); - let painter = ui.painter(); - let steps = 6; - for i in 0..steps { - let t0 = i as f32 / steps as f32; - let t1 = (i + 1) as f32 / steps as f32; - let mix = |t: f32| { - let u = t; - Color32::from_rgba_unmultiplied( - (c1.r() as f32 * (1.0 - u) + c2.r() as f32 * u) as u8, - (c1.g() as f32 * (1.0 - u) + c2.g() as f32 * u) as u8, - (c1.b() as f32 * (1.0 - u) + c2.b() as f32 * u) as u8, - 255, - ) - }; - let band = - Rect::from_min_max(egui::pos2(rect.left() + rect.width() * t0, rect.top()), egui::pos2(rect.left() + rect.width() * t1, rect.bottom())); - painter.rect_filled(band, 0.0, mix((t0 + t1) * 0.5)); +fn stack_batch_commands(stack: &[StackEntry]) -> Vec { + let mut commands = Vec::with_capacity(stack.len() * 2); + for (i, entry) in stack.iter().enumerate() { + let params = Value::Object(entry.params.clone()); + commands.push(json!({ "command": "effect.apply", "params": { "effect": entry.effect, "params": params } })); + if !entry.visible { + commands.push(json!({ "command": "effect.setParams", "params": { "index": i, "visible": false } })); + } + } + commands +} + +fn stack_batch_params(stack: &[StackEntry]) -> Value { + json!({ "label": "Effect Gallery", "commands": stack_batch_commands(stack) }) +} + +/// Live canvas preview of the stack (`command.batch` would settle the dialog interaction each frame). +fn preview_stack_on_canvas(app: &mut VectorcraftApp, stack: &[StackEntry]) { + if app.session.in_interaction() { + let _ = app.session.cancel_interaction(); + } + let _ = app.session.begin_interaction("Effect Gallery"); + for (i, entry) in stack.iter().enumerate() { + let params = Value::Object(entry.params.clone()); + if app.run("effect.apply", json!({ "effect": entry.effect, "params": params })).is_err() { + let _ = app.session.cancel_interaction(); + return; + } + if !entry.visible && app.run("effect.setParams", json!({ "index": i, "visible": false })).is_err() { + let _ = app.session.cancel_interaction(); + return; + } + } +} + +fn stack_preview_row(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, stack: &[StackEntry]) { + const LAST: &str = super::form::PREVIEWED; + ui.add_space(8.0); + let mut on = d.bool("preview"); + if widgets::check(ui, tl!("Preview"), on, true) { + on = !on; + d.fields.insert("preview".into(), json!(on)); + } + if !on { + if d.fields.remove(LAST).is_some() { + let _ = app.session.cancel_interaction(); + } + return; + } + if stack.is_empty() { + if d.fields.remove(LAST).is_some() { + let _ = app.session.cancel_interaction(); + } + return; + } + let batch = stack_batch_params(stack); + if d.fields.get(LAST) != Some(&batch) || !app.session.in_interaction() { + preview_stack_on_canvas(app, stack); + d.fields.insert(LAST.into(), batch); } - painter.rect_stroke(rect, 0.0, Stroke::new(1.0, Tokens::get(ui.ctx()).input_border), egui::StrokeKind::Inside); } fn effect_label(id: &str) -> String { @@ -251,21 +296,18 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { egui::ScrollArea::vertical().max_height(220.0).show(ui, |ui| { for e in &effects { let picked = e.id == sel; - ui.horizontal(|ui| { - thumb(ui, e.id, 28.0); - let name = tl!(e.label).trim_end_matches('…').to_string(); - let resp = ui.add(egui::Button::selectable(picked, name)); - if resp.clicked() { - set_selected(d, e.id); - set_stack_focus(d, false); - } - if resp.double_clicked() { - set_selected(d, e.id); - set_stack_focus(d, false); - add_selected(d, e.id); - stack = stack_of(d); - } - }); + let name = tl!(e.label).trim_end_matches('…').to_string(); + let resp = ui.add(egui::Button::selectable(picked, name)); + if resp.clicked() { + set_selected(d, e.id); + set_stack_focus(d, false); + } + if resp.double_clicked() { + set_selected(d, e.id); + set_stack_focus(d, false); + add_selected(d, e.id); + stack = stack_of(d); + } } }); if widgets::flat_button(ui, tl!("Add to Stack"), 120.0).clicked() && !sel.is_empty() { @@ -273,18 +315,6 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { stack = stack_of(d); } }); - ui.add_space(8.0); - ui.vertical(|ui| { - widgets::subheader(ui, tl!("Preview")); - ui.set_width(200.0); - let (param_effect, _) = param_target(d, &stack, stack_sel, &sel); - if !param_effect.is_empty() { - thumb(ui, ¶m_effect, 160.0); - ui.label(egui::RichText::new(effect_label(¶m_effect)).color(t.text_strong)); - } else { - ui.label(egui::RichText::new(tl!("Choose a filter")).color(t.text_dim)); - } - }); }); ui.add_space(10.0); @@ -353,6 +383,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { } write_stack(d, &stack); d.fields.insert("stackSelected".into(), json!(stack_sel)); + stack_preview_row(app, ui, d, &stack); false } @@ -369,18 +400,17 @@ fn add_selected(d: &mut Dialog, id: &str) { fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { let stack = stack_of(d); if stack.is_empty() { + if app.session.in_interaction() { + let _ = app.session.cancel_interaction(); + } app.ui.dialog = None; return Ok(Value::Null); } - let mut commands = Vec::with_capacity(stack.len() * 2); - for (i, entry) in stack.iter().enumerate() { - let params = Value::Object(entry.params.clone()); - commands.push(json!({ "command": "effect.apply", "params": { "effect": entry.effect, "params": params } })); - if !entry.visible { - commands.push(json!({ "command": "effect.setParams", "params": { "index": i, "visible": false } })); - } + let batch = stack_batch_params(&stack); + if d.bool("preview") && app.session.in_interaction() { + return super::form::commit_preview(app, "command.batch", batch); } - let r = app.run("command.batch", json!({ "label": "Effect Gallery", "commands": commands })); + let r = app.run("command.batch", batch); if r.is_ok() { app.ui.dialog = None; } @@ -403,6 +433,21 @@ mod tests { out.textures_delta.clear(); } + #[test] + fn stack_preview_is_one_interaction() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 200})).unwrap(); + let id = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 40, "height": 40})).unwrap()["id"].as_u64().unwrap(); + app.run("ui.effectGalleryDialog", json!({})).unwrap(); + let d = app.ui.dialog.as_mut().unwrap(); + d.fields.insert("stack".into(), json!([{ "effect": "artistic.cutout", "visible": true, "params": { "levels": 3 } }])); + frame(&mut app); + assert!(app.session.in_interaction(), "the stack previews on the canvas"); + crate::dialogs::cancel(&mut app); + assert!(!app.session.in_interaction()); + assert!(app.session.active().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().appearance.effects.is_empty()); + } + #[test] fn stack_ok_is_one_undo_step() { let mut app = VectorcraftApp::new(Session::new(), Default::default()); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index f8ab8ca23..0ab57b7a3 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -5351,3 +5351,5 @@ theme Appearance Външен вид Shift: Изместване: Fresco… Фреска… Fresco Фреска + Effect Gallery… Галерия с ефекти… + Effect Gallery Dialog Диалог „Галерия с ефекти“ diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 0124a1f20..54f38c94b 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -5394,3 +5394,5 @@ mark-style Japanese Japonské Shift: Posun: Fresco… Freska… Fresco Freska + Effect Gallery… Galerie efektů… + Effect Gallery Dialog Dialog Galerie efektů diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 047f7b073..14bdad93a 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5367,3 +5367,5 @@ swatch Bright Violet Kraftig violet Shift: Forskydning: Fresco… Fresko… Fresco Fresko + Effect Gallery… Effektgalleri… + Effect Gallery Dialog Dialogboksen Effektgalleri diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 8fca8c3e9..6aacd6e3c 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4975,3 +4975,5 @@ effect Soft Weich Shift: Verschiebung: Fresco… Fresko… Fresco Fresko + Effect Gallery… Effekt-Galerie… + Effect Gallery Dialog Dialogfeld „Effekt-Galerie“ diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index c97154f1f..2ea3887d3 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5346,3 +5346,5 @@ effect Points Σημεία Shift: Μετατόπιση: Fresco… Νωπογραφία… Fresco Νωπογραφία + Effect Gallery… Συλλογή εφέ… + Effect Gallery Dialog Παράθυρο διαλόγου Συλλογή εφέ diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index a5d9eae6d..00a483a19 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5336,3 +5336,5 @@ mark-style Japanese Japonesas Shift: Desplazamiento: Fresco… Fresco… Fresco Fresco + Effect Gallery… Galería de efectos… + Effect Gallery Dialog Cuadro de diálogo Galería de efectos diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 88f14d09b..19db20bff 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -5425,3 +5425,5 @@ swatch Bright Violet Kirkas violetti Shift: Siirtymä: Fresco… Fresko… Fresco Fresko + Effect Gallery… Tehostegalleria… + Effect Gallery Dialog Tehostegalleria-valintaikkuna diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 69cd9ab56..a1abcc6ba 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -5393,3 +5393,5 @@ theme Appearance Apparence Shift: Décalage : Fresco… Fresque… Fresco Fresque + Effect Gallery… Galerie d'effets… + Effect Gallery Dialog Boîte de dialogue Galerie d'effets diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 992560a4d..448ee605f 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -5380,3 +5380,5 @@ imageTrace Outline Körvonal Shift: Eltolás: Fresco… Freskó… Fresco Freskó + Effect Gallery… Effektusgaléria… + Effect Gallery Dialog Effektusgaléria párbeszédpanel diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 230b914f0..917c48816 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5367,3 +5367,5 @@ swatch Bright Violet Violet cerah Shift: Pergeseran: Fresco… Fresko… Fresco Fresko + Effect Gallery… Galeri Efek… + Effect Gallery Dialog Dialog Galeri Efek diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 14824bafd..ac59807ef 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5009,3 +5009,5 @@ lighting Light Luce Shift: Spostamento: Fresco… Affresco… Fresco Affresco + Effect Gallery… Galleria effetti… + Effect Gallery Dialog Finestra di dialogo Galleria effetti diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 21709cca2..fd68fd1f9 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5056,3 +5056,5 @@ lighting Light ライト Shift: シフト: Fresco… フレスコ… Fresco フレスコ + Effect Gallery… 効果ギャラリー… + Effect Gallery Dialog 効果ギャラリーダイアログ diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index abebbb73d..db1915b3b 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5296,3 +5296,12 @@ imageTrace Outline კონტური Shift: წანაცვლება: Fresco… ფრესკა… Fresco ფრესკა + Effect Gallery ეფექტების გალერეა + Add filters from the list above დაამატეთ ფილტრები ზემოთ მოცემული სიიდან + Add to Stack დასტაში დამატება + Applied Filters გამოყენებული ფილტრები + Categories კატეგორიები + Filters ფილტრები + Select artwork to apply effects აირჩიეთ ნამუშევარი ეფექტების გამოსაყენებლად + Effect Gallery… ეფექტების გალერეა… + Effect Gallery Dialog ეფექტების გალერეა დიალოგი diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 71b16eb8c..2337fad72 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4972,3 +4972,5 @@ lighting Light 광원 Shift: 이동: Fresco… 프레스코… Fresco 프레스코 + Effect Gallery… 효과 갤러리… + Effect Gallery Dialog 효과 갤러리 대화 상자 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 452a7d650..a0040b531 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5409,3 +5409,5 @@ swatch Bright Violet Felviolet Shift: Verschuiving: Fresco… Fresco… Fresco Fresco + Effect Gallery… Effectgalerij… + Effect Gallery Dialog Dialoogvenster Effectgalerij diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 16e2a9cfc..761bb0dae 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5246,3 +5246,5 @@ mark-style Japanese Japońskie Shift: Przesunięcie: Fresco… Fresk… Fresco Fresk + Effect Gallery… Galeria efektów… + Effect Gallery Dialog Okno dialogowe Galeria efektów diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index c54d1ad32..e40d6801b 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5311,3 +5311,5 @@ mark-style Japanese Japonesas Shift: Deslocamento: Fresco… Afresco… Fresco Afresco + Effect Gallery… Galeria de Efeitos… + Effect Gallery Dialog Caixa de diálogo Galeria de Efeitos diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 62d504105..2925e2fd8 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -5005,3 +5005,5 @@ lighting Light Свет Shift: Сдвиг: Fresco… Фреска… Fresco Фреска + Effect Gallery… Галерея эффектов… + Effect Gallery Dialog Диалог «Галерея эффектов» diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 6d0b1c417..2ba5a4201 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -5350,3 +5350,5 @@ theme Appearance Utseende Shift: Förskjutning: Fresco… Fresk… Fresco Fresk + Effect Gallery… Effektgalleri… + Effect Gallery Dialog Dialogrutan Effektgalleri diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index cb4a2e80a..e9fea9f52 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4989,3 +4989,5 @@ lighting Light Світло Shift: Зсув: Fresco… Фреска… Fresco Фреска + Effect Gallery… Галерея ефектів… + Effect Gallery Dialog Діалог «Галерея ефектів» diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index bef27182b..1858de13b 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4989,3 +4989,5 @@ theme Appearance Giao diện Shift: Dịch chuyển: Fresco… Bích họa… Fresco Bích họa + Effect Gallery… Thư viện hiệu ứng… + Effect Gallery Dialog Hộp thoại Thư viện hiệu ứng diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 95f9fd49c..849044e52 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5233,3 +5233,5 @@ lighting Light 光源 Shift: 偏移: Fresco… 壁画… Fresco 壁画 + Effect Gallery… 效果库… + Effect Gallery Dialog 效果库对话框 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 8654c00c8..05ac2082a 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5028,3 +5028,5 @@ lighting Light 光源 Shift: 偏移: Fresco… 壁畫… Fresco 壁畫 + Effect Gallery… 效果庫… + Effect Gallery Dialog 效果庫對話框