Preferences (File menu): theme, grid, perspective, auto-project face edges, body selection; all kept between runs

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 01:20:33 +00:00
1 parent 967bee179a
commit 7a0cc85392
3 files changed
+63 -8

No files matched your search

+36 -4
View File
@@ -78,6 +78,8 @@ pub enum Kind {
Sweep {
operation: usize,
},
/// Application preferences (applied as they change; kept between runs).
Preferences,
/// Physical material (and with it the appearance) of bodies.
Material {
index: usize,
@@ -286,6 +288,10 @@ impl Dialog {
Some(d)
}
pub fn preferences() -> Dialog {
Dialog::new(Kind::Preferences, vec![])
}
pub fn rename(feature: u64, name: &str) -> Dialog {
Dialog::new(Kind::Rename { feature, name: name.to_string() }, vec![])
}
@@ -495,6 +501,7 @@ fn title(k: &Kind) -> &'static str {
Kind::Mirror => "MIRROR",
Kind::Measure { .. } => "MEASURE",
Kind::Material { .. } => "PHYSICAL MATERIAL",
Kind::Preferences => "PREFERENCES",
Kind::Section { .. } => "SECTION ANALYSIS",
Kind::PatternRect { .. } => "RECTANGULAR PATTERN",
Kind::PatternCirc { .. } => "CIRCULAR PATTERN",
@@ -592,6 +599,30 @@ fn update_measure(app: &SolveApp, d: &mut Dialog) {
}
}
/// The preferences, applied as they change.
fn preferences_rows(app: &mut SolveApp, ui: &mut egui::Ui) {
ui.label("Theme");
let mut theme = usize::from(!app.ui.dark);
combo(ui, "pref_theme", &["Dark", "Light"], &mut theme);
app.ui.dark = theme == 0;
ui.end_row();
ui.label("Grid");
ui.checkbox(&mut app.ui.show_grid, "");
ui.end_row();
ui.label("Perspective view");
ui.checkbox(&mut app.ui.perspective, "");
ui.end_row();
ui.label("Project face edges into new sketches");
let mut auto = app.session.auto_project;
if ui.checkbox(&mut auto, "").changed() {
let _ = app.run("sketch.auto_project", json!({ "value": auto }));
}
ui.end_row();
ui.label("Click selects whole bodies");
ui.checkbox(&mut app.ui.pick_bodies, "");
ui.end_row();
}
fn fmt_mm(v: &Value) -> String {
v.as_f64().map(|x| format!("{x:.3} mm")).unwrap_or_default()
}
@@ -755,6 +786,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
}
Kind::Mirror => {}
Kind::Measure { result, .. } => measure_rows(ui, result.as_ref()),
Kind::Preferences => preferences_rows(app, ui),
Kind::Material { index } => {
let names: Vec<&str> = solvecraft_engine::doc::MATERIALS.iter().map(|(n, _)| *n).collect();
ui.label("Material");
@@ -888,7 +920,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
});
ui.add_space(6.0);
ui.horizontal(|ui| {
if !matches!(d.kind, Kind::Sketch | Kind::Params { .. } | Kind::Measure { .. })
if !matches!(d.kind, Kind::Sketch | Kind::Params { .. } | Kind::Measure { .. } | Kind::Preferences)
&& ui
.add(
egui::Button::new(
@@ -901,7 +933,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
{
ok = true;
}
let close = if matches!(d.kind, Kind::Params { .. } | Kind::Measure { .. }) { "Close" } else { "Cancel" };
let close = if matches!(d.kind, Kind::Params { .. } | Kind::Measure { .. } | Kind::Preferences) { "Close" } else { "Cancel" };
if ui.add(egui::Button::new(close).min_size(vec2(70.0, 24.0))).clicked() {
cancel = true;
}
@@ -914,7 +946,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
let (enter_free, esc) = ctx.input(|i| (i.key_pressed(egui::Key::Enter), i.key_pressed(egui::Key::Escape)));
let nothing_focused = ctx.memory(|m| m.focused().is_none());
let canvas_enter = enter_free && ctx.memory(|m| m.had_focus_last_frame(egui::Id::new("sc_canvas_value")));
let enter_applies = !matches!(d.kind, Kind::Sketch | Kind::Params { .. } | Kind::Measure { .. } | Kind::ConfirmDelete { .. });
let enter_applies = !matches!(d.kind, Kind::Sketch | Kind::Params { .. } | Kind::Measure { .. } | Kind::ConfirmDelete { .. } | Kind::Preferences);
if enter_applies && (enter || canvas_enter || (enter_free && nothing_focused)) {
ok = true;
}
@@ -1300,7 +1332,7 @@ fn dialog_commands(app: &SolveApp, d: &Dialog) -> Result<Vec<(String, Value)>, S
Kind::EditParam { name, expr } => ("ChangeParameterCommand", json!({"name": name, "expression": expr})),
Kind::Rename { feature, name } => ("FusionRenameTimelineEntryCommand", json!({"feature": feature, "name": name})),
Kind::ConfirmDelete { feature, .. } => ("FusionDeleteCommand", json!({ "features": [feature.to_string()] })),
Kind::Sketch | Kind::Params { .. } | Kind::Measure { .. } => return Ok(Vec::new()),
Kind::Sketch | Kind::Params { .. } | Kind::Measure { .. } | Kind::Preferences => return Ok(Vec::new()),
};
let mut params = params;
if let Value::Object(m) = &mut params {
+23 -4
View File
@@ -159,16 +159,35 @@ impl SolveApp {
/// Preferences kept between runs (JSON), for the host to store.
pub fn prefs(&self) -> String {
json!({ "dark": self.ui.dark }).to_string()
json!({
"dark": self.ui.dark,
"grid": self.ui.show_grid,
"perspective": self.ui.perspective,
"pick_bodies": self.ui.pick_bodies,
"auto_project": self.session.auto_project,
})
.to_string()
}
/// Restore preferences saved with [`SolveApp::prefs`].
pub fn load_prefs(&mut self, prefs: &str) {
if let Ok(v) = serde_json::from_str::<Value>(prefs)
&& let Some(d) = v.get("dark").and_then(Value::as_bool)
{
let Ok(v) = serde_json::from_str::<Value>(prefs) else { return };
let flag = |k: &str| v.get(k).and_then(Value::as_bool);
if let Some(d) = flag("dark") {
self.ui.dark = d;
}
if let Some(g) = flag("grid") {
self.ui.show_grid = g;
}
if let Some(p) = flag("perspective") {
self.ui.perspective = p;
}
if let Some(b) = flag("pick_bodies") {
self.ui.pick_bodies = b;
}
if let Some(a) = flag("auto_project") {
self.session.auto_project = a;
}
}
pub fn with_control(mut self, rx: Receiver<ControlRequest>) -> Self {
+4
View File
@@ -159,6 +159,10 @@ fn file_menu(app: &mut SolveApp, ctx: &egui::Context) {
close = true;
}
ui.separator();
if item(ui, "Preferences…", "") {
app.dialog = Some(crate::dialogs::Dialog::preferences());
close = true;
}
if item(ui, "Quit", "") {
app.quit_requested = true;
close = true;