zng_ext_window/
monitor.rs

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