feat(options): interface scale for the whole window

On a non-Retina screen macOS draws the interface at 1x, which reads small
on a Full HD monitor, and there was no way to enlarge it. Options >
Display now offers an interface scale of 75–200 % in 25 % steps, applied
through the window's scale factor on top of the display's own and kept
with the user settings. It is a list rather than a slider because the
dialog rescales under the pointer as the value changes.

Fixes #1655

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-08 14:41:46 +03:00
1 parent ff5bfc8c47
commit 340104bf1b
29 files changed
+65

No files matched your search

+6
View File
@@ -561,6 +561,8 @@ pub(super) struct OpenCADStudio {
zoom_wheel_reversed: bool,
/// Scroll pans and Ctrl+scroll zooms (touchpads); see `scroll_intent`.
touchpad_pan: bool,
/// Interface scale in percent, applied through the window scale factor.
ui_scale_percent: u16,
/// Mouse-wheel zoom sensitivity (ZOOMFACTOR), held inside
/// `settings::ZOOM_FACTOR_MIN..=settings::ZOOM_FACTOR_MAX`. `SETVAR`
/// only sets the range the system variable itself has; the Options
@@ -2378,6 +2380,8 @@ pub enum Message {
/// Toggle reversing the mouse-wheel zoom direction (ZOOMWHEEL).
ZoomWheelReversedChanged(bool),
TouchpadPanChanged(bool),
/// Interface scale in percent (Options > Display).
UiScaleChanged(u16),
/// Change how far one wheel notch zooms, from the Options slider
/// (ZOOMFACTOR).
ZoomFactorChanged(i32),
@@ -4161,6 +4165,7 @@ impl OpenCADStudio {
polar_increment_deg: 45.0,
zoom_wheel_reversed: false,
touchpad_pan: false,
ui_scale_percent: 100,
zoom_factor: 60,
zoom_factor_input: 60.to_string(),
cursor_size: 5,
@@ -4904,6 +4909,7 @@ pub fn run() -> iced::Result {
..iced::Settings::default()
})
.subscription(OpenCADStudio::subscription)
.scale_factor(|state: &OpenCADStudio, _| f32::from(state.ui_scale_percent) / 100.0)
.title(|state: &OpenCADStudio, window_id: window::Id| {
let _ = window_id; // all dialogs are in-canvas modals now
if let Some(tab) = state.tabs.get(state.active_tab) {
+8
View File
@@ -183,6 +183,11 @@ pub const ZOOM_FACTOR_MAX: i32 = 500;
/// Hold a ZOOMFACTOR inside the range the application can store, wherever it
/// arrives from — the slider, the Options field, or a configuration file
/// written by hand or by an older build.
/// Interface scale in percent, kept to the range the Options slider offers.
pub fn clamp_ui_scale(percent: u16) -> u16 {
percent.clamp(75, 200)
}
pub fn clamp_zoom_factor(v: i32) -> i32 {
v.clamp(ZOOM_FACTOR_MIN, ZOOM_FACTOR_MAX)
}
@@ -391,6 +396,8 @@ pub struct UserSettings {
/// Scrolling pans and Ctrl+scroll (a touchpad pinch) zooms, for a
/// touchpad that reports two-finger drags as wheel scrolls.
pub touchpad_pan: bool,
/// Interface scale in percent, on top of the display's own scale.
pub ui_scale_percent: u16,
pub zoom_factor: i32,
/// CURSORSIZE: normalized crosshair reach; 5 retains the original 60 px arms.
pub cursor_size: i32,
@@ -811,6 +818,7 @@ impl Default for UserSettings {
polar_increment_deg: 45.0,
zoom_wheel_reversed: false,
touchpad_pan: false,
ui_scale_percent: 100,
zoom_factor: 60,
cursor_size: 5,
pick_box: 3,
+2
View File
@@ -663,6 +663,7 @@ impl OpenCADStudio {
polar_increment_deg: self.polar_increment_deg,
zoom_wheel_reversed: self.zoom_wheel_reversed,
touchpad_pan: self.touchpad_pan,
ui_scale_percent: self.ui_scale_percent,
zoom_factor: self.zoom_factor,
cursor_size: self.cursor_size,
pick_box: self.pick_box,
@@ -764,6 +765,7 @@ impl OpenCADStudio {
self.polar_increment_deg = s.polar_increment_deg;
self.zoom_wheel_reversed = s.zoom_wheel_reversed;
self.touchpad_pan = s.touchpad_pan;
self.ui_scale_percent = crate::app::settings::clamp_ui_scale(s.ui_scale_percent);
self.zoom_factor = crate::app::settings::clamp_zoom_factor(s.zoom_factor);
// Like the drafting-rotation field below, the Options field edits a
// buffer rather than the value, so it is reseeded whenever the value
+6
View File
@@ -8391,6 +8391,12 @@ impl OpenCADStudio {
Task::none()
}
Message::UiScaleChanged(percent) => {
self.ui_scale_percent = crate::app::settings::clamp_ui_scale(percent);
self.persist_settings_if_changed();
Task::none()
}
Message::ZoomFactorChanged(factor) => {
self.zoom_factor = crate::app::settings::clamp_zoom_factor(factor);
// The field beside the slider reads the same value, so the
+1
View File
@@ -317,6 +317,7 @@ impl OpenCADStudio {
commandline_fade_ms: self.commandline_fade_ms,
zoom_wheel_reversed: self.zoom_wheel_reversed,
touchpad_pan: self.touchpad_pan,
ui_scale_percent: self.ui_scale_percent,
zoom_factor: self.zoom_factor,
texteditmode: self.texteditmode,
dimension_continue_mode: self.dimension_continue_mode,
+1
View File
@@ -4798,6 +4798,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Zoom" => Some(("common", "zoom")),
"Reverse mouse wheel zoom (ZOOMWHEEL)" => Some(("common", "reverse-mouse-wheel-zoom-zoomwheel")),
"Touchpad: scroll pans, Ctrl+scroll or pinch zooms" => Some(("common", "touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms")),
"Interface scale" => Some(("common", "interface-scale")),
"Zoom factor" => Some(("common", "zoom-factor")),
"How far one wheel notch zooms (ZOOMFACTOR)." => Some(("common", "how-far-one-wheel-notch-zooms-zoomfactor")),
"Text and Dimensions" => Some(("common", "text-and-dimensions")),
+19
View File
@@ -61,6 +61,8 @@ pub struct AppPrefs {
pub zoom_wheel_reversed: bool,
/// Scroll pans and Ctrl+scroll zooms (touchpads).
pub touchpad_pan: bool,
/// Interface scale in percent.
pub ui_scale_percent: u16,
/// ZOOMFACTOR. The slider sets the range the system variable has; the
/// field beside it reaches `settings::ZOOM_FACTOR_MAX`.
pub zoom_factor: i32,
@@ -679,6 +681,23 @@ pub fn view_window<'a>(
.width(sizing.width),
Space::new().height(12),
color_controls,
Space::new().height(16),
// A list, not a slider: the dialog rescales under the cursor as the
// value changes, which would drag a slider along with it.
row![
text(crate::t!("Interface scale")).size(12).width(150),
iced::widget::pick_list(
Some(format!("{}%", prefs.ui_scale_percent)),
[75u16, 100, 125, 150, 175, 200].map(|p| format!("{p}%")).to_vec(),
|label: &String| label.clone(),
)
.on_select(|label: String| {
Message::UiScaleChanged(label.trim_end_matches('%').parse().unwrap_or(100))
})
.width(sizing.width),
]
.spacing(12)
.align_y(iced::Center),
Space::new().height(24),
row![
text(crate::t!("Model Space Appearance")).size(15),