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