mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-24 02:46:51 -06:00
Options: an interface scale preference (#1508)
interfaceScale (0.5-3, default 1) in the OPTIONS JSON form and the Display tab scales every menu, palette, dialog and the command line through egui's zoom factor, and is kept in the profile. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
94aad92f1d
commit
64e55f903c
@@ -1219,6 +1219,7 @@
|
||||
Show System Cursor Показувати системний курсор
|
||||
Animation Effects Ефекти анімації
|
||||
Animation effects Ефекти анімації
|
||||
Interface scale Масштаб інтерфейсу
|
||||
Import... Імпорт...
|
||||
Export... Експорт...
|
||||
Batch Publish... Пакетна публікація...
|
||||
|
||||
|
@@ -224,6 +224,8 @@ pub struct CadApp {
|
||||
shown_follows_system: bool,
|
||||
/// Whether egui was last told to animate (`None` until the first frame).
|
||||
shown_animations: Option<bool>,
|
||||
/// The interface scale last handed to egui (`None` until the first frame).
|
||||
shown_interface_scale: Option<f32>,
|
||||
pub frame_ms: f64,
|
||||
pub quit_requested: bool,
|
||||
/// The app may close although drawings have unsaved changes (they were dealt with, or the
|
||||
@@ -271,6 +273,7 @@ impl CadApp {
|
||||
shown_theme: None,
|
||||
shown_follows_system: false,
|
||||
shown_animations: None,
|
||||
shown_interface_scale: None,
|
||||
frame_ms: 0.0,
|
||||
quit_requested: false,
|
||||
quit_confirmed: false,
|
||||
@@ -561,6 +564,13 @@ impl CadApp {
|
||||
if !animate {
|
||||
animation::unsmooth_scroll(ctx);
|
||||
}
|
||||
// Applied only when the saved scale changes, so egui's own keyboard zoom keeps working
|
||||
// within a session (and is never saved).
|
||||
let scale = self.options.interface_scale;
|
||||
if self.shown_interface_scale != Some(scale) {
|
||||
ctx.set_zoom_factor(scale);
|
||||
self.shown_interface_scale = Some(scale);
|
||||
}
|
||||
self.drain_control(ctx);
|
||||
self.finish_opening();
|
||||
self.opening.show(ctx);
|
||||
|
||||
@@ -161,6 +161,19 @@ fn display(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
||||
}
|
||||
ui.end_row();
|
||||
|
||||
ui.label(crate::tl!("Interface scale"));
|
||||
let scale = app.options.interface_scale;
|
||||
let percent = |s: f32| format!("{} %", (s * 100.0).round());
|
||||
egui::ComboBox::from_id_salt("options_interface_scale").selected_text(percent(scale)).show_ui(ui, |ui| {
|
||||
for n in [75u16, 100, 125, 150, 175, 200, 250, 300] {
|
||||
let s = f32::from(n) / 100.0;
|
||||
if ui.selectable_label((scale - s).abs() < 1e-4, format!("{n} %")).clicked() && (scale - s).abs() >= 1e-4 {
|
||||
changes.insert("interfaceScale".into(), json!(s));
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label(crate::tl!("Model background"));
|
||||
color_row(ui, app.options.model_background, Tokens::DARK.canvas, "modelBackground", changes);
|
||||
ui.end_row();
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
//! | `modelBackground`, `paperBackground` | `"#rrggbb"`, or `null` for the default |
|
||||
//! | `selectionColor` | the colour selected objects are highlighted in: `"#rrggbb"`, or `null` for the theme's |
|
||||
//! | `snapMarkerSize` | object snap marker size in points, 5–31 |
|
||||
//! | `interfaceScale` | the interface's size, 0.5–3 (1: the system's own) |
|
||||
//!
|
||||
//! SAVETIME and SAVEFILEPATH are the engine's autosave settings, kept with the autosave state
|
||||
//! rather than in the profile.
|
||||
@@ -52,6 +53,9 @@ const MAX_PATH_CHARS: usize = 1024;
|
||||
/// Object snap marker sizes the Options dialog offers, in points.
|
||||
pub const SNAP_MARKER_SIZES: std::ops::RangeInclusive<f32> = 5.0..=31.0;
|
||||
|
||||
/// Interface scales the JSON form accepts (1: the system's own size).
|
||||
pub const INTERFACE_SCALES: std::ops::RangeInclusive<f32> = 0.5..=3.0;
|
||||
|
||||
/// The interface's own preferences (the drafting ones are engine settings).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct UiOptions {
|
||||
@@ -66,6 +70,8 @@ pub struct UiOptions {
|
||||
/// While a command runs: a quick right-click is Enter and a held one opens the shortcut menu
|
||||
/// (AutoCAD's time-sensitive right-click). Off: a right-click always opens the menu.
|
||||
pub time_sensitive_right_click: bool,
|
||||
/// The size of every menu, palette, dialog and the command line (1: the system's own).
|
||||
pub interface_scale: f32,
|
||||
}
|
||||
|
||||
impl Default for UiOptions {
|
||||
@@ -76,6 +82,7 @@ impl Default for UiOptions {
|
||||
snap_marker_size: theme::SNAP_MARKER,
|
||||
selection_color: None,
|
||||
time_sensitive_right_click: true,
|
||||
interface_scale: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,6 +148,7 @@ struct UiChanges {
|
||||
savetime: Option<u32>,
|
||||
savefilepath: Option<String>,
|
||||
time_sensitive_right_click: Option<bool>,
|
||||
interface_scale: Option<f32>,
|
||||
}
|
||||
|
||||
fn color_param(key: &str, v: &Value) -> Result<Option<Color32>, String> {
|
||||
@@ -174,6 +182,10 @@ fn parse_ui(key: &str, v: &Value, c: &mut UiChanges) -> Result<bool, String> {
|
||||
let n = v.as_f64().filter(|n| SNAP_MARKER_SIZES.contains(&(*n as f32)) && n.is_finite()).ok_or("snapMarkerSize must be from 5 to 31")?;
|
||||
c.snap_marker_size = Some(n as f32);
|
||||
}
|
||||
"interfaceScale" => {
|
||||
let n = v.as_f64().filter(|n| n.is_finite() && INTERFACE_SCALES.contains(&(*n as f32))).ok_or("interfaceScale must be from 0.5 to 3")?;
|
||||
c.interface_scale = Some(n as f32);
|
||||
}
|
||||
"saveVersion" => {
|
||||
let name = v.as_str().map_or_else(|| v.to_string(), str::to_string);
|
||||
c.save_version = Some(crate::SaveVersion::parse(name.trim()).ok_or("saveVersion must be 2000, 2004, 2007, 2010, 2013 or 2018")?);
|
||||
@@ -210,6 +222,7 @@ fn ui_json(app: &CadApp) -> Map<String, Value> {
|
||||
m.insert("paperBackground".into(), color_json(o.paper_background));
|
||||
m.insert("selectionColor".into(), color_json(o.selection_color));
|
||||
m.insert("snapMarkerSize".into(), json!(o.snap_marker_size));
|
||||
m.insert("interfaceScale".into(), json!(o.interface_scale));
|
||||
m.insert("saveVersion".into(), json!(app.ui.save_version));
|
||||
m.insert("SAVETIME".into(), json!(cadcraft_engine::autosave::savetime()));
|
||||
m.insert("SAVEFILEPATH".into(), json!(cadcraft_engine::autosave::savefilepath()));
|
||||
@@ -272,6 +285,9 @@ fn run_json(app: &mut CadApp, params: &Value) -> Result<Value, String> {
|
||||
if let Some(n) = ui.snap_marker_size {
|
||||
app.options.snap_marker_size = n;
|
||||
}
|
||||
if let Some(n) = ui.interface_scale {
|
||||
app.options.interface_scale = n;
|
||||
}
|
||||
if let Some(v) = ui.save_version {
|
||||
app.ui.save_version = v.year().to_string();
|
||||
}
|
||||
@@ -314,6 +330,7 @@ impl CadApp {
|
||||
"selectionColor": color_json(self.options.selection_color),
|
||||
"snapMarkerSize": self.options.snap_marker_size,
|
||||
"timeSensitiveRightClick": self.options.time_sensitive_right_click,
|
||||
"interfaceScale": self.options.interface_scale,
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
@@ -329,7 +346,9 @@ impl CadApp {
|
||||
cadcraft_engine::cmd::options::restore(&mut self.session, vars);
|
||||
}
|
||||
let mut c = UiChanges::default();
|
||||
for key in ["systemCursor", "modelBackground", "paperBackground", "selectionColor", "snapMarkerSize", "timeSensitiveRightClick"] {
|
||||
for key in
|
||||
["systemCursor", "modelBackground", "paperBackground", "selectionColor", "snapMarkerSize", "timeSensitiveRightClick", "interfaceScale"]
|
||||
{
|
||||
if let Some(value) = v.get(key) {
|
||||
let _ = parse_ui(key, value, &mut c);
|
||||
}
|
||||
@@ -353,6 +372,9 @@ impl CadApp {
|
||||
if let Some(b) = c.time_sensitive_right_click {
|
||||
self.options.time_sensitive_right_click = b;
|
||||
}
|
||||
if let Some(n) = c.interface_scale {
|
||||
self.options.interface_scale = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,3 +107,49 @@ fn autosave_save_version_and_right_click_in_options() {
|
||||
assert!(text.contains("Cancel") && text.contains("Snap Overrides"), "the menu opens:\n{text}");
|
||||
assert!(a.session.prompt_text().contains("next point"), "no Enter: {}", a.session.prompt_text());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interface_scale_in_options() {
|
||||
let mut a = app();
|
||||
assert_eq!(a.run("options", json!({})).unwrap()["interfaceScale"], json!(1.0));
|
||||
|
||||
let r = a.run("options", json!({"interfaceScale": 1.5})).unwrap();
|
||||
assert_eq!(r["interfaceScale"], json!(1.5));
|
||||
let ctx = egui::Context::default();
|
||||
frame(&mut a, &ctx, Vec::new(), 0.0);
|
||||
assert_eq!(ctx.zoom_factor(), 1.5, "applied as egui's zoom factor");
|
||||
|
||||
// Refused all or nothing.
|
||||
let before = a.run("options", json!({})).unwrap();
|
||||
for bad in [
|
||||
json!({"interfaceScale": 0.4, "PICKBOX": 9}),
|
||||
json!({"interfaceScale": 3.1}),
|
||||
json!({"interfaceScale": "big", "snapMarkerSize": 20}),
|
||||
json!({"interfaceScale": null}),
|
||||
json!({"interfaceScale": f64::NAN}),
|
||||
] {
|
||||
let err = a.run("options", bad.clone()).expect_err(&bad.to_string());
|
||||
assert!(err.to_string().contains("interfaceScale must be from 0.5 to 3"), "{bad}: {err}");
|
||||
assert_eq!(a.run("options", json!({})).unwrap(), before, "{bad} changed nothing");
|
||||
}
|
||||
|
||||
// It survives a restart; an out-of-range saved value is skipped.
|
||||
let mut b = app();
|
||||
b.load_profile(&a.profile_json());
|
||||
assert_eq!(b.options.interface_scale, 1.5);
|
||||
let mut c = app();
|
||||
c.load_profile(&a.profile_json().replace("\"interfaceScale\":1.5", "\"interfaceScale\":9.0"));
|
||||
assert_eq!(c.options.interface_scale, 1.0);
|
||||
|
||||
// Reset brings back the system's own size.
|
||||
assert_eq!(a.run("options", json!({"reset": true})).unwrap()["interfaceScale"], json!(1.0));
|
||||
frame(&mut a, &ctx, Vec::new(), 0.1);
|
||||
assert_eq!(ctx.zoom_factor(), 1.0);
|
||||
|
||||
// The Display tab shows it.
|
||||
a.run("options", json!({"interfaceScale": 1.25})).unwrap();
|
||||
a.ui.dialog = Some("options:display".into());
|
||||
frame(&mut a, &ctx, Vec::new(), 0.2);
|
||||
let text = frame(&mut a, &ctx, Vec::new(), 0.3);
|
||||
assert!(text.contains("Interface scale") && text.contains("125 %"), "{text}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user