Effects: Roughen and Pucker & Bloat show sliders (#4488)

This commit is contained in:
bflatastic
2026-10-29 21:06:58 +07:00
parent 41dda1029f
commit d0ef81e9f7
2 changed files with 65 additions and 12 deletions
+36 -12
View File
@@ -166,7 +166,8 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
None if id == TEXTURIZER => glass_fields(app, ui, d, TEXTURIZER),
None if id == TRANSFORM => transform_fields(ui, d, app.session.general_unit()),
None if id == SCRIBBLE => scribble_fields(ui, d, app.session.general_unit()),
None if id == ZIG_ZAG => zig_zag_fields(ui, d, app.session.general_unit()),
None if id == ZIG_ZAG || id == ROUGHEN => size_fields(ui, d, &id, app.session.general_unit()),
None if id == PUCKER_BLOAT => pucker_bloat_fields(ui, d),
None if id == DROP_SHADOW => drop_shadow_fields(ui, d, app.session.general_unit()),
None if id == "artistic.filmGrain" => film_grain_fields(ui, d),
None if id == free_distort::FREE_DISTORT => {
@@ -559,27 +560,40 @@ fn transform_fields(ui: &mut egui::Ui, d: &mut Dialog, unit: vectorcraft_doc::Un
/// The Zig Zag effect (Effect › Distort & Transform › Zig Zag…).
const ZIG_ZAG: &str = "distort.zigZag";
/// Zig Zag's dialog (#1133): Size and Ridges per segment as sliders with their fields, Relative or
/// Absolute and the Points as radio buttons. Returns whether the values differ from those last
/// previewed.
fn zig_zag_fields(ui: &mut egui::Ui, d: &mut Dialog, unit: vectorcraft_doc::Unit) -> bool {
/// The Roughen effect (Effect › Distort & Transform › Roughen…).
const ROUGHEN: &str = "distort.roughen";
/// The Pucker & Bloat effect (Effect › Distort & Transform › Pucker & Bloat…).
const PUCKER_BLOAT: &str = "distort.puckerBloat";
/// The second slider of [`size_fields`]: Zig Zag's Ridges per segment, Roughen's Detail (per inch),
/// as (key, label, default, suffix).
fn second_slider(effect: &str) -> (&'static str, &'static str, f64, &'static str) {
if effect == ROUGHEN { ("detail", "Detail:", 10.0, "/in") } else { ("ridges", "Ridges per segment:", 4.0, "") }
}
/// Zig Zag's and Roughen's dialog (#1133, #4488): Size and Ridges per segment (Roughen: Detail) as
/// sliders with their fields, Relative or Absolute and the Points as radio buttons. Returns
/// whether the values differ from those last previewed.
fn size_fields(ui: &mut egui::Ui, d: &mut Dialog, effect: &str, unit: vectorcraft_doc::Unit) -> bool {
const LABEL_W: f32 = 124.0;
const FIELD_W: f32 = 70.0;
let rail = Tokens::get(ui.ctx()).input_border;
let track = move |_| rail;
let relative = d.bool("relative");
let size = if effect == ROUGHEN { 5.0 } else { 10.0 };
widgets::subheader(ui, tl!("Options"));
// Size is a share of the object's size (Relative) or a distance (Absolute).
widgets::label_row(ui, tl!("Size:"), LABEL_W, |ui| {
let shown = if relative {
Some(widgets::SliderValue::field(d.f64("size", 10.0).clamp(0.0, 100.0), 0.0..=100.0, "%", 0))
Some(widgets::SliderValue::field(d.f64("size", size).clamp(0.0, 100.0), 0.0..=100.0, "%", 0))
} else {
form::length_value(d, "size", unit, 0.0..=100.0)
};
form::slider_rail_valued(ui, d, "size", tl!("Size:"), 0.0..=100.0, shown, &track);
if !relative {
form::length(ui, d, "size", unit, FIELD_W);
} else if let Some(n) = widgets::range_field(ui, ("zz", "size"), d.f64("size", 10.0), 0.0..=100.0, "%", 0, FIELD_W) {
} else if let Some(n) = widgets::range_field(ui, ("zz", "size"), d.f64("size", size), 0.0..=100.0, "%", 0, FIELD_W) {
d.fields.insert("size".into(), json!(n));
}
});
@@ -591,17 +605,18 @@ fn zig_zag_fields(ui: &mut egui::Ui, d: &mut Dialog, unit: vectorcraft_doc::Unit
}
}
});
widgets::label_row(ui, tl!("Ridges per segment:"), LABEL_W, |ui| {
form::slider_rail_named(ui, d, "ridges", tl!("Ridges per segment:"), 0.0..=100.0, &track);
if let Some(n) = widgets::range_field(ui, ("zz", "ridges"), d.f64("ridges", 4.0), 0.0..=100.0, "", 0, FIELD_W) {
d.fields.insert("ridges".into(), json!(n));
let (key, label, default, suffix) = second_slider(effect);
widgets::label_row(ui, tl!(label), LABEL_W, |ui| {
form::slider_rail_named(ui, d, key, tl!(label), 0.0..=100.0, &track);
if let Some(n) = widgets::range_field(ui, ("zz", key), d.f64(key, default), 0.0..=100.0, suffix, 0, FIELD_W) {
d.fields.insert(key.into(), json!(n));
}
});
ui.add_space(4.0);
widgets::subheader(ui, crate::i18n::tr_ctx(crate::i18n::current(), "effect", "Points"));
let points = d.str("points");
ui.horizontal(|ui| {
for (label, value) in choices(ZIG_ZAG, "points").unwrap_or_default() {
for (label, value) in choices(effect, "points").unwrap_or_default() {
if widgets::radio(ui, crate::i18n::tr_ctx(crate::i18n::current(), "effect", label), points == *value, true) {
d.fields.insert("points".into(), json!(value));
}
@@ -610,6 +625,15 @@ fn zig_zag_fields(ui: &mut egui::Ui, d: &mut Dialog, unit: vectorcraft_doc::Unit
values_changed(d)
}
/// Pucker & Bloat's dialog (#4488): its amount as a slider from Pucker (−200 %) to Bloat (200 %)
/// with its field. Returns whether the value differs from the one last previewed.
fn pucker_bloat_fields(ui: &mut egui::Ui, d: &mut Dialog) -> bool {
let rail = Tokens::get(ui.ctx()).input_border;
form::slider(ui, d, "amount", tl!("Amount:"), -200.0..=200.0, "%", &move |_| rail);
form::slider_ends(ui, form::SLIDER_LABEL, (tl!("Pucker"), tl!("Bloat")));
values_changed(d)
}
/// Drop Shadow (Effect › Stylize › Drop Shadow…).
const DROP_SHADOW: &str = "stylize.dropShadow";
+29
View File
@@ -1413,6 +1413,35 @@ fn zig_zag_has_sliders_and_radio_buttons() {
assert!(!lines.contains(&"Ridges:"), "{lines:?}");
}
/// #4488: Roughen's dialog has Size and Detail sliders laid out as Zig Zag's, and Pucker & Bloat's
/// its amount as a slider from Pucker to Bloat.
#[test]
fn roughen_and_pucker_bloat_have_sliders() {
use crate::widgets::accessibility_tests::{slider_value, tree};
let mut app = app();
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap();
app.run("effect.dialog", json!({"effect": "distort.roughen"})).unwrap();
let text = crate::tests_labels::painted_text(&mut app, |app, ui| show(app, ui.ctx()));
let lines: Vec<&str> = text.lines().map(str::trim).collect();
for want in ["Size:", "Relative", "Absolute", "Detail:", "Points", "Smooth", "Corner"] {
assert!(lines.contains(&want), "no {want:?} in {lines:?}");
}
let shown = tree(|ui| show(&mut app, ui.ctx()));
assert_eq!(slider_value(&shown, "Size:"), (Some(5.0), Some(0.0), Some(100.0), Some("5%".into())));
assert_eq!(slider_value(&shown, "Detail:"), (Some(10.0), Some(0.0), Some(100.0), None));
app.ui.dialog = None;
app.run("effect.dialog", json!({"effect": "distort.puckerBloat"})).unwrap();
app.ui.dialog.as_mut().unwrap().fields.insert("amount".into(), json!(-60));
let text = crate::tests_labels::painted_text(&mut app, |app, ui| show(app, ui.ctx()));
let lines: Vec<&str> = text.lines().map(str::trim).collect();
for want in ["Amount:", "Pucker", "Bloat"] {
assert!(lines.contains(&want), "no {want:?} in {lines:?}");
}
let shown = tree(|ui| show(&mut app, ui.ctx()));
let (value, min, max, _) = slider_value(&shown, "Amount:");
assert_eq!((value, min, max), (Some(-60.0), Some(-200.0), Some(200.0)));
}
/// Screen readers get the number and text of the field beside each dialog slider, in the field's
/// unit, with the rail's range (extended to a number past the rail's end): Transform Each's Scale
/// and Move, Zig Zag's Size and Ridges, a form slider (Vector Halftone's Frequency), the Pencil's