Skip to main content

gpui_platform/
window.rs

1//! Window vocabulary shared by `gpui` and its platform backends.
2
3use gpui_shared_string::SharedString;
4use gpui_types::{Bounds, Edges, Pixels, Point, Size, px, size};
5use std::{sync::Arc, time::Duration};
6
7use crate::{DisplayId, WindowId, popup::PopupOptions};
8
9/// Default window size used when no explicit size is provided.
10pub const DEFAULT_WINDOW_SIZE: Size<Pixels> = size(px(1536.), px(1095.));
11
12/// The appearance of the window, as defined by the operating system.
13///
14/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
15/// values.
16#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
17pub enum WindowAppearance {
18    /// A light appearance.
19    ///
20    /// On macOS, this corresponds to the `aqua` appearance.
21    #[default]
22    Light,
23
24    /// A light appearance with vibrant colors.
25    ///
26    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
27    VibrantLight,
28
29    /// A dark appearance.
30    ///
31    /// On macOS, this corresponds to the `darkAqua` appearance.
32    Dark,
33
34    /// A dark appearance with vibrant colors.
35    ///
36    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
37    VibrantDark,
38}
39
40/// The appearance of the background of the window itself, when there is
41/// no content or the content is transparent.
42#[derive(Copy, Clone, Debug, Default, PartialEq)]
43pub enum WindowBackgroundAppearance {
44    /// Opaque.
45    ///
46    /// This lets the window manager know that content behind this
47    /// window does not need to be drawn.
48    ///
49    /// Actual color depends on the system and themes should define a fully
50    /// opaque background color instead.
51    #[default]
52    Opaque,
53    /// Plain alpha transparency.
54    Transparent,
55    /// Transparency, but the contents behind the window are blurred.
56    ///
57    /// Not always supported.
58    Blurred,
59    /// The Mica backdrop material, supported on Windows 11.
60    MicaBackdrop,
61    /// The Mica Alt backdrop material, supported on Windows 11.
62    MicaAltBackdrop,
63}
64
65/// Which part of the window to resize
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum ResizeEdge {
68    /// The top edge
69    Top,
70    /// The top right corner
71    TopRight,
72    /// The right edge
73    Right,
74    /// The bottom right corner
75    BottomRight,
76    /// The bottom edge
77    Bottom,
78    /// The bottom left corner
79    BottomLeft,
80    /// The left edge
81    Left,
82    /// The top left corner
83    TopLeft,
84}
85
86/// A type to describe the appearance of a window
87#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
88pub enum WindowDecorations {
89    #[default]
90    /// Server side decorations
91    Server,
92    /// Client side decorations
93    Client,
94}
95
96/// A type to describe how this window is currently configured
97#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
98pub enum Decorations {
99    /// The window is configured to use server side decorations
100    #[default]
101    Server,
102    /// The window is configured to use client side decorations
103    Client {
104        /// The edge tiling state
105        tiling: Tiling,
106    },
107}
108
109/// What window controls this platform supports
110#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
111pub struct WindowControls {
112    /// Whether this platform supports fullscreen
113    pub fullscreen: bool,
114    /// Whether this platform supports maximize
115    pub maximize: bool,
116    /// Whether this platform supports minimize
117    pub minimize: bool,
118    /// Whether this platform supports a window menu
119    pub window_menu: bool,
120}
121
122impl Default for WindowControls {
123    fn default() -> Self {
124        // Assume that we can do anything, unless told otherwise
125        Self {
126            fullscreen: true,
127            maximize: true,
128            minimize: true,
129            window_menu: true,
130        }
131    }
132}
133
134/// A window control button type used in [`WindowButtonLayout`].
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
136pub enum WindowButton {
137    /// The minimize button
138    Minimize,
139    /// The maximize button
140    Maximize,
141    /// The close button
142    Close,
143}
144
145impl WindowButton {
146    /// Returns a stable element ID for rendering this button.
147    pub fn id(&self) -> &'static str {
148        match self {
149            WindowButton::Minimize => "minimize",
150            WindowButton::Maximize => "maximize",
151            WindowButton::Close => "close",
152        }
153    }
154
155    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
156    fn index(&self) -> usize {
157        match self {
158            WindowButton::Minimize => 0,
159            WindowButton::Maximize => 1,
160            WindowButton::Close => 2,
161        }
162    }
163}
164
165/// Maximum number of [`WindowButton`]s per side in the titlebar.
166pub const MAX_BUTTONS_PER_SIDE: usize = 3;
167
168/// Describes which [`WindowButton`]s appear on each side of the titlebar.
169///
170/// On Linux, this is read from the desktop environment's configuration
171/// (e.g. GNOME's `gtk-decoration-layout` gsetting) via [`WindowButtonLayout::parse`].
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub struct WindowButtonLayout {
174    /// Buttons on the left side of the titlebar.
175    pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
176    /// Buttons on the right side of the titlebar.
177    pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
178}
179
180#[cfg(any(target_os = "linux", target_os = "freebsd"))]
181impl WindowButtonLayout {
182    /// Returns Zed's built-in fallback button layout for Linux titlebars.
183    pub fn linux_default() -> Self {
184        Self {
185            left: [None; MAX_BUTTONS_PER_SIDE],
186            right: [
187                Some(WindowButton::Minimize),
188                Some(WindowButton::Maximize),
189                Some(WindowButton::Close),
190            ],
191        }
192    }
193
194    /// Parses a GNOME-style `button-layout` string (e.g. `"close,minimize:maximize"`).
195    pub fn parse(layout_string: &str) -> anyhow::Result<Self> {
196        fn parse_side(
197            s: &str,
198            seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
199            unrecognized: &mut Vec<String>,
200        ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
201            let mut result = [None; MAX_BUTTONS_PER_SIDE];
202            let mut i = 0;
203            for name in s.split(',') {
204                let trimmed = name.trim();
205                if trimmed.is_empty() {
206                    continue;
207                }
208                let button = match trimmed {
209                    "minimize" => Some(WindowButton::Minimize),
210                    "maximize" => Some(WindowButton::Maximize),
211                    "close" => Some(WindowButton::Close),
212                    other => {
213                        unrecognized.push(other.to_string());
214                        None
215                    }
216                };
217                if let Some(button) = button {
218                    if seen_buttons[button.index()] {
219                        continue;
220                    }
221                    if let Some(slot) = result.get_mut(i) {
222                        *slot = Some(button);
223                        seen_buttons[button.index()] = true;
224                        i += 1;
225                    }
226                }
227            }
228            result
229        }
230
231        let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
232        let mut unrecognized = Vec::new();
233        let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
234        let layout = Self {
235            left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
236            right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
237        };
238
239        if !unrecognized.is_empty()
240            && layout.left.iter().all(Option::is_none)
241            && layout.right.iter().all(Option::is_none)
242        {
243            anyhow::bail!(
244                "button layout string {:?} contains no valid buttons (unrecognized: {})",
245                layout_string,
246                unrecognized.join(", ")
247            );
248        }
249
250        Ok(layout)
251    }
252
253    /// Formats the layout back into a GNOME-style `button-layout` string.
254    pub fn format(&self) -> String {
255        fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
256            buttons
257                .iter()
258                .flatten()
259                .map(|button| match button {
260                    WindowButton::Minimize => "minimize",
261                    WindowButton::Maximize => "maximize",
262                    WindowButton::Close => "close",
263                })
264                .collect::<Vec<_>>()
265                .join(",")
266        }
267
268        format!("{}:{}", format_side(&self.left), format_side(&self.right))
269    }
270}
271
272/// A type to describe which sides of the window are currently tiled in some way
273#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
274pub struct Tiling {
275    /// Whether the top edge is tiled
276    pub top: bool,
277    /// Whether the left edge is tiled
278    pub left: bool,
279    /// Whether the right edge is tiled
280    pub right: bool,
281    /// Whether the bottom edge is tiled
282    pub bottom: bool,
283}
284
285impl Tiling {
286    /// Initializes a [`Tiling`] type with all sides tiled
287    pub fn tiled() -> Self {
288        Self {
289            top: true,
290            left: true,
291            right: true,
292            bottom: true,
293        }
294    }
295
296    /// Whether any edge is tiled
297    pub fn is_tiled(&self) -> bool {
298        self.top || self.left || self.right || self.bottom
299    }
300}
301
302#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
303#[expect(missing_docs)]
304pub struct RequestFrameOptions {
305    /// Whether a presentation is required.
306    pub require_presentation: bool,
307    /// Force refresh of all rendering states when true.
308    pub force_render: bool,
309}
310
311/// Regions of a window that are obscured or reserved by the system.
312///
313/// Mobile applications often share space in their window with system-specific
314/// geometry, from keyboards to camera notches. In GPUI, all this is abstracted
315/// into a single "inset" which should be overlaid on the window's bounds.
316/// It is up to the application develop to determine how to handle these cases.
317#[derive(Debug, Clone, Default, PartialEq)]
318pub struct WindowInsets {
319    /// Regions covered by system UI or hardware: status bar, display
320    /// cutouts/notch, home indicator, navigation bars.
321    /// (iOS: `safeAreaInsets`. Android: `WindowInsets` of types
322    /// `systemBars() | displayCutout()`.)
323    pub safe_area: Edges<Pixels>,
324    /// The region covered by the keyboard, when present.
325    /// (iOS: derived from `keyboardWillShow`/frame-change notifications.
326    /// Android: `WindowInsets.Type.ime()`.)
327    pub ime: Edges<Pixels>,
328}
329
330impl WindowInsets {
331    /// The combined inset content should avoid.
332    pub fn effective(&self) -> Edges<Pixels> {
333        Edges {
334            top: self.safe_area.top.max(self.ime.top),
335            right: self.safe_area.right.max(self.ime.right),
336            bottom: self.safe_area.bottom.max(self.ime.bottom),
337            left: self.safe_area.left.max(self.ime.left),
338        }
339    }
340}
341
342/// A change in the state of the focused text input.
343#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
344pub enum TextInputStateChange {
345    /// The window changed from having no active text input to having one.
346    FocusGained,
347    /// The window no longer has an active text input.
348    FocusLost,
349    /// The selection or caret moved
350    SelectionChanged,
351    /// The document content changed outside of platform-initiated edits.
352    ContentChanged,
353}
354
355/// The variables that can be configured when creating a new window
356#[derive(Debug)]
357pub struct WindowOptions {
358    /// Specifies the state and bounds of the window in screen coordinates.
359    /// - `None`: Inherit the bounds.
360    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
361    pub window_bounds: Option<WindowBounds>,
362
363    /// The titlebar configuration of the window
364    pub titlebar: Option<TitlebarOptions>,
365
366    /// Whether the window should be focused when created
367    pub focus: bool,
368
369    /// Whether the window should be shown when created
370    pub show: bool,
371
372    /// The kind of window to create
373    pub kind: WindowKind,
374
375    /// Whether the window can be moved by the user. When `false`, the user cannot drag
376    /// the window (on macOS this sets `NSWindow.isMovable`, which also disables the
377    /// Window-menu tiling items); programmatic moves are still allowed.
378    pub is_movable: bool,
379
380    /// Whether the application owns dragging of the (custom) titlebar, rather than
381    /// AppKit. Only has an effect on macOS.
382    ///
383    /// Set this to `true` for windows that draw their own titlebar and move the window
384    /// themselves via `Window::start_window_move`. It marks the whole content view as
385    /// app-owned titlebar content, so AppKit neither drags the window from the titlebar
386    /// nor delays titlebar clicks while disambiguating double-clicks (a delay first
387    /// observed on macOS 27). It is independent of `is_movable`, so such windows stay
388    /// user-movable (via their own drag) and keep the Window-menu tiling items enabled.
389    ///
390    /// Leave this `false` for windows that rely on AppKit's native titlebar dragging.
391    pub app_owns_titlebar_drag: bool,
392
393    /// The minimum interval between animation frames while the window is inactive.
394    ///
395    /// Set to `None` to disable inactive-window animation frame throttling.
396    pub inactive_frame_interval: Option<Duration>,
397
398    /// Whether the window should be resizable by the user
399    pub is_resizable: bool,
400
401    /// Whether the window should be minimized by the user
402    pub is_minimizable: bool,
403
404    /// The display to create the window on, if this is None,
405    /// the window will be created on the main display
406    pub display_id: Option<DisplayId>,
407
408    /// The appearance of the window background.
409    pub window_background: WindowBackgroundAppearance,
410
411    /// Application identifier of the window. Can by used by desktop environments to group applications together.
412    pub app_id: Option<String>,
413
414    /// Window minimum size
415    pub window_min_size: Option<Size<Pixels>>,
416
417    /// Whether to use client or server-side decorations on X11 and Wayland.
418    /// The platform may ignore requests it cannot satisfy.
419    pub window_decorations: Option<WindowDecorations>,
420
421    /// Icon image (X11 only)
422    pub icon: Option<Arc<image::RgbaImage>>,
423
424    /// Tab group name, allows opening the window as a native tab on macOS 10.12+. Windows with the same tabbing identifier will be grouped together.
425    pub tabbing_identifier: Option<String>,
426}
427
428/// The variables that can be configured when creating a new window
429#[derive(Debug)]
430#[cfg_attr(
431    all(
432        any(target_os = "linux", target_os = "freebsd"),
433        not(any(feature = "x11", feature = "wayland"))
434    ),
435    allow(dead_code)
436)]
437#[allow(missing_docs)]
438pub struct WindowParams {
439    pub bounds: Bounds<Pixels>,
440
441    /// The titlebar configuration of the window
442    #[cfg_attr(feature = "wayland", allow(dead_code))]
443    pub titlebar: Option<TitlebarOptions>,
444
445    /// The kind of window to create
446    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
447    pub kind: WindowKind,
448
449    /// Whether the window should be movable by the user
450    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
451    pub is_movable: bool,
452
453    /// Whether the application owns dragging of the (custom) titlebar (macOS only)
454    #[cfg_attr(
455        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
456        allow(dead_code)
457    )]
458    pub app_owns_titlebar_drag: bool,
459
460    /// Whether the window should be resizable by the user
461    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
462    pub is_resizable: bool,
463
464    /// Whether the window should be minimized by the user
465    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
466    pub is_minimizable: bool,
467
468    #[cfg_attr(
469        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
470        allow(dead_code)
471    )]
472    pub focus: bool,
473
474    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
475    pub show: bool,
476
477    /// An image to set as the window icon (x11 only)
478    #[cfg_attr(feature = "wayland", allow(dead_code))]
479    pub icon: Option<Arc<image::RgbaImage>>,
480
481    #[cfg_attr(feature = "wayland", allow(dead_code))]
482    pub display_id: Option<DisplayId>,
483
484    #[cfg_attr(feature = "wayland", allow(dead_code))]
485    pub app_id: Option<String>,
486
487    pub window_min_size: Option<Size<Pixels>>,
488
489    #[cfg(target_os = "macos")]
490    pub tabbing_identifier: Option<String>,
491}
492
493/// Represents the status of how a window should be opened.
494#[derive(Debug, Copy, Clone, PartialEq)]
495pub enum WindowBounds {
496    /// Indicates that the window should open in a windowed state with the given bounds.
497    Windowed(Bounds<Pixels>),
498    /// Indicates that the window should open in a maximized state.
499    /// The bounds provided here represent the restore size of the window.
500    Maximized(Bounds<Pixels>),
501    /// Indicates that the window should open in fullscreen mode.
502    /// The bounds provided here represent the restore size of the window.
503    Fullscreen(Bounds<Pixels>),
504}
505
506impl Default for WindowBounds {
507    fn default() -> Self {
508        WindowBounds::Windowed(Bounds::default())
509    }
510}
511
512impl WindowBounds {
513    /// Retrieve the inner bounds
514    pub fn get_bounds(&self) -> Bounds<Pixels> {
515        match self {
516            WindowBounds::Windowed(bounds) => *bounds,
517            WindowBounds::Maximized(bounds) => *bounds,
518            WindowBounds::Fullscreen(bounds) => *bounds,
519        }
520    }
521}
522
523impl Default for WindowOptions {
524    fn default() -> Self {
525        Self {
526            window_bounds: None,
527            titlebar: Some(TitlebarOptions {
528                title: Default::default(),
529                appears_transparent: Default::default(),
530                traffic_light_position: Default::default(),
531            }),
532            focus: true,
533            show: true,
534            kind: WindowKind::Normal,
535            is_movable: true,
536            app_owns_titlebar_drag: false,
537            inactive_frame_interval: Some(Duration::from_micros(33_333)),
538            is_resizable: true,
539            is_minimizable: true,
540            display_id: None,
541            window_background: WindowBackgroundAppearance::default(),
542            icon: None,
543            app_id: None,
544            window_min_size: None,
545            window_decorations: None,
546            tabbing_identifier: None,
547        }
548    }
549}
550
551/// The options that can be configured for a window's titlebar
552#[derive(Debug, Default)]
553pub struct TitlebarOptions {
554    /// The initial title of the window
555    pub title: Option<SharedString>,
556
557    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
558    /// Refer to [`WindowOptions::window_decorations`] on Linux
559    pub appears_transparent: bool,
560
561    /// The position of the macOS traffic light buttons
562    pub traffic_light_position: Option<Point<Pixels>>,
563}
564
565/// The kind of window to create
566#[derive(Clone, Debug, PartialEq, Eq)]
567pub enum WindowKind {
568    /// A normal application window
569    Normal,
570    /// A window that appears above all other windows, usually used for alerts or popups
571    /// use sparingly!
572    PopUp,
573
574    /// A parent-anchored, platform-native popup window for menus, comboboxes, context menus and
575    /// tooltips. Unlike [`WindowKind::PopUp`], it is positioned relative to a parent window.
576    ///
577    /// The popup's size comes from [`WindowOptions::window_bounds`], whose origin is ignored.
578    /// See [`PopupOptions`] for the placement options. Platforms without a native
579    /// implementation reject it with [`PopupNotSupportedError`].
580    AnchoredPopup(PopupOptions),
581
582    /// A floating window that appears on top of its parent window
583    Floating,
584
585    /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
586    /// docks, notifications or wallpapers.
587    #[cfg(all(target_os = "linux", feature = "wayland"))]
588    LayerShell(crate::layer_shell::LayerShellOptions),
589
590    /// A window that appears on top of its parent window and blocks interaction with it
591    /// until the modal window is closed
592    Dialog,
593}
594
595/// A type of window control area that corresponds to the platform window.
596#[derive(Clone, Copy, Debug, Eq, PartialEq)]
597pub enum WindowControlArea {
598    /// An area that allows dragging of the platform window.
599    Drag,
600    /// An area that allows closing of the platform window.
601    Close,
602    /// An area that allows maximizing of the platform window.
603    Max,
604    /// An area that allows minimizing of the platform window.
605    Min,
606}
607
608#[derive(Clone, Debug, Default, PartialEq, Eq)]
609#[expect(missing_docs)]
610pub struct DispatchEventResult {
611    pub propagate: bool,
612    pub default_prevented: bool,
613}
614
615/// A tab in the platform's native window tab bar.
616#[doc(hidden)]
617#[derive(Clone, PartialEq, Eq)]
618pub struct SystemWindowTab {
619    /// The window id of the tab.
620    pub id: WindowId,
621    /// The title shown on the tab.
622    pub title: SharedString,
623    /// The window id this tab activates.
624    pub handle: WindowId,
625    /// When this tab was last the active tab.
626    pub last_active_at: scheduler::Instant,
627}
628
629impl SystemWindowTab {
630    /// Create a new instance of the window tab.
631    pub fn new(title: SharedString, handle: WindowId) -> Self {
632        Self {
633            id: handle,
634            title,
635            handle,
636            last_active_at: scheduler::Instant::now(),
637        }
638    }
639}