zng_ext_window/
monitor.rs

1use core::fmt;
2use std::sync::Arc;
3
4use zng_app::event::{event, event_args};
5use zng_app::view_process::raw_events::RAW_MONITORS_CHANGED_EVENT;
6use zng_app::window::{MonitorId, WindowId};
7use zng_app_context::app_local;
8use zng_layout::unit::{
9    Dip, DipRect, DipSize, DipToPx, Factor, FactorUnits, Frequency, FrequencyUnits as _, Px, PxDensity, PxPoint, PxRect, PxSize, PxToDip,
10};
11use zng_txt::{ToTxt, Txt};
12use zng_unique_id::IdMap;
13use zng_var::{Var, VarValue, impl_from_and_into_var, var};
14use zng_view_api::window::VideoMode;
15
16use crate::WINDOWS;
17
18app_local! {
19    static MONITORS_SV: MonitorsService = MonitorsService::new();
20}
21
22/// Monitors service.
23///
24/// List monitor screens and configure the pixel density of a given monitor.
25///
26/// # Uses
27///
28/// Uses of this service:
29///
30/// #### Start Position
31///
32/// Windows are positioned on a virtual screen that overlaps all monitors, but all position configuration is done relative to
33/// an specific parent monitor, it is important to track the parent monitor as it defines properties that affect the layout of the window.
34/// This service is used to provide information to implement this feature.
35///
36/// #### Fullscreen
37///
38/// To set a window to fullscreen a monitor must be selected, by default it can be the one the window is at but
39/// the users may want to select a monitor. To enter fullscreen exclusive the video mode must also be decided, all video
40/// modes supported by the monitor are available in the [`MonitorInfo`] value.
41///
42/// #### Real-Size Preview
43///
44/// Some apps, like image editors, may implement a feature where the user can preview the *real* dimensions of
45/// the content they are editing, to accurately implement this you must known the real dimensions of the monitor screen,
46/// unfortunately this information is not provided by display drivers. You can ask the user to measure their screen and
47/// set the pixel density for the screen using the [`density`] variable, this value is then available in the [`LayoutMetrics`]
48/// for the next layout. If not set, the default is `96.0ppi`.
49///
50/// [`density`]: MonitorInfo::density
51/// [`scale_factor`]: MonitorInfo::scale_factor
52/// [`LayoutMetrics`]: zng_layout::context::LayoutMetrics
53pub struct MONITORS;
54impl MONITORS {
55    /// Get monitor info.
56    ///
57    /// Returns `None` if the monitor was not found or the app is running in headless mode without renderer.
58    pub fn monitor(&self, monitor_id: MonitorId) -> Option<MonitorInfo> {
59        MONITORS_SV.read().monitors.with(|m| m.get(&monitor_id).cloned())
60    }
61
62    /// List all available monitors.
63    ///
64    /// Is empty if no monitor was found or the app is running in headless mode without renderer.
65    pub fn available_monitors(&self) -> Var<Vec<MonitorInfo>> {
66        MONITORS_SV.read().monitors.map(|w| {
67            let mut list: Vec<_> = w.values().cloned().collect();
68            list.sort_by(|a, b| a.name.with(|a| b.name.with(|b| a.cmp(b))));
69            list
70        })
71    }
72
73    /// Gets the monitor info marked as primary.
74    pub fn primary_monitor(&self) -> Var<Option<MonitorInfo>> {
75        MONITORS_SV
76            .read()
77            .monitors
78            .map(|w| w.values().find(|m| m.is_primary().get()).cloned())
79    }
80}
81
82struct MonitorsService {
83    monitors: Var<IdMap<MonitorId, MonitorInfo>>,
84}
85impl MonitorsService {
86    fn new() -> Self {
87        // track monitor scale factors
88        #[allow(deprecated)]
89        zng_app::view_process::raw_events::RAW_SCALE_FACTOR_CHANGED_EVENT
90            .hook(|args| {
91                MONITORS_SV.read().monitors.with(|a| {
92                    if let Some(m) = a.get(&args.monitor_id) {
93                        tracing::trace!("monitor scale factor changed, {:?} {:?}", args.monitor_id, args.scale_factor);
94                        m.scale_factor.set(args.scale_factor);
95                    }
96                });
97                true
98            })
99            .perm();
100
101        // track monitors
102        RAW_MONITORS_CHANGED_EVENT
103            .hook(|args| {
104                let mut available_monitors: IdMap<_, _> = args.available_monitors.iter().cloned().collect();
105                let event_ts = args.timestamp;
106                let event_propagation = args.propagation.clone();
107
108                MONITORS_SV.read().monitors.modify(move |m| {
109                    let mut removed = vec![];
110                    let mut changed = vec![];
111
112                    m.retain(|key, value| {
113                        if let Some(new) = available_monitors.remove(key) {
114                            if value.update(new) {
115                                tracing::trace!(
116                                    "monitor update, {:?} {:?}",
117                                    key,
118                                    (&value.name.get(), value.position.get(), value.size.get(), value.density.get())
119                                );
120                                changed.push(*key);
121                            }
122                            true
123                        } else {
124                            tracing::trace!(
125                                "monitor removed, {:?} {:?}",
126                                key,
127                                (&value.name.get(), value.position.get(), value.size.get(), value.density.get())
128                            );
129                            removed.push(*key);
130                            false
131                        }
132                    });
133
134                    let mut added = Vec::with_capacity(available_monitors.len());
135
136                    for (id, info) in available_monitors {
137                        added.push(id);
138                        tracing::trace!(
139                            "monitor added, {:?} {:?}",
140                            id,
141                            (&info.name, info.position, info.size, info.scale_factor)
142                        );
143                        m.insert(id, MonitorInfo::from_gen(id, info));
144                    }
145
146                    if !removed.is_empty() || !added.is_empty() || !changed.is_empty() {
147                        let args = MonitorsChangedArgs::new(event_ts, event_propagation, removed, added, changed);
148                        MONITORS_CHANGED_EVENT.notify(args);
149                    }
150                });
151                true
152            })
153            .perm();
154
155        Self {
156            monitors: var(IdMap::new()),
157        }
158    }
159}
160
161/// "Monitor" configuration used by windows in [headless mode].
162///
163/// [headless mode]: zng_app::window::WindowMode::is_headless
164#[derive(Clone, Copy, PartialEq)]
165#[non_exhaustive]
166pub struct HeadlessMonitor {
167    /// The scale factor used for the headless layout and rendering.
168    ///
169    /// If set to `None`, falls back to the [`parent`] scale-factor, or `1.0` if the headless window has not parent.
170    ///
171    /// `None` by default.
172    ///
173    /// [`parent`]: crate::WindowVars::parent
174    pub scale_factor: Option<Factor>,
175
176    /// Size of the imaginary monitor screen that contains the headless window.
177    ///
178    /// This is used to calculate relative lengths in the window size definition and is defined in
179    /// layout pixels instead of device like in a real monitor info.
180    ///
181    /// `(11608, 8708)` by default.
182    pub size: DipSize,
183
184    /// Pixel density used for the headless layout and rendering.
185    pub density: PxDensity,
186}
187impl fmt::Debug for HeadlessMonitor {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        if f.alternate() || self.density != PxDensity::default() {
190            f.debug_struct("HeadlessMonitor")
191                .field("scale_factor", &self.scale_factor)
192                .field("screen_size", &self.size)
193                .field("density", &self.density)
194                .finish()
195        } else {
196            write!(f, "({:?}, ({}, {}))", self.scale_factor, self.size.width, self.size.height)
197        }
198    }
199}
200impl HeadlessMonitor {
201    /// New with custom size at `None` scale.
202    pub fn new(size: DipSize) -> Self {
203        HeadlessMonitor {
204            scale_factor: None,
205            size,
206            density: PxDensity::default(),
207        }
208    }
209
210    /// New with custom size and scale.
211    pub fn new_scaled(size: DipSize, scale_factor: Factor) -> Self {
212        HeadlessMonitor {
213            scale_factor: Some(scale_factor),
214            size,
215            density: PxDensity::default(),
216        }
217    }
218
219    /// New with default size `(11608, 8708)` and custom scale.
220    pub fn new_scale(scale_factor: Factor) -> Self {
221        HeadlessMonitor {
222            scale_factor: Some(scale_factor),
223            ..Self::default()
224        }
225    }
226}
227impl Default for HeadlessMonitor {
228    /// New `(1920, 1080)` at `None` scale.
229    fn default() -> Self {
230        (1920, 1080).into()
231    }
232}
233impl_from_and_into_var! {
234    fn from<W: Into<Dip>, H: Into<Dip>>((width, height): (W, H)) -> HeadlessMonitor {
235        HeadlessMonitor::new(DipSize::new(width.into(), height.into()))
236    }
237    fn from<W: Into<Dip>, H: Into<Dip>, F: Into<Factor>>((width, height, scale): (W, H, F)) -> HeadlessMonitor {
238        HeadlessMonitor::new_scaled(DipSize::new(width.into(), height.into()), scale.into())
239    }
240}
241
242/// All information about a monitor that [`MONITORS`] can provide.
243#[derive(Clone)]
244pub struct MonitorInfo {
245    id: MonitorId,
246    is_primary: Var<bool>,
247    name: Var<Txt>,
248    position: Var<PxPoint>,
249    size: Var<PxSize>,
250    video_modes: Var<Vec<VideoMode>>,
251    scale_factor: Var<Factor>,
252    density: Var<PxDensity>,
253    refresh_rate: Var<Frequency>,
254}
255impl fmt::Debug for MonitorInfo {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        f.debug_struct("MonitorInfo")
258            .field("id", &self.id)
259            .field("name", &self.name.get())
260            .field("position", &self.position.get())
261            .field("size", &self.size.get())
262            .field("refresh_rate", &self.refresh_rate.get())
263            .finish_non_exhaustive()
264    }
265}
266impl PartialEq for MonitorInfo {
267    fn eq(&self, other: &Self) -> bool {
268        self.id == other.id && self.name.var_eq(&other.name)
269    }
270}
271impl MonitorInfo {
272    /// New from a [`zng_view_api::MonitorInfo`].
273    fn from_gen(id: MonitorId, info: zng_view_api::window::MonitorInfo) -> Self {
274        MonitorInfo {
275            id,
276            is_primary: var(info.is_primary),
277            name: var(info.name.to_txt()),
278            position: var(info.position),
279            size: var(info.size),
280            scale_factor: var(info.scale_factor),
281            video_modes: var(info.video_modes),
282            refresh_rate: var(info.refresh_rate),
283            density: var(PxDensity::default()),
284        }
285    }
286
287    /// Update variables from fresh [`zng_view_api::MonitorInfo`],
288    /// returns if any value changed.
289    fn update(&self, info: zng_view_api::window::MonitorInfo) -> bool {
290        fn check_set<T: VarValue + PartialEq>(var: &Var<T>, value: T) -> bool {
291            let ne = var.with(|v| v != &value);
292            var.set(value);
293            ne
294        }
295
296        check_set(&self.is_primary, info.is_primary)
297            | check_set(&self.name, info.name.to_txt())
298            | check_set(&self.position, info.position)
299            | check_set(&self.size, info.size)
300            | check_set(&self.scale_factor, info.scale_factor)
301            | check_set(&self.video_modes, info.video_modes)
302            | check_set(&self.refresh_rate, info.refresh_rate)
303    }
304
305    /// Unique ID in this process instance.
306    pub fn id(&self) -> MonitorId {
307        self.id
308    }
309
310    /// If this monitor is the primary screen.
311    pub fn is_primary(&self) -> Var<bool> {
312        self.is_primary.read_only()
313    }
314
315    /// Name of the monitor.
316    pub fn name(&self) -> Var<Txt> {
317        self.name.read_only()
318    }
319    /// Top-left offset of the monitor region in the virtual screen, in pixels.
320    pub fn position(&self) -> Var<PxPoint> {
321        self.position.read_only()
322    }
323    /// Width/height of the monitor region in the virtual screen, in pixels.
324    pub fn size(&self) -> Var<PxSize> {
325        self.size.read_only()
326    }
327
328    /// Exclusive fullscreen video modes.
329    pub fn video_modes(&self) -> Var<Vec<VideoMode>> {
330        self.video_modes.read_only()
331    }
332
333    /// The monitor scale factor.
334    ///
335    /// Can update if the user changes system settings.
336    pub fn scale_factor(&self) -> Var<Factor> {
337        self.scale_factor.read_only()
338    }
339    /// Pixel density config var.
340    pub fn density(&self) -> Var<PxDensity> {
341        self.density.clone()
342    }
343
344    /// The monitor refresh rate.
345    pub fn refresh_rate(&self) -> Var<Frequency> {
346        self.refresh_rate.read_only()
347    }
348
349    /// Gets the monitor area in pixels.
350    pub fn px_rect(&self) -> PxRect {
351        let pos = self.position.get();
352        let size = self.size.get();
353
354        PxRect::new(pos, size)
355    }
356
357    /// Gets the monitor area in device independent pixels.
358    pub fn dip_rect(&self) -> DipRect {
359        let pos = self.position.get();
360        let size = self.size.get();
361        let factor = self.scale_factor.get();
362
363        PxRect::new(pos, size).to_dip(factor)
364    }
365
366    /// Bogus metadata for the [`MonitorId::fallback`].
367    ///
368    /// [`MonitorId::fallback`]: crate::monitor::MonitorId::fallback
369    pub fn fallback() -> Self {
370        let defaults = HeadlessMonitor::default();
371        let fct = 1.fct();
372
373        Self {
374            id: MonitorId::fallback(),
375            is_primary: var(false),
376            name: var("<fallback>".into()),
377            position: var(PxPoint::zero()),
378            size: var(defaults.size.to_px(fct)),
379            video_modes: var(vec![]),
380            scale_factor: var(fct),
381            density: var(PxDensity::default()),
382            refresh_rate: var(60.hertz()),
383        }
384    }
385}
386
387/// A selector that returns a [`MonitorInfo`].
388#[derive(Clone, Default)]
389pub enum MonitorQuery {
390    /// The parent window monitor, or `Primary` if the window has no parent.
391    ///
392    /// Note that the window is not moved automatically if the parent window is moved to another monitor, only
393    /// after the query variable updates.
394    ///
395    /// This is the default value.
396    #[default]
397    ParentOrPrimary,
398
399    /// The primary monitor, if there is any monitor.
400    Primary,
401    /// Custom query closure.
402    ///
403    /// If the closure returns `None` the `ParentOrPrimary` query is used, if there is any.
404    ///
405    /// You can use the [`MONITORS`] service in the query closure to select a monitor.
406    Query(Arc<dyn Fn() -> Option<MonitorInfo> + Send + Sync>),
407}
408impl std::fmt::Debug for MonitorQuery {
409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410        if f.alternate() {
411            write!(f, "MonitorQuery::")?;
412        }
413        match self {
414            Self::ParentOrPrimary => write!(f, "ParentOrPrimary"),
415            Self::Primary => write!(f, "Primary"),
416            Self::Query(_) => write!(f, "Query(_)"),
417        }
418    }
419}
420impl MonitorQuery {
421    /// New query.
422    pub fn new(query: impl Fn() -> Option<MonitorInfo> + Send + Sync + 'static) -> Self {
423        Self::Query(Arc::new(query))
424    }
425
426    /// Runs the query.
427    pub fn select(&self, window_id: WindowId) -> Option<MonitorInfo> {
428        match self {
429            MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
430            MonitorQuery::Primary => Self::primary_query(),
431            MonitorQuery::Query(q) => q(),
432        }
433    }
434
435    /// Runs the query. Falls back to `Primary`, or the largest or [`MonitorInfo::fallback`].
436    pub fn select_fallback(&self, window_id: WindowId) -> MonitorInfo {
437        match self {
438            MonitorQuery::ParentOrPrimary => Self::parent_or_primary_query(window_id),
439            MonitorQuery::Primary => Self::primary_query(),
440            MonitorQuery::Query(q) => q().or_else(Self::primary_query),
441        }
442        .unwrap_or_else(Self::fallback)
443    }
444
445    fn fallback() -> MonitorInfo {
446        MONITORS_SV.read().monitors.with(|m| {
447            let mut best = None;
448            let mut best_area = Px(0);
449            for m in m.values() {
450                let m_area = m.px_rect().area();
451                if m_area > best_area {
452                    best = Some(m);
453                    best_area = m_area;
454                }
455            }
456            best.cloned().unwrap_or_else(MonitorInfo::fallback)
457        })
458    }
459
460    fn parent_or_primary_query(win_id: WindowId) -> Option<MonitorInfo> {
461        if let Some(parent) = WINDOWS.vars(win_id).unwrap().parent().get()
462            && let Some(w) = WINDOWS.vars(parent)
463        {
464            return if let Some(monitor) = w.actual_monitor().get() {
465                MONITORS.monitor(monitor)
466            } else {
467                w.monitor().get().select(parent)
468            };
469        }
470        MONITORS.primary_monitor().get()
471    }
472
473    fn primary_query() -> Option<MonitorInfo> {
474        MONITORS.primary_monitor().get()
475    }
476}
477impl PartialEq for MonitorQuery {
478    /// Returns `true` only if both are [`MonitorQuery::Primary`].
479    fn eq(&self, other: &Self) -> bool {
480        matches!((self, other), (Self::Primary, Self::Primary))
481    }
482}
483impl_from_and_into_var! {
484    fn from(id: MonitorId) -> MonitorQuery {
485        MonitorQuery::new(move || MONITORS.monitor(id))
486    }
487}
488
489event_args! {
490    /// [`MONITORS_CHANGED_EVENT`] args.
491    pub struct MonitorsChangedArgs {
492        /// Removed monitors.
493        pub removed: Vec<MonitorId>,
494
495        /// Added monitors.
496        ///
497        /// Use the [`MONITORS`] service to get metadata about the added monitors.
498        pub added: Vec<MonitorId>,
499
500        /// Modified monitors.
501        ///
502        /// The monitor metadata is tracked using variables that are now flagged new.
503        pub modified: Vec<MonitorId>,
504
505        ..
506
507        /// Broadcast to all widgets.
508        fn is_in_target(&self, _id: WidgetId) -> bool {
509            true
510        }
511    }
512}
513
514event! {
515    /// Monitors added, removed or modified event.
516    pub static MONITORS_CHANGED_EVENT: MonitorsChangedArgs;
517}