Skip to main content

gpui/
platform.rs

1mod app_menu;
2mod keyboard;
3mod keystroke;
4
5#[cfg(all(target_os = "linux", feature = "wayland"))]
6#[expect(missing_docs)]
7pub mod layer_shell;
8
9/// Types for configuring parent-anchored popup windows such as menus, dropdowns and tooltips.
10pub mod popup;
11
12#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
13mod threaded_dispatcher;
14
15#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
16mod test;
17
18#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
19mod visual_test;
20
21#[cfg(all(
22    feature = "screen-capture",
23    any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
24))]
25pub mod scap_screen_capture;
26
27#[cfg(all(
28    any(target_os = "windows", target_os = "linux"),
29    feature = "screen-capture"
30))]
31pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
32#[cfg(not(feature = "screen-capture"))]
33pub(crate) type PlatformScreenCaptureFrame = ();
34#[cfg(all(target_os = "macos", feature = "screen-capture"))]
35pub(crate) type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
36
37use crate::{
38    Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
39    DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Edges, ExternalDragPayload, Font,
40    FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, GpuSpecs, Hsla, ImageSource, Keymap,
41    LineLayout, Pixels, PlatformGestures, PlatformInput, Point, Priority, RenderGlyphParams,
42    RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString,
43    Size, SvgRenderer, SystemWindowTab, Task, Window, WindowControlArea, hash, point, px, size,
44};
45#[cfg(any(target_os = "linux", target_os = "freebsd"))]
46use anyhow::bail;
47use anyhow::{Context as _, Result};
48use async_task::Runnable;
49use futures::channel::oneshot;
50#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
51use image::RgbaImage;
52use image::codecs::gif::GifDecoder;
53use image::{AnimationDecoder as _, DynamicImage, Frame};
54use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
55use scheduler::Instant;
56pub use scheduler::RunnableMeta;
57use schemars::JsonSchema;
58use seahash::SeaHasher;
59use serde::{Deserialize, Serialize};
60use smallvec::SmallVec;
61use std::borrow::Cow;
62use std::hash::{Hash, Hasher};
63use std::io::Cursor;
64use std::ops;
65use std::time::Duration;
66use std::{
67    ffi::OsString,
68    fmt::{self, Debug},
69    ops::Range,
70    path::{Path, PathBuf},
71    rc::Rc,
72    sync::Arc,
73};
74use strum::EnumIter;
75use uuid::Uuid;
76
77pub use app_menu::*;
78pub use keyboard::*;
79pub use keystroke::*;
80
81/// Whether the platform is presenting a window's frames.
82///
83/// This is about presentation, not the window's shown/hidden state: a shown
84/// window that is fully covered by other windows, minimized, on another
85/// Space or virtual desktop, or on a display that is asleep is `Hidden`. A
86/// window only partly covered by other windows is `Visible`.
87///
88/// Each platform reports from a single source, and what that source can see
89/// differs:
90///
91/// * macOS: `NSWindow.occlusionState`. Covers all of the cases above.
92/// * Windows: `WS_VISIBLE` and the minimized state. Windows keeps compositing
93///   covered windows for thumbnails and Alt-Tab and offers no occlusion
94///   notification, so a fully covered window stays `Visible`. Display sleep is
95///   not reported either.
96/// * Wayland: the `xdg_toplevel` `suspended` state (xdg-shell v6). Compositors
97///   that don't support it never report `Hidden`.
98/// * X11: mapped state plus `VisibilityNotify`. Compositing window managers
99///   generally never report a window as fully obscured, so covering is only
100///   detected without compositing.
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum WindowVisibility {
103    /// At least part of the window is being presented; frames drawn for it
104    /// will be shown.
105    Visible,
106    /// No part of the window is being presented. The platform will not
107    /// request frames for it until it becomes visible again.
108    Hidden,
109}
110
111impl WindowVisibility {
112    /// Whether frames drawn for the window will be shown.
113    pub fn is_visible(self) -> bool {
114        self == Self::Visible
115    }
116}
117
118#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
119pub(crate) use test::*;
120
121#[cfg(any(test, feature = "test-support"))]
122pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
123
124#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
125pub use threaded_dispatcher::ThreadedDispatcher;
126
127#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
128pub use visual_test::VisualTestPlatform;
129
130/// Keeps an operating system activity, such as an idle sleep inhibitor, alive until dropped.
131pub struct ActivityGuard {
132    _release: gpui_util::Deferred<Box<dyn FnOnce() + Send>>,
133}
134
135impl ActivityGuard {
136    /// Runs `release` when the guard is dropped.
137    pub fn new(release: impl FnOnce() + Send + 'static) -> Self {
138        Self {
139            _release: gpui_util::defer(Box::new(release)),
140        }
141    }
142
143    /// A guard for platforms without a corresponding activity.
144    pub fn noop() -> Self {
145        Self::new(|| {})
146    }
147}
148
149// TODO(jk): return an enum instead of a string
150/// Return which compositor we're guessing we'll use.
151/// Does not attempt to connect to the given compositor.
152#[cfg(any(target_os = "linux", target_os = "freebsd"))]
153#[inline]
154pub fn guess_compositor() -> &'static str {
155    if std::env::var_os("ZED_HEADLESS").is_some() {
156        return "Headless";
157    }
158
159    #[cfg(feature = "wayland")]
160    let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
161    #[cfg(not(feature = "wayland"))]
162    let wayland_display: Option<std::ffi::OsString> = None;
163
164    #[cfg(feature = "x11")]
165    let x11_display = std::env::var_os("DISPLAY");
166    #[cfg(not(feature = "x11"))]
167    let x11_display: Option<std::ffi::OsString> = None;
168
169    let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
170    let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
171
172    if use_wayland {
173        "Wayland"
174    } else if use_x11 {
175        "X11"
176    } else {
177        "Headless"
178    }
179}
180
181#[expect(missing_docs)]
182pub trait Platform: 'static {
183    fn background_executor(&self) -> BackgroundExecutor;
184    fn foreground_executor(&self) -> ForegroundExecutor;
185    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
186
187    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
188    fn quit(&self);
189    fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
190    fn activate(&self, ignoring_other_apps: bool);
191    fn hide(&self);
192    fn hide_other_apps(&self);
193    fn unhide_other_apps(&self);
194
195    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
196    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
197    fn active_window(&self) -> Option<AnyWindowHandle>;
198    fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
199        None
200    }
201
202    fn is_screen_capture_supported(&self) -> bool {
203        false
204    }
205
206    fn screen_capture_sources(
207        &self,
208    ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
209        let (sources_tx, sources_rx) = oneshot::channel();
210        sources_tx
211            .send(Err(anyhow::anyhow!(
212                "gpui was compiled without the screen-capture feature"
213            )))
214            .ok();
215        sources_rx
216    }
217
218    fn open_window(
219        &self,
220        handle: AnyWindowHandle,
221        options: WindowParams,
222    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
223
224    /// Returns the appearance of the application's windows.
225    fn window_appearance(&self) -> WindowAppearance;
226
227    /// Overrides the appearance (light/dark) applied to the app's windows, independent
228    /// of the OS-wide setting. Pass `None` to clear the override and follow the system
229    /// again. The override is reflected by [`Platform::window_appearance`].
230    ///
231    /// Currently only implemented on macOS, where it sets `NSApplication.appearance` so
232    /// the native window chrome (the window border and titlebar) of every window matches
233    /// a dark app theme even when the system is in light mode (or vice versa). A no-op on
234    /// other platforms.
235    fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
236
237    /// Returns the window button layout configuration when supported.
238    fn button_layout(&self) -> Option<WindowButtonLayout> {
239        None
240    }
241
242    fn open_url(&self, url: &str);
243    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
244    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
245
246    fn prompt_for_paths(
247        &self,
248        options: PathPromptOptions,
249    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
250    fn prompt_for_new_path(
251        &self,
252        directory: &Path,
253        suggested_name: Option<&str>,
254    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
255    fn can_select_mixed_files_and_dirs(&self) -> bool;
256    fn reveal_path(&self, path: &Path);
257    fn open_with_system(&self, path: &Path);
258
259    fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
260    fn on_reopen(&self, callback: Box<dyn FnMut()>);
261    /// Registers the callback invoked when the system is about to sleep.
262    fn on_system_sleep(&self, callback: Box<dyn FnMut()>);
263    /// Registers the callback invoked when the system resumes from sleep.
264    fn on_system_wake(&self, callback: Box<dyn FnMut()>);
265
266    // Mobile platform methods. On mobile the OS owns the application
267    // lifecycle: apps are backgrounded, foregrounded, and killed at the
268    // system's discretion, and must react rather than decide.
269
270    /// Registers a callback invoked whenever the application's lifecycle
271    /// phase changes. See [`AppLifecyclePhase`] for the phase vocabulary and
272    /// its mapping onto iOS and Android.
273    ///
274    /// Desktop platforms never invoke this.
275    fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
276
277    /// Registers a callback invoked when the OS signals memory pressure
278    /// (iOS `didReceiveMemoryWarning`, Android `onTrimMemory`).
279    ///
280    /// Desktop platforms never invoke this.
281    fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
282
283    /// The platform's gesture recognition services, if it provides any
284    /// beyond gpui's portable recognizers. See
285    /// [`PlatformGestures`](crate::PlatformGestures).
286    fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
287        None
288    }
289
290    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
291    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
292        None
293    }
294
295    fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
296    fn perform_dock_menu_action(&self, _action: usize) {}
297    fn add_recent_document(&self, _path: &Path) {}
298    fn update_jump_list(
299        &self,
300        _menus: Vec<MenuItem>,
301        _entries: Vec<SmallVec<[PathBuf; 2]>>,
302    ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
303        Task::ready(Vec::new())
304    }
305    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
306    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
307    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
308
309    fn thermal_state(&self) -> ThermalState;
310    fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
311    fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
312
313    /// Sets the application's process-wide identity and user-visible name.
314    ///
315    /// The identifier is used for platform identity mechanisms such as the
316    /// Windows AppUserModelID. The name is used wherever the operating system
317    /// presents the application to the user. Call this once, early in startup,
318    /// before opening windows or posting notifications.
319    fn set_app_identity(&self, identifier: &str, name: &str) {
320        _ = (identifier, name);
321    }
322
323    /// Posts a notification to the operating system's notification center.
324    ///
325    /// Posting a notification whose [`SystemNotification::tag`] matches an
326    /// earlier one replaces that notification where the platform supports it.
327    /// No-op on platforms without notification support, or when delivery is
328    /// unavailable (e.g. authorization was denied).
329    fn show_system_notification(&self, notification: SystemNotification) {
330        _ = notification;
331    }
332
333    /// Removes the delivered or pending notification with this tag.
334    ///
335    /// Best-effort: some platforms cannot retract a notification once shown,
336    /// in which case it ages out of the notification center on its own.
337    fn dismiss_system_notification(&self, tag: &str) {
338        _ = tag;
339    }
340
341    /// Registers the callback invoked when the user activates a system
342    /// notification, either by clicking its body or one of its action
343    /// buttons.
344    ///
345    /// Implementations must invoke the callback on the main thread.
346    fn on_system_notification_response(
347        &self,
348        callback: Box<dyn FnMut(SystemNotificationResponse)>,
349    ) {
350        _ = callback;
351    }
352
353    fn compositor_name(&self) -> &'static str {
354        ""
355    }
356    fn app_path(&self) -> Result<PathBuf>;
357    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
358
359    fn set_cursor_style(&self, style: CursorStyle);
360
361    /// Hides the mouse cursor until the user moves the mouse over one of
362    /// this application's windows.
363    fn hide_cursor_until_mouse_moves(&self);
364
365    /// Returns whether the mouse cursor is currently visible.
366    fn is_cursor_visible(&self) -> bool;
367
368    fn should_auto_hide_scrollbars(&self) -> bool;
369
370    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
371    fn write_to_clipboard(&self, item: ClipboardItem);
372
373    /// Reads the clipboard, resolving once its contents are available.
374    ///
375    /// Most platforms read synchronously and return a ready task. Platforms
376    /// whose clipboard access is inherently asynchronous and permission-gated
377    /// (e.g. the browser's async clipboard API) override this method; on those
378    /// platforms [`Platform::read_from_clipboard`] cannot return the clipboard
379    /// contents, so callers that can await should prefer this method.
380    fn read_from_clipboard_async(&self) -> Task<Result<Option<ClipboardItem>, ClipboardReadError>> {
381        Task::ready(Ok(self.read_from_clipboard()))
382    }
383
384    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
385    fn read_from_primary(&self) -> Option<ClipboardItem>;
386    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
387    fn write_to_primary(&self, item: ClipboardItem);
388
389    #[cfg(target_os = "macos")]
390    fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
391    #[cfg(target_os = "macos")]
392    fn write_to_find_pasteboard(&self, item: ClipboardItem);
393
394    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
395    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
396    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
397
398    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
399    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
400    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
401}
402
403/// A handle to a platform's display, e.g. a monitor or laptop screen.
404pub trait PlatformDisplay: Debug {
405    /// Get the ID for this display
406    fn id(&self) -> DisplayId;
407
408    /// Returns a stable identifier for this display that can be persisted and used
409    /// across system restarts.
410    fn uuid(&self) -> Result<Uuid>;
411
412    /// Get the bounds for this display
413    fn bounds(&self) -> Bounds<Pixels>;
414
415    /// Get the visible bounds for this display, excluding taskbar/dock areas.
416    /// This is the usable area where windows can be placed without being obscured.
417    /// Defaults to the full display bounds if not overridden.
418    fn visible_bounds(&self) -> Bounds<Pixels> {
419        self.bounds()
420    }
421
422    /// Get the default bounds for this display to place a window
423    fn default_bounds(&self) -> Bounds<Pixels> {
424        let bounds = self.bounds();
425        let center = bounds.center();
426        let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
427
428        let offset = clipped_window_size / 2.0;
429        let origin = point(center.x - offset.width, center.y - offset.height);
430        Bounds::new(origin, clipped_window_size)
431    }
432}
433
434/// A notification posted to the operating system's notification center,
435/// rather than rendered as in-app UI.
436#[derive(Clone, Debug, PartialEq, Eq)]
437pub struct SystemNotification {
438    /// Stable identity for the notification. Posting a new notification with
439    /// the same tag replaces the previous one where the platform supports it,
440    /// and responses carry the tag back to the application.
441    pub tag: SharedString,
442    /// The notification's headline.
443    pub title: SharedString,
444    /// Additional text displayed below the title.
445    pub body: SharedString,
446    /// Buttons offered on the notification. Platforms that cannot display
447    /// action buttons show the notification without them.
448    pub actions: Vec<SystemNotificationAction>,
449}
450
451/// A button offered on a [`SystemNotification`].
452#[derive(Clone, Debug, PartialEq, Eq, Hash)]
453pub struct SystemNotificationAction {
454    /// Identifies the action in [`SystemNotificationResponse::action_id`]
455    /// when the user presses this button.
456    pub id: SharedString,
457    /// The button's user-visible label.
458    pub label: SharedString,
459}
460
461/// The user's activation of a [`SystemNotification`].
462#[derive(Clone, Debug, PartialEq, Eq)]
463pub struct SystemNotificationResponse {
464    /// The [`SystemNotification::tag`] of the activated notification.
465    pub tag: SharedString,
466    /// The pressed action button's [`SystemNotificationAction::id`], or
467    /// `None` when the user activated the notification body itself.
468    pub action_id: Option<SharedString>,
469}
470
471/// Thermal state of the system
472#[derive(Debug, Clone, Copy, PartialEq, Eq)]
473pub enum ThermalState {
474    /// System has no thermal constraints
475    Nominal,
476    /// System is slightly constrained, reduce discretionary work
477    Fair,
478    /// System is moderately constrained, reduce CPU/GPU intensive work
479    Serious,
480    /// System is critically constrained, minimize all resource usage
481    Critical,
482}
483
484/// Metadata for a given [ScreenCaptureSource]
485#[derive(Clone)]
486pub struct SourceMetadata {
487    /// Opaque identifier of this screen.
488    pub id: u64,
489    /// Human-readable label for this source.
490    pub label: Option<SharedString>,
491    /// Whether this source is the main display.
492    pub is_main: Option<bool>,
493    /// Video resolution of this source.
494    pub resolution: Size<DevicePixels>,
495}
496
497/// A source of on-screen video content that can be captured.
498pub trait ScreenCaptureSource {
499    /// Returns metadata for this source.
500    fn metadata(&self) -> Result<SourceMetadata>;
501
502    /// Start capture video from this source, invoking the given callback
503    /// with each frame.
504    fn stream(
505        &self,
506        foreground_executor: &ForegroundExecutor,
507        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
508    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
509}
510
511/// A video stream captured from a screen.
512pub trait ScreenCaptureStream {
513    /// Returns metadata for this source.
514    fn metadata(&self) -> Result<SourceMetadata>;
515}
516
517/// A frame of video captured from a screen.
518pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
519
520/// An opaque identifier for a hardware display
521#[derive(PartialEq, Eq, Hash, Copy, Clone)]
522pub struct DisplayId(pub(crate) u64);
523
524impl DisplayId {
525    /// Create a new `DisplayId` from a raw platform display identifier.
526    pub fn new(id: u64) -> Self {
527        Self(id)
528    }
529}
530
531impl From<u64> for DisplayId {
532    fn from(id: u64) -> Self {
533        Self(id)
534    }
535}
536
537impl From<DisplayId> for u64 {
538    fn from(id: DisplayId) -> Self {
539        id.0
540    }
541}
542
543impl Debug for DisplayId {
544    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545        write!(f, "DisplayId({})", self.0)
546    }
547}
548
549/// Which part of the window to resize
550#[derive(Debug, Clone, Copy, PartialEq, Eq)]
551pub enum ResizeEdge {
552    /// The top edge
553    Top,
554    /// The top right corner
555    TopRight,
556    /// The right edge
557    Right,
558    /// The bottom right corner
559    BottomRight,
560    /// The bottom edge
561    Bottom,
562    /// The bottom left corner
563    BottomLeft,
564    /// The left edge
565    Left,
566    /// The top left corner
567    TopLeft,
568}
569
570/// A type to describe the appearance of a window
571#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
572pub enum WindowDecorations {
573    #[default]
574    /// Server side decorations
575    Server,
576    /// Client side decorations
577    Client,
578}
579
580/// A type to describe how this window is currently configured
581#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
582pub enum Decorations {
583    /// The window is configured to use server side decorations
584    #[default]
585    Server,
586    /// The window is configured to use client side decorations
587    Client {
588        /// The edge tiling state
589        tiling: Tiling,
590    },
591}
592
593/// What window controls this platform supports
594#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
595pub struct WindowControls {
596    /// Whether this platform supports fullscreen
597    pub fullscreen: bool,
598    /// Whether this platform supports maximize
599    pub maximize: bool,
600    /// Whether this platform supports minimize
601    pub minimize: bool,
602    /// Whether this platform supports a window menu
603    pub window_menu: bool,
604}
605
606impl Default for WindowControls {
607    fn default() -> Self {
608        // Assume that we can do anything, unless told otherwise
609        Self {
610            fullscreen: true,
611            maximize: true,
612            minimize: true,
613            window_menu: true,
614        }
615    }
616}
617
618/// A window control button type used in [`WindowButtonLayout`].
619#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
620pub enum WindowButton {
621    /// The minimize button
622    Minimize,
623    /// The maximize button
624    Maximize,
625    /// The close button
626    Close,
627}
628
629impl WindowButton {
630    /// Returns a stable element ID for rendering this button.
631    pub fn id(&self) -> &'static str {
632        match self {
633            WindowButton::Minimize => "minimize",
634            WindowButton::Maximize => "maximize",
635            WindowButton::Close => "close",
636        }
637    }
638
639    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
640    fn index(&self) -> usize {
641        match self {
642            WindowButton::Minimize => 0,
643            WindowButton::Maximize => 1,
644            WindowButton::Close => 2,
645        }
646    }
647}
648
649/// Maximum number of [`WindowButton`]s per side in the titlebar.
650pub const MAX_BUTTONS_PER_SIDE: usize = 3;
651
652/// Describes which [`WindowButton`]s appear on each side of the titlebar.
653///
654/// On Linux, this is read from the desktop environment's configuration
655/// (e.g. GNOME's `gtk-decoration-layout` gsetting) via [`WindowButtonLayout::parse`].
656#[derive(Debug, Clone, Copy, PartialEq, Eq)]
657pub struct WindowButtonLayout {
658    /// Buttons on the left side of the titlebar.
659    pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
660    /// Buttons on the right side of the titlebar.
661    pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
662}
663
664#[cfg(any(target_os = "linux", target_os = "freebsd"))]
665impl WindowButtonLayout {
666    /// Returns Zed's built-in fallback button layout for Linux titlebars.
667    pub fn linux_default() -> Self {
668        Self {
669            left: [None; MAX_BUTTONS_PER_SIDE],
670            right: [
671                Some(WindowButton::Minimize),
672                Some(WindowButton::Maximize),
673                Some(WindowButton::Close),
674            ],
675        }
676    }
677
678    /// Parses a GNOME-style `button-layout` string (e.g. `"close,minimize:maximize"`).
679    pub fn parse(layout_string: &str) -> Result<Self> {
680        fn parse_side(
681            s: &str,
682            seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
683            unrecognized: &mut Vec<String>,
684        ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
685            let mut result = [None; MAX_BUTTONS_PER_SIDE];
686            let mut i = 0;
687            for name in s.split(',') {
688                let trimmed = name.trim();
689                if trimmed.is_empty() {
690                    continue;
691                }
692                let button = match trimmed {
693                    "minimize" => Some(WindowButton::Minimize),
694                    "maximize" => Some(WindowButton::Maximize),
695                    "close" => Some(WindowButton::Close),
696                    other => {
697                        unrecognized.push(other.to_string());
698                        None
699                    }
700                };
701                if let Some(button) = button {
702                    if seen_buttons[button.index()] {
703                        continue;
704                    }
705                    if let Some(slot) = result.get_mut(i) {
706                        *slot = Some(button);
707                        seen_buttons[button.index()] = true;
708                        i += 1;
709                    }
710                }
711            }
712            result
713        }
714
715        let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
716        let mut unrecognized = Vec::new();
717        let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
718        let layout = Self {
719            left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
720            right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
721        };
722
723        if !unrecognized.is_empty()
724            && layout.left.iter().all(Option::is_none)
725            && layout.right.iter().all(Option::is_none)
726        {
727            bail!(
728                "button layout string {:?} contains no valid buttons (unrecognized: {})",
729                layout_string,
730                unrecognized.join(", ")
731            );
732        }
733
734        Ok(layout)
735    }
736
737    /// Formats the layout back into a GNOME-style `button-layout` string.
738    #[cfg(test)]
739    pub fn format(&self) -> String {
740        fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
741            buttons
742                .iter()
743                .flatten()
744                .map(|button| match button {
745                    WindowButton::Minimize => "minimize",
746                    WindowButton::Maximize => "maximize",
747                    WindowButton::Close => "close",
748                })
749                .collect::<Vec<_>>()
750                .join(",")
751        }
752
753        format!("{}:{}", format_side(&self.left), format_side(&self.right))
754    }
755}
756
757/// A type to describe which sides of the window are currently tiled in some way
758#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
759pub struct Tiling {
760    /// Whether the top edge is tiled
761    pub top: bool,
762    /// Whether the left edge is tiled
763    pub left: bool,
764    /// Whether the right edge is tiled
765    pub right: bool,
766    /// Whether the bottom edge is tiled
767    pub bottom: bool,
768}
769
770impl Tiling {
771    /// Initializes a [`Tiling`] type with all sides tiled
772    pub fn tiled() -> Self {
773        Self {
774            top: true,
775            left: true,
776            right: true,
777            bottom: true,
778        }
779    }
780
781    /// Whether any edge is tiled
782    pub fn is_tiled(&self) -> bool {
783        self.top || self.left || self.right || self.bottom
784    }
785}
786
787/// Callbacks for the accessibility adapter.
788pub struct A11yCallbacks {
789    /// Called when the adapter is activated (a screen reader connects).
790    pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
791    /// Called when an action is requested by the screen reader.
792    pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
793    /// Called when the adapter is deactivated (screen reader disconnects).
794    pub deactivation: Box<dyn Fn() + Send + 'static>,
795}
796
797#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
798#[expect(missing_docs)]
799pub struct RequestFrameOptions {
800    /// Whether a presentation is required.
801    pub require_presentation: bool,
802    /// Force refresh of all rendering states when true.
803    pub force_render: bool,
804}
805
806/// The application's lifecycle phase, as owned and reported by a mobile OS.
807///
808/// `Inactive` means visible but not receiving input (a system dialog on
809/// top), while `Background` means not visible at all, with process death
810/// possible at any time thereafter.
811///
812/// | Phase        | iOS                          | Android      |
813/// |--------------|------------------------------|--------------|
814/// | `Active`     | `didBecomeActive`            | `onResume`   |
815/// | `Inactive`   | `willResignActive`           | `onPause`    |
816/// | `Background` | `didEnterBackground`         | `onStop`     |
817/// | `Foreground` | `willEnterForeground`        | `onStart`    |
818#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
819pub enum AppLifecyclePhase {
820    /// Foreground and receiving input.
821    Active,
822    /// Foreground (visible) but not receiving input.
823    Inactive,
824    /// Not visible. The GPU surface may be destroyed while backgrounded and
825    /// the process may be killed without further notice.
826    Background,
827    /// Becoming visible again, before input is restored.
828    Foreground,
829}
830
831/// Regions of a window that are obscured or reserved by the system.
832///
833/// Mobile applications often share space in their window with system-specific
834/// geometry, from keyboards to camera notches. In GPUI, all this is abstracted
835/// into a single "inset" which should be overlaid on the window's bounds.
836/// It is up to the application develop to determine how to handle these cases.
837#[derive(Debug, Clone, Default, PartialEq)]
838pub struct WindowInsets {
839    /// Regions covered by system UI or hardware: status bar, display
840    /// cutouts/notch, home indicator, navigation bars.
841    /// (iOS: `safeAreaInsets`. Android: `WindowInsets` of types
842    /// `systemBars() | displayCutout()`.)
843    pub safe_area: Edges<Pixels>,
844    /// The region covered by the keyboard, when present.
845    /// (iOS: derived from `keyboardWillShow`/frame-change notifications.
846    /// Android: `WindowInsets.Type.ime()`.)
847    pub ime: Edges<Pixels>,
848}
849
850impl WindowInsets {
851    /// The combined inset content should avoid.
852    pub fn effective(&self) -> Edges<Pixels> {
853        Edges {
854            top: self.safe_area.top.max(self.ime.top),
855            right: self.safe_area.right.max(self.ime.right),
856            bottom: self.safe_area.bottom.max(self.ime.bottom),
857            left: self.safe_area.left.max(self.ime.left),
858        }
859    }
860}
861
862/// A change in the state of the focused text input.
863#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
864pub enum TextInputStateChange {
865    /// The window changed from having no active text input to having one.
866    FocusGained,
867    /// The window no longer has an active text input.
868    FocusLost,
869    /// The selection or caret moved
870    SelectionChanged,
871    /// The document content changed outside of platform-initiated edits.
872    ContentChanged,
873}
874
875#[expect(missing_docs)]
876pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
877    fn bounds(&self) -> Bounds<Pixels>;
878    fn is_maximized(&self) -> bool;
879    fn window_bounds(&self) -> WindowBounds;
880    fn content_size(&self) -> Size<Pixels>;
881    /// Returns the visible viewport in logical pixels relative to the content origin.
882    ///
883    /// This may be smaller or offset when a keyboard or zoom obscures content;
884    /// it must not change the full layout size returned by `content_size`.
885    /// Implementations should return a frame snapshot, not query platform layout here.
886    fn visual_viewport_bounds(&self) -> Bounds<Pixels> {
887        Bounds::new(Point::default(), self.content_size())
888    }
889    /// Registers a callback when visible geometry may have changed.
890    ///
891    /// This requests a frame; backends can sample the new viewport and safe-area
892    /// geometry in `prepare_frame` rather than updating it inside the callback.
893    fn on_visual_viewport_changed(&self, _callback: Box<dyn FnMut()>) {}
894    /// Samples platform geometry before a draw, returning whether view caches must be invalidated.
895    ///
896    /// Geometry getters must remain consistent throughout the ensuing draw.
897    /// Do not invoke callbacks here: GPUI is already updating this window.
898    fn prepare_frame(&self) -> bool {
899        false
900    }
901    fn resize(&mut self, size: Size<Pixels>);
902    fn scale_factor(&self) -> f32;
903    fn appearance(&self) -> WindowAppearance;
904    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
905    fn mouse_position(&self) -> Point<Pixels>;
906    fn modifiers(&self) -> Modifiers;
907    fn capslock(&self) -> Capslock;
908    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
909    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
910    /// Apply the focused text region's [`TextInputConfiguration`] to the
911    /// platform's text input session (e.g. attributes of the hidden editable
912    /// element on web). Called only when the configuration changes, because
913    /// reconfiguring a live input session can restart the IME connection.
914    fn set_text_input_configuration(&mut self, _configuration: TextInputConfiguration) {}
915    fn prompt(
916        &self,
917        level: PromptLevel,
918        msg: &str,
919        detail: Option<&str>,
920        answers: &[PromptButton],
921    ) -> Option<oneshot::Receiver<usize>>;
922    fn activate(&self);
923    /// Requests that the operating system draw attention to this window.
924    fn request_attention(&self) {}
925    fn is_active(&self) -> bool;
926    /// The current [`WindowVisibility`]. Read once when the window is created;
927    /// afterwards changes arrive through [`Self::on_visibility_change`].
928    fn visibility(&self) -> WindowVisibility;
929    fn is_hovered(&self) -> bool;
930    fn background_appearance(&self) -> WindowBackgroundAppearance;
931    fn set_title(&mut self, title: &str);
932    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
933    fn minimize(&self);
934    fn zoom(&self);
935    fn toggle_fullscreen(&self);
936    fn is_fullscreen(&self) -> bool;
937    fn frame_waker(&self) -> Option<Rc<dyn Fn()>> {
938        None
939    }
940    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
941    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
942    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
943    /// Registers the callback invoked when [`Self::visibility`] changes. Only
944    /// transitions are reported; the callback runs on the main thread outside
945    /// of any window update.
946    fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
947    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
948    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
949    fn on_moved(&self, callback: Box<dyn FnMut()>);
950    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
951    fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
952    fn on_close(&self, callback: Box<dyn FnOnce()>);
953    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
954    fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
955    fn draw(&self, scene: &Scene);
956    fn schedule_frame(&self) {}
957    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
958    fn is_subpixel_rendering_supported(&self) -> bool;
959
960    // macOS specific methods
961    fn get_title(&self) -> String {
962        String::new()
963    }
964    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
965        None
966    }
967    fn tab_bar_visible(&self) -> bool {
968        false
969    }
970    fn set_edited(&mut self, _edited: bool) {}
971    fn set_document_path(&self, _path: Option<&std::path::Path>) {}
972    fn toggle_simple_fullscreen(&self) {}
973    fn is_simple_fullscreen(&self) -> bool {
974        false
975    }
976    #[cfg(target_os = "macos")]
977    fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
978    fn show_character_palette(&self) {}
979    fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
980    fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
981    fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
982    fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
983    fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
984    fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
985    fn merge_all_windows(&self) {}
986    fn move_tab_to_new_window(&self) {}
987    fn toggle_window_tab_overview(&self) {}
988    fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
989
990    fn native_window_state(&self) -> Option<Vec<u8>> {
991        None
992    }
993    fn restore_native_window_state(&self, _state: &[u8]) {}
994
995    #[cfg(target_os = "windows")]
996    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
997
998    // Linux specific methods
999    fn inner_window_bounds(&self) -> WindowBounds {
1000        self.window_bounds()
1001    }
1002    fn request_decorations(&self, _decorations: WindowDecorations) {}
1003    fn show_window_menu(&self, _position: Point<Pixels>) {}
1004    fn start_window_move(&self) {}
1005    fn can_start_external_drag(&self) -> bool {
1006        false
1007    }
1008    fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
1009        false
1010    }
1011    fn start_window_resize(&self, _edge: ResizeEdge) {}
1012    fn set_exclusive_zone(&self, _zone: Pixels) {}
1013    #[cfg(all(target_os = "linux", feature = "wayland"))]
1014    fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
1015    fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
1016    fn window_decorations(&self) -> Decorations {
1017        Decorations::Server
1018    }
1019    fn set_app_id(&mut self, _app_id: &str) {}
1020    fn map_window(&mut self) -> anyhow::Result<()> {
1021        Ok(())
1022    }
1023    fn window_controls(&self) -> WindowControls {
1024        WindowControls::default()
1025    }
1026    fn set_client_inset(&self, _inset: Pixels) {}
1027    fn gpu_specs(&self) -> Option<GpuSpecs>;
1028
1029    fn update_ime_position(&self, _bounds: Bounds<Pixels>);
1030
1031    // Mobile platform methods.
1032
1033    /// The regions of this window currently obscured or reserved by the
1034    /// system. Zero on platforms without such regions.
1035    fn insets(&self) -> WindowInsets {
1036        WindowInsets::default()
1037    }
1038
1039    /// Registers a callback invoked whenever [`Self::insets`] change.
1040    ///
1041    /// Contract: fires continuously during animated transitions (Android
1042    /// `WindowInsetsAnimation` progress; on iOS the platform interpolates
1043    /// the keyboard animation curve on frame ticks) and is exact at rest.
1044    fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
1045
1046    /// Sets the handler for the system back action (Android back
1047    /// button/gesture; no source on iOS or desktop).
1048    fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
1049
1050    /// Declares whether the application would currently handle the system
1051    /// back action (e.g. navigation depth > 0).
1052    fn set_back_enabled(&self, _enabled: bool) {}
1053
1054    /// Requests that the soft keyboard be shown.
1055    fn show_soft_keyboard(&self) {}
1056
1057    /// Requests that the soft keyboard be hidden.
1058    fn hide_soft_keyboard(&self) {}
1059
1060    /// Inform the operating system that the text input state has changed
1061    fn text_input_state_changed(&self, _change: TextInputStateChange) {}
1062
1063    fn play_system_bell(&self) {}
1064
1065    /// Initialize the accessibility adapter with callbacks.
1066    fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1067
1068    /// Provide a TreeUpdate to the accessibility adapter.
1069    fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1070
1071    /// Inform the adapter of updated window bounds.
1072    fn a11y_update_window_bounds(&self) {}
1073
1074    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1075    fn as_test(&mut self) -> Option<&mut TestWindow> {
1076        None
1077    }
1078
1079    /// Renders the given scene to a texture and returns the pixel data as an RGBA image.
1080    /// This does not present the frame to screen - useful for visual testing where we want
1081    /// to capture what would be rendered without displaying it or requiring the window to be visible.
1082    #[cfg(any(test, feature = "test-support"))]
1083    fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1084        anyhow::bail!("render_to_image not implemented for this platform")
1085    }
1086}
1087
1088/// A renderer for headless windows that can produce real rendered output.
1089#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1090pub trait PlatformHeadlessRenderer {
1091    /// Render a scene and return the result as an RGBA image.
1092    fn render_scene_to_image(
1093        &mut self,
1094        scene: &Scene,
1095        size: Size<DevicePixels>,
1096    ) -> Result<RgbaImage>;
1097
1098    /// Render a scene to an offscreen target without reading the result back.
1099    ///
1100    /// This is the headless analogue of presenting a frame: it performs the
1101    /// same CPU-side scene encoding and GPU submission as drawing to a real
1102    /// window, but doesn't block on GPU completion or copy pixels back.
1103    fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1104
1105    /// Returns the sprite atlas used by this renderer.
1106    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1107}
1108
1109/// Type alias for runnables with metadata.
1110/// Previously an enum with a single variant, now simplified to a direct type alias.
1111#[doc(hidden)]
1112pub type RunnableVariant = Runnable<RunnableMeta>;
1113
1114#[doc(hidden)]
1115pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1116
1117#[doc(hidden)]
1118pub enum TasksIncluded {
1119    OnlyCompleted,
1120    CompletedAndRunning,
1121}
1122
1123/// This type is public so that our test macro can generate and use it, but it should not
1124/// be considered part of our public API.
1125#[doc(hidden)]
1126pub trait PlatformDispatcher: Send + Sync {
1127    fn is_main_thread(&self) -> bool;
1128    fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1129    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1130    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1131
1132    fn dispatch_on_main_thread_when_idle(
1133        &self,
1134        runnable: RunnableVariant,
1135        timeout: Option<Duration>,
1136    ) {
1137        let _ = timeout;
1138        self.dispatch_on_main_thread(runnable, Priority::Low);
1139    }
1140
1141    fn idle_time_remaining(&self) -> Option<Duration> {
1142        None
1143    }
1144
1145    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1146
1147    fn now(&self) -> Instant {
1148        Instant::now()
1149    }
1150
1151    fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1152        gpui_util::defer(Box::new(|| {}))
1153    }
1154
1155    fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
1156        ActivityGuard::noop()
1157    }
1158
1159    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1160    fn as_test(&self) -> Option<&TestDispatcher> {
1161        None
1162    }
1163
1164    // This cfg must match the `threaded_dispatcher` module's, which implements
1165    // this method whenever it compiles.
1166    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1167    fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1168        None
1169    }
1170}
1171
1172#[expect(missing_docs)]
1173pub trait PlatformTextSystem: Send + Sync {
1174    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1175    /// Get all available font names.
1176    fn all_font_names(&self) -> Vec<String>;
1177    /// Get the font ID for a font descriptor.
1178    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1179    /// Prewarm any system font caches needed to shape text.
1180    fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1181    /// Get metrics for a font.
1182    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1183    /// Get typographic bounds for a glyph.
1184    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1185    /// Get the advance width for a glyph.
1186    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1187    /// Get the glyph ID for a character.
1188    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1189    /// Get raster bounds for a glyph.
1190    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1191    /// Rasterize a glyph.
1192    fn rasterize_glyph(
1193        &self,
1194        params: &RenderGlyphParams,
1195        raster_bounds: Bounds<DevicePixels>,
1196    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1197    /// Layout a line of text with the given font runs.
1198    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1199    /// Returns the recommended text rendering mode for the given font and size.
1200    fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1201    -> TextRenderingMode;
1202    /// Returns the dilation level to use for a glyph painted in the given color.
1203    fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1204        0
1205    }
1206}
1207
1208#[expect(missing_docs)]
1209pub struct NoopTextSystem;
1210
1211#[expect(missing_docs)]
1212impl NoopTextSystem {
1213    #[allow(dead_code)]
1214    pub fn new() -> Self {
1215        Self
1216    }
1217}
1218
1219impl PlatformTextSystem for NoopTextSystem {
1220    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1221        Ok(())
1222    }
1223
1224    fn all_font_names(&self) -> Vec<String> {
1225        Vec::new()
1226    }
1227
1228    fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1229        Ok(FontId(1))
1230    }
1231
1232    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1233        FontMetrics {
1234            units_per_em: 1000,
1235            ascent: 1025.0,
1236            descent: -275.0,
1237            line_gap: 0.0,
1238            underline_position: -95.0,
1239            underline_thickness: 60.0,
1240            cap_height: 698.0,
1241            x_height: 516.0,
1242            bounding_box: Bounds {
1243                origin: Point {
1244                    x: -260.0,
1245                    y: -245.0,
1246                },
1247                size: Size {
1248                    width: 1501.0,
1249                    height: 1364.0,
1250                },
1251            },
1252        }
1253    }
1254
1255    fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1256        Ok(Bounds {
1257            origin: Point { x: 54.0, y: 0.0 },
1258            size: size(392.0, 528.0),
1259        })
1260    }
1261
1262    fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1263        Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1264    }
1265
1266    fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1267        Some(GlyphId(ch.len_utf16() as u32))
1268    }
1269
1270    fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1271        Ok(Default::default())
1272    }
1273
1274    fn rasterize_glyph(
1275        &self,
1276        _params: &RenderGlyphParams,
1277        raster_bounds: Bounds<DevicePixels>,
1278    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1279        Ok((raster_bounds.size, Vec::new()))
1280    }
1281
1282    fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1283        let mut position = px(0.);
1284        let metrics = self.font_metrics(FontId(0));
1285        let em_width = font_size
1286            * self
1287                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1288                .unwrap()
1289                .width
1290            / metrics.units_per_em as f32;
1291        let mut glyphs = Vec::new();
1292        for (ix, c) in text.char_indices() {
1293            if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1294                glyphs.push(ShapedGlyph {
1295                    id: glyph,
1296                    position: point(position, px(0.)),
1297                    index: ix,
1298                    is_emoji: glyph.0 == 2,
1299                });
1300                if glyph.0 == 2 {
1301                    position += em_width * 2.0;
1302                } else {
1303                    position += em_width;
1304                }
1305            } else {
1306                position += em_width
1307            }
1308        }
1309        let mut runs = Vec::default();
1310        if !glyphs.is_empty() {
1311            runs.push(ShapedRun {
1312                font_id: FontId(0),
1313                glyphs,
1314            });
1315        } else {
1316            position = px(0.);
1317        }
1318
1319        LineLayout {
1320            font_size,
1321            width: position,
1322            ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1323            descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1324            runs,
1325            len: text.len(),
1326        }
1327    }
1328
1329    fn recommended_rendering_mode(
1330        &self,
1331        _font_id: FontId,
1332        _font_size: Pixels,
1333    ) -> TextRenderingMode {
1334        TextRenderingMode::Grayscale
1335    }
1336}
1337
1338// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
1339// Copyright (c) Microsoft Corporation.
1340// Licensed under the MIT license.
1341/// Compute gamma correction ratios for subpixel text rendering.
1342#[allow(dead_code)]
1343pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1344    const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1345        [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
1346        [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
1347        [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
1348        [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
1349        [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
1350        [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
1351        [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
1352        [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
1353        [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
1354        [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
1355        [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
1356        [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
1357        [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
1358    ];
1359
1360    const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1361    const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1362
1363    let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1364    let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1365
1366    [
1367        ratios[0] * NORM13,
1368        ratios[1] * NORM24,
1369        ratios[2] * NORM13,
1370        ratios[3] * NORM24,
1371    ]
1372}
1373
1374#[derive(PartialEq, Eq, Hash, Clone)]
1375#[expect(missing_docs)]
1376pub enum AtlasKey {
1377    Glyph(RenderGlyphParams),
1378    Svg(RenderSvgParams),
1379    Image(RenderImageParams),
1380}
1381
1382impl AtlasKey {
1383    #[cfg_attr(
1384        all(
1385            any(target_os = "linux", target_os = "freebsd"),
1386            not(any(feature = "x11", feature = "wayland"))
1387        ),
1388        allow(dead_code)
1389    )]
1390    /// Returns the texture kind for this atlas key.
1391    pub fn texture_kind(&self) -> AtlasTextureKind {
1392        match self {
1393            AtlasKey::Glyph(params) => {
1394                if params.is_emoji {
1395                    AtlasTextureKind::Polychrome
1396                } else if params.subpixel_rendering {
1397                    AtlasTextureKind::Subpixel
1398                } else {
1399                    AtlasTextureKind::Monochrome
1400                }
1401            }
1402            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1403            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1404        }
1405    }
1406}
1407
1408impl From<RenderGlyphParams> for AtlasKey {
1409    fn from(params: RenderGlyphParams) -> Self {
1410        Self::Glyph(params)
1411    }
1412}
1413
1414impl From<RenderSvgParams> for AtlasKey {
1415    fn from(params: RenderSvgParams) -> Self {
1416        Self::Svg(params)
1417    }
1418}
1419
1420impl From<RenderImageParams> for AtlasKey {
1421    fn from(params: RenderImageParams) -> Self {
1422        Self::Image(params)
1423    }
1424}
1425
1426#[expect(missing_docs)]
1427pub trait PlatformAtlas {
1428    fn get_or_insert_with<'a>(
1429        &self,
1430        key: &AtlasKey,
1431        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1432    ) -> Result<Option<AtlasTile>>;
1433    fn remove(&self, key: &AtlasKey);
1434
1435    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1436    fn contains(&self, _key: &AtlasKey) -> bool {
1437        false
1438    }
1439}
1440
1441#[doc(hidden)]
1442pub struct AtlasTextureList<T> {
1443    pub textures: Vec<Option<T>>,
1444    pub free_list: Vec<usize>,
1445}
1446
1447impl<T> Default for AtlasTextureList<T> {
1448    fn default() -> Self {
1449        Self {
1450            textures: Vec::default(),
1451            free_list: Vec::default(),
1452        }
1453    }
1454}
1455
1456impl<T> ops::Index<usize> for AtlasTextureList<T> {
1457    type Output = Option<T>;
1458
1459    fn index(&self, index: usize) -> &Self::Output {
1460        &self.textures[index]
1461    }
1462}
1463
1464impl<T> AtlasTextureList<T> {
1465    #[allow(unused)]
1466    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1467        self.free_list.clear();
1468        self.textures.drain(..)
1469    }
1470
1471    #[allow(dead_code)]
1472    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1473        self.textures.iter_mut().flatten()
1474    }
1475}
1476
1477#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1478#[repr(C)]
1479#[expect(missing_docs)]
1480pub struct AtlasTile {
1481    /// The texture this tile belongs to.
1482    pub texture_id: AtlasTextureId,
1483    /// The unique ID of this tile within its texture.
1484    pub tile_id: TileId,
1485    /// Padding around the tile content in pixels.
1486    pub padding: u32,
1487    /// The bounds of this tile within the texture.
1488    pub bounds: Bounds<DevicePixels>,
1489}
1490
1491#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1492#[repr(C)]
1493#[expect(missing_docs)]
1494pub struct AtlasTextureId {
1495    // We use u32 instead of usize for Metal Shader Language compatibility
1496    /// The index of this texture in the atlas.
1497    pub index: u32,
1498    /// The kind of content stored in this texture.
1499    pub kind: AtlasTextureKind,
1500}
1501
1502#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1503#[repr(C)]
1504#[cfg_attr(
1505    all(
1506        any(target_os = "linux", target_os = "freebsd"),
1507        not(any(feature = "x11", feature = "wayland"))
1508    ),
1509    allow(dead_code)
1510)]
1511#[expect(missing_docs)]
1512pub enum AtlasTextureKind {
1513    Monochrome = 0,
1514    Polychrome = 1,
1515    Subpixel = 2,
1516}
1517
1518#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1519#[repr(C)]
1520#[expect(missing_docs)]
1521pub struct TileId(pub u32);
1522
1523impl From<etagere::AllocId> for TileId {
1524    fn from(id: etagere::AllocId) -> Self {
1525        Self(id.serialize())
1526    }
1527}
1528
1529impl From<TileId> for etagere::AllocId {
1530    fn from(id: TileId) -> Self {
1531        Self::deserialize(id.0)
1532    }
1533}
1534
1535#[expect(missing_docs)]
1536pub struct PlatformInputHandler {
1537    cx: AsyncWindowContext,
1538    handler: Box<dyn InputHandler>,
1539}
1540
1541#[expect(missing_docs)]
1542#[cfg_attr(
1543    all(
1544        any(target_os = "linux", target_os = "freebsd"),
1545        not(any(feature = "x11", feature = "wayland"))
1546    ),
1547    allow(dead_code)
1548)]
1549impl PlatformInputHandler {
1550    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1551        Self { cx, handler }
1552    }
1553
1554    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1555        self.cx
1556            .update(|window, cx| {
1557                self.handler
1558                    .selected_text_range(ignore_disabled_input, window, cx)
1559            })
1560            .ok()
1561            .flatten()
1562    }
1563
1564    #[cfg_attr(target_os = "windows", allow(dead_code))]
1565    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1566        self.cx
1567            .update(|window, cx| self.handler.marked_text_range(window, cx))
1568            .ok()
1569            .flatten()
1570    }
1571
1572    #[cfg_attr(
1573        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1574        allow(dead_code)
1575    )]
1576    pub fn text_for_range(
1577        &mut self,
1578        range_utf16: Range<usize>,
1579        adjusted: &mut Option<Range<usize>>,
1580    ) -> Option<String> {
1581        self.cx
1582            .update(|window, cx| {
1583                self.handler
1584                    .text_for_range(range_utf16, adjusted, window, cx)
1585            })
1586            .ok()
1587            .flatten()
1588    }
1589
1590    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1591        self.cx
1592            .update(|window, cx| {
1593                self.handler
1594                    .replace_text_in_range(replacement_range, text, window, cx);
1595            })
1596            .ok();
1597    }
1598
1599    pub fn replace_and_mark_text_in_range(
1600        &mut self,
1601        range_utf16: Option<Range<usize>>,
1602        new_text: &str,
1603        new_selected_range: Option<Range<usize>>,
1604    ) {
1605        self.cx
1606            .update(|window, cx| {
1607                self.handler.replace_and_mark_text_in_range(
1608                    range_utf16,
1609                    new_text,
1610                    new_selected_range,
1611                    window,
1612                    cx,
1613                )
1614            })
1615            .ok();
1616    }
1617
1618    #[cfg_attr(target_os = "windows", allow(dead_code))]
1619    pub fn unmark_text(&mut self) {
1620        self.cx
1621            .update(|window, cx| self.handler.unmark_text(window, cx))
1622            .ok();
1623    }
1624
1625    pub fn paste(&mut self, item: ClipboardItem) {
1626        self.cx
1627            .update(|window, cx| self.handler.paste(item, window, cx))
1628            .ok();
1629    }
1630
1631    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1632        self.cx
1633            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1634            .ok()
1635            .flatten()
1636    }
1637
1638    #[allow(dead_code)]
1639    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1640        self.handler.apple_press_and_hold_enabled()
1641    }
1642
1643    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1644        self.handler.replace_text_in_range(None, input, window, cx);
1645    }
1646
1647    pub fn compute_ime_candidate_bounds(
1648        marked_range: Option<Range<usize>>,
1649        selection: &UTF16Selection,
1650        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1651    ) -> Option<Bounds<Pixels>> {
1652        if let Some(marked_range) = marked_range {
1653            // Default to the start of the marked (composing) range.
1654            let mut line_start = marked_range.start;
1655
1656            // Walk backward from the caret looking for a line break. A change in
1657            // the Y coordinate means we crossed into the previous visual line, so
1658            // the line start is one position after the break point.
1659            let caret = selection.range.end;
1660            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1661                for i in (marked_range.start..caret).rev() {
1662                    if let Some(b) = bounds_for_range(i..i) {
1663                        if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1664                            line_start = i + 1;
1665                            break;
1666                        }
1667                    }
1668                }
1669            }
1670            bounds_for_range(line_start..line_start)
1671        } else {
1672            // No active composition — use the selection endpoint.
1673            let offset = if selection.reversed {
1674                selection.range.start
1675            } else {
1676                selection.range.end
1677            };
1678            bounds_for_range(offset..offset)
1679        }
1680    }
1681
1682    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1683        let marked_range = self.handler.marked_text_range(window, cx);
1684        let selection = self.handler.selected_text_range(true, window, cx)?;
1685        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1686            self.handler.bounds_for_range(range, window, cx)
1687        })
1688    }
1689
1690    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1691        let marked_range = self.marked_text_range();
1692        let selection = self.selected_text_range(true)?;
1693        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1694            self.bounds_for_range(range)
1695        })
1696    }
1697
1698    #[allow(unused)]
1699    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1700        self.cx
1701            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1702            .ok()
1703            .flatten()
1704    }
1705
1706    /// See [`InputHandler::set_selected_text_range`].
1707    pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1708        self.cx
1709            .update(|window, cx| {
1710                self.handler
1711                    .set_selected_text_range(range_utf16, window, cx)
1712            })
1713            .ok();
1714    }
1715
1716    /// See [`InputHandler::element_bounds`].
1717    pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1718        self.cx
1719            .update(|window, cx| self.handler.element_bounds(window, cx))
1720            .ok()
1721            .flatten()
1722    }
1723
1724    /// See [`InputHandler::text_length_utf16`].
1725    pub fn text_length_utf16(&mut self) -> Option<usize> {
1726        self.cx
1727            .update(|window, cx| self.handler.text_length_utf16(window, cx))
1728            .ok()
1729            .flatten()
1730    }
1731
1732    #[allow(dead_code)]
1733    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1734        self.handler.accepts_text_input(window, cx)
1735    }
1736
1737    #[allow(dead_code)]
1738    pub fn query_accepts_text_input(&mut self) -> bool {
1739        self.cx
1740            .update(|window, cx| self.handler.accepts_text_input(window, cx))
1741            .unwrap_or(true)
1742    }
1743
1744    /// See [`InputHandler::prefers_ime_for_printable_keys`].
1745    ///
1746    /// This is not a pure delegation to the handler: while a multi-stroke binding is pending this
1747    /// returns `false` regardless of the handler's preference, because the next printable key may
1748    /// complete a binding whose prefix already bypassed the IME.
1749    pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1750        self.cx
1751            .update(|window, cx| {
1752                // The next printable key may complete a chord whose prefix bypassed the IME.
1753                !window.has_pending_keystrokes()
1754                    && self.handler.prefers_ime_for_printable_keys(window, cx)
1755            })
1756            .unwrap_or(false)
1757    }
1758
1759    /// See [`InputHandler::text_input_configuration`].
1760    pub fn text_input_configuration(
1761        &mut self,
1762        window: &mut Window,
1763        cx: &mut App,
1764    ) -> TextInputConfiguration {
1765        self.handler.text_input_configuration(window, cx)
1766    }
1767
1768    /// See [`InputHandler::text_input_editable_range`].
1769    pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
1770        self.cx
1771            .update(|window, cx| self.handler.text_input_editable_range(window, cx))
1772            .ok()
1773            .flatten()
1774    }
1775}
1776
1777/// A struct representing a selection in a text buffer, in UTF16 characters.
1778/// This is different from a range because the head may be before the tail.
1779#[derive(Debug)]
1780pub struct UTF16Selection {
1781    /// The range of text in the document this selection corresponds to
1782    /// in UTF16 characters.
1783    pub range: Range<usize>,
1784    /// Whether the head of this selection is at the start (true), or end (false)
1785    /// of the range
1786    pub reversed: bool,
1787}
1788
1789/// Zed's interface for handling text input from the platform's IME system
1790/// This is currently a 1:1 exposure of the NSTextInputClient API:
1791///
1792/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
1793pub trait InputHandler: 'static {
1794    /// Get the range of the user's currently selected text, if any
1795    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
1796    ///
1797    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1798    fn selected_text_range(
1799        &mut self,
1800        ignore_disabled_input: bool,
1801        window: &mut Window,
1802        cx: &mut App,
1803    ) -> Option<UTF16Selection>;
1804
1805    /// Get the range of the currently marked text, if any
1806    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
1807    ///
1808    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1809    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1810
1811    /// Get the text for the given document range in UTF-16 characters
1812    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
1813    ///
1814    /// range_utf16 is in terms of UTF-16 characters
1815    fn text_for_range(
1816        &mut self,
1817        range_utf16: Range<usize>,
1818        adjusted_range: &mut Option<Range<usize>>,
1819        window: &mut Window,
1820        cx: &mut App,
1821    ) -> Option<String>;
1822
1823    /// Replace the text in the given document range with the given text
1824    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
1825    ///
1826    /// replacement_range is in terms of UTF-16 characters
1827    fn replace_text_in_range(
1828        &mut self,
1829        replacement_range: Option<Range<usize>>,
1830        text: &str,
1831        window: &mut Window,
1832        cx: &mut App,
1833    );
1834
1835    /// Replace the text in the given document range with the given text,
1836    /// and mark the given text as part of an IME 'composing' state
1837    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
1838    ///
1839    /// range_utf16 is in terms of UTF-16 characters
1840    /// new_selected_range is in terms of UTF-16 characters
1841    fn replace_and_mark_text_in_range(
1842        &mut self,
1843        range_utf16: Option<Range<usize>>,
1844        new_text: &str,
1845        new_selected_range: Option<Range<usize>>,
1846        window: &mut Window,
1847        cx: &mut App,
1848    );
1849
1850    /// Remove the IME 'composing' state from the document
1851    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
1852    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1853
1854    /// Insert a platform-initiated paste at the current selection.
1855    ///
1856    /// Platforms that deliver paste as an input event rather than through an
1857    /// application-defined action (e.g. the DOM `paste` event on web) call
1858    /// this with the full clipboard contents. The default implementation
1859    /// inserts only the plain-text portion of the item.
1860    fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1861        if let Some(text) = item.text() {
1862            self.replace_text_in_range(None, &text, window, cx);
1863        }
1864    }
1865
1866    /// Get the bounds of the given document range in screen coordinates
1867    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
1868    ///
1869    /// This is used for positioning the IME candidate window
1870    fn bounds_for_range(
1871        &mut self,
1872        range_utf16: Range<usize>,
1873        window: &mut Window,
1874        cx: &mut App,
1875    ) -> Option<Bounds<Pixels>>;
1876
1877    /// Get the character offset for the given point in terms of UTF16 characters
1878    ///
1879    /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
1880    fn character_index_for_point(
1881        &mut self,
1882        point: Point<Pixels>,
1883        window: &mut Window,
1884        cx: &mut App,
1885    ) -> Option<usize>;
1886
1887    /// Set the range of the user's currently selected text.
1888    ///
1889    /// This is the reverse data-flow direction from [`Self::selected_text_range`]:
1890    /// platforms call it when the system text machinery moves the selection on the
1891    /// application's behalf — e.g. the user drags a system selection handle or
1892    /// invokes Select All from system UI (iOS `UITextInput setSelectedTextRange:`,
1893    /// Android `InputConnection.setSelection`).
1894    ///
1895    /// range_utf16 is in terms of UTF-16 characters, from 0 to the length of the document
1896    fn set_selected_text_range(
1897        &mut self,
1898        _range_utf16: Range<usize>,
1899        _window: &mut Window,
1900        _cx: &mut App,
1901    ) {
1902    }
1903
1904    /// Get the bounds of the focused text element in window coordinates, if known.
1905    ///
1906    /// This is the pull counterpart to the [`PlatformWindow::update_ime_position`]
1907    /// push: mobile platforms ask for the focused element's geometry when they
1908    /// need it (e.g. to frame system text-interaction UI overlaid on the focused
1909    /// element).
1910    fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
1911        None
1912    }
1913
1914    /// Get the length of the document in UTF-16 characters, if known.
1915    fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
1916        None
1917    }
1918
1919    /// Allows a given input context to opt into getting raw key repeats instead of
1920    /// sending these to the platform.
1921    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
1922    /// (which is how iTerm does it) but it doesn't seem to work for me.
1923    #[allow(dead_code)]
1924    fn apple_press_and_hold_enabled(&mut self) -> bool {
1925        true
1926    }
1927
1928    /// Returns whether this handler is accepting text input to be inserted.
1929    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1930        true
1931    }
1932
1933    /// The contiguous range of text, in UTF-16 code units, that platform text
1934    /// input may read and edit around the current selection.
1935    ///
1936    /// Platforms that mirror document text into an IME-editable buffer clamp
1937    /// the mirrored window to this range, so multi-step IME edit gestures
1938    /// (word deletion, autocorrect rewrites, suggestion picks) cannot reach
1939    /// content outside it. The range should contain the current selection;
1940    /// when it cannot (a selection spanning a region boundary), platforms
1941    /// degrade the mirrored IME context rather than widening the range.
1942    /// `None` places no bound.
1943    fn text_input_editable_range(
1944        &mut self,
1945        _window: &mut Window,
1946        _cx: &mut App,
1947    ) -> Option<Range<usize>> {
1948        None
1949    }
1950
1951    /// Returns whether printable keys should be routed to the IME before keybinding
1952    /// matching when a non-ASCII input source (e.g. Japanese, Korean, Chinese IME)
1953    /// is active. This prevents multi-stroke keybindings like `jj` from intercepting
1954    /// keys that the IME should compose.
1955    ///
1956    /// Defaults to `false`. The editor overrides this based on whether it expects
1957    /// character input (e.g. Vim insert mode returns `true`, normal mode returns `false`).
1958    /// The terminal keeps the default `false` so that raw keys reach the terminal process.
1959    fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1960        false
1961    }
1962
1963    /// Get this handler's preferences for platform text assistance.
1964    ///
1965    /// GPUI re-queries this every frame and forwards it to the platform window
1966    /// only when it changes, so implementations must be cheap and may vary the
1967    /// result with application state (e.g. with the cursor's position).
1968    fn text_input_configuration(
1969        &mut self,
1970        _window: &mut Window,
1971        _cx: &mut App,
1972    ) -> TextInputConfiguration {
1973        TextInputConfiguration::default()
1974    }
1975}
1976
1977/// Platform text-assistance preferences for the focused text region.
1978///
1979/// Returned by [`InputHandler::text_input_configuration`] and forwarded to the
1980/// platform whenever it changes; the platform maps the fields onto its native
1981/// input-session attributes (on web, DOM attributes of the hidden editable
1982/// element such as `autocorrect` and `enterkeyhint`).
1983///
1984/// The default disables all text assistance and requests no particular action
1985/// key presentation.
1986#[derive(Clone, Debug, Default, PartialEq, Eq)]
1987pub struct TextInputConfiguration {
1988    /// Whether the platform may automatically correct entered text.
1989    pub autocorrect: bool,
1990    /// How software keyboards automatically capitalize entered text.
1991    pub autocapitalize: Autocapitalize,
1992    /// Whether software keyboards may offer word suggestions and spellcheck.
1993    pub suggestions: bool,
1994    /// The action advertised on a software keyboard's confirm ("enter") key.
1995    pub input_action: TextInputAction,
1996}
1997
1998/// Automatic capitalization applied by software keyboards.
1999#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2000pub enum Autocapitalize {
2001    /// No automatic capitalization.
2002    #[default]
2003    None,
2004    /// Capitalize the first letter of each word.
2005    Words,
2006    /// Capitalize the first letter of each sentence.
2007    Sentences,
2008    /// Capitalize every letter.
2009    Characters,
2010}
2011
2012/// The action a software keyboard advertises on its confirm ("enter") key.
2013///
2014/// This affects only how the key is presented (icon or label); pressing it is
2015/// still delivered as ordinary input.
2016///
2017/// The variants are the HTML `enterkeyhint` attribute's value set
2018/// (<https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-enterkeyhint-attribute>),
2019/// which also maps onto Android's `IME_ACTION_*` constants and iOS's
2020/// `UIReturnKeyType`; [`TextInputAction::Unspecified`] means "emit no hint".
2021#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2022pub enum TextInputAction {
2023    /// Let the platform choose its default presentation.
2024    #[default]
2025    Unspecified,
2026    /// Inserting a line break.
2027    Enter,
2028    /// Committing the field's value.
2029    Done,
2030    /// Navigating to the typed target.
2031    Go,
2032    /// Moving to the next field.
2033    Next,
2034    /// Moving to the previous field.
2035    Previous,
2036    /// Executing a search.
2037    Search,
2038    /// Sending a message.
2039    Send,
2040}
2041
2042/// The variables that can be configured when creating a new window
2043#[derive(Debug)]
2044pub struct WindowOptions {
2045    /// Specifies the state and bounds of the window in screen coordinates.
2046    /// - `None`: Inherit the bounds.
2047    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
2048    pub window_bounds: Option<WindowBounds>,
2049
2050    /// The titlebar configuration of the window
2051    pub titlebar: Option<TitlebarOptions>,
2052
2053    /// Whether the window should be focused when created
2054    pub focus: bool,
2055
2056    /// Whether the window should be shown when created
2057    pub show: bool,
2058
2059    /// The kind of window to create
2060    pub kind: WindowKind,
2061
2062    /// Whether the window can be moved by the user. When `false`, the user cannot drag
2063    /// the window (on macOS this sets `NSWindow.isMovable`, which also disables the
2064    /// Window-menu tiling items); programmatic moves are still allowed.
2065    pub is_movable: bool,
2066
2067    /// Whether the application owns dragging of the (custom) titlebar, rather than
2068    /// AppKit. Only has an effect on macOS.
2069    ///
2070    /// Set this to `true` for windows that draw their own titlebar and move the window
2071    /// themselves via [`Window::start_window_move`]. It marks the whole content view as
2072    /// app-owned titlebar content, so AppKit neither drags the window from the titlebar
2073    /// nor delays titlebar clicks while disambiguating double-clicks (a delay first
2074    /// observed on macOS 27). It is independent of `is_movable`, so such windows stay
2075    /// user-movable (via their own drag) and keep the Window-menu tiling items enabled.
2076    ///
2077    /// Leave this `false` for windows that rely on AppKit's native titlebar dragging.
2078    pub app_owns_titlebar_drag: bool,
2079
2080    /// The minimum interval between animation frames while the window is inactive.
2081    ///
2082    /// Set to `None` to disable inactive-window animation frame throttling.
2083    pub inactive_frame_interval: Option<Duration>,
2084
2085    /// Whether the window should be resizable by the user
2086    pub is_resizable: bool,
2087
2088    /// Whether the window should be minimized by the user
2089    pub is_minimizable: bool,
2090
2091    /// The display to create the window on, if this is None,
2092    /// the window will be created on the main display
2093    pub display_id: Option<DisplayId>,
2094
2095    /// The appearance of the window background.
2096    pub window_background: WindowBackgroundAppearance,
2097
2098    /// Application identifier of the window. Can by used by desktop environments to group applications together.
2099    pub app_id: Option<String>,
2100
2101    /// Window minimum size
2102    pub window_min_size: Option<Size<Pixels>>,
2103
2104    /// Whether to use client or server-side decorations on X11 and Wayland.
2105    /// The platform may ignore requests it cannot satisfy.
2106    pub window_decorations: Option<WindowDecorations>,
2107
2108    /// Icon image (X11 only)
2109    pub icon: Option<Arc<image::RgbaImage>>,
2110
2111    /// 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.
2112    pub tabbing_identifier: Option<String>,
2113}
2114
2115/// The variables that can be configured when creating a new window
2116#[derive(Debug)]
2117#[cfg_attr(
2118    all(
2119        any(target_os = "linux", target_os = "freebsd"),
2120        not(any(feature = "x11", feature = "wayland"))
2121    ),
2122    allow(dead_code)
2123)]
2124#[allow(missing_docs)]
2125pub struct WindowParams {
2126    pub bounds: Bounds<Pixels>,
2127
2128    /// The titlebar configuration of the window
2129    #[cfg_attr(feature = "wayland", allow(dead_code))]
2130    pub titlebar: Option<TitlebarOptions>,
2131
2132    /// The kind of window to create
2133    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2134    pub kind: WindowKind,
2135
2136    /// Whether the window should be movable by the user
2137    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2138    pub is_movable: bool,
2139
2140    /// Whether the application owns dragging of the (custom) titlebar (macOS only)
2141    #[cfg_attr(
2142        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2143        allow(dead_code)
2144    )]
2145    pub app_owns_titlebar_drag: bool,
2146
2147    /// Whether the window should be resizable by the user
2148    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2149    pub is_resizable: bool,
2150
2151    /// Whether the window should be minimized by the user
2152    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2153    pub is_minimizable: bool,
2154
2155    #[cfg_attr(
2156        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2157        allow(dead_code)
2158    )]
2159    pub focus: bool,
2160
2161    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2162    pub show: bool,
2163
2164    /// An image to set as the window icon (x11 only)
2165    #[cfg_attr(feature = "wayland", allow(dead_code))]
2166    pub icon: Option<Arc<image::RgbaImage>>,
2167
2168    #[cfg_attr(feature = "wayland", allow(dead_code))]
2169    pub display_id: Option<DisplayId>,
2170
2171    #[cfg_attr(feature = "wayland", allow(dead_code))]
2172    pub app_id: Option<String>,
2173
2174    pub window_min_size: Option<Size<Pixels>>,
2175
2176    #[cfg(target_os = "macos")]
2177    pub tabbing_identifier: Option<String>,
2178}
2179
2180/// Represents the status of how a window should be opened.
2181#[derive(Debug, Copy, Clone, PartialEq)]
2182pub enum WindowBounds {
2183    /// Indicates that the window should open in a windowed state with the given bounds.
2184    Windowed(Bounds<Pixels>),
2185    /// Indicates that the window should open in a maximized state.
2186    /// The bounds provided here represent the restore size of the window.
2187    Maximized(Bounds<Pixels>),
2188    /// Indicates that the window should open in fullscreen mode.
2189    /// The bounds provided here represent the restore size of the window.
2190    Fullscreen(Bounds<Pixels>),
2191}
2192
2193impl Default for WindowBounds {
2194    fn default() -> Self {
2195        WindowBounds::Windowed(Bounds::default())
2196    }
2197}
2198
2199impl WindowBounds {
2200    /// Retrieve the inner bounds
2201    pub fn get_bounds(&self) -> Bounds<Pixels> {
2202        match self {
2203            WindowBounds::Windowed(bounds) => *bounds,
2204            WindowBounds::Maximized(bounds) => *bounds,
2205            WindowBounds::Fullscreen(bounds) => *bounds,
2206        }
2207    }
2208
2209    /// Creates a new window bounds that centers the window on the screen.
2210    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2211        WindowBounds::Windowed(Bounds::centered(None, size, cx))
2212    }
2213}
2214
2215impl Default for WindowOptions {
2216    fn default() -> Self {
2217        Self {
2218            window_bounds: None,
2219            titlebar: Some(TitlebarOptions {
2220                title: Default::default(),
2221                appears_transparent: Default::default(),
2222                traffic_light_position: Default::default(),
2223            }),
2224            focus: true,
2225            show: true,
2226            kind: WindowKind::Normal,
2227            is_movable: true,
2228            app_owns_titlebar_drag: false,
2229            inactive_frame_interval: Some(Duration::from_micros(33_333)),
2230            is_resizable: true,
2231            is_minimizable: true,
2232            display_id: None,
2233            window_background: WindowBackgroundAppearance::default(),
2234            icon: None,
2235            app_id: None,
2236            window_min_size: None,
2237            window_decorations: None,
2238            tabbing_identifier: None,
2239        }
2240    }
2241}
2242
2243/// The options that can be configured for a window's titlebar
2244#[derive(Debug, Default)]
2245pub struct TitlebarOptions {
2246    /// The initial title of the window
2247    pub title: Option<SharedString>,
2248
2249    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
2250    /// Refer to [`WindowOptions::window_decorations`] on Linux
2251    pub appears_transparent: bool,
2252
2253    /// The position of the macOS traffic light buttons
2254    pub traffic_light_position: Option<Point<Pixels>>,
2255}
2256
2257/// The kind of window to create
2258#[derive(Clone, Debug, PartialEq, Eq)]
2259pub enum WindowKind {
2260    /// A normal application window
2261    Normal,
2262
2263    /// A window that appears above all other windows, usually used for alerts or popups
2264    /// use sparingly!
2265    PopUp,
2266
2267    /// A parent-anchored, platform-native popup window for menus, comboboxes, context menus and
2268    /// tooltips. Unlike [`WindowKind::PopUp`], it is positioned relative to a parent window.
2269    ///
2270    /// The popup's size comes from [`WindowOptions::window_bounds`], whose origin is ignored.
2271    /// See [`popup::PopupOptions`] for the placement options. Platforms without a native
2272    /// implementation reject it with [`popup::PopupNotSupportedError`].
2273    AnchoredPopup(popup::PopupOptions),
2274
2275    /// A floating window that appears on top of its parent window
2276    Floating,
2277
2278    /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
2279    /// docks, notifications or wallpapers.
2280    #[cfg(all(target_os = "linux", feature = "wayland"))]
2281    LayerShell(layer_shell::LayerShellOptions),
2282
2283    /// A window that appears on top of its parent window and blocks interaction with it
2284    /// until the modal window is closed
2285    Dialog,
2286}
2287
2288/// The appearance of the window, as defined by the operating system.
2289///
2290/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
2291/// values.
2292#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2293pub enum WindowAppearance {
2294    /// A light appearance.
2295    ///
2296    /// On macOS, this corresponds to the `aqua` appearance.
2297    #[default]
2298    Light,
2299
2300    /// A light appearance with vibrant colors.
2301    ///
2302    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
2303    VibrantLight,
2304
2305    /// A dark appearance.
2306    ///
2307    /// On macOS, this corresponds to the `darkAqua` appearance.
2308    Dark,
2309
2310    /// A dark appearance with vibrant colors.
2311    ///
2312    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
2313    VibrantDark,
2314}
2315
2316/// The appearance of the background of the window itself, when there is
2317/// no content or the content is transparent.
2318#[derive(Copy, Clone, Debug, Default, PartialEq)]
2319pub enum WindowBackgroundAppearance {
2320    /// Opaque.
2321    ///
2322    /// This lets the window manager know that content behind this
2323    /// window does not need to be drawn.
2324    ///
2325    /// Actual color depends on the system and themes should define a fully
2326    /// opaque background color instead.
2327    #[default]
2328    Opaque,
2329    /// Plain alpha transparency.
2330    Transparent,
2331    /// Transparency, but the contents behind the window are blurred.
2332    ///
2333    /// Not always supported.
2334    Blurred,
2335    /// The Mica backdrop material, supported on Windows 11.
2336    MicaBackdrop,
2337    /// The Mica Alt backdrop material, supported on Windows 11.
2338    MicaAltBackdrop,
2339}
2340
2341/// The text rendering mode to use for drawing glyphs.
2342#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2343pub enum TextRenderingMode {
2344    /// Use the platform's default text rendering mode.
2345    #[default]
2346    PlatformDefault,
2347    /// Use subpixel (ClearType-style) text rendering.
2348    Subpixel,
2349    /// Use grayscale text rendering.
2350    Grayscale,
2351}
2352
2353/// The options that can be configured for a file dialog prompt
2354#[derive(Clone, Debug)]
2355pub struct PathPromptOptions {
2356    /// Should the prompt allow files to be selected?
2357    pub files: bool,
2358    /// Should the prompt allow directories to be selected?
2359    pub directories: bool,
2360    /// Should the prompt allow multiple files to be selected?
2361    pub multiple: bool,
2362    /// The prompt to show to a user when selecting a path
2363    pub prompt: Option<SharedString>,
2364}
2365
2366/// What kind of prompt styling to show
2367#[derive(Copy, Clone, Debug, PartialEq)]
2368pub enum PromptLevel {
2369    /// A prompt that is shown when the user should be notified of something
2370    Info,
2371
2372    /// A prompt that is shown when the user needs to be warned of a potential problem
2373    Warning,
2374
2375    /// A prompt that is shown when a critical problem has occurred
2376    Critical,
2377}
2378
2379/// Prompt Button
2380#[derive(Clone, Debug, PartialEq)]
2381pub enum PromptButton {
2382    /// Ok button
2383    Ok(SharedString),
2384    /// Cancel button
2385    Cancel(SharedString),
2386    /// Other button
2387    Other(SharedString),
2388}
2389
2390impl PromptButton {
2391    /// Create a button with label
2392    pub fn new(label: impl Into<SharedString>) -> Self {
2393        PromptButton::Other(label.into())
2394    }
2395
2396    /// Create an Ok button
2397    pub fn ok(label: impl Into<SharedString>) -> Self {
2398        PromptButton::Ok(label.into())
2399    }
2400
2401    /// Create a Cancel button
2402    pub fn cancel(label: impl Into<SharedString>) -> Self {
2403        PromptButton::Cancel(label.into())
2404    }
2405
2406    /// Returns true if this button is a cancel button.
2407    #[allow(dead_code)]
2408    pub fn is_cancel(&self) -> bool {
2409        matches!(self, PromptButton::Cancel(_))
2410    }
2411
2412    /// Returns the label of the button
2413    pub fn label(&self) -> &SharedString {
2414        match self {
2415            PromptButton::Ok(label) => label,
2416            PromptButton::Cancel(label) => label,
2417            PromptButton::Other(label) => label,
2418        }
2419    }
2420}
2421
2422impl From<&str> for PromptButton {
2423    fn from(value: &str) -> Self {
2424        match value.to_lowercase().as_str() {
2425            "ok" => PromptButton::Ok("OK".into()),
2426            "cancel" => PromptButton::Cancel("Cancel".into()),
2427            _ => PromptButton::Other(SharedString::from(value.to_owned())),
2428        }
2429    }
2430}
2431
2432/// The style of the cursor (pointer)
2433#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2434pub enum CursorStyle {
2435    /// The default cursor
2436    #[default]
2437    Arrow,
2438
2439    /// A text input cursor
2440    /// corresponds to the CSS cursor value `text`
2441    IBeam,
2442
2443    /// A crosshair cursor
2444    /// corresponds to the CSS cursor value `crosshair`
2445    Crosshair,
2446
2447    /// A closed hand cursor
2448    /// corresponds to the CSS cursor value `grabbing`
2449    ClosedHand,
2450
2451    /// An open hand cursor
2452    /// corresponds to the CSS cursor value `grab`
2453    OpenHand,
2454
2455    /// A pointing hand cursor
2456    /// corresponds to the CSS cursor value `pointer`
2457    PointingHand,
2458
2459    /// A resize left cursor
2460    /// corresponds to the CSS cursor value `w-resize`
2461    ResizeLeft,
2462
2463    /// A resize right cursor
2464    /// corresponds to the CSS cursor value `e-resize`
2465    ResizeRight,
2466
2467    /// A resize cursor to the left and right
2468    /// corresponds to the CSS cursor value `ew-resize`
2469    ResizeLeftRight,
2470
2471    /// A resize up cursor
2472    /// corresponds to the CSS cursor value `n-resize`
2473    ResizeUp,
2474
2475    /// A resize down cursor
2476    /// corresponds to the CSS cursor value `s-resize`
2477    ResizeDown,
2478
2479    /// A resize cursor directing up and down
2480    /// corresponds to the CSS cursor value `ns-resize`
2481    ResizeUpDown,
2482
2483    /// A resize cursor directing up-left and down-right
2484    /// corresponds to the CSS cursor value `nesw-resize`
2485    ResizeUpLeftDownRight,
2486
2487    /// A resize cursor directing up-right and down-left
2488    /// corresponds to the CSS cursor value `nwse-resize`
2489    ResizeUpRightDownLeft,
2490
2491    /// A cursor indicating that the item/column can be resized horizontally.
2492    /// corresponds to the CSS cursor value `col-resize`
2493    ResizeColumn,
2494
2495    /// A cursor indicating that the item/row can be resized vertically.
2496    /// corresponds to the CSS cursor value `row-resize`
2497    ResizeRow,
2498
2499    /// A text input cursor for vertical layout
2500    /// corresponds to the CSS cursor value `vertical-text`
2501    IBeamCursorForVerticalLayout,
2502
2503    /// A cursor indicating that the operation is not allowed
2504    /// corresponds to the CSS cursor value `not-allowed`
2505    OperationNotAllowed,
2506
2507    /// A cursor indicating that the operation will result in a link
2508    /// corresponds to the CSS cursor value `alias`
2509    DragLink,
2510
2511    /// A cursor indicating that the operation will result in a copy
2512    /// corresponds to the CSS cursor value `copy`
2513    DragCopy,
2514
2515    /// A cursor indicating that the operation will result in a context menu
2516    /// corresponds to the CSS cursor value `context-menu`
2517    ContextualMenu,
2518}
2519
2520/// A clipboard item that should be copied to the clipboard
2521#[derive(Clone, Debug, Eq, PartialEq)]
2522pub struct ClipboardItem {
2523    /// The entries in this clipboard item.
2524    pub entries: Vec<ClipboardEntry>,
2525}
2526
2527/// An error produced by [`Platform::read_from_clipboard_async`].
2528///
2529/// Callers surface these failures to users, so the variants distinguish
2530/// conditions that call for different user-facing guidance.
2531#[derive(Clone, Debug, PartialEq, Eq)]
2532pub enum ClipboardReadError {
2533    /// The platform clipboard is not available in this context, e.g. the
2534    /// browser does not expose the async clipboard API or the page is not a
2535    /// secure context.
2536    Unavailable,
2537    /// The platform refused access, e.g. the user declined the browser's
2538    /// clipboard permission prompt or paste confirmation.
2539    Denied(String),
2540    /// The clipboard contents could not be converted into a
2541    /// [`ClipboardItem`].
2542    UnsupportedContent,
2543}
2544
2545impl std::fmt::Display for ClipboardReadError {
2546    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2547        match self {
2548            Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2549            Self::Denied(message) => {
2550                write!(formatter, "clipboard access was denied: {message}")
2551            }
2552            Self::UnsupportedContent => {
2553                formatter.write_str("the clipboard contents are unsupported")
2554            }
2555        }
2556    }
2557}
2558
2559impl std::error::Error for ClipboardReadError {}
2560
2561/// Either a ClipboardString or a ClipboardImage
2562#[derive(Clone, Debug, Eq, PartialEq)]
2563pub enum ClipboardEntry {
2564    /// A string entry
2565    String(ClipboardString),
2566    /// An image entry
2567    Image(Image),
2568    /// A file entry
2569    ExternalPaths(crate::ExternalPaths),
2570}
2571
2572impl ClipboardItem {
2573    /// Create a new ClipboardItem::String with no associated metadata
2574    pub fn new_string(text: String) -> Self {
2575        Self {
2576            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2577        }
2578    }
2579
2580    /// Create a new ClipboardItem::String with the given text and associated metadata
2581    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2582        Self {
2583            entries: vec![ClipboardEntry::String(ClipboardString {
2584                text,
2585                metadata: Some(metadata),
2586            })],
2587        }
2588    }
2589
2590    /// Create a new ClipboardItem::String with the given text and associated metadata
2591    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2592        Self {
2593            entries: vec![ClipboardEntry::String(
2594                ClipboardString::new(text).with_json_metadata(metadata),
2595            )],
2596        }
2597    }
2598
2599    /// Create a new ClipboardItem::Image with the given image with no associated metadata
2600    pub fn new_image(image: &Image) -> Self {
2601        Self {
2602            entries: vec![ClipboardEntry::Image(image.clone())],
2603        }
2604    }
2605
2606    /// Concatenates together all the ClipboardString entries in the item.
2607    /// Returns None if there were no ClipboardString entries.
2608    pub fn text(&self) -> Option<String> {
2609        let mut answer = String::new();
2610
2611        for entry in self.entries.iter() {
2612            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2613                answer.push_str(text);
2614            }
2615        }
2616
2617        if answer.is_empty() {
2618            for entry in self.entries.iter() {
2619                if let ClipboardEntry::ExternalPaths(paths) = entry {
2620                    for path in &paths.0 {
2621                        use std::fmt::Write as _;
2622                        _ = write!(answer, "{}", path.display());
2623                    }
2624                }
2625            }
2626        }
2627
2628        if !answer.is_empty() {
2629            Some(answer)
2630        } else {
2631            None
2632        }
2633    }
2634
2635    /// If this item is one ClipboardEntry::String, returns its metadata.
2636    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2637    pub fn metadata(&self) -> Option<&String> {
2638        match self.entries().first() {
2639            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2640                clipboard_string.metadata.as_ref()
2641            }
2642            _ => None,
2643        }
2644    }
2645
2646    /// Get the item's entries
2647    pub fn entries(&self) -> &[ClipboardEntry] {
2648        &self.entries
2649    }
2650
2651    /// Get owned versions of the item's entries
2652    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2653        self.entries.into_iter()
2654    }
2655}
2656
2657impl From<ClipboardString> for ClipboardEntry {
2658    fn from(value: ClipboardString) -> Self {
2659        Self::String(value)
2660    }
2661}
2662
2663impl From<String> for ClipboardEntry {
2664    fn from(value: String) -> Self {
2665        Self::from(ClipboardString::from(value))
2666    }
2667}
2668
2669impl From<Image> for ClipboardEntry {
2670    fn from(value: Image) -> Self {
2671        Self::Image(value)
2672    }
2673}
2674
2675impl From<ClipboardEntry> for ClipboardItem {
2676    fn from(value: ClipboardEntry) -> Self {
2677        Self {
2678            entries: vec![value],
2679        }
2680    }
2681}
2682
2683impl From<String> for ClipboardItem {
2684    fn from(value: String) -> Self {
2685        Self::from(ClipboardEntry::from(value))
2686    }
2687}
2688
2689impl From<Image> for ClipboardItem {
2690    fn from(value: Image) -> Self {
2691        Self::from(ClipboardEntry::from(value))
2692    }
2693}
2694
2695/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
2696#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2697pub enum ImageFormat {
2698    // Sorted from most to least likely to be pasted into an editor,
2699    // which matters when we iterate through them trying to see if
2700    // clipboard content matches them.
2701    /// .png
2702    Png,
2703    /// .jpeg or .jpg
2704    Jpeg,
2705    /// .webp
2706    Webp,
2707    /// .gif
2708    Gif,
2709    /// .svg
2710    Svg,
2711    /// .bmp
2712    Bmp,
2713    /// .tif or .tiff
2714    Tiff,
2715    /// .ico
2716    Ico,
2717    /// Netpbm image formats (.pbm, .ppm, .pgm).
2718    Pnm,
2719}
2720
2721impl ImageFormat {
2722    /// Returns the mime type for the ImageFormat
2723    pub const fn mime_type(self) -> &'static str {
2724        match self {
2725            ImageFormat::Png => "image/png",
2726            ImageFormat::Jpeg => "image/jpeg",
2727            ImageFormat::Webp => "image/webp",
2728            ImageFormat::Gif => "image/gif",
2729            ImageFormat::Svg => "image/svg+xml",
2730            ImageFormat::Bmp => "image/bmp",
2731            ImageFormat::Tiff => "image/tiff",
2732            ImageFormat::Ico => "image/ico",
2733            ImageFormat::Pnm => "image/x-portable-anymap",
2734        }
2735    }
2736
2737    /// Returns the file extension for this image format (without leading dot).
2738    pub const fn extension(self) -> &'static str {
2739        match self {
2740            ImageFormat::Png => "png",
2741            ImageFormat::Jpeg => "jpg",
2742            ImageFormat::Webp => "webp",
2743            ImageFormat::Gif => "gif",
2744            ImageFormat::Svg => "svg",
2745            ImageFormat::Bmp => "bmp",
2746            ImageFormat::Tiff => "tiff",
2747            ImageFormat::Ico => "ico",
2748            ImageFormat::Pnm => "pnm",
2749        }
2750    }
2751
2752    /// Returns the ImageFormat for the given mime type, including known aliases.
2753    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2754        use strum::IntoEnumIterator;
2755        Self::iter()
2756            .find(|format| format.mime_type() == mime_type)
2757            .or_else(|| Self::from_mime_type_alias(mime_type))
2758    }
2759
2760    /// Non-canonical mime types that some producers use in the wild.
2761    /// Unlike `mime_type()` which returns the single canonical form,
2762    /// these are legacy or shortened variants we still need to recognize.
2763    fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2764        match mime_type {
2765            "image/jpg" => Some(Self::Jpeg),
2766            "image/tif" => Some(Self::Tiff),
2767            _ => None,
2768        }
2769    }
2770}
2771
2772/// An image, with a format and certain bytes
2773#[derive(Clone, Debug, PartialEq, Eq)]
2774pub struct Image {
2775    /// The image format the bytes represent (e.g. PNG)
2776    pub format: ImageFormat,
2777    /// The raw image bytes
2778    pub bytes: Vec<u8>,
2779    /// The unique ID for the image
2780    pub id: u64,
2781}
2782
2783pub(crate) fn decode_static_image(
2784    bytes: &[u8],
2785    format: image::ImageFormat,
2786) -> Result<SmallVec<[Frame; 1]>> {
2787    let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2788        .into_decoder()
2789        .context("creating image decoder")?;
2790    decode_static_image_from_decoder(decoder)
2791}
2792
2793pub(crate) fn decode_static_image_from_decoder(
2794    mut decoder: impl image::ImageDecoder,
2795) -> Result<SmallVec<[Frame; 1]>> {
2796    let orientation = decoder
2797        .orientation()
2798        .context("reading decoder's orientation")?;
2799    let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2800    image.apply_orientation(orientation);
2801
2802    let mut data = image.into_rgba8();
2803    for pixel in data.chunks_exact_mut(4) {
2804        pixel.swap(0, 2);
2805    }
2806
2807    Ok(SmallVec::from_elem(Frame::new(data), 1))
2808}
2809
2810impl Hash for Image {
2811    fn hash<H: Hasher>(&self, state: &mut H) {
2812        state.write_u64(self.id);
2813    }
2814}
2815
2816impl Image {
2817    /// An empty image containing no data
2818    pub fn empty() -> Self {
2819        Self::from_bytes(ImageFormat::Png, Vec::new())
2820    }
2821
2822    /// Create an image from a format and bytes
2823    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2824        Self {
2825            id: hash(&bytes),
2826            format,
2827            bytes,
2828        }
2829    }
2830
2831    /// Get this image's ID
2832    pub fn id(&self) -> u64 {
2833        self.id
2834    }
2835
2836    /// Use the GPUI `use_asset` API to make this image renderable
2837    pub fn use_render_image(
2838        self: Arc<Self>,
2839        window: &mut Window,
2840        cx: &mut App,
2841    ) -> Option<Arc<RenderImage>> {
2842        ImageSource::Image(self)
2843            .use_data(None, window, cx)
2844            .and_then(|result| result.ok())
2845    }
2846
2847    /// Use the GPUI `get_asset` API to make this image renderable
2848    pub fn get_render_image(
2849        self: Arc<Self>,
2850        window: &mut Window,
2851        cx: &mut App,
2852    ) -> Option<Arc<RenderImage>> {
2853        ImageSource::Image(self)
2854            .get_data(None, window, cx)
2855            .and_then(|result| result.ok())
2856    }
2857
2858    /// Use the GPUI `remove_asset` API to drop this image, if possible.
2859    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2860        ImageSource::Image(self).remove_asset(cx);
2861    }
2862
2863    /// Check whether this image is present in GPUI's asset cache (loading or
2864    /// loaded), without fetching it.
2865    #[cfg(any(test, feature = "test-support"))]
2866    pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
2867        ImageSource::Image(self.clone()).is_asset_cached(cx)
2868    }
2869
2870    /// Convert the clipboard image to an `ImageData` object.
2871    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2872        let frames = match self.format {
2873            ImageFormat::Gif => {
2874                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2875                let mut frames = SmallVec::new();
2876
2877                for frame in decoder.into_frames() {
2878                    match frame {
2879                        Ok(mut frame) => {
2880                            // Convert from RGBA to BGRA.
2881                            for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2882                                pixel.swap(0, 2);
2883                            }
2884                            frames.push(frame);
2885                        }
2886                        Err(err) => {
2887                            log::debug!("Skipping GIF frame due to decode error: {err}");
2888                        }
2889                    }
2890                }
2891
2892                if frames.is_empty() {
2893                    anyhow::bail!("GIF could not be decoded: all frames failed");
2894                }
2895
2896                frames
2897            }
2898            ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2899            ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2900            ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2901            ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2902            ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2903            ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
2904            ImageFormat::Svg => {
2905                return svg_renderer
2906                    .render_single_frame(&self.bytes, 1.0)
2907                    .map_err(Into::into);
2908            }
2909            ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
2910        };
2911
2912        Ok(Arc::new(RenderImage::new(frames)))
2913    }
2914
2915    /// Get the format of the clipboard image
2916    pub fn format(&self) -> ImageFormat {
2917        self.format
2918    }
2919
2920    /// Get the raw bytes of the clipboard image
2921    pub fn bytes(&self) -> &[u8] {
2922        self.bytes.as_slice()
2923    }
2924}
2925
2926/// A clipboard item that should be copied to the clipboard
2927#[derive(Clone, Debug, Eq, PartialEq)]
2928pub struct ClipboardString {
2929    /// The text content.
2930    pub text: String,
2931    /// Optional metadata associated with this clipboard string.
2932    pub metadata: Option<String>,
2933}
2934
2935impl ClipboardString {
2936    /// Create a new clipboard string with the given text
2937    pub fn new(text: String) -> Self {
2938        Self {
2939            text,
2940            metadata: None,
2941        }
2942    }
2943
2944    /// Return a new clipboard item with the metadata replaced by the given metadata,
2945    /// after serializing it as JSON.
2946    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2947        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2948        self
2949    }
2950
2951    /// Get the text of the clipboard string
2952    pub fn text(&self) -> &String {
2953        &self.text
2954    }
2955
2956    /// Get the owned text of the clipboard string
2957    pub fn into_text(self) -> String {
2958        self.text
2959    }
2960
2961    /// Get the metadata of the clipboard string, formatted as JSON
2962    pub fn metadata_json<T>(&self) -> Option<T>
2963    where
2964        T: for<'a> Deserialize<'a>,
2965    {
2966        self.metadata
2967            .as_ref()
2968            .and_then(|m| serde_json::from_str(m).ok())
2969    }
2970
2971    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2972    /// Compute a hash of the given text for clipboard change detection.
2973    pub fn text_hash(text: &str) -> u64 {
2974        let mut hasher = SeaHasher::new();
2975        text.hash(&mut hasher);
2976        hasher.finish()
2977    }
2978}
2979
2980impl From<String> for ClipboardString {
2981    fn from(value: String) -> Self {
2982        Self {
2983            text: value,
2984            metadata: None,
2985        }
2986    }
2987}
2988
2989#[cfg(test)]
2990mod image_tests {
2991    use super::*;
2992    use std::sync::Arc;
2993
2994    #[test]
2995    fn test_image_to_image_data_applies_exif_orientation() {
2996        let image = Image::from_bytes(
2997            ImageFormat::Jpeg,
2998            include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
2999        );
3000
3001        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3002
3003        assert_eq!(render_image.size(0), size(16.into(), 32.into()));
3004
3005        let bytes = render_image.as_bytes(0).unwrap();
3006        assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
3007        assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
3008    }
3009
3010    #[test]
3011    fn test_svg_image_to_image_data_converts_to_bgra() {
3012        let image = Image::from_bytes(
3013            ImageFormat::Svg,
3014            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3015<rect width="1" height="1" fill="#38BDF8"/>
3016</svg>"##
3017                .to_vec(),
3018        );
3019
3020        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3021        let bytes = render_image.as_bytes(0).unwrap();
3022
3023        for pixel in bytes.chunks_exact(4) {
3024            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3025        }
3026    }
3027}
3028
3029#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3030mod tests {
3031    use super::*;
3032    use std::collections::HashSet;
3033
3034    #[test]
3035    fn test_window_button_layout_parse_standard() {
3036        let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3037        assert_eq!(
3038            layout.left,
3039            [
3040                Some(WindowButton::Close),
3041                Some(WindowButton::Minimize),
3042                None
3043            ]
3044        );
3045        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3046    }
3047
3048    #[test]
3049    fn test_window_button_layout_parse_right_only() {
3050        let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3051        assert_eq!(layout.left, [None, None, None]);
3052        assert_eq!(
3053            layout.right,
3054            [
3055                Some(WindowButton::Minimize),
3056                Some(WindowButton::Maximize),
3057                Some(WindowButton::Close)
3058            ]
3059        );
3060    }
3061
3062    #[test]
3063    fn test_window_button_layout_parse_left_only() {
3064        let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3065        assert_eq!(
3066            layout.left,
3067            [
3068                Some(WindowButton::Close),
3069                Some(WindowButton::Minimize),
3070                Some(WindowButton::Maximize)
3071            ]
3072        );
3073        assert_eq!(layout.right, [None, None, None]);
3074    }
3075
3076    #[test]
3077    fn test_window_button_layout_parse_with_whitespace() {
3078        let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3079        assert_eq!(
3080            layout.left,
3081            [
3082                Some(WindowButton::Close),
3083                Some(WindowButton::Minimize),
3084                None
3085            ]
3086        );
3087        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3088    }
3089
3090    #[test]
3091    fn test_window_button_layout_parse_empty() {
3092        let layout = WindowButtonLayout::parse("").unwrap();
3093        assert_eq!(layout.left, [None, None, None]);
3094        assert_eq!(layout.right, [None, None, None]);
3095    }
3096
3097    #[test]
3098    fn test_window_button_layout_parse_intentionally_empty() {
3099        let layout = WindowButtonLayout::parse(":").unwrap();
3100        assert_eq!(layout.left, [None, None, None]);
3101        assert_eq!(layout.right, [None, None, None]);
3102    }
3103
3104    #[test]
3105    fn test_window_button_layout_parse_invalid_buttons() {
3106        let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3107        assert_eq!(
3108            layout.left,
3109            [
3110                Some(WindowButton::Close),
3111                Some(WindowButton::Minimize),
3112                None
3113            ]
3114        );
3115        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3116    }
3117
3118    #[test]
3119    fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3120        let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3121        assert_eq!(
3122            layout.right,
3123            [
3124                Some(WindowButton::Close),
3125                Some(WindowButton::Minimize),
3126                None
3127            ]
3128        );
3129        assert_eq!(layout.format(), ":close,minimize");
3130    }
3131
3132    #[test]
3133    fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3134        let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3135        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3136        assert_eq!(
3137            layout.right,
3138            [
3139                Some(WindowButton::Maximize),
3140                Some(WindowButton::Minimize),
3141                None
3142            ]
3143        );
3144
3145        let button_ids: Vec<_> = layout
3146            .left
3147            .iter()
3148            .chain(layout.right.iter())
3149            .flatten()
3150            .map(WindowButton::id)
3151            .collect();
3152        let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3153        assert_eq!(unique_button_ids.len(), button_ids.len());
3154        assert_eq!(layout.format(), "close:maximize,minimize");
3155    }
3156
3157    #[test]
3158    fn test_window_button_layout_parse_gnome_style() {
3159        let layout = WindowButtonLayout::parse("close").unwrap();
3160        assert_eq!(layout.left, [None, None, None]);
3161        assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3162    }
3163
3164    #[test]
3165    fn test_window_button_layout_parse_elementary_style() {
3166        let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3167        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3168        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3169    }
3170
3171    #[test]
3172    fn test_window_button_layout_round_trip() {
3173        let cases = [
3174            "close:minimize,maximize",
3175            "minimize,maximize,close:",
3176            ":close",
3177            "close:",
3178            "close:maximize",
3179            ":",
3180        ];
3181
3182        for case in cases {
3183            let layout = WindowButtonLayout::parse(case).unwrap();
3184            assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3185        }
3186    }
3187
3188    #[test]
3189    fn test_window_button_layout_linux_default() {
3190        let layout = WindowButtonLayout::linux_default();
3191        assert_eq!(layout.left, [None, None, None]);
3192        assert_eq!(
3193            layout.right,
3194            [
3195                Some(WindowButton::Minimize),
3196                Some(WindowButton::Maximize),
3197                Some(WindowButton::Close)
3198            ]
3199        );
3200
3201        let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3202        assert_eq!(round_tripped, layout);
3203    }
3204
3205    #[test]
3206    fn test_window_button_layout_parse_all_invalid() {
3207        assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3208    }
3209}