Skip to main content

gpui_platform/
display.rs

1//! Display abstractions shared by `gpui` and its platform backends.
2
3use crate::DEFAULT_WINDOW_SIZE;
4use anyhow::Result;
5use gpui_types::{Bounds, Pixels, point};
6use std::fmt::{self, Debug};
7use std::time::Duration;
8use uuid::Uuid;
9
10/// An opaque identifier for a hardware display
11#[derive(PartialEq, Eq, Hash, Copy, Clone)]
12pub struct DisplayId(pub(crate) u64);
13
14impl DisplayId {
15    /// Create a new `DisplayId` from a raw platform display identifier.
16    pub fn new(id: u64) -> Self {
17        Self(id)
18    }
19}
20
21impl From<u64> for DisplayId {
22    fn from(id: u64) -> Self {
23        Self(id)
24    }
25}
26
27impl From<DisplayId> for u64 {
28    fn from(id: DisplayId) -> Self {
29        id.0
30    }
31}
32
33impl Debug for DisplayId {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(f, "DisplayId({})", self.0)
36    }
37}
38
39/// A change to the connected displays, reported through
40/// `App::observe_displays`.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42#[non_exhaustive]
43pub enum DisplayEvent {
44    /// A display was connected.
45    Added(DisplayId),
46    /// A display was disconnected. Windows on it are moved to another
47    /// display by the platform.
48    Removed(DisplayId),
49    /// A connected display's properties changed.
50    Changed {
51        /// The display that changed.
52        id: DisplayId,
53        /// Which of its properties changed.
54        changes: DisplayChanges,
55    },
56}
57
58bitflags::bitflags! {
59    /// The properties of a display that a [`DisplayEvent::Changed`] reports.
60    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61    pub struct DisplayChanges: u8 {
62        /// [`PlatformDisplay::bounds`] changed.
63        const BOUNDS = 1 << 0;
64        /// [`PlatformDisplay::refresh_interval`] changed.
65        const REFRESH_INTERVAL = 1 << 1;
66    }
67}
68
69/// Converts a refresh rate in hertz to the time between refreshes, rejecting
70/// the zero, negative, and non-finite rates platforms use to mean "unknown".
71pub fn refresh_interval_from_hz(hertz: f64) -> Option<Duration> {
72    (hertz.is_finite() && hertz > 0.0).then(|| Duration::from_secs_f64(1.0 / hertz))
73}
74
75/// A handle to a platform's display, e.g. a monitor or laptop screen.
76pub trait PlatformDisplay: Debug {
77    /// Get the ID for this display
78    fn id(&self) -> DisplayId;
79
80    /// Returns a stable identifier for this display that can be persisted and used
81    /// across system restarts.
82    fn uuid(&self) -> Result<Uuid>;
83
84    /// Get the bounds for this display
85    fn bounds(&self) -> Bounds<Pixels>;
86
87    /// The time between the display's refreshes. Variable refresh rate
88    /// displays report their maximum rate. `None` when the platform doesn't
89    /// report it.
90    ///
91    /// May query the operating system; GPUI caches it per display, see
92    /// `Window::refresh_interval`.
93    fn refresh_interval(&self) -> Option<Duration>;
94
95    /// Get the visible bounds for this display, excluding taskbar/dock areas.
96    /// This is the usable area where windows can be placed without being obscured.
97    /// Defaults to the full display bounds if not overridden.
98    fn visible_bounds(&self) -> Bounds<Pixels> {
99        self.bounds()
100    }
101
102    /// Get the default bounds for this display to place a window
103    fn default_bounds(&self) -> Bounds<Pixels> {
104        let bounds = self.bounds();
105        let center = bounds.center();
106        let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
107
108        let offset = clipped_window_size / 2.0;
109        let origin = point(center.x - offset.width, center.y - offset.height);
110        Bounds::new(origin, clipped_window_size)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_refresh_interval_from_hz() {
120        assert_eq!(
121            refresh_interval_from_hz(50.0),
122            Some(Duration::from_millis(20))
123        );
124        assert_eq!(
125            refresh_interval_from_hz(120.0),
126            Some(Duration::from_secs(1) / 120)
127        );
128        for unknown in [0.0, -60.0, f64::NAN, f64::INFINITY] {
129            assert_eq!(refresh_interval_from_hz(unknown), None);
130        }
131    }
132}