zng_ext_window/
monitor.rs

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