Merge pull request #2883 from nrq6993-a11y/stream/vc-feat-2434-effect-gallery

ui: Effect Gallery dialog (browse raster filters and apply a stack)
This commit is contained in:
BFlat
2026-10-22 07:13:32 +07:00
committed by GitHub
26 changed files with 718 additions and 1 deletions
@@ -0,0 +1,478 @@
//! 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;
use serde_json::{Map, Value, json};
use vectorcraft_effects::{EffectInfo, PIXEL_EFFECTS};
use super::{DialogSpec, effect, form};
use crate::state::Dialog;
use crate::theme::Tokens;
use crate::{VectorcraftApp, widgets};
/// Dialog kind for the Effect Gallery.
pub const KIND: &str = "effectGallery";
/// Raster effect submenus in the gallery (the six Photoshop-style folders it holds).
const CATEGORIES: [(&str, &[&str]); 6] = [
("Artistic", &["Effect", "Artistic"]),
("Brush Strokes", &["Effect", "Brush Strokes"]),
("Distort", &["Effect", "Distort"]),
("Sketch", &["Effect", "Sketch"]),
("Stylize", &["Effect", "Stylize"]),
("Texture", &["Effect", "Texture"]),
];
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 {
effect: String,
visible: bool,
params: Map<String, Value>,
}
/// Open the gallery on the current selection.
pub fn open(app: &mut VectorcraftApp, _p: &Value) -> Result<Value, String> {
if !app.session.active().is_some_and(|s| !s.selection.is_empty()) {
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, "preview": true }),
));
Ok(Value::Null)
}
fn effects_in_category(cat: usize) -> Vec<EffectInfo> {
let path = CATEGORIES.get(cat).map(|(_, p)| *p).unwrap_or(CATEGORIES[0].1);
let mut out: Vec<EffectInfo> =
vectorcraft_effects::effect_catalog().into_iter().filter(|e| e.menu == path && PIXEL_EFFECTS.contains(&e.id)).collect();
out.sort_by(|a, b| a.label.cmp(b.label));
out
}
fn default_params(id: &str) -> Map<String, Value> {
match vectorcraft_effects::effect_info(id).map(|e| e.defaults) {
Some(Value::Object(m)) => m,
_ => Map::new(),
}
}
fn merged_params(id: &str, stored: &Map<String, Value>) -> Map<String, Value> {
match vectorcraft_effects::merged_params(id, &Value::Object(stored.clone())) {
Value::Object(m) => m,
_ => default_params(id),
}
}
fn stack_of(d: &Dialog) -> Vec<StackEntry> {
d.fields
.get("stack")
.and_then(Value::as_array)
.map(|rows| {
rows.iter()
.filter_map(|row| {
let effect = row.get("effect").and_then(Value::as_str)?;
let visible = row.get("visible").and_then(Value::as_bool).unwrap_or(true);
let params = row
.get("params")
.and_then(Value::as_object)
.cloned()
.map(|m| merged_params(effect, &m))
.unwrap_or_else(|| default_params(effect));
Some(StackEntry { effect: effect.to_string(), visible, params })
})
.collect()
})
.unwrap_or_default()
}
fn write_stack(d: &mut Dialog, stack: &[StackEntry]) {
let rows: Vec<Value> = stack.iter().map(|e| json!({ "effect": e.effect, "visible": e.visible, "params": e.params })).collect();
d.fields.insert("stack".into(), Value::Array(rows));
}
fn category_ix(d: &Dialog) -> usize {
d.fields.get("category").and_then(Value::as_u64).unwrap_or(0) as usize
}
fn selected_id(d: &Dialog) -> String {
d.str("selected")
}
fn set_selected(d: &mut Dialog, id: &str) {
d.fields.insert("selected".into(), json!(id));
}
fn stack_focus(d: &Dialog) -> bool {
d.fields.get("stackFocus").and_then(Value::as_bool).unwrap_or(false)
}
fn set_stack_focus(d: &mut Dialog, on: bool) {
d.fields.insert("stackFocus".into(), json!(on));
}
fn browse_params(d: &Dialog) -> Map<String, Value> {
let effect = d.fields.get("browseEffect").and_then(Value::as_str).unwrap_or("");
let stored = d.fields.get("browseParams").and_then(Value::as_object).cloned().unwrap_or_default();
if effect.is_empty() { stored } else { merged_params(effect, &stored) }
}
fn set_browse_params(d: &mut Dialog, effect: &str, params: Map<String, Value>) {
d.fields.insert("browseEffect".into(), json!(effect));
d.fields.insert("browseParams".into(), Value::Object(params));
}
/// The effect whose parameters the panel edits, and its values.
fn param_target(d: &Dialog, stack: &[StackEntry], stack_sel: usize, browse: &str) -> (String, Map<String, Value>) {
if stack_focus(d) {
let entry = stack.get(stack_sel);
if let Some(e) = entry {
return (e.effect.clone(), e.params.clone());
}
}
let id = browse.to_string();
let params = if d.fields.get("browseEffect").and_then(Value::as_str) == Some(browse) { browse_params(d) } else { default_params(browse) };
(id, params)
}
fn write_param_target(d: &mut Dialog, stack: &mut [StackEntry], stack_sel: usize, browse: &str, effect: &str, params: Map<String, Value>) {
if stack_focus(d) {
if let Some(e) = stack.get_mut(stack_sel)
&& e.effect == effect
{
e.params = params;
}
return;
}
if browse == effect {
set_browse_params(d, effect, params);
}
}
/// Reuse the live effect dialog's fields for `effect_id` and `params`.
fn effect_param_fields(app: &mut VectorcraftApp, ui: &mut egui::Ui, effect_id: &str, params: &mut Map<String, Value>) {
if effect_id.is_empty() {
return;
}
let label =
vectorcraft_effects::effect_info(effect_id).map(|e| e.label.trim_end_matches('…').to_string()).unwrap_or_else(|| effect_id.to_string());
let mut fields = params.clone();
fields.insert("__effect".into(), json!(effect_id));
fields.insert("__label".into(), json!(label));
fields.insert("preview".into(), json!(false));
let mut temp = Dialog::new("effect", Value::Object(fields));
(effect::SPEC.body)(app, ui, &mut temp);
if let Value::Object(m) = form::params(&temp) {
*params = m;
}
if let Value::Object(m) = vectorcraft_effects::upgrade_params(effect_id, &Value::Object(params.clone())) {
*params = m;
}
}
fn stack_batch_commands(stack: &[StackEntry]) -> Vec<Value> {
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);
}
}
fn effect_label(id: &str) -> String {
vectorcraft_effects::effect_info(id).map(|e| tl!(e.label).trim_end_matches('…').to_string()).unwrap_or_else(|| id.to_string())
}
fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
let t = Tokens::get(ui.ctx());
let cat = category_ix(d);
let effects = effects_in_category(cat);
let mut sel = selected_id(d);
if !effects.iter().any(|e| e.id == sel) {
sel = effects.first().map(|e| e.id.to_string()).unwrap_or_default();
set_selected(d, &sel);
}
let mut stack = stack_of(d);
let mut stack_sel = d.fields.get("stackSelected").and_then(Value::as_u64).unwrap_or(0) as usize;
if stack_sel >= stack.len() && !stack.is_empty() {
stack_sel = stack.len() - 1;
}
let (param_effect, mut param_values) = param_target(d, &stack, stack_sel, &sel);
if !param_effect.is_empty() {
widgets::subheader(ui, tl!("Settings"));
egui::ScrollArea::vertical().max_height(200.0).show(ui, |ui| {
effect_param_fields(app, ui, &param_effect, &mut param_values);
});
write_param_target(d, &mut stack, stack_sel, &sel, &param_effect, param_values);
}
ui.horizontal_top(|ui| {
ui.vertical(|ui| {
widgets::subheader(ui, tl!("Categories"));
ui.set_width(120.0);
for (i, (name, _)) in CATEGORIES.iter().enumerate() {
let count = effects_in_category(i).len();
if count == 0 {
continue;
}
let label = tl!(name);
if ui.add(egui::Button::selectable(cat == i, label)).clicked() {
d.fields.insert("category".into(), json!(i));
let first = effects_in_category(i).first().map(|e| e.id).unwrap_or("");
set_selected(d, first);
set_stack_focus(d, false);
}
}
});
ui.add_space(8.0);
ui.vertical(|ui| {
widgets::subheader(ui, tl!("Filters"));
ui.set_width(220.0);
egui::ScrollArea::vertical().max_height(220.0).show(ui, |ui| {
for e in &effects {
let picked = e.id == sel;
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() {
add_selected(d, &sel);
stack = stack_of(d);
}
});
});
ui.add_space(10.0);
widgets::subheader(ui, tl!("Applied Filters"));
let mut stack_sel = d.fields.get("stackSelected").and_then(Value::as_u64).unwrap_or(0) as usize;
if stack_sel >= stack.len() && !stack.is_empty() {
stack_sel = stack.len() - 1;
}
enum StackOp {
Toggle(usize),
Up(usize),
Down(usize),
Remove(usize),
Select(usize),
}
let mut op = None;
let stack_len = stack.len();
egui::ScrollArea::vertical().max_height(120.0).show(ui, |ui| {
if stack.is_empty() {
ui.label(egui::RichText::new(tl!("Add filters from the list above")).color(t.text_dim));
return;
}
for (i, entry) in stack.iter().enumerate().take(stack_len) {
ui.horizontal(|ui| {
let eye = if entry.visible { "eye" } else { "eye-off" };
if widgets::icon_button(ui, eye, tl!("Click to toggle visibility"), false, 22.0).clicked() {
op = Some(StackOp::Toggle(i));
}
let name = effect_label(&entry.effect);
if ui.add(egui::Button::selectable(stack_sel == i, name)).clicked() {
op = Some(StackOp::Select(i));
}
if widgets::icon_button_enabled(ui, "dc-arrow-up", tl!("Move Up"), false, i > 0, 22.0).clicked() {
op = Some(StackOp::Up(i));
}
if widgets::icon_button_enabled(ui, "dc-arrow-down", tl!("Move Down"), false, i + 1 < stack_len, 22.0).clicked() {
op = Some(StackOp::Down(i));
}
if widgets::flat_button(ui, tl!("Remove"), 64.0).clicked() {
op = Some(StackOp::Remove(i));
}
});
}
});
match op {
Some(StackOp::Toggle(i)) if stack.get(i).is_some() => stack[i].visible = !stack[i].visible,
Some(StackOp::Select(i)) => {
stack_sel = i;
set_stack_focus(d, true);
}
Some(StackOp::Up(i)) if i > 0 => {
stack.swap(i, i - 1);
stack_sel = i - 1;
}
Some(StackOp::Down(i)) if i + 1 < stack.len() => {
stack.swap(i, i + 1);
stack_sel = i + 1;
}
Some(StackOp::Remove(i)) if i < stack.len() => {
stack.remove(i);
if stack_sel >= stack.len() && !stack.is_empty() {
stack_sel = stack.len() - 1;
}
}
_ => {}
}
write_stack(d, &stack);
d.fields.insert("stackSelected".into(), json!(stack_sel));
stack_preview_row(app, ui, d, &stack);
false
}
fn add_selected(d: &mut Dialog, id: &str) {
let params =
if !stack_focus(d) && d.fields.get("browseEffect").and_then(Value::as_str) == Some(id) { browse_params(d) } else { default_params(id) };
let mut stack = stack_of(d);
stack.push(StackEntry { effect: id.to_string(), visible: true, params });
write_stack(d, &stack);
d.fields.insert("stackSelected".into(), json!(stack.len().saturating_sub(1)));
set_stack_focus(d, true);
}
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
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 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", batch);
if r.is_ok() {
app.ui.dialog = None;
}
r
}
#[cfg(test)]
mod tests {
use serde_json::json;
use vectorcraft_engine::Session;
use crate::VectorcraftApp;
fn frame(app: &mut VectorcraftApp) {
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
crate::theme::apply(&ctx, Default::default());
let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1200.0, 900.0))), ..Default::default() };
let mut out = ctx.run_ui(raw, |ui| crate::dialogs::show(app, ui.ctx()));
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());
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();
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(super::KIND));
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert(
"stack".into(),
json!([
{ "effect": "artistic.cutout", "visible": true, "params": { "levels": 3 } },
{ "effect": "stylize.glowingEdges", "visible": false, "params": { "edgeWidth": 2 } }
]),
);
frame(&mut app);
let steps = app.session.active().unwrap().history.undo.len();
crate::dialogs::confirm(&mut app).unwrap();
let st = app.session.active().unwrap();
let n = st.doc.node(vectorcraft_doc::NodeId(id)).unwrap();
assert_eq!(n.appearance.effects.len(), 2);
assert_eq!(n.appearance.effects[0].id, "artistic.cutout");
assert_eq!(n.appearance.effects[0].params.get("levels").and_then(|v| v.as_i64()), Some(3));
assert_eq!(n.appearance.effects[1].id, "stylize.glowingEdges");
assert!(!n.appearance.effects[1].visible);
assert_eq!(st.history.undo.len(), steps + 1);
}
}
+2
View File
@@ -24,6 +24,7 @@ pub mod dxf_import;
pub mod dxf_options;
pub mod edit_selection;
mod effect;
pub mod effect_gallery;
pub mod envelope;
pub mod eps_options;
pub mod expand;
@@ -301,6 +302,7 @@ registry! {
SwatchConflict: [swatch_conflict::KIND] => swatch_conflict::SPEC,
FileInfo: [file_info::KIND] => file_info::SPEC,
RasterEffectsSettings: [raster_effects::KIND] => raster_effects::SPEC,
EffectGallery: [effect_gallery::KIND] => effect_gallery::SPEC,
MissingLinks: [missing_links::KIND] => missing_links::SPEC,
MissingFonts: [missing_fonts::KIND] => missing_fonts::SPEC,
FindLinks: [find_links::KIND] => find_links::SPEC,
+10
View File
@@ -5310,6 +5310,14 @@ theme Appearance Външен вид
Full Justify Пълно двустранно
Minimum must not exceed Desired, nor Desired Maximum. Минималната стойност не може да надвишава желаната, нито желаната – максималната.
Justification Подравняване
Add filters from the list above Добавете филтри от списъка по-горе
Add to Stack Добавяне към стека
Applied Filters Приложени филтри
Categories Категории
Choose a filter Изберете филтър
Effect Gallery Галерия с ефекти
Filters Филтри
Select artwork to apply effects Изберете графика, за да приложите ефекти
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them променливите и наборите от данни на файла не бяха прочетени (променливи: {variables}, набори от данни: {data_sets}): панелът „Променливи“ не ги показва
# Select by Color (select.color)
Select by Color Избиране по цвят
@@ -5343,3 +5351,5 @@ theme Appearance Външен вид
Shift: Изместване:
Fresco… Фреска…
Fresco Фреска
Effect Gallery… Галерия с ефекти…
Effect Gallery Dialog Диалог „Галерия с ефекти“
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5353,6 +5353,14 @@ mark-style Japanese Japonské
Full Justify Zarovnat do bloku
Minimum must not exceed Desired, nor Desired Maximum. Minimum nesmí překročit požadovanou hodnotu ani požadovaná hodnota maximum.
Justification Zarovnání do bloku
Add filters from the list above Přidejte filtry ze seznamu výše
Add to Stack Přidat na zásobník
Applied Filters Použité filtry
Categories Kategorie
Choose a filter Vyberte filtr
Effect Gallery Galerie efektů
Filters Filtry
Select artwork to apply effects Vyberte grafiku, na kterou chcete použít efekty
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them proměnné a sady dat souboru nebyly načteny (proměnné: {variables}, sady dat: {data_sets}): panel Proměnné je nezobrazuje
# Select by Color (select.color)
Select by Color Vybrat podle barvy
@@ -5386,3 +5394,5 @@ mark-style Japanese Japonské
Shift: Posun:
Fresco… Freska…
Fresco Freska
Effect Gallery… Galerie efektů…
Effect Gallery Dialog Dialog Galerie efektů
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5306,6 +5306,14 @@ swatch Bright Violet Kraftig violet
Full Justify Lige margener
Minimum must not exceed Desired, nor Desired Maximum. Minimum må ikke overstige Ønsket, og Ønsket ikke Maksimum.
Justification Justering
Add filters from the list above Tilføj filtre fra listen ovenfor
Add to Stack Tilføj til stak
Applied Filters Anvendte filtre
Categories Kategorier
Choose a filter Vælg et filter
Effect Gallery Effektgalleri
Filters Filtre
Select artwork to apply effects Vælg grafik for at anvende effekter
# Select by Color (select.color)
Select by Color Markér efter farve
Smudge Stick… Udtværingsstift…
@@ -5359,3 +5367,5 @@ swatch Bright Violet Kraftig violet
Shift: Forskydning:
Fresco… Fresko…
Fresco Fresko
Effect Gallery… Effektgalleri…
Effect Gallery Dialog Dialogboksen Effektgalleri
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -4934,6 +4934,14 @@ effect Soft Weich
Minimum must not exceed Desired, nor Desired Maximum. Das Minimum darf den optimalen Wert nicht übersteigen, der optimale Wert nicht das Maximum.
Minimum Minimum
Justification Abstände
Add filters from the list above Filter aus der Liste oben hinzufügen
Add to Stack Zum Stapel hinzufügen
Applied Filters Angewendete Filter
Categories Kategorien
Choose a filter Filter wählen
Effect Gallery Effekt-Galerie
Filters Filter
Select artwork to apply effects Wählen Sie Grafik aus, um Effekte anzuwenden
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them die Variablen und Datensätze der Datei wurden nicht gelesen (Variablen: {variables}, Datensätze: {data_sets}): Das Bedienfeld „Variablen“ listet sie nicht auf
# Select by Color (select.color)
Select by Color Nach Farbe auswählen
@@ -4967,3 +4975,5 @@ effect Soft Weich
Shift: Verschiebung:
Fresco… Fresko…
Fresco Fresko
Effect Gallery… Effekt-Galerie…
Effect Gallery Dialog Dialogfeld „Effekt-Galerie“
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5305,6 +5305,14 @@ effect Points Σημεία
Full Justify Στοίχιση και στα δύο άκρα
Minimum must not exceed Desired, nor Desired Maximum. Το ελάχιστο δεν πρέπει να υπερβαίνει το επιθυμητό, ούτε το επιθυμητό το μέγιστο.
Justification Πλήρης στοίχιση
Add filters from the list above Προσθέστε φίλτρα από τη λίστα παραπάνω
Add to Stack Προσθήκη στη στοίβα
Applied Filters Εφαρμοσμένα φίλτρα
Categories Κατηγορίες
Choose a filter Επιλέξτε φίλτρο
Effect Gallery Συλλογή εφέ
Filters Φίλτρα
Select artwork to apply effects Επιλέξτε γραφικά για να εφαρμόσετε εφέ
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them οι μεταβλητές και τα σύνολα δεδομένων του αρχείου δεν διαβάστηκαν (μεταβλητές: {variables}, σύνολα δεδομένων: {data_sets}): ο πίνακας Μεταβλητές δεν τα εμφανίζει
# Select by Color (select.color)
Select by Color Επιλογή βάσει χρώματος
@@ -5338,3 +5346,5 @@ effect Points Σημεία
Shift: Μετατόπιση:
Fresco… Νωπογραφία…
Fresco Νωπογραφία
Effect Gallery… Συλλογή εφέ…
Effect Gallery Dialog Παράθυρο διαλόγου Συλλογή εφέ
Can't render this file because it contains an unexpected character in line 2040 and column 19.
+10
View File
@@ -5295,6 +5295,14 @@ mark-style Japanese Japonesas
Full Justify Justificar completamente
Minimum must not exceed Desired, nor Desired Maximum. El mínimo no puede superar el deseado, ni el deseado el máximo.
Justification Justificación
Add filters from the list above Añada filtros de la lista de arriba
Add to Stack Añadir a la pila
Applied Filters Filtros aplicados
Categories Categorías
Choose a filter Elija un filtro
Effect Gallery Galería de efectos
Filters Filtros
Select artwork to apply effects Seleccione gráficos para aplicar efectos
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them no se leyeron las variables ni los conjuntos de datos del archivo (variables: {variables}, conjuntos de datos: {data_sets}): el panel Variables no los muestra
# Select by Color (select.color)
Select by Color Seleccionar por color
@@ -5328,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5317,6 +5317,14 @@ mark-style Japanese Japanilainen
Full Justify Täysi tasaus
Minimum must not exceed Desired, nor Desired Maximum. Minimi ei saa ylittää haluttua arvoa eikä haluttu arvo maksimia.
Justification Tasaus
Add filters from the list above Lisää suodattimia yllä olevasta luettelosta
Add to Stack Lisää pinoon
Applied Filters Käytetyt suodattimet
Categories Luokat
Choose a filter Valitse suodatin
Effect Gallery Tehostegalleria
Filters Suodattimet
Select artwork to apply effects Valitse grafiikka, johon tehosteet otetaan
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them tiedoston muuttujia ja tietojoukkoja ei luettu (muuttujia: {variables}, tietojoukkoja: {data_sets}): Muuttujat-paneeli ei näytä niitä
# Select by Color (select.color)
Select by Color Valitse värin mukaan
@@ -5417,3 +5425,5 @@ swatch Bright Violet Kirkas violetti
Shift: Siirtymä:
Fresco… Fresko…
Fresco Fresko
Effect Gallery… Tehostegalleria…
Effect Gallery Dialog Tehostegalleria-valintaikkuna
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5352,6 +5352,14 @@ theme Appearance Apparence
Full Justify Justification complète
Minimum must not exceed Desired, nor Desired Maximum. Le minimum ne doit pas dépasser la valeur optimale, ni la valeur optimale le maximum.
Justification Justification
Add filters from the list above Ajoutez des filtres à partir de la liste ci-dessus
Add to Stack Ajouter à la pile
Applied Filters Filtres appliqués
Categories Catégories
Choose a filter Choisir un filtre
Effect Gallery Galerie d'effets
Filters Filtres
Select artwork to apply effects Sélectionnez des éléments graphiques pour appliquer des effets
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them les variables et jeux de données du fichier n’ont pas été lus (variables : {variables}, jeux de données : {data_sets}) : le panneau Variables ne les affiche pas
# Select by Color (select.color)
Select by Color Sélectionner par couleur
@@ -5385,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5339,6 +5339,14 @@ imageTrace Outline Körvonal
Full Justify Teljes sorkizárás
Minimum must not exceed Desired, nor Desired Maximum. A minimum nem haladhatja meg a kívánt értéket, sem a kívánt érték a maximumot.
Justification Sorkizárás
Add filters from the list above Adjon hozzá szűrőket a fenti listából
Add to Stack Hozzáadás a veremhez
Applied Filters Alkalmazott szűrők
Categories Kategóriák
Choose a filter Válasszon szűrőt
Effect Gallery Effektusgaléria
Filters Szűrők
Select artwork to apply effects Válasszon ki grafikát az effektusok alkalmazásához
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them a fájl változói és adatkészletei nem lettek beolvasva (változók: {variables}, adatkészletek: {data_sets}): a Változók panel nem listázza őket
# Select by Color (select.color)
Select by Color Kijelölés szín szerint
@@ -5372,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5306,6 +5306,14 @@ swatch Bright Violet Violet cerah
Full Justify Rata Penuh
Minimum must not exceed Desired, nor Desired Maximum. Minimum tidak boleh melebihi Diinginkan, dan Diinginkan tidak boleh melebihi Maksimum.
Justification Justifikasi
Add filters from the list above Tambahkan filter dari daftar di atas
Add to Stack Tambahkan ke Tumpukan
Applied Filters Filter yang Diterapkan
Categories Kategori
Choose a filter Pilih filter
Effect Gallery Galeri Efek
Filters Filter
Select artwork to apply effects Pilih grafis untuk menerapkan efek
# Select by Color (select.color)
Select by Color Pilih berdasarkan warna
Smudge Stick… Tongkat olesan…
@@ -5359,3 +5367,5 @@ swatch Bright Violet Violet cerah
Shift: Pergeseran:
Fresco… Fresko…
Fresco Fresko
Effect Gallery… Galeri Efek…
Effect Gallery Dialog Dialog Galeri Efek
Can't render this file because it contains an unexpected character in line 3811 and column 19.
+10
View File
@@ -4968,6 +4968,14 @@ lighting Light Luce
Minimum must not exceed Desired, nor Desired Maximum. Il minimo non può superare il valore desiderato, né il desiderato il massimo.
Minimum Minimo
Justification Giustificazione
Add filters from the list above Aggiungi filtri dall'elenco sopra
Add to Stack Aggiungi alla pila
Applied Filters Filtri applicati
Categories Categorie
Choose a filter Scegli un filtro
Effect Gallery Galleria effetti
Filters Filtri
Select artwork to apply effects Seleziona la grafica a cui applicare gli effetti
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them le variabili e i set di dati del file non sono stati letti (variabili: {variables}, set di dati: {data_sets}): il pannello Variabili non li elenca
# Select by Color (select.color)
Select by Color Seleziona per colore
@@ -5001,3 +5009,5 @@ lighting Light Luce
Shift: Spostamento:
Fresco… Affresco…
Fresco Affresco
Effect Gallery… Galleria effetti…
Effect Gallery Dialog Finestra di dialogo Galleria effetti
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5008,6 +5008,14 @@ lighting Light ライト
Minimum must not exceed Desired, nor Desired Maximum. 最小値は最適値を、最適値は最大値を超えることはできません。
Minimum 最小
Justification 文字組み
Add filters from the list above 上のリストからフィルターを追加
Add to Stack スタックに追加
Applied Filters 適用したフィルター
Categories カテゴリ
Choose a filter フィルターを選択
Effect Gallery 効果ギャラリー
Filters フィルター
Select artwork to apply effects 効果を適用するグラフィックを選択してください
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them ファイルの変数とデータセットは読み込まれませんでした (変数: {variables}、データセット: {data_sets})。変数パネルには表示されません
# Select by Color (select.color)
Select by Color カラーで選択
@@ -5048,3 +5056,5 @@ lighting Light ライト
Shift: シフト:
Fresco… フレスコ…
Fresco フレスコ
Effect Gallery… 効果ギャラリー…
Effect Gallery Dialog 効果ギャラリーダイアログ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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 ეფექტების გალერეა დიალოგი
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -4931,6 +4931,14 @@ lighting Light 광원
Minimum must not exceed Desired, nor Desired Maximum. 최소값은 희망값을, 희망값은 최대값을 넘을 수 없습니다.
Minimum 최소
Justification 양끝 맞춤
Add filters from the list above 위 목록에서 필터 추가
Add to Stack 스택에 추가
Applied Filters 적용된 필터
Categories 범주
Choose a filter 필터 선택
Effect Gallery 효과 갤러리
Filters 필터
Select artwork to apply effects 효과를 적용할 그래픽을 선택하세요
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them 파일의 변수와 데이터 세트를 읽지 않았습니다(변수: {variables}, 데이터 세트: {data_sets}). 변수 패널에 표시되지 않습니다
# Select by Color (select.color)
Select by Color 색상별 선택
@@ -4964,3 +4972,5 @@ lighting Light 광원
Shift: 이동:
Fresco… 프레스코…
Fresco 프레스코
Effect Gallery… 효과 갤러리…
Effect Gallery Dialog 효과 갤러리 대화 상자
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5348,6 +5348,14 @@ swatch Bright Violet Felviolet
Full Justify Volledig uitvullen
Minimum must not exceed Desired, nor Desired Maximum. Minimum mag niet groter zijn dan Gewenst, en Gewenst niet groter dan Maximum.
Justification Uitvullen
Add filters from the list above Voeg filters toe uit de lijst hierboven
Add to Stack Toevoegen aan stapel
Applied Filters Toegepaste filters
Categories Categorieën
Choose a filter Kies een filter
Effect Gallery Effectgalerij
Filters Filters
Select artwork to apply effects Selecteer grafiek om effecten toe te passen
# Select by Color (select.color)
Select by Color Selecteren op kleur
Smudge Stick… Uitsmeerstift…
@@ -5401,3 +5409,5 @@ swatch Bright Violet Felviolet
Shift: Verschuiving:
Fresco… Fresco…
Fresco Fresco
Effect Gallery… Effectgalerij…
Effect Gallery Dialog Dialoogvenster Effectgalerij
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5205,6 +5205,14 @@ mark-style Japanese Japońskie
Full Justify Pełne justowanie
Minimum must not exceed Desired, nor Desired Maximum. Minimum nie może przekraczać wartości optymalnej, a wartość optymalna maksimum.
Justification Justowanie
Add filters from the list above Dodaj filtry z listy powyżej
Add to Stack Dodaj do stosu
Applied Filters Zastosowane filtry
Categories Kategorie
Choose a filter Wybierz filtr
Effect Gallery Galeria efektów
Filters Filtry
Select artwork to apply effects Wybierz grafikę, aby zastosować efekty
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them zmienne i zestawy danych pliku nie zostały wczytane (zmienne: {variables}, zestawy danych: {data_sets}): panel Zmienne ich nie wyświetla
# Select by Color (select.color)
Select by Color Zaznacz według koloru
@@ -5238,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5270,6 +5270,14 @@ mark-style Japanese Japonesas
Full Justify Justificação Completa
Minimum must not exceed Desired, nor Desired Maximum. O mínimo não pode passar do desejado, nem o desejado do máximo.
Justification Justificação
Add filters from the list above Adicione filtros da lista acima
Add to Stack Adicionar à Pilha
Applied Filters Filtros Aplicados
Categories Categorias
Choose a filter Escolha um filtro
Effect Gallery Galeria de Efeitos
Filters Filtros
Select artwork to apply effects Selecione a arte para aplicar efeitos
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them as variáveis e os conjuntos de dados do arquivo não foram lidos (variáveis: {variables}, conjuntos de dados: {data_sets}): o painel Variáveis não os lista
# Select by Color (select.color)
Select by Color Selecionar por Cor
@@ -5303,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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+10
View File
@@ -4964,6 +4964,14 @@ lighting Light Свет
Minimum must not exceed Desired, nor Desired Maximum. Минимум не может превышать желательное значение, а желательное — максимум.
Minimum Минимум
Justification Выключка
Add filters from the list above Добавьте фильтры из списка выше
Add to Stack Добавить в стек
Applied Filters Применённые фильтры
Categories Категории
Choose a filter Выберите фильтр
Effect Gallery Галерея эффектов
Filters Фильтры
Select artwork to apply effects Выберите графику, чтобы применить эффекты
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them переменные и наборы данных файла не прочитаны (переменных: {variables}, наборов данных: {data_sets}): панель «Переменные» их не показывает
# Select by Color (select.color)
Select by Color Выделить по цвету
@@ -4997,3 +5005,5 @@ lighting Light Свет
Shift: Сдвиг:
Fresco… Фреска…
Fresco Фреска
Effect Gallery… Галерея эффектов…
Effect Gallery Dialog Диалог «Галерея эффектов»
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -5309,6 +5309,14 @@ theme Appearance Utseende
Full Justify Fullständig marginaljustering
Minimum must not exceed Desired, nor Desired Maximum. Minimum får inte överstiga Önskat, och Önskat inte Maximum.
Justification Justering
Add filters from the list above Lägg till filter från listan ovan
Add to Stack Lägg till i stacken
Applied Filters Tillämpade filter
Categories Kategorier
Choose a filter Välj ett filter
Effect Gallery Effektgalleri
Filters Filter
Select artwork to apply effects Välj grafik för att tillämpa effekter
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them filens variabler och datauppsättningar lästes inte (variabler: {variables}, datauppsättningar: {data_sets}): panelen Variabler visar dem inte
# Select by Color (select.color)
Select by Color Markera efter färg
@@ -5342,3 +5350,5 @@ theme Appearance Utseende
Shift: Förskjutning:
Fresco… Fresk…
Fresco Fresk
Effect Gallery… Effektgalleri…
Effect Gallery Dialog Dialogrutan Effektgalleri
Can't render this file because it contains an unexpected character in line 5 and column 25.
+10
View File
@@ -4948,6 +4948,14 @@ lighting Light Світло
Minimum must not exceed Desired, nor Desired Maximum. Мінімум не може перевищувати бажане значення, а бажане — максимум.
Minimum Мінімум
Justification Вирівнювання за шириною
Add filters from the list above Додайте фільтри зі списку вище
Add to Stack Додати до стека
Applied Filters Застосовані фільтри
Categories Категорії
Choose a filter Виберіть фільтр
Effect Gallery Галерея ефектів
Filters Фільтри
Select artwork to apply effects Виберіть графіку, щоб застосувати ефекти
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them змінні та набори даних файлу не прочитано (змінних: {variables}, наборів даних: {data_sets}): панель «Змінні» їх не показує
# Select by Color (select.color)
Select by Color Виділити за кольором
@@ -4981,3 +4989,5 @@ lighting Light Світло
Shift: Зсув:
Fresco… Фреска…
Fresco Фреска
Effect Gallery… Галерея ефектів…
Effect Gallery Dialog Діалог «Галерея ефектів»
Can't render this file because it contains an unexpected character in line 733 and column 40.
+10
View File
@@ -4948,6 +4948,14 @@ theme Appearance Giao diện
Minimum must not exceed Desired, nor Desired Maximum. Giá trị tối thiểu không được vượt quá giá trị mong muốn, và giá trị mong muốn không được vượt quá tối đa.
Minimum Tối thiểu
Justification Căn đều
Add filters from the list above Thêm bộ lọc từ danh sách phía trên
Add to Stack Thêm vào ngăn xếp
Applied Filters Bộ lọc đã áp dụng
Categories Danh mục
Choose a filter Chọn bộ lọc
Effect Gallery Thư viện hiệu ứng
Filters Bộ lọc
Select artwork to apply effects Chọn đồ họa để áp dụng hiệu ứng
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them các biến và tập dữ liệu của tệp chưa được đọc (biến: {variables}, tập dữ liệu: {data_sets}): bảng Biến không liệt kê chúng
# Select by Color (select.color)
Select by Color Chọn theo màu
@@ -4981,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
Can't render this file because it contains an unexpected character in line 4023 and column 10.
+10
View File
@@ -5192,6 +5192,14 @@ lighting Light 光源
Minimum must not exceed Desired, nor Desired Maximum. 最小值不能超过所需值,所需值不能超过最大值。
Minimum 最小值
Justification 对齐设置
Add filters from the list above 从上方列表添加滤镜
Add to Stack 添加到堆栈
Applied Filters 已应用的滤镜
Categories 类别
Choose a filter 选择滤镜
Effect Gallery 效果库
Filters 滤镜
Select artwork to apply effects 选择要应用效果的图稿
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them 文件的变量和数据集未被读取(变量:{variables},数据集:{data_sets}):“变量”面板不会列出它们
# Select by Color (select.color)
Select by Color 按颜色选择
@@ -5225,3 +5233,5 @@ lighting Light 光源
Shift: 偏移:
Fresco… 壁画…
Fresco 壁画
Effect Gallery… 效果库…
Effect Gallery Dialog 效果库对话框
Can't render this file because it contains an unexpected character in line 2 and column 73.
+10
View File
@@ -4982,6 +4982,14 @@ lighting Light 光源
Minimum must not exceed Desired, nor Desired Maximum. 最小值不可超過所需值,所需值不可超過最大值。
Minimum 最小值
Justification 對齊設定
Add filters from the list above 從上方清單新增濾鏡
Add to Stack 加入堆疊
Applied Filters 已套用的濾鏡
Categories 類別
Choose a filter 選擇濾鏡
Effect Gallery 效果庫
Filters 濾鏡
Select artwork to apply effects 選取要套用效果的圖稿
# Select by Color (select.color)
Select by Color 依顏色選取
@msg missing integer `{key}` 未提供整數 `{key}`
@@ -5020,3 +5028,5 @@ lighting Light 光源
Shift: 偏移:
Fresco… 壁畫…
Fresco 壁畫
Effect Gallery… 效果庫…
Effect Gallery Dialog 效果庫對話框
Can't render this file because it contains an unexpected character in line 6 and column 47.
+9 -1
View File
@@ -857,6 +857,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{} open Document Raster Effects Settings (dialog `rasterEffectsSettings`: the document.rasterEffectsSettings fields); OK runs it. The menu item opens it too",
),
(
"ui.effectGalleryDialog",
"Effect Gallery Dialog",
"",
"{} open Effect Gallery (dialog `effectGallery`: browse raster filters, build a stack, OK applies it as separate effects in one undo step). Effect → Effect Gallery… opens it too",
),
(
"ui.pdfPresetsDialog",
"PDF Presets…",
@@ -1770,6 +1776,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
"file.exportAs" => io::export(app, s("format").as_deref(), s("path"), p),
"ui.fileInfoDialog" => crate::dialogs::file_info::open(app),
"ui.rasterEffectsSettingsDialog" => crate::dialogs::raster_effects::open(app),
"ui.effectGalleryDialog" => crate::dialogs::effect_gallery::open(app, p),
"ui.pdfPresetsDialog" => {
crate::dialogs::pdf_presets::open(app, s("selected").as_deref());
Ok(Value::Null)
@@ -2436,7 +2443,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool {
id if id.starts_with("view.goto") => {
id["view.goto".len()..].parse::<usize>().is_ok_and(|n| n >= 1 && app.session.active().is_some_and(|d| n <= d.doc.views.len()))
}
"effect.dialog" | "ui.recolorDialog" => app.session.active().is_some_and(|d| !d.selection.is_empty()),
"effect.dialog" | "ui.recolorDialog" | "ui.effectGalleryDialog" => app.session.active().is_some_and(|d| !d.selection.is_empty()),
"ui.cropImage" => selected_image(app, |_| true),
"effect.applyLast" | "effect.last" => app.last_effect.is_some() && app.session.active().is_some_and(|d| !d.selection.is_empty()),
"file.export.pdf" | "ui.savePdfDialog" | "ui.fileInfoDialog" | "ui.rasterEffectsSettingsDialog" => app.session.active().is_some(),
@@ -4055,6 +4062,7 @@ fn effect_menu() -> Vec<Item> {
if !raster.is_empty() {
out.push(Sep);
out.push(Item::Header("Raster Effects"));
out.push(c("Effect Gallery…", "ui.effectGalleryDialog"));
out.extend(raster);
}
// Anything not placed above (future effects) still shows up.