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 scheduler::Instant;
6use std::{rc::Rc, sync::Arc, time::Duration};
7
8use crate::{DisplayId, DynRendererFactory, RendererFactory, WindowId, popup::PopupOptions};
9
10/// Default window size used when no explicit size is provided.
11pub const DEFAULT_WINDOW_SIZE: Size<Pixels> = size(px(1536.), px(1095.));
12
13/// The appearance of the window, as defined by the operating system.
14///
15/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
16/// values.
17#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
18pub enum WindowAppearance {
19 /// A light appearance.
20 ///
21 /// On macOS, this corresponds to the `aqua` appearance.
22 #[default]
23 Light,
24
25 /// A light appearance with vibrant colors.
26 ///
27 /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
28 VibrantLight,
29
30 /// A dark appearance.
31 ///
32 /// On macOS, this corresponds to the `darkAqua` appearance.
33 Dark,
34
35 /// A dark appearance with vibrant colors.
36 ///
37 /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
38 VibrantDark,
39}
40
41/// The appearance of the background of the window itself, when there is
42/// no content or the content is transparent.
43#[derive(Copy, Clone, Debug, Default, PartialEq)]
44pub enum WindowBackgroundAppearance {
45 /// Opaque.
46 ///
47 /// This lets the window manager know that content behind this
48 /// window does not need to be drawn.
49 ///
50 /// Actual color depends on the system and themes should define a fully
51 /// opaque background color instead.
52 #[default]
53 Opaque,
54 /// Plain alpha transparency.
55 Transparent,
56 /// Transparency, but the contents behind the window are blurred.
57 ///
58 /// Not always supported.
59 Blurred,
60 /// The Mica backdrop material, supported on Windows 11.
61 MicaBackdrop,
62 /// The Mica Alt backdrop material, supported on Windows 11.
63 MicaAltBackdrop,
64}
65
66/// Which part of the window to resize
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum ResizeEdge {
69 /// The top edge
70 Top,
71 /// The top right corner
72 TopRight,
73 /// The right edge
74 Right,
75 /// The bottom right corner
76 BottomRight,
77 /// The bottom edge
78 Bottom,
79 /// The bottom left corner
80 BottomLeft,
81 /// The left edge
82 Left,
83 /// The top left corner
84 TopLeft,
85}
86
87/// A type to describe the appearance of a window
88#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
89pub enum WindowDecorations {
90 #[default]
91 /// Server side decorations
92 Server,
93 /// Client side decorations
94 Client,
95}
96
97/// A type to describe how this window is currently configured
98#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
99pub enum Decorations {
100 /// The window is configured to use server side decorations
101 #[default]
102 Server,
103 /// The window is configured to use client side decorations
104 Client {
105 /// The edge tiling state
106 tiling: Tiling,
107 },
108}
109
110/// What window controls this platform supports
111#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
112pub struct WindowControls {
113 /// Whether this platform supports fullscreen
114 pub fullscreen: bool,
115 /// Whether this platform supports maximize
116 pub maximize: bool,
117 /// Whether this platform supports minimize
118 pub minimize: bool,
119 /// Whether this platform supports a window menu
120 pub window_menu: bool,
121}
122
123impl Default for WindowControls {
124 fn default() -> Self {
125 // Assume that we can do anything, unless told otherwise
126 Self {
127 fullscreen: true,
128 maximize: true,
129 minimize: true,
130 window_menu: true,
131 }
132 }
133}
134
135/// A window control button type used in [`WindowButtonLayout`].
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137pub enum WindowButton {
138 /// The minimize button
139 Minimize,
140 /// The maximize button
141 Maximize,
142 /// The close button
143 Close,
144}
145
146impl WindowButton {
147 /// Returns a stable element ID for rendering this button.
148 pub fn id(&self) -> &'static str {
149 match self {
150 WindowButton::Minimize => "minimize",
151 WindowButton::Maximize => "maximize",
152 WindowButton::Close => "close",
153 }
154 }
155
156 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
157 fn index(&self) -> usize {
158 match self {
159 WindowButton::Minimize => 0,
160 WindowButton::Maximize => 1,
161 WindowButton::Close => 2,
162 }
163 }
164}
165
166/// Maximum number of [`WindowButton`]s per side in the titlebar.
167pub const MAX_BUTTONS_PER_SIDE: usize = 3;
168
169/// Describes which [`WindowButton`]s appear on each side of the titlebar.
170///
171/// On Linux, this is read from the desktop environment's configuration
172/// (e.g. GNOME's `gtk-decoration-layout` gsetting) via [`WindowButtonLayout::parse`].
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub struct WindowButtonLayout {
175 /// Buttons on the left side of the titlebar.
176 pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
177 /// Buttons on the right side of the titlebar.
178 pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
179}
180
181#[cfg(any(target_os = "linux", target_os = "freebsd"))]
182impl WindowButtonLayout {
183 /// Returns Zed's built-in fallback button layout for Linux titlebars.
184 pub fn linux_default() -> Self {
185 Self {
186 left: [None; MAX_BUTTONS_PER_SIDE],
187 right: [
188 Some(WindowButton::Minimize),
189 Some(WindowButton::Maximize),
190 Some(WindowButton::Close),
191 ],
192 }
193 }
194
195 /// Parses a GNOME-style `button-layout` string (e.g. `"close,minimize:maximize"`).
196 pub fn parse(layout_string: &str) -> anyhow::Result<Self> {
197 fn parse_side(
198 s: &str,
199 seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
200 unrecognized: &mut Vec<String>,
201 ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
202 let mut result = [None; MAX_BUTTONS_PER_SIDE];
203 let mut i = 0;
204 for name in s.split(',') {
205 let trimmed = name.trim();
206 if trimmed.is_empty() {
207 continue;
208 }
209 let button = match trimmed {
210 "minimize" => Some(WindowButton::Minimize),
211 "maximize" => Some(WindowButton::Maximize),
212 "close" => Some(WindowButton::Close),
213 other => {
214 unrecognized.push(other.to_string());
215 None
216 }
217 };
218 if let Some(button) = button {
219 if seen_buttons[button.index()] {
220 continue;
221 }
222 if let Some(slot) = result.get_mut(i) {
223 *slot = Some(button);
224 seen_buttons[button.index()] = true;
225 i += 1;
226 }
227 }
228 }
229 result
230 }
231
232 let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
233 let mut unrecognized = Vec::new();
234 let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
235 let layout = Self {
236 left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
237 right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
238 };
239
240 if !unrecognized.is_empty()
241 && layout.left.iter().all(Option::is_none)
242 && layout.right.iter().all(Option::is_none)
243 {
244 anyhow::bail!(
245 "button layout string {:?} contains no valid buttons (unrecognized: {})",
246 layout_string,
247 unrecognized.join(", ")
248 );
249 }
250
251 Ok(layout)
252 }
253
254 /// Formats the layout back into a GNOME-style `button-layout` string.
255 pub fn format(&self) -> String {
256 fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
257 buttons
258 .iter()
259 .flatten()
260 .map(|button| match button {
261 WindowButton::Minimize => "minimize",
262 WindowButton::Maximize => "maximize",
263 WindowButton::Close => "close",
264 })
265 .collect::<Vec<_>>()
266 .join(",")
267 }
268
269 format!("{}:{}", format_side(&self.left), format_side(&self.right))
270 }
271}
272
273/// A type to describe which sides of the window are currently tiled in some way
274#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
275pub struct Tiling {
276 /// Whether the top edge is tiled
277 pub top: bool,
278 /// Whether the left edge is tiled
279 pub left: bool,
280 /// Whether the right edge is tiled
281 pub right: bool,
282 /// Whether the bottom edge is tiled
283 pub bottom: bool,
284}
285
286impl Tiling {
287 /// Initializes a [`Tiling`] type with all sides tiled
288 pub fn tiled() -> Self {
289 Self {
290 top: true,
291 left: true,
292 right: true,
293 bottom: true,
294 }
295 }
296
297 /// Whether any edge is tiled
298 pub fn is_tiled(&self) -> bool {
299 self.top || self.left || self.right || self.bottom
300 }
301}
302
303#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
304#[expect(missing_docs)]
305pub struct RequestFrameOptions {
306 /// Whether a presentation is required.
307 pub require_presentation: bool,
308 /// Force refresh of all rendering states when true.
309 pub force_render: bool,
310 /// When the platform first requested this frame, before main-thread dispatch.
311 ///
312 /// `None` means the captured request time is unavailable.
313 /// Coalesced requests carry their first request time, not their delivery time.
314 /// Built-in backends collect timestamps only with the `profiler` feature.
315 pub signal_at: Option<Instant>,
316 /// Distinguishes native callbacks from local scheduling requests.
317 pub signal_source: FrameRequestSource,
318}
319
320/// The source of a platform frame request's timestamp.
321#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
322pub enum FrameRequestSource {
323 /// An OS callback or compositor-paced frame request.
324 #[default]
325 NativeCallback,
326 /// A refresh timer, retry, queued wakeup, or fallback sleep paced by the app.
327 LocalSchedule,
328}
329
330/// Preserves the first platform frame request time across coalesced notifications.
331///
332/// Producers may record from a platform thread without waiting for the UI thread.
333/// The consumer drains the timestamp before dispatching the request on the UI thread.
334/// The timestamp offset and source share one atomic word so coalescing cannot
335/// mix a request's time with another request's source. The low bit encodes the
336/// source; the remaining bits encode nanoseconds from `origin`. `u64::MAX` is empty.
337/// Without the `profiler` feature, this accumulator has no timestamp storage.
338pub struct PlatformFrameSignal {
339 #[cfg(feature = "profiler")]
340 origin: Instant,
341 #[cfg(feature = "profiler")]
342 first_signal: std::sync::atomic::AtomicU64,
343}
344
345impl Default for PlatformFrameSignal {
346 fn default() -> Self {
347 Self::new()
348 }
349}
350
351impl PlatformFrameSignal {
352 /// Creates an empty signal accumulator.
353 pub fn new() -> Self {
354 Self {
355 #[cfg(feature = "profiler")]
356 origin: Instant::now(),
357 #[cfg(feature = "profiler")]
358 first_signal: std::sync::atomic::AtomicU64::new(u64::MAX),
359 }
360 }
361
362 /// Captures a platform request timestamp only when profiling is enabled.
363 ///
364 /// The closure is not invoked in profiler-disabled builds.
365 #[inline]
366 pub fn capture(capture: impl FnOnce() -> Instant) -> Option<Instant> {
367 if cfg!(feature = "profiler") {
368 Some(capture())
369 } else {
370 None
371 }
372 }
373
374 /// Records a platform frame request, retaining the first undrained timestamp.
375 #[inline]
376 #[cfg_attr(not(feature = "profiler"), expect(unused_variables))]
377 pub fn record(&self, at: Instant, source: FrameRequestSource) {
378 #[cfg(feature = "profiler")]
379 {
380 let nanoseconds = at
381 .saturating_duration_since(self.origin)
382 .as_nanos()
383 .min(u128::from((u64::MAX >> 1) - 1)) as u64;
384 let source_bit = match source {
385 FrameRequestSource::NativeCallback => 0,
386 FrameRequestSource::LocalSchedule => 1,
387 };
388 self.first_signal.fetch_min(
389 (nanoseconds << 1) | source_bit,
390 std::sync::atomic::Ordering::Relaxed,
391 );
392 }
393 }
394
395 /// Drains the first platform frame request time and source, leaving the accumulator empty.
396 #[inline]
397 pub fn take(&self) -> Option<(Instant, FrameRequestSource)> {
398 #[cfg(feature = "profiler")]
399 {
400 let signal = self
401 .first_signal
402 .swap(u64::MAX, std::sync::atomic::Ordering::Relaxed);
403 (signal != u64::MAX).then(|| {
404 let source = match signal & 1 {
405 0 => FrameRequestSource::NativeCallback,
406 _ => FrameRequestSource::LocalSchedule,
407 };
408 (self.origin + Duration::from_nanos(signal >> 1), source)
409 })
410 }
411 #[cfg(not(feature = "profiler"))]
412 {
413 None
414 }
415 }
416}
417
418/// Regions of a window that are obscured or reserved by the system.
419///
420/// Mobile applications often share space in their window with system-specific
421/// geometry, from keyboards to camera notches. In GPUI, all this is abstracted
422/// into a single "inset" which should be overlaid on the window's bounds.
423/// It is up to the application develop to determine how to handle these cases.
424#[derive(Debug, Clone, Default, PartialEq)]
425pub struct WindowInsets {
426 /// Regions covered by system UI or hardware: status bar, display
427 /// cutouts/notch, home indicator, navigation bars.
428 /// (iOS: `safeAreaInsets`. Android: `WindowInsets` of types
429 /// `systemBars() | displayCutout()`.)
430 pub safe_area: Edges<Pixels>,
431 /// The region covered by the keyboard, when present.
432 /// (iOS: derived from `keyboardWillShow`/frame-change notifications.
433 /// Android: `WindowInsets.Type.ime()`.)
434 pub ime: Edges<Pixels>,
435}
436
437impl WindowInsets {
438 /// The combined inset content should avoid.
439 pub fn effective(&self) -> Edges<Pixels> {
440 Edges {
441 top: self.safe_area.top.max(self.ime.top),
442 right: self.safe_area.right.max(self.ime.right),
443 bottom: self.safe_area.bottom.max(self.ime.bottom),
444 left: self.safe_area.left.max(self.ime.left),
445 }
446 }
447}
448
449/// A change in the state of the focused text input.
450#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
451pub enum TextInputStateChange {
452 /// The window changed from having no active text input to having one.
453 FocusGained,
454 /// The window no longer has an active text input.
455 FocusLost,
456 /// The selection or caret moved
457 SelectionChanged,
458 /// The document content changed outside of platform-initiated edits.
459 ContentChanged,
460}
461
462/// The variables that can be configured when creating a new window
463#[derive(Debug)]
464pub struct WindowOptions {
465 /// Specifies the state and bounds of the window in screen coordinates.
466 /// - `None`: Inherit the bounds.
467 /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
468 pub window_bounds: Option<WindowBounds>,
469
470 /// The titlebar configuration of the window
471 pub titlebar: Option<TitlebarOptions>,
472
473 /// Whether the window should be focused when created
474 pub focus: bool,
475
476 /// Whether the window should be shown when created
477 pub show: bool,
478
479 /// The kind of window to create
480 pub kind: WindowKind,
481
482 /// Whether the window can be moved by the user. When `false`, the user cannot drag
483 /// the window (on macOS this sets `NSWindow.isMovable`, which also disables the
484 /// Window-menu tiling items); programmatic moves are still allowed.
485 pub is_movable: bool,
486
487 /// Whether the application owns dragging of the (custom) titlebar, rather than
488 /// AppKit. Only has an effect on macOS.
489 ///
490 /// Set this to `true` for windows that draw their own titlebar and move the window
491 /// themselves via `Window::start_window_move`. It marks the whole content view as
492 /// app-owned titlebar content, so AppKit neither drags the window from the titlebar
493 /// nor delays titlebar clicks while disambiguating double-clicks (a delay first
494 /// observed on macOS 27). It is independent of `is_movable`, so such windows stay
495 /// user-movable (via their own drag) and keep the Window-menu tiling items enabled.
496 ///
497 /// Leave this `false` for windows that rely on AppKit's native titlebar dragging.
498 pub app_owns_titlebar_drag: bool,
499
500 /// The minimum interval between animation frames while the window is inactive.
501 ///
502 /// Set to `None` to disable inactive-window animation frame throttling.
503 pub inactive_frame_interval: Option<Duration>,
504
505 /// Whether the window should be resizable by the user
506 pub is_resizable: bool,
507
508 /// Whether the window should be minimized by the user
509 pub is_minimizable: bool,
510
511 /// The display to create the window on, if this is None,
512 /// the window will be created on the main display
513 pub display_id: Option<DisplayId>,
514
515 /// The appearance of the window background.
516 pub window_background: WindowBackgroundAppearance,
517
518 /// Application identifier of the window. Can by used by desktop environments to group applications together.
519 pub app_id: Option<String>,
520
521 /// Window minimum size
522 pub window_min_size: Option<Size<Pixels>>,
523
524 /// Whether to use client or server-side decorations on X11 and Wayland.
525 /// The platform may ignore requests it cannot satisfy.
526 pub window_decorations: Option<WindowDecorations>,
527
528 /// Icon image (X11 only)
529 pub icon: Option<Arc<image::RgbaImage>>,
530
531 /// 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.
532 pub tabbing_identifier: Option<String>,
533
534 /// The renderer this window should draw through, if the application wants its own.
535 ///
536 /// The factory runs once for the window, after the native window exists and before the
537 /// first frame, because a renderer binds to a surface and the surface comes from the
538 /// window. With `None` the backend builds the renderer it builds today, by the same
539 /// construction call, so a window that installs nothing behaves exactly as before.
540 pub renderer_factory: Option<DynRendererFactory>,
541}
542
543/// The variables that can be configured when creating a new window
544#[derive(Debug)]
545#[cfg_attr(
546 all(
547 any(target_os = "linux", target_os = "freebsd"),
548 not(any(feature = "x11", feature = "wayland"))
549 ),
550 allow(dead_code)
551)]
552#[allow(missing_docs)]
553pub struct WindowParams {
554 pub bounds: Bounds<Pixels>,
555
556 /// The titlebar configuration of the window
557 #[cfg_attr(feature = "wayland", allow(dead_code))]
558 pub titlebar: Option<TitlebarOptions>,
559
560 /// The kind of window to create
561 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
562 pub kind: WindowKind,
563
564 /// Whether the window should be movable by the user
565 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
566 pub is_movable: bool,
567
568 /// Whether the application owns dragging of the (custom) titlebar (macOS only)
569 #[cfg_attr(
570 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
571 allow(dead_code)
572 )]
573 pub app_owns_titlebar_drag: bool,
574
575 /// Whether the window should be resizable by the user
576 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
577 pub is_resizable: bool,
578
579 /// Whether the window should be minimized by the user
580 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
581 pub is_minimizable: bool,
582
583 #[cfg_attr(
584 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
585 allow(dead_code)
586 )]
587 pub focus: bool,
588
589 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
590 pub show: bool,
591
592 /// An image to set as the window icon (x11 only)
593 #[cfg_attr(feature = "wayland", allow(dead_code))]
594 pub icon: Option<Arc<image::RgbaImage>>,
595
596 #[cfg_attr(feature = "wayland", allow(dead_code))]
597 pub display_id: Option<DisplayId>,
598
599 #[cfg_attr(feature = "wayland", allow(dead_code))]
600 pub app_id: Option<String>,
601
602 pub window_min_size: Option<Size<Pixels>>,
603
604 #[cfg(target_os = "macos")]
605 pub tabbing_identifier: Option<String>,
606
607 pub renderer_factory: Option<DynRendererFactory>,
608}
609
610/// Represents the status of how a window should be opened.
611#[derive(Debug, Copy, Clone, PartialEq)]
612pub enum WindowBounds {
613 /// Indicates that the window should open in a windowed state with the given bounds.
614 Windowed(Bounds<Pixels>),
615 /// Indicates that the window should open in a maximized state.
616 /// The bounds provided here represent the restore size of the window.
617 Maximized(Bounds<Pixels>),
618 /// Indicates that the window should open in fullscreen mode.
619 /// The bounds provided here represent the restore size of the window.
620 Fullscreen(Bounds<Pixels>),
621}
622
623impl Default for WindowBounds {
624 fn default() -> Self {
625 WindowBounds::Windowed(Bounds::default())
626 }
627}
628
629impl WindowBounds {
630 /// Retrieve the inner bounds
631 pub fn get_bounds(&self) -> Bounds<Pixels> {
632 match self {
633 WindowBounds::Windowed(bounds) => *bounds,
634 WindowBounds::Maximized(bounds) => *bounds,
635 WindowBounds::Fullscreen(bounds) => *bounds,
636 }
637 }
638}
639
640impl Default for WindowOptions {
641 fn default() -> Self {
642 Self {
643 window_bounds: None,
644 titlebar: Some(TitlebarOptions {
645 title: Default::default(),
646 appears_transparent: Default::default(),
647 traffic_light_position: Default::default(),
648 }),
649 focus: true,
650 show: true,
651 kind: WindowKind::Normal,
652 is_movable: true,
653 app_owns_titlebar_drag: false,
654 inactive_frame_interval: Some(Duration::from_micros(33_333)),
655 is_resizable: true,
656 is_minimizable: true,
657 display_id: None,
658 window_background: WindowBackgroundAppearance::default(),
659 icon: None,
660 app_id: None,
661 window_min_size: None,
662 window_decorations: None,
663 tabbing_identifier: None,
664 renderer_factory: None,
665 }
666 }
667}
668
669impl WindowOptions {
670 /// Draw this window through a renderer of the application's own rather than the
671 /// backend's default.
672 ///
673 /// The factory is invoked once, when the window is created: see
674 /// [`renderer_factory`](Self::renderer_factory).
675 pub fn with_renderer_factory(mut self, factory: impl RendererFactory) -> Self {
676 self.renderer_factory = Some(DynRendererFactory(Rc::new(factory)));
677 self
678 }
679}
680
681/// The options that can be configured for a window's titlebar
682#[derive(Debug, Default)]
683pub struct TitlebarOptions {
684 /// The initial title of the window
685 pub title: Option<SharedString>,
686
687 /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
688 /// Refer to [`WindowOptions::window_decorations`] on Linux
689 pub appears_transparent: bool,
690
691 /// The position of the macOS traffic light buttons
692 pub traffic_light_position: Option<Point<Pixels>>,
693}
694
695/// The kind of window to create
696#[derive(Clone, Debug, PartialEq, Eq)]
697pub enum WindowKind {
698 /// A normal application window
699 Normal,
700 /// A window that appears above all other windows, usually used for alerts or popups
701 /// use sparingly!
702 PopUp,
703
704 /// A parent-anchored, platform-native popup window for menus, comboboxes, context menus and
705 /// tooltips. Unlike [`WindowKind::PopUp`], it is positioned relative to a parent window.
706 ///
707 /// The popup's size comes from [`WindowOptions::window_bounds`], whose origin is ignored.
708 /// See [`PopupOptions`] for the placement options. Platforms without a native
709 /// implementation reject it with [`PopupNotSupportedError`].
710 AnchoredPopup(PopupOptions),
711
712 /// A floating window that appears on top of its parent window
713 Floating,
714
715 /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
716 /// docks, notifications or wallpapers.
717 #[cfg(all(target_os = "linux", feature = "wayland"))]
718 LayerShell(crate::layer_shell::LayerShellOptions),
719
720 /// A window that appears on top of its parent window and blocks interaction with it
721 /// until the modal window is closed
722 Dialog,
723}
724
725/// A type of window control area that corresponds to the platform window.
726#[derive(Clone, Copy, Debug, Eq, PartialEq)]
727pub enum WindowControlArea {
728 /// An area that allows dragging of the platform window.
729 Drag,
730 /// An area that allows closing of the platform window.
731 Close,
732 /// An area that allows maximizing of the platform window.
733 Max,
734 /// An area that allows minimizing of the platform window.
735 Min,
736}
737
738#[derive(Clone, Debug, Default, PartialEq, Eq)]
739#[expect(missing_docs)]
740pub struct DispatchEventResult {
741 pub propagate: bool,
742 pub default_prevented: bool,
743}
744
745/// A tab in the platform's native window tab bar.
746#[doc(hidden)]
747#[derive(Clone, PartialEq, Eq)]
748pub struct SystemWindowTab {
749 /// The window id of the tab.
750 pub id: WindowId,
751 /// The title shown on the tab.
752 pub title: SharedString,
753 /// The window id this tab activates.
754 pub handle: WindowId,
755 /// When this tab was last the active tab.
756 pub last_active_at: scheduler::Instant,
757}
758
759impl SystemWindowTab {
760 /// Create a new instance of the window tab.
761 pub fn new(title: SharedString, handle: WindowId) -> Self {
762 Self {
763 id: handle,
764 title,
765 handle,
766 last_active_at: scheduler::Instant::now(),
767 }
768 }
769}
770
771#[cfg(test)]
772mod frame_signal_tests {
773 use super::*;
774
775 #[cfg(feature = "profiler")]
776 #[test]
777 fn coalesced_signals_retain_the_earliest_until_drained() {
778 for (first_source, later_source) in [
779 (
780 FrameRequestSource::NativeCallback,
781 FrameRequestSource::LocalSchedule,
782 ),
783 (
784 FrameRequestSource::LocalSchedule,
785 FrameRequestSource::NativeCallback,
786 ),
787 ] {
788 let signal = PlatformFrameSignal::new();
789 let first =
790 PlatformFrameSignal::capture(Instant::now).expect("profiling captures requests");
791 assert_eq!(signal.take(), None);
792 signal.record(first + Duration::from_millis(16), later_source);
793 signal.record(first, first_source);
794 signal.record(first + Duration::from_millis(32), later_source);
795 assert_eq!(signal.take(), Some((first, first_source)));
796 assert_eq!(signal.take(), None);
797 let next = first + Duration::from_millis(48);
798 signal.record(next, later_source);
799 assert_eq!(signal.take(), Some((next, later_source)));
800 }
801 }
802
803 #[cfg(not(feature = "profiler"))]
804 #[test]
805 fn disabled_profiling_does_not_capture_or_store_timestamps() {
806 let captured = PlatformFrameSignal::capture(|| {
807 panic!("profiler-disabled builds must not evaluate the capture closure");
808 });
809 assert_eq!(captured, None);
810 let signal = PlatformFrameSignal::new();
811 signal.record(Instant::now(), FrameRequestSource::LocalSchedule);
812 assert_eq!(signal.take(), None);
813 }
814}