Skip to main content

gpui/
platform.rs

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