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}