From fa2faf99e20294f2d2ebec079104b7523b2bfb59 Mon Sep 17 00:00:00 2001 From: Frank <58932224+plexi32@users.noreply.github.com> Date: Thu, 5 Nov 2026 09:48:28 +0000 Subject: [PATCH] Dialogs: an opt-in setting to remember values and options for this app session (#11135) * Dialogs: remember values and options for this app session (off by default) Co-Authored-By: Claude Sonnet 5.5 * Dialogs: translate the remember-values help text again The help text changed to say the setting is off by default, and the complete catalogs (ar, he, ja, vi, zh) lost their translation of it. Co-Authored-By: Claude Opus 5.5 * Dialogs: recall gives a new face a fresh thread suggestion and rebuilds Move/Hole mode inputs Co-Authored-By: Claude Opus 5.5 * Dialogs: format recall Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Sonnet 5.5 --- .../pending/session-dialog-values.json | 42 ++++ crates/ui-egui/src/dialogs.rs | 196 ++++++++++++++++++ crates/ui-egui/src/dialogs_all_tests.rs | 130 ++++++++++++ crates/ui-egui/src/lib.rs | 3 + crates/ui-egui/src/prefs.rs | 21 ++ crates/ui-egui/src/prefs/tools.rs | 3 + 6 files changed, 395 insertions(+) create mode 100644 assets/locales/pending/session-dialog-values.json diff --git a/assets/locales/pending/session-dialog-values.json b/assets/locales/pending/session-dialog-values.json new file mode 100644 index 0000000000..044c817388 --- /dev/null +++ b/assets/locales/pending/session-dialog-values.json @@ -0,0 +1,42 @@ +{ + "ar": { + "prefs.command_dialog_values": "مربعات حوار الأوامر", + "prefs.remember_dialog_values": "تذكّر قيم مربعات حوار الأوامر", + "prefs.remember_dialog_values.help": "معطّل افتراضيًا. عند تفعيله، بعد نجاح الأمر، يُعاد استخدام قيمه وخياراته الآمنة في المرة التالية التي تفتح فيها الأمر نفسه، حتى إغلاق SolidCraft." + }, + "de": { + "prefs.command_dialog_values": "Befehlsdialoge", + "prefs.remember_dialog_values": "Befehlsdialogwerte merken", + "prefs.remember_dialog_values.help": "Standardmäßig aus. Wenn aktiviert, werden nach erfolgreicher Ausführung eines Befehls seine sicheren Werte und Optionen beim nächsten Öffnen desselben Befehls wiederverwendet, bis SolidCraft geschlossen wird." + }, + "en": { + "prefs.command_dialog_values": "Command dialogs", + "prefs.remember_dialog_values": "Remember command dialog values", + "prefs.remember_dialog_values.help": "Off by default. When on, after a command succeeds, its safe values and options are reused the next time you open the same command, until SolidCraft closes." + }, + "he": { + "prefs.command_dialog_values": "תיבות דו-שיח של פקודות", + "prefs.remember_dialog_values": "זכור ערכים בתיבות דו-שיח של פקודות", + "prefs.remember_dialog_values.help": "כבוי כברירת מחדל. כשהוא פועל, לאחר הצלחת פקודה, הערכים והאפשרויות הבטוחים שלה ישמשו שוב בפעם הבאה שתפתח את אותה פקודה, עד לסגירת SolidCraft." + }, + "ja": { + "prefs.command_dialog_values": "コマンドダイアログ", + "prefs.remember_dialog_values": "コマンドダイアログの値を記憶", + "prefs.remember_dialog_values.help": "既定ではオフです。オンにすると、コマンドが成功した後、SolidCraft を閉じるまで、同じコマンドを次に開いたときに安全な値とオプションを再利用します。" + }, + "ko": { + "prefs.command_dialog_values": "명령 대화 상자", + "prefs.remember_dialog_values": "명령 대화 상자 값 기억", + "prefs.remember_dialog_values.help": "기본값은 꺼짐입니다. 켜면 명령이 성공한 후 SolidCraft를 닫을 때까지 같은 명령을 다음에 열 때 안전한 값과 옵션을 다시 사용합니다." + }, + "vi": { + "prefs.command_dialog_values": "Hộp thoại lệnh", + "prefs.remember_dialog_values": "Ghi nhớ giá trị hộp thoại lệnh", + "prefs.remember_dialog_values.help": "Tắt theo mặc định. Khi bật, sau khi lệnh thành công, các giá trị và tùy chọn an toàn của lệnh sẽ được dùng lại vào lần tiếp theo bạn mở cùng lệnh đó, cho đến khi SolidCraft đóng." + }, + "zh": { + "prefs.command_dialog_values": "命令对话框", + "prefs.remember_dialog_values": "记住命令对话框的值", + "prefs.remember_dialog_values.help": "默认关闭。开启后,命令成功执行后,在关闭 SolidCraft 前,下次打开同一命令时会重用其安全的值和选项。" + } +} diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 8165a26294..34c2177505 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1131,6 +1131,7 @@ impl Dialog { Dialog::new(kind, inputs) } }; + recall(app, &mut d); // Patterns of features picked in the timeline: the objects are features. if is_pattern(&d.kind) && s.selection.iter().any(|x| matches!(x, Sel::Feature { .. })) @@ -4129,6 +4130,9 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { d.repeat_after_apply = false; let _ = app.session.execute("select.clear", &json!({})); } + if applied && d.editing.is_none() { + remember(app, &d); + } // Cancel while computing: the result goes (its worker runs on to its end). if cancel && !applied { app.applying = None; @@ -4168,6 +4172,198 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { } } +/// The values retained after a command succeeds; selection-dependent values are refreshed. +#[derive(Clone, Debug)] +pub(crate) struct RememberedCommand { + kind: Kind, + extra: serde_json::Map, +} + +/// Whether two dialog kinds are the same command (a chamfer is not a fillet). +fn same_command(a: &Kind, b: &Kind) -> bool { + let Some(command) = dense::command(a) else { return false }; + dense::command(b) == Some(command) +} + +/// Dialog values that are safe to reuse; picks and document-specific editors are excluded. +fn rememberable(kind: &Kind) -> bool { + matches!( + kind, + Kind::Mirror + | Kind::Extrude { .. } + | Kind::Revolve { .. } + | Kind::Fillet { .. } + | Kind::PatternRect { .. } + | Kind::PatternCirc { .. } + | Kind::Loft { .. } + | Kind::Sweep { .. } + | Kind::MeshConvert { .. } + | Kind::Construct { .. } + | Kind::PathPlane { .. } + | Kind::Rib { .. } + | Kind::Thread { .. } + | Kind::BoundaryFill { .. } + | Kind::Align { .. } + | Kind::PathPattern { .. } + | Kind::Section { .. } + | Kind::Move { .. } + | Kind::Hole { .. } + | Kind::Primitive { .. } + | Kind::Combine { .. } + | Kind::Shell { .. } + | Kind::Draft { .. } + | Kind::OffsetPlane { .. } + | Kind::AnglePlane { .. } + | Kind::Scale { .. } + | Kind::OffsetFaces { .. } + | Kind::Stock { .. } + | Kind::Pipe { .. } + | Kind::Emboss { .. } + | Kind::ViewDxf { .. } + | Kind::SketchModify(_) + | Kind::Sheet( + crate::dialogs_sheet::Sm::Perforate { .. } + | crate::dialogs_sheet::Sm::Hem { .. } + | crate::dialogs_sheet::Sm::Fold { .. } + | crate::dialogs_sheet::Sm::Path { .. } + | crate::dialogs_sheet::Sm::Flange { .. } + | crate::dialogs_sheet::Sm::Unfold { .. }, + ) + | Kind::Surface(_) + | Kind::Plastic( + crate::dialogs_plastic::Pl::Boss { .. } + | crate::dialogs_plastic::Pl::Lip { .. } + | crate::dialogs_plastic::Pl::Rest { .. } + | crate::dialogs_plastic::Pl::SnapFit { .. }, + ) + | Kind::Frame(_) + ) +} + +fn remembered_extra_keys(kind: &Kind) -> &'static [&'static str] { + match dense::command(kind) { + Some("solid.extrude") => &["convention"], + Some("solid.revolve") => &["angle2", "direction"], + Some("solid.shell") => &["direction", "tangent_chain"], + Some("solid.fillet") => &["type", "fit", "corner", "radius2", "mid", "radii", "swap_ends", "smooth"], + Some("solid.mirror") => &["combine"], + Some("solid.pattern.rectangular") => &["alignment", "reversed", "alignment2", "reversed2"], + Some("solid.pattern.circular") => &["alignment", "reversed"], + Some("solid.hole") => &["all_points", "flip"], + Some("solid.align") => &["from_snap", "to_snap"], + Some("solid.boundary_fill") => &["remove_tools"], + Some("solid.thread") => &["modeled"], + Some("solid.move") => &["copy"], + Some("solid.coil") => &["section", "section_position", "start_angle", "clockwise"], + _ => &[], + } +} + +/// Keep the options and values of an applied dialog for the next one of the same command. +fn remember(app: &mut SolveApp, dialog: &Dialog) { + if !app.preferences.remember_dialog_values { + return; + } + let kind = &dialog.kind; + if !rememberable(kind) { + return; + } + let Some(command) = dense::command(kind) else { return }; + let extra = + remembered_extra_keys(kind).iter().filter_map(|key| dialog.extra.get(*key).map(|value| ((*key).to_string(), value.clone()))).collect(); + app.remembered_commands.retain(|remembered| dense::command(&remembered.kind) != Some(command)); + app.remembered_commands.push(RememberedCommand { kind: kind.clone(), extra }); +} + +/// A new dialog starts from what the last one of its command applied. Picks and context-dependent +/// values come from the newly constructed dialog instead. +fn recall(app: &SolveApp, dialog: &mut Dialog) { + if !app.preferences.remember_dialog_values { + return; + } + let Some(last) = app.remembered_commands.iter().find(|entry| same_command(&entry.kind, &dialog.kind)) else { return }; + let mut next = last.kind.clone(); + match (&mut next, &dialog.kind) { + (Kind::Extrude { smart, to_offset, .. }, Kind::Extrude { smart: fresh, to_offset: fresh_offset, .. }) => { + *smart = fresh.clone(); + *to_offset = fresh_offset.clone(); + } + (Kind::Revolve { axis, .. }, Kind::Revolve { axis: fresh, .. }) => *axis = fresh.clone(), + (Kind::PathPlane { sketch, .. }, Kind::PathPlane { sketch: fresh, .. }) => *sketch = *fresh, + (Kind::Thread { designation, suggested, of, .. }, Kind::Thread { suggested: fresh_suggested, of: fresh_of, .. }) => { + // A remembered auto-suggestion belonged to the old face: the new face gets its own. + // An authored designation stays. + if suggested.as_deref() == Some(designation.as_str()) { + *designation = fresh_suggested.clone().unwrap_or_default(); + } + *suggested = fresh_suggested.clone(); + *of = fresh_of.clone(); + } + (Kind::BoundaryFill { cells, of, .. }, Kind::BoundaryFill { cells: fresh_cells, of: fresh_of, .. }) => { + *cells = fresh_cells.clone(); + *of = fresh_of.clone(); + } + (Kind::Hole { opts, .. }, Kind::Hole { opts: fresh, .. }) => opts.refs = fresh.refs.clone(), + (Kind::Primitive { at, .. }, Kind::Primitive { at: fresh, .. }) => *at = *fresh, + (Kind::Sweep { opts, .. }, Kind::Sweep { opts: fresh, .. }) => opts.direction = fresh.direction, + ( + Kind::Surface(crate::dialogs_surface::Surf::Revolve { axis, .. }), + Kind::Surface(crate::dialogs_surface::Surf::Revolve { axis: fresh, .. }), + ) => *axis = fresh.clone(), + (Kind::Surface(crate::dialogs_surface::Surf::Sweep(opts)), Kind::Surface(crate::dialogs_surface::Surf::Sweep(fresh))) => { + opts.direction = fresh.direction; + } + ( + Kind::Surface(crate::dialogs_surface::Surf::Trim { direction, .. }), + Kind::Surface(crate::dialogs_surface::Surf::Trim { direction: fresh, .. }), + ) => *direction = *fresh, + (Kind::Sheet(crate::dialogs_sheet::Sm::Flange { own, .. }), Kind::Sheet(crate::dialogs_sheet::Sm::Flange { own: fresh, .. })) => { + *own = fresh.clone(); + } + (Kind::Plastic(crate::dialogs_plastic::Pl::SnapFit { hook, .. }), Kind::Plastic(crate::dialogs_plastic::Pl::SnapFit { hook: fresh, .. })) => { + *hook = *fresh; + } + (Kind::Frame(previous), Kind::Frame(fresh)) => previous.whole = fresh.whole, + _ => {} + } + let fresh_mode = mode_inputs_key(&dialog.kind); + dialog.kind = next; + // The remembered mode needs its own inputs (Rotate an Axis, Multiple Hole its points); + // the fresh selection of the first input stays. + if mode_inputs_key(&dialog.kind) != fresh_mode { + let rebuilt = match &dialog.kind { + Kind::Move { mode, .. } => Some(crate::dialogs_move::inputs(*mode)), + Kind::Hole { opts, .. } if opts.multiple => Some(vec![SelInput::new("Points", selection::POINTS, true)]), + Kind::Hole { .. } => Some(single_hole_inputs()), + _ => None, + }; + if let Some(mut inputs) = rebuilt { + let keep = matches!(dialog.kind, Kind::Move { .. }); + if keep && let (Some(fresh), Some(first)) = (dialog.inputs.first(), inputs.first_mut()) { + first.items = fresh.items.clone(); + } + dialog.inputs = inputs; + dialog.active = 0; + } + } + for key in remembered_extra_keys(&dialog.kind) { + if let Some(value) = last.extra.get(*key) { + dialog.extra.insert((*key).to_string(), value.clone()); + } else { + dialog.extra.remove(*key); + } + } +} + +/// Which mode-dependent input set a dialog shows (`None` for dialogs whose inputs don't change). +fn mode_inputs_key(kind: &Kind) -> Option { + match kind { + Kind::Move { mode, .. } => Some(*mode), + Kind::Hole { opts, .. } => Some(usize::from(opts.multiple)), + _ => None, + } +} + /// Close the dialog without applying it (Esc, Cancel). An edit puts the timeline marker back. pub fn cancel(app: &mut SolveApp) { app.session.feature_edit_sketch = None; diff --git a/crates/ui-egui/src/dialogs_all_tests.rs b/crates/ui-egui/src/dialogs_all_tests.rs index def03ae2de..0d614910ef 100644 --- a/crates/ui-egui/src/dialogs_all_tests.rs +++ b/crates/ui-egui/src/dialogs_all_tests.rs @@ -84,6 +84,136 @@ fn point_at_edge_and_plane_picks_a_construction_axis() { } } +#[test] +fn command_dialog_remembers_values_for_the_same_command_only_in_this_session() { + let mut h = Harness::new(); + h.app.preferences.remember_dialog_values = true; + let mut pattern = Dialog::for_command(&h.app, "solid.pattern.rectangular").unwrap(); + if let Kind::PatternRect { count, spacing, count2, spacing2 } = &mut pattern.kind { + *count = "7".into(); + *spacing = "12 mm".into(); + *count2 = "3".into(); + *spacing2 = "8 mm".into(); + } else { + panic!("rectangular pattern dialog has the wrong kind"); + } + remember(&mut h.app, &pattern); + + let recalled = Dialog::for_command(&h.app, "solid.pattern.rectangular").unwrap(); + assert!(matches!( + &recalled.kind, + Kind::PatternRect { count, spacing, count2, spacing2 } + if count == "7" && spacing == "12 mm" && count2 == "3" && spacing2 == "8 mm" + )); + + let fresh_app = Harness::new(); + let fresh = Dialog::for_command(&fresh_app.app, "solid.pattern.rectangular").unwrap(); + assert!(matches!( + &fresh.kind, + Kind::PatternRect { count, count2, .. } if count == "3" && count2 == "1" + )); +} + +#[test] +fn primitive_dialog_remembers_values_without_reusing_its_picked_placement() { + let mut h = Harness::new(); + h.app.preferences.remember_dialog_values = true; + let mut box_dialog = Dialog::for_command(&h.app, "solid.box").unwrap(); + if let Kind::Primitive { fields, operation, at, .. } = &mut box_dialog.kind { + fields[0].1 = "42 mm".into(); + *operation = 2; + *at = Some((Vec3::ZERO, None)); + } else { + panic!("box dialog has the wrong kind"); + } + remember(&mut h.app, &box_dialog); + + let recalled = Dialog::for_command(&h.app, "solid.box").unwrap(); + assert!(matches!( + &recalled.kind, + Kind::Primitive { fields, operation: 2, at: None, .. } if fields[0].1 == "42 mm" + )); + + let different_command = Dialog::for_command(&h.app, "solid.cylinder").unwrap(); + assert!(matches!( + &different_command.kind, + Kind::Primitive { fields, operation: 0, .. } if fields[0].1 == "20 mm" + )); +} + +#[test] +fn command_dialog_remembers_option_fields() { + let mut h = Harness::new(); + h.app.preferences.remember_dialog_values = true; + let mut shell = Dialog::for_command(&h.app, "solid.shell").unwrap(); + shell.extra.insert("direction".into(), json!("outside")); + shell.extra.insert("tangent_chain".into(), json!(false)); + remember(&mut h.app, &shell); + + let recalled = Dialog::for_command(&h.app, "solid.shell").unwrap(); + assert_eq!(recalled.extra.get("direction"), Some(&json!("outside"))); + assert_eq!(recalled.extra.get("tangent_chain"), Some(&json!(false))); +} + +#[test] +fn thread_recall_drops_a_remembered_suggestion_but_keeps_an_authored_size() { + let mut h = Harness::new(); + h.app.preferences.remember_dialog_values = true; + let mut thread = Dialog::for_command(&h.app, "solid.thread").unwrap(); + if let Kind::Thread { designation, suggested, .. } = &mut thread.kind { + *designation = "M6x1".into(); + *suggested = Some("M6x1".into()); + } + remember(&mut h.app, &thread); + let recalled = Dialog::for_command(&h.app, "solid.thread").unwrap(); + assert!(matches!(&recalled.kind, Kind::Thread { designation, .. } if designation.is_empty()), "{:?}", recalled.kind); + + if let Kind::Thread { designation, .. } = &mut thread.kind { + *designation = "M8x1.25".into(); + } + remember(&mut h.app, &thread); + let recalled = Dialog::for_command(&h.app, "solid.thread").unwrap(); + assert!(matches!(&recalled.kind, Kind::Thread { designation, .. } if designation == "M8x1.25")); +} + +#[test] +fn move_and_hole_recall_rebuild_the_inputs_of_the_remembered_mode() { + let mut h = Harness::new(); + h.app.preferences.remember_dialog_values = true; + let mut rotate = Dialog::for_command(&h.app, "solid.move").unwrap(); + rotate.kind = crate::dialogs_move::kind(crate::dialogs_move::ROTATE, &h.app.session.doc); + rotate.inputs = crate::dialogs_move::inputs(crate::dialogs_move::ROTATE); + remember(&mut h.app, &rotate); + let recalled = Dialog::for_command(&h.app, "solid.move").unwrap(); + let labels: Vec<&str> = recalled.inputs.iter().map(|i| i.label).collect(); + assert!(labels.contains(&"Axis") && !labels.contains(&"Pivot"), "{labels:?}"); + + let mut hole = Dialog::for_command(&h.app, "solid.hole").unwrap(); + if let Kind::Hole { opts, .. } = &mut hole.kind { + opts.multiple = true; + } + remember(&mut h.app, &hole); + let recalled = Dialog::for_command(&h.app, "solid.hole").unwrap(); + assert_eq!(recalled.inputs.len(), 1); + assert_eq!(recalled.inputs[0].label, "Points"); +} + +#[test] +fn command_dialog_value_memory_is_off_by_default_and_resets_each_time() { + let mut h = Harness::new(); + assert!(!h.app.preferences.remember_dialog_values); + let mut pattern = Dialog::for_command(&h.app, "solid.pattern.rectangular").unwrap(); + if let Kind::PatternRect { count, .. } = &mut pattern.kind { + *count = "7".into(); + } else { + panic!("rectangular pattern dialog has the wrong kind"); + } + remember(&mut h.app, &pattern); + + let recalled = Dialog::for_command(&h.app, "solid.pattern.rectangular").unwrap(); + assert!(matches!(&recalled.kind, Kind::PatternRect { count, .. } if count == "3")); +} + fn fixture() -> Harness { let mut h = Harness::new(); for (name, x) in [("A", 0), ("B", 30), ("C", 60), ("D", 90)] { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 2d65512e94..211d911765 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -568,6 +568,8 @@ pub struct SolveApp { pub render_workspace: render_workspace::State, pub tool: Option, pub dialog: Option, + /// Each command dialog's options and values as last applied (this session only). + pub(crate) remembered_commands: Vec, /// The Change Parameters window, when open. pub params: Option, /// The Design Properties window, when open. @@ -839,6 +841,7 @@ impl SolveApp { render_workspace, tool: None, dialog: None, + remembered_commands: Vec::new(), params: None, design_properties: None, sketch_report: None, diff --git a/crates/ui-egui/src/prefs.rs b/crates/ui-egui/src/prefs.rs index 524ab8534a..c25af97abc 100644 --- a/crates/ui-egui/src/prefs.rs +++ b/crates/ui-egui/src/prefs.rs @@ -1025,6 +1025,9 @@ macro_rules! preference_settings { (@control $app:ident, $ui:ident, $field:ident, check_id($label:literal)) => { ui_kit::checkbox($ui, $app.tr($label), &mut $app.preferences.$field) }; + (@control $app:ident, $ui:ident, $field:ident, check_id_help($label:literal, $help:literal)) => { + ui_kit::checkbox($ui, $app.tr($label), &mut $app.preferences.$field).on_hover_text($app.tr($help)) + }; (@control $app:ident, $ui:ident, $field:ident, number($label:literal, $range:expr)) => { ui_kit::row($ui, $app.tr($label), |ui| { ui_kit::number_field(ui, &mut $app.preferences.$field, $range, 1.0, "") @@ -1445,6 +1448,8 @@ preference_settings! { /// Feature windows choose Join, New Body or Cut from what their material touches (`op_auto`, /// #6901). Off: they keep the operation they open with (Extrude its own Join or Cut), as before. auto_operation: bool = true, PAGE_TOOLS => check_id("prefs.auto_operation"); + /// Reuse a command's safe values and options after it succeeds, until the app closes. Off by default: each dialog opens with its usual defaults. + remember_dialog_values: bool = false, PAGE_TOOLS => check_id_help("prefs.remember_dialog_values", "prefs.remember_dialog_values.help"); /// Placing a Single hole snaps to the middle of the face and the centres of its round edges /// (`hole_place`). Off by default: a hole goes where it is clicked. hole_snaps: bool = false, PAGE_TOOLS => check("Snap to face and edge centres"); @@ -4369,6 +4374,7 @@ mod tests { restored.load_prefs(&app.prefs()); assert_eq!(restored.preferences.trackpad_zoom_modifier, value); } + for invalid in [json!("super"), json!(""), json!(42), json!(null)] { assert!(set(&mut app, &json!({"trackpad_zoom_modifier": invalid})).is_err()); assert_eq!(app.preferences.trackpad_zoom_modifier, TrackpadZoomModifier::Off); @@ -4381,6 +4387,21 @@ mod tests { assert_eq!(app.preferences.trackpad_zoom_modifier, TrackpadZoomModifier::Off); } + #[test] + fn command_dialog_value_memory_defaults_off_persists_and_resets() { + assert!(!Prefs::default().remember_dialog_values); + let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default()); + assert!(!app.preferences.remember_dialog_values); + app.preferences.remember_dialog_values = true; + assert_eq!(serde_json::to_value(&app.preferences).unwrap()["remember_dialog_values"], true); + + let mut restored = SolveApp::new(solidcraft_engine::Session::default(), Default::default()); + restored.load_prefs(&app.prefs()); + assert!(restored.preferences.remember_dialog_values); + reset_preferences(&mut app.preferences, PAGE_TOOLS); + assert!(!app.preferences.remember_dialog_values); + } + #[test] fn navigation_bar_preferences_round_trip_and_recover_hostile_fields() { use crate::navigation_bar::{Edge, Idle, Labels, NavigationBar, Position, Size}; diff --git a/crates/ui-egui/src/prefs/tools.rs b/crates/ui-egui/src/prefs/tools.rs index 75269a1e7c..627921872c 100644 --- a/crates/ui-egui/src/prefs/tools.rs +++ b/crates/ui-egui/src/prefs/tools.rs @@ -64,6 +64,9 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) { }); }); }); + ui_kit::settings_section(ui, "command_dialog_values", app.tr("prefs.command_dialog_values"), |ui| { + setting::remember_dialog_values(app, ui); + }); ui_kit::settings_section(ui, "joint_move", app.tr("joint_move.settings"), |ui| { tool_group(ui, "", ui_kit::paint_assembly_move, |ui| { ui.horizontal_wrapped(|ui| {