agg_gui/device_scale.rs
1//! Global device-pixel-ratio (DPI) scale factor.
2//!
3//! All layout values (`margin`, `padding`, `min_size`, `max_size`) are stored
4//! in **logical (device-independent) units**. Layout algorithms call
5//! [`device_scale`] once per frame (or per layout pass) and multiply logical
6//! values by the factor to obtain physical pixel values.
7//!
8//! # Typical usage
9//!
10//! ```rust,ignore
11//! // At startup / when the window moves to a different monitor:
12//! agg_gui::device_scale::set_device_scale(window.scale_factor());
13//!
14//! // Inside a layout algorithm:
15//! let physical_margin = child.margin().scale(device_scale());
16//! ```
17//!
18//! # Thread safety
19//!
20//! GUI layout always runs on the main thread. The value is stored in a
21//! thread-local [`Cell`] so reads are zero-cost (no atomic, no lock).
22//! On WASM there is only one thread, so this works correctly there too.
23
24use std::cell::Cell;
25
26thread_local! {
27 static DEVICE_SCALE: Cell<f64> = Cell::new(1.0);
28}
29
30/// Return the current device scale factor (default `1.0`).
31///
32/// This is the ratio of physical pixels to logical pixels. For a 2× HiDPI
33/// display this is `2.0`; for a standard display it is `1.0`.
34#[inline]
35pub fn device_scale() -> f64 {
36 DEVICE_SCALE.with(|s| s.get())
37}
38
39/// Set the device scale factor.
40///
41/// Call this at application startup and whenever the window moves to a monitor
42/// with a different DPI. A value of `1.0` means one logical unit equals one
43/// physical pixel.
44///
45/// # Panics
46///
47/// Panics in debug builds if `scale` is not positive.
48pub fn set_device_scale(scale: f64) {
49 debug_assert!(
50 scale > 0.0,
51 "DeviceScale must be a positive value, got {scale}"
52 );
53 DEVICE_SCALE.with(|s| s.set(scale));
54}