diff --git a/crates/ui-egui/src/dialogs/effect.rs b/crates/ui-egui/src/dialogs/effect.rs index 3e2c86798..5539d65fd 100644 --- a/crates/ui-egui/src/dialogs/effect.rs +++ b/crates/ui-egui/src/dialogs/effect.rs @@ -90,6 +90,9 @@ pub fn open(app: &mut VectorcraftApp, p: &Value) -> Result { return Ok(json!({ "pending": EXISTS })); } fields.extend(info.defaults.as_object().cloned().unwrap_or_default()); + if super::warp_effect::is_warp(id) { + super::warp_effect::merge_remembered(&mut fields, app.last_warp.as_ref()); + } } } fields.insert("preview".into(), json!(true)); @@ -119,8 +122,12 @@ fn confirm_exists(app: &mut VectorcraftApp, d: &Dialog) -> Result let target = fields.remove("__target").unwrap_or(Value::Null); if d.bool("discard") { fields.remove("__index"); - let defaults = vectorcraft_effects::default_params(&d.str("__effect")); + let id = d.str("__effect"); + let defaults = vectorcraft_effects::default_params(&id); fields.extend(defaults.and_then(|v| v.as_object().cloned()).unwrap_or_default()); + if super::warp_effect::is_warp(id.as_str()) { + super::warp_effect::merge_remembered(&mut fields, app.last_warp.as_ref()); + } } else { let k = fields.get("__index").and_then(Value::as_u64).unwrap_or(0) as usize; fields.extend(target_effects(app, &target).get(k).map(params_of).unwrap_or_default()); @@ -673,7 +680,11 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { let r = form::commit_preview(app, cmd, p.clone())?; // Last Effect repeats applied effects, not edits. if cmd == "effect.apply" { - app.last_effect = Some((d.str("__effect"), p["params"].clone())); + let id = d.str("__effect"); + app.last_effect = Some((id.clone(), p["params"].clone())); + if super::warp_effect::is_warp(id.as_str()) { + app.last_warp = Some(super::warp_effect::remembered_params(d)); + } } Ok(r) } diff --git a/crates/ui-egui/src/dialogs/tests_warp.rs b/crates/ui-egui/src/dialogs/tests_warp.rs index de2b53aa9..184ca1c0b 100644 --- a/crates/ui-egui/src/dialogs/tests_warp.rs +++ b/crates/ui-egui/src/dialogs/tests_warp.rs @@ -187,6 +187,25 @@ fn warp_options_existing_style_edit_keeps_stack_target_visibility_and_history() } } +/// A new Warp Options dialog opens on the sliders confirmed last time (#1760 follow-up). +#[test] +fn warp_dialog_remembers_last_confirmed_sliders() { + let (mut app, _) = app(); + app.run("effect.dialog", json!({"effect": "warp.arc", "item": null})).unwrap(); + let ctx = context(); + settle(&mut app, &ctx); + set(&mut app, json!({"bend": -40.0, "horizontal": 12.0, "vertical": -8.0, "orientation": "vertical"})); + confirm(&mut app).unwrap(); + app.run("effect.dialog", json!({"effect": "warp.flag", "item": null})).unwrap(); + settle(&mut app, &ctx); + let d = app.ui.dialog.as_ref().unwrap(); + assert_eq!(d.str("__effect"), "warp.flag"); + assert_eq!(d.f64("bend", 0.0), -40.0); + assert_eq!(d.f64("horizontal", 0.0), 12.0); + assert_eq!(d.f64("vertical", 0.0), -8.0); + assert_eq!(d.str("orientation"), "vertical"); +} + #[test] fn warp_options_preview_toggle_and_ok_use_latest_values() { let (mut app, id) = app(); diff --git a/crates/ui-egui/src/dialogs/warp_effect.rs b/crates/ui-egui/src/dialogs/warp_effect.rs index 1b06fbbef..e0a2490aa 100644 --- a/crates/ui-egui/src/dialogs/warp_effect.rs +++ b/crates/ui-egui/src/dialogs/warp_effect.rs @@ -1,6 +1,6 @@ //! Effect > Warp controls. The style remains an effect ID; Envelope Warp has a different schema. use egui::{Pos2, Rect, Stroke, vec2}; -use serde_json::json; +use serde_json::{Map, Value, json}; use vectorcraft_effects::{WARP_STYLES, WarpStyle, warp_point}; use super::form; @@ -8,10 +8,28 @@ use crate::state::Dialog; use crate::theme::Tokens; use crate::widgets; +const REMEMBERED: [&str; 4] = ["bend", "horizontal", "vertical", "orientation"]; + pub(super) fn is_warp(id: &str) -> bool { id.strip_prefix("warp.").and_then(WarpStyle::from_id).is_some() } +/// Slider and orientation values to seed the next new Warp Options dialog. +pub(super) fn remembered_params(d: &Dialog) -> Value { + let kept = REMEMBERED.iter().filter_map(|k| Some((k.to_string(), d.fields.get(*k)?.clone()))); + Value::Object(kept.collect()) +} + +/// Last confirmed warp settings over the catalog defaults for a new dialog. +pub(super) fn merge_remembered(fields: &mut Map, remembered: Option<&Value>) { + let Some(obj) = remembered.and_then(Value::as_object) else { return }; + for key in REMEMBERED { + if let Some(v) = obj.get(key) { + fields.insert(key.into(), v.clone()); + } + } +} + fn shown(label: &str) -> String { tl!(label).trim_end_matches('…').to_string() } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index e78c121fe..a5706b5fa 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -398,6 +398,8 @@ pub struct VectorcraftApp { pub titlebar_inset: f32, /// Last applied effect (Effect → Apply Last Effect). pub last_effect: Option<(String, serde_json::Value)>, + /// Last confirmed Warp Options (bend, distortion, orientation) for the next new warp dialog. + pub last_warp: Option, /// Commands run through [`Self::run`] so far: the native menu bar reads its rows again when it /// moves ([`native_menu::sync`]). run_count: u64, @@ -542,6 +544,7 @@ impl VectorcraftApp { perf: Perf::default(), titlebar_inset: 0.0, last_effect: None, + last_warp: None, run_count: 0, clipboard_out: None, clipboard_published: None,