mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-17 04:39:54 +02:00
Co-authored-by: John Hass <john@basement-lab.hasshouse.com>
This commit is contained in:
parent
a0823c5e41
commit
a502a227a9
|
|
@ -143,11 +143,38 @@ fn presets_store(app: &mut PhotocraftApp) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resolve an absolute pixels-per-point preference without multiplying the OS DPI twice.
|
||||
/// Monitor dimensions are physical pixels, independent of window size and UI zoom.
|
||||
fn display_scale(pref: prefs::UiScale, native: Option<f32>, monitor_px: Option<egui::Vec2>) -> f32 {
|
||||
let native = native.filter(|v| v.is_finite() && *v > 0.0).unwrap_or(1.0);
|
||||
match pref {
|
||||
prefs::UiScale::P100 => 1.0,
|
||||
prefs::UiScale::P200 => 2.0,
|
||||
prefs::UiScale::Auto => {
|
||||
// A 4K display needs at least 200%; preserve larger system scales.
|
||||
let is_4k = monitor_px.is_some_and(|s| s.x.is_finite() && s.y.is_finite() && s.x.min(s.y) >= 2160.0 && s.x.max(s.y) >= 3840.0);
|
||||
if is_4k { native.max(2.0) } else { native }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_display_scale(app: &PhotocraftApp, ctx: &egui::Context) {
|
||||
let native = ctx.native_pixels_per_point();
|
||||
// ViewportInfo uses egui points, including the current UI zoom. Converting back to
|
||||
// physical pixels avoids oscillating between 100% and 200% on successive frames.
|
||||
// logic() can receive new viewport DPI before InputState::pixels_per_point updates.
|
||||
let native_scale = native.filter(|v| v.is_finite() && *v > 0.0).unwrap_or(1.0);
|
||||
let monitor_px = ctx.input(|i| i.viewport().monitor_size).map(|s| s * (native_scale * ctx.zoom_factor()));
|
||||
let scale = display_scale(app.session.prefs().interface.ui_scale, native, monitor_px);
|
||||
ctx.set_zoom_factor(scale / native_scale);
|
||||
}
|
||||
|
||||
/// Per-frame upkeep: theme sync, persistence, autosave and the history log.
|
||||
pub fn tick(app: &mut PhotocraftApp, ctx: &egui::Context) {
|
||||
if !app.prefs_rt.loaded {
|
||||
load(app);
|
||||
}
|
||||
sync_display_scale(app, ctx);
|
||||
// Interface theme: a preference change applies to the UI; a theme picked from the Window
|
||||
// menu is stored as the preference.
|
||||
let pref = app.session.prefs().interface.theme;
|
||||
|
|
@ -1054,6 +1081,58 @@ mod tests {
|
|||
(PhotocraftApp::new(photocraft_engine::Session::new(), services), store)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_scale_detects_4k_and_preserves_larger_system_dpi() {
|
||||
use prefs::UiScale::Auto;
|
||||
for size in [vec2(3840.0, 2160.0), vec2(4096.0, 2160.0), vec2(2160.0, 3840.0)] {
|
||||
assert_eq!(display_scale(Auto, Some(1.0), Some(size)), 2.0);
|
||||
for dpi in [1.25, 1.5, 2.0] {
|
||||
assert_eq!(display_scale(Auto, Some(dpi), Some(size)), 2.0);
|
||||
}
|
||||
}
|
||||
assert_eq!(display_scale(Auto, Some(3.0), Some(vec2(3840.0, 2160.0))), 3.0);
|
||||
for size in [vec2(1920.0, 1080.0), vec2(2560.0, 1440.0), vec2(3840.0, 1080.0)] {
|
||||
assert_eq!(display_scale(Auto, Some(1.0), Some(size)), 1.0);
|
||||
}
|
||||
assert_eq!(display_scale(Auto, None, None), 1.0);
|
||||
assert_eq!(display_scale(Auto, Some(f32::NAN), Some(vec2(f32::INFINITY, 2160.0))), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_preferences_and_monitor_changes_apply_live() {
|
||||
let (mut app, _) = app_with_store();
|
||||
let ctx = egui::Context::default();
|
||||
{
|
||||
let mut step = |physical: egui::Vec2, native: f32, expected: f32| {
|
||||
let mut input = egui::RawInput::default();
|
||||
let viewport = input.viewports.get_mut(&egui::ViewportId::ROOT).unwrap();
|
||||
viewport.native_pixels_per_point = Some(native);
|
||||
viewport.monitor_size = Some(physical / (native * ctx.zoom_factor()));
|
||||
ctx.run_ui(input, |ui| tick(&mut app, ui.ctx())).textures_delta.clear();
|
||||
// Scale changes take effect on the following pass.
|
||||
let mut input = egui::RawInput::default();
|
||||
let viewport = input.viewports.get_mut(&egui::ViewportId::ROOT).unwrap();
|
||||
viewport.native_pixels_per_point = Some(native);
|
||||
viewport.monitor_size = Some(physical / (native * ctx.zoom_factor()));
|
||||
ctx.run_ui(input, |ui| tick(&mut app, ui.ctx())).textures_delta.clear();
|
||||
assert!((ctx.pixels_per_point() - expected).abs() < 1e-4);
|
||||
};
|
||||
for _ in 0..4 {
|
||||
step(vec2(3840.0, 2160.0), 1.0, 2.0);
|
||||
}
|
||||
step(vec2(1920.0, 1080.0), 1.0, 1.0);
|
||||
step(vec2(3840.0, 2160.0), 1.5, 2.0);
|
||||
}
|
||||
for (pref, expected) in [("200", 2.0), ("100", 1.0), ("auto", 1.5)] {
|
||||
app.run("prefs.set", json!({"values": {"interface.uiScale": pref}})).unwrap();
|
||||
let mut input = egui::RawInput::default();
|
||||
input.viewports.get_mut(&egui::ViewportId::ROOT).unwrap().native_pixels_per_point = Some(1.5);
|
||||
ctx.run_ui(input.clone(), |ui| tick(&mut app, ui.ctx())).textures_delta.clear();
|
||||
ctx.run_ui(input, |ui| tick(&mut app, ui.ctx())).textures_delta.clear();
|
||||
assert!((ctx.pixels_per_point() - expected).abs() < 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferences_persist_through_services_and_theme_follows() {
|
||||
let (mut app, store) = app_with_store();
|
||||
|
|
|
|||
|
|
@ -53,3 +53,13 @@ missing-glyph boxes. Check every new panel with the offscreen snapshot tool (`do
|
|||
## Automation for visual checks
|
||||
|
||||
Use `ui.click {x,y}`, `ui.move`, `ui.key` and `ui.type` (synthetic input in screen points) to open menus, popups and context menus, then `ui.screenshot`.
|
||||
|
||||
## High DPI and 4K displays
|
||||
|
||||
Edit → Preferences → Interface → UI Scale applies immediately. Auto follows the operating
|
||||
system's display scale (including fractional scales). For a 4K or larger monitor,
|
||||
Auto uses at least 200% so text and controls remain readable. Detection uses the current monitor,
|
||||
including portrait displays, and updates when the window moves between monitors. If monitor
|
||||
size is unavailable, Auto follows system DPI. The 100% and 200% choices set an absolute UI
|
||||
scale, allowing large 4K displays to use smaller controls when desired. Canvas zoom shortcuts
|
||||
continue to control the document independently of UI scaling.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user