Merge pull request #672 from vangel76/fix-457-ui-scale
#457: Settings > Appearance > UI Scale
This commit is contained in:
commit
66a7239115
|
|
@ -104,6 +104,9 @@ pub struct AppearancePrefs {
|
|||
pub accessible_contrast: bool,
|
||||
/// Highlight (accent) colour of selections, focus and primary buttons (`#rrggbb`).
|
||||
pub highlight_color: String,
|
||||
/// "UI Scale": `auto` (default: the scale the operating system reports) or a fixed
|
||||
/// percentage of [`UI_SCALES`] (`100` = one point per physical pixel).
|
||||
pub ui_scale: String,
|
||||
}
|
||||
|
||||
impl Default for AppearancePrefs {
|
||||
|
|
@ -115,11 +118,19 @@ impl Default for AppearancePrefs {
|
|||
light_theme: "light".into(),
|
||||
accessible_contrast: false,
|
||||
highlight_color: DEFAULT_HIGHLIGHT.into(),
|
||||
ui_scale: "auto".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppearancePrefs {
|
||||
/// The fixed UI scale (physical pixels per point) "UI Scale" asks for, or `None` for `auto`
|
||||
/// (and for a value outside [`UI_SCALES`]), which keeps the operating system's scale.
|
||||
pub fn ui_scale_factor(&self) -> Option<f32> {
|
||||
// `auto` doesn't parse; every other listed value is a positive whole percentage
|
||||
UI_SCALES.iter().any(|(k, _)| *k == self.ui_scale).then(|| self.ui_scale.parse::<f32>().ok()).flatten().map(|pct| pct / 100.0)
|
||||
}
|
||||
|
||||
/// The theme to show (`darkest` | `dark` | `light`): the light or dark theme of the mode. Auto
|
||||
/// follows `system_light` (the operating system's appearance) and is dark without an answer.
|
||||
pub fn shown_theme(&self, system_light: Option<bool>) -> &str {
|
||||
|
|
@ -669,6 +680,17 @@ const BIN_OPEN: &[(&str, &str)] = &[("openInPlace", "Open in place"), ("openNewT
|
|||
const PROJECT_OPEN: &[(&str, &str)] = &[("openNewTab", "Open new tab"), ("openNewWindow", "Open in new window")];
|
||||
const THEMES: &[(&str, &str)] = &[("darkest", "Darkest"), ("dark", "Dark"), ("light", "Light")];
|
||||
pub const APPEARANCE_MODES: &[(&str, &str)] = &[("auto", "Sync with system"), ("dark", "Dark"), ("light", "Light")];
|
||||
pub const UI_SCALES: &[(&str, &str)] = &[
|
||||
("auto", "Auto (system)"),
|
||||
("75", "75%"),
|
||||
("100", "100%"),
|
||||
("125", "125%"),
|
||||
("150", "150%"),
|
||||
("175", "175%"),
|
||||
("200", "200%"),
|
||||
("250", "250%"),
|
||||
("300", "300%"),
|
||||
];
|
||||
pub const DARK_THEMES: &[(&str, &str)] = &[("darkest", "Darkest"), ("dark", "Dark")];
|
||||
pub const LIGHT_THEMES: &[(&str, &str)] = &[("light", "Light")];
|
||||
const MIXDOWN: &[(&str, &str)] = &[("front", "Front Only"), ("frontRear", "Front + Rear"), ("frontLfe", "Front + LFE"), ("frontRearLfe", "Front + Rear + LFE")];
|
||||
|
|
@ -795,6 +817,7 @@ static CATEGORIES: &[Category] = &[
|
|||
f("appearance.colorTheme", "Color Theme", Kind::Choice(THEMES), true),
|
||||
b("appearance.accessibleContrast", "Accessible color contrast", true),
|
||||
f("appearance.highlightColor", "Highlight Color", Kind::Color, true),
|
||||
f("appearance.uiScale", "UI Scale", Kind::Choice(UI_SCALES), true),
|
||||
],
|
||||
},
|
||||
Category {
|
||||
|
|
|
|||
|
|
@ -3504,6 +3504,8 @@
|
|||
Light Theme Tema claro
|
||||
Next Appearance Mode Siguiente modo de apariencia
|
||||
Sync with system Sincronizar con el sistema
|
||||
UI Scale Escala de la interfaz
|
||||
Auto (system) Automática (sistema)
|
||||
In Entrada
|
||||
Out Salida
|
||||
Export Markers as CSV… Exportar marcadores como CSV…
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 3 and column 95.
|
|
|
@ -3448,6 +3448,8 @@
|
|||
Light Theme ライトテーマ
|
||||
Next Appearance Mode 次の外観モード
|
||||
Sync with system システムと同期
|
||||
UI Scale UI のスケール
|
||||
Auto (system) 自動 (システム)
|
||||
Go to previous keyframe 前のキーフレームへ移動
|
||||
Go to next keyframe 次のキーフレームへ移動
|
||||
Add keyframe キーフレームを追加
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 11 and column 59.
|
|
|
@ -3490,6 +3490,8 @@
|
|||
Appearance Mode 外观模式
|
||||
Next Appearance Mode 下一个外观模式
|
||||
Sync with system 跟随系统
|
||||
UI Scale 界面缩放
|
||||
Auto (system) 自动(系统)
|
||||
GPU Rendering GPU 渲染
|
||||
Composite on the GPU when available 可用时在 GPU 上合成
|
||||
Text to Speech 文字转语音
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 3 and column 95.
|
|
|
@ -321,6 +321,8 @@ pub struct FilmcraftApp {
|
|||
watched_render: Option<(u64, Tick)>,
|
||||
/// Settings last applied to the UI (theme, tooltips, frame cache, audio device).
|
||||
applied_prefs: Option<filmcraft_engine::autosave::Preferences>,
|
||||
/// Settings ▸ Appearance ▸ UI Scale last applied, with the system scale it was applied for.
|
||||
applied_ui_scale: Option<(Option<f32>, f32)>,
|
||||
workspace_restored: bool,
|
||||
/// Window ▸ Workspaces: saved layouts ([`dock::WORKSPACES_FILE`] in the data directory).
|
||||
pub workspaces: dock::WorkspacePrefs,
|
||||
|
|
@ -528,6 +530,7 @@ impl FilmcraftApp {
|
|||
gpu: None,
|
||||
watched_render: None,
|
||||
applied_prefs: None,
|
||||
applied_ui_scale: None,
|
||||
workspace_restored: false,
|
||||
workspaces,
|
||||
menu_workspaces: Default::default(),
|
||||
|
|
@ -587,9 +590,28 @@ impl FilmcraftApp {
|
|||
ctx.global_style_mut(|s| s.interaction.tooltip_delay = delay);
|
||||
}
|
||||
|
||||
/// Settings ▸ Appearance ▸ UI Scale: a fixed scale makes a point that many physical pixels,
|
||||
/// whatever scale the system reports. It is applied only when the setting or the system scale
|
||||
/// changes, so Ctrl+- / Ctrl++ still zoom the interface for the session; going back to Auto
|
||||
/// restores the system's scale.
|
||||
fn apply_ui_scale(&mut self, ctx: &egui::Context) {
|
||||
let want = self.session.prefs.appearance.ui_scale_factor();
|
||||
let native = ctx.native_pixels_per_point().filter(|v| v.is_finite() && *v > 0.0).unwrap_or(1.0);
|
||||
if self.applied_ui_scale == Some((want, native)) {
|
||||
return;
|
||||
}
|
||||
let prev = self.applied_ui_scale.replace((want, native));
|
||||
match want {
|
||||
Some(scale) => ctx.set_zoom_factor(scale / native),
|
||||
None if prev.is_some_and(|(p, _)| p.is_some()) => ctx.set_zoom_factor(1.0),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the UI follow the settings after they change (theme, tooltips, frame cache budget,
|
||||
/// play after rendering, audio device).
|
||||
pub fn apply_prefs(&mut self, ctx: &egui::Context) {
|
||||
self.apply_ui_scale(ctx);
|
||||
// Appearance Mode ▸ Sync with system: follow the system while running. The host reports
|
||||
// changes as they happen (no polling) and wakes the UI only when the value changes.
|
||||
if self.applied_prefs.is_some() {
|
||||
|
|
|
|||
|
|
@ -785,6 +785,30 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::theme::ThemeKind;
|
||||
|
||||
/// #457: on a desktop that reports 150%, UI Scale 100% makes a point one physical pixel. It is
|
||||
/// applied once, so Ctrl+- still zooms for the session, and Auto gives the system's scale back.
|
||||
#[test]
|
||||
fn ui_scale_overrides_the_system_scale_and_auto_restores_it() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = FilmcraftApp::new(filmcraft_engine::Session::default());
|
||||
// a frame applying the settings, then the next one, where a new zoom takes effect
|
||||
let pass = |app: &mut FilmcraftApp| {
|
||||
let mut input = egui::RawInput::default();
|
||||
input.viewports.entry(egui::ViewportId::ROOT).or_default().native_pixels_per_point = Some(1.5);
|
||||
ctx.run_ui(input.clone(), |ui| app.apply_prefs(ui.ctx())).textures_delta.clear();
|
||||
ctx.run_ui(input, |_| {}).textures_delta.clear();
|
||||
ctx.zoom_factor()
|
||||
};
|
||||
assert_eq!(pass(&mut app), 1.0, "Auto keeps the system's scale");
|
||||
app.session.execute("prefs.set", json!({"key": "appearance.uiScale", "value": "100"})).unwrap();
|
||||
assert_eq!(pass(&mut app), 1.0 / 1.5, "100% is one physical pixel per point");
|
||||
ctx.set_zoom_factor(0.9 / 1.5); // Ctrl+-
|
||||
assert_eq!(pass(&mut app), 0.9 / 1.5, "a session zoom isn't undone every frame");
|
||||
assert!(app.session.execute("prefs.set", json!({"key": "appearance.uiScale", "value": "130"})).is_err());
|
||||
app.session.execute("prefs.set", json!({"key": "appearance.uiScale", "value": "auto"})).unwrap();
|
||||
assert_eq!(pass(&mut app), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appearance_button_cycles_modes_without_losing_theme_choices() {
|
||||
let ctx = egui::Context::default();
|
||||
|
|
|
|||
Loading…
Reference in New Issue