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, webp::WebPDecoder};
57use image::{DynamicImage, Frame, Rgba};
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
3390fn decode_animated_frames<'a>(
3391    decoder: impl image::AnimationDecoder<'a>,
3392    format_name: &str,
3393) -> Result<SmallVec<[Frame; 1]>> {
3394    let mut frames = SmallVec::new();
3395
3396    for frame in decoder.into_frames() {
3397        match frame {
3398            Ok(mut frame) => {
3399                // Convert from RGBA to BGRA.
3400                for pixel in frame.buffer_mut().chunks_exact_mut(4) {
3401                    pixel.swap(0, 2);
3402                }
3403                frames.push(frame);
3404            }
3405            Err(err) => {
3406                log::debug!("Skipping {format_name} frame due to decode error: {err}");
3407            }
3408        }
3409    }
3410
3411    if frames.is_empty() {
3412        anyhow::bail!("{format_name} could not be decoded: all frames failed");
3413    }
3414
3415    Ok(frames)
3416}
3417
3418pub(crate) fn decode_gif_image(bytes: &[u8]) -> Result<SmallVec<[Frame; 1]>> {
3419    let decoder = GifDecoder::new(Cursor::new(bytes))?;
3420    decode_animated_frames(decoder, "GIF")
3421}
3422
3423pub(crate) fn decode_webp_image(bytes: &[u8]) -> Result<SmallVec<[Frame; 1]>> {
3424    let mut decoder = WebPDecoder::new(Cursor::new(bytes))?;
3425
3426    if decoder.has_animation() {
3427        let _ = decoder.set_background_color(Rgba([0, 0, 0, 0]));
3428        decode_animated_frames(decoder, "WebP")
3429    } else {
3430        decode_static_image_from_decoder(decoder)
3431    }
3432}
3433
3434impl Hash for Image {
3435    fn hash<H: Hasher>(&self, state: &mut H) {
3436        state.write_u64(self.id);
3437    }
3438}
3439
3440impl Image {
3441    /// An empty image containing no data
3442    pub fn empty() -> Self {
3443        Self::from_bytes(ImageFormat::Png, Vec::new())
3444    }
3445
3446    /// Create an image from a format and bytes
3447    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
3448        Self {
3449            id: hash(&bytes),
3450            format,
3451            bytes,
3452        }
3453    }
3454
3455    /// Get this image's ID
3456    pub fn id(&self) -> u64 {
3457        self.id
3458    }
3459
3460    /// Use the GPUI `use_asset` API to make this image renderable
3461    pub fn use_render_image(
3462        self: Arc<Self>,
3463        window: &mut Window,
3464        cx: &mut App,
3465    ) -> Option<Arc<RenderImage>> {
3466        ImageSource::Image(self)
3467            .use_data(None, window, cx)
3468            .and_then(|result| result.ok())
3469    }
3470
3471    /// Use the GPUI `get_asset` API to make this image renderable
3472    pub fn get_render_image(
3473        self: Arc<Self>,
3474        window: &mut Window,
3475        cx: &mut App,
3476    ) -> Option<Arc<RenderImage>> {
3477        ImageSource::Image(self)
3478            .get_data(None, window, cx)
3479            .and_then(|result| result.ok())
3480    }
3481
3482    /// Use the GPUI `remove_asset` API to drop this image, if possible.
3483    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
3484        ImageSource::Image(self).remove_asset(cx);
3485    }
3486
3487    /// Check whether this image is present in GPUI's asset cache (loading or
3488    /// loaded), without fetching it.
3489    #[cfg(any(test, feature = "test-support"))]
3490    pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
3491        ImageSource::Image(self.clone()).is_asset_cached(cx)
3492    }
3493
3494    /// Convert the clipboard image to an `ImageData` object.
3495    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
3496        let frames = match self.format {
3497            ImageFormat::Gif => decode_gif_image(&self.bytes)?,
3498            ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
3499            ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
3500            ImageFormat::Webp => decode_webp_image(&self.bytes)?,
3501            ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
3502            ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
3503            ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
3504            ImageFormat::Svg => {
3505                return svg_renderer
3506                    .render_single_frame(&self.bytes, 1.0)
3507                    .map_err(Into::into);
3508            }
3509            ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
3510        };
3511
3512        Ok(Arc::new(RenderImage::new(frames)))
3513    }
3514
3515    /// Get the format of the clipboard image
3516    pub fn format(&self) -> ImageFormat {
3517        self.format
3518    }
3519
3520    /// Get the raw bytes of the clipboard image
3521    pub fn bytes(&self) -> &[u8] {
3522        self.bytes.as_slice()
3523    }
3524}
3525
3526/// A clipboard item that should be copied to the clipboard
3527#[derive(Clone, Debug, Eq, PartialEq)]
3528pub struct ClipboardString {
3529    /// The text content.
3530    pub text: String,
3531    /// Optional metadata associated with this clipboard string.
3532    pub metadata: Option<String>,
3533}
3534
3535impl ClipboardString {
3536    /// Create a new clipboard string with the given text
3537    pub fn new(text: String) -> Self {
3538        Self {
3539            text,
3540            metadata: None,
3541        }
3542    }
3543
3544    /// Return a new clipboard item with the metadata replaced by the given metadata,
3545    /// after serializing it as JSON.
3546    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
3547        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
3548        self
3549    }
3550
3551    /// Get the text of the clipboard string
3552    pub fn text(&self) -> &String {
3553        &self.text
3554    }
3555
3556    /// Get the owned text of the clipboard string
3557    pub fn into_text(self) -> String {
3558        self.text
3559    }
3560
3561    /// Get the metadata of the clipboard string, formatted as JSON
3562    pub fn metadata_json<T>(&self) -> Option<T>
3563    where
3564        T: for<'a> Deserialize<'a>,
3565    {
3566        self.metadata
3567            .as_ref()
3568            .and_then(|m| serde_json::from_str(m).ok())
3569    }
3570
3571    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3572    /// Compute a hash of the given text for clipboard change detection.
3573    pub fn text_hash(text: &str) -> u64 {
3574        let mut hasher = SeaHasher::new();
3575        text.hash(&mut hasher);
3576        hasher.finish()
3577    }
3578}
3579
3580impl From<String> for ClipboardString {
3581    fn from(value: String) -> Self {
3582        Self {
3583            text: value,
3584            metadata: None,
3585        }
3586    }
3587}
3588
3589#[cfg(test)]
3590mod image_tests {
3591    use super::*;
3592    use std::sync::Arc;
3593
3594    #[test]
3595    fn test_image_to_image_data_applies_exif_orientation() {
3596        let image = Image::from_bytes(
3597            ImageFormat::Jpeg,
3598            include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
3599        );
3600
3601        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3602
3603        assert_eq!(render_image.size(0), size(16.into(), 32.into()));
3604
3605        let bytes = render_image.as_bytes(0).unwrap();
3606        assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
3607        assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
3608    }
3609
3610    #[test]
3611    fn test_svg_image_to_image_data_converts_to_bgra() {
3612        let image = Image::from_bytes(
3613            ImageFormat::Svg,
3614            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3615<rect width="1" height="1" fill="#38BDF8"/>
3616</svg>"##
3617                .to_vec(),
3618        );
3619
3620        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3621        let bytes = render_image.as_bytes(0).unwrap();
3622
3623        for pixel in bytes.chunks_exact(4) {
3624            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3625        }
3626    }
3627
3628    #[test]
3629    fn test_animated_webp_image_to_image_data_decodes_frames() {
3630        // 1x1 2-frame animated WebP (frame 0: red, frame 1: blue, 100ms delay each).
3631        const ANIMATED_WEBP: &[u8] = &[
3632            82, 73, 70, 70, 192, 0, 0, 0, 87, 69, 66, 80, 86, 80, 56, 88, 10, 0, 0, 0, 2, 0, 0, 0,
3633            0, 0, 0, 0, 0, 0, 65, 78, 73, 77, 6, 0, 0, 0, 255, 255, 255, 255, 0, 0, 65, 78, 77, 70,
3634            72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 2, 86, 80, 56, 32, 48, 0,
3635            0, 0, 208, 1, 0, 157, 1, 42, 1, 0, 1, 0, 2, 0, 52, 37, 160, 2, 116, 186, 1, 248, 0, 3,
3636            176, 0, 254, 240, 196, 11, 255, 32, 185, 97, 117, 200, 215, 255, 32, 63, 228, 7, 252,
3637            128, 255, 248, 242, 0, 0, 0, 65, 78, 77, 70, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3638            0, 0, 100, 0, 0, 0, 86, 80, 56, 32, 44, 0, 0, 0, 148, 1, 0, 157, 1, 42, 1, 0, 1, 0, 0,
3639            0, 52, 37, 160, 2, 116, 186, 0, 3, 152, 0, 254, 249, 147, 111, 255, 144, 31, 255, 144,
3640            31, 255, 144, 31, 255, 32, 63, 226, 23, 123, 32, 48, 0,
3641        ];
3642
3643        let image = Image::from_bytes(ImageFormat::Webp, ANIMATED_WEBP.to_vec());
3644        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3645
3646        assert_eq!(render_image.frame_count(), 2);
3647        assert_eq!(render_image.size(0), size(1.into(), 1.into()));
3648        assert_eq!(
3649            Duration::from(render_image.delay(0)),
3650            Duration::from_millis(100)
3651        );
3652        assert_eq!(
3653            Duration::from(render_image.delay(1)),
3654            Duration::from_millis(100)
3655        );
3656    }
3657}
3658
3659#[cfg(test)]
3660mod atlas_tests {
3661    use super::*;
3662
3663    const TILE_SIZE: Size<DevicePixels> = Size {
3664        width: DevicePixels(1),
3665        height: DevicePixels(1),
3666    };
3667
3668    #[derive(Default)]
3669    struct RecordingAtlasBackend {
3670        insert_calls: u32,
3671        fail_next_insert: bool,
3672        removed_tiles: Vec<AtlasTile>,
3673    }
3674
3675    impl AtlasBackend for RecordingAtlasBackend {
3676        fn insert(
3677            &mut self,
3678            kind: AtlasTextureKind,
3679            size: Size<DevicePixels>,
3680            _bytes: &[u8],
3681        ) -> Result<AtlasTile> {
3682            self.insert_calls += 1;
3683            if std::mem::take(&mut self.fail_next_insert) {
3684                anyhow::bail!("backend failed");
3685            }
3686            Ok(AtlasTile {
3687                texture_id: AtlasTextureId { index: 0, kind },
3688                tile_id: TileId(self.insert_calls),
3689                padding: 0,
3690                bounds: Bounds {
3691                    origin: Point::default(),
3692                    size,
3693                },
3694            })
3695        }
3696
3697        fn remove(&mut self, tile: AtlasTile) {
3698            self.removed_tiles.push(tile);
3699        }
3700    }
3701
3702    fn image_key(image_id: usize) -> AtlasKey {
3703        AtlasKey::Image(RenderImageParams {
3704            image_id: crate::ImageId(image_id),
3705            frame_index: 0,
3706        })
3707    }
3708
3709    fn build_tile() -> Result<Option<(Size<DevicePixels>, Cow<'static, [u8]>)>> {
3710        Ok(Some((TILE_SIZE, Cow::Borrowed(&[0, 0, 0, 255]))))
3711    }
3712
3713    #[test]
3714    fn only_successful_inserts_are_cached() -> Result<()> {
3715        let mut state = AtlasState::new(RecordingAtlasBackend::default());
3716        let key = image_key(1);
3717
3718        assert_eq!(
3719            state.get_or_insert_with(key.clone(), &mut || Ok(None))?,
3720            None
3721        );
3722        state
3723            .get_or_insert_with(key.clone(), &mut || anyhow::bail!("builder failed"))
3724            .expect_err("builder error should propagate");
3725        assert!(!state.contains(&key));
3726        assert_eq!(state.backend.insert_calls, 0);
3727
3728        state.backend.fail_next_insert = true;
3729        state
3730            .get_or_insert_with(key.clone(), &mut build_tile)
3731            .expect_err("backend error should propagate");
3732        assert!(!state.contains(&key));
3733        assert_eq!(state.backend.insert_calls, 1);
3734
3735        let tile = state
3736            .get_or_insert_with(key.clone(), &mut build_tile)?
3737            .context("builder should produce a tile")?;
3738        assert_eq!(tile.texture_id.kind, key.texture_kind());
3739        assert_eq!(
3740            state.get_or_insert_with(key.clone(), &mut || {
3741                anyhow::bail!("cache hit must not call the builder")
3742            })?,
3743            Some(tile)
3744        );
3745        assert!(state.contains(&key));
3746        assert_eq!(state.backend.insert_calls, 2);
3747        Ok(())
3748    }
3749
3750    #[test]
3751    fn remove_and_clear_invalidate_keys() -> Result<()> {
3752        let mut state = AtlasState::new(RecordingAtlasBackend::default());
3753        let key = image_key(1);
3754        let other_key = image_key(2);
3755        let tile = state
3756            .get_or_insert_with(key.clone(), &mut build_tile)?
3757            .context("builder should produce a tile")?;
3758        state
3759            .get_or_insert_with(other_key.clone(), &mut build_tile)?
3760            .context("builder should produce another tile")?;
3761
3762        state.remove(&key);
3763        state.remove(&key);
3764        assert!(!state.contains(&key));
3765        assert!(state.contains(&other_key));
3766        assert_eq!(state.backend.removed_tiles, vec![tile]);
3767
3768        let mut reset_calls = 0;
3769        state.clear(|_| reset_calls += 1);
3770        assert_eq!(reset_calls, 1);
3771        assert!(!state.contains(&other_key));
3772        assert_eq!(state.backend.removed_tiles, vec![tile]);
3773        Ok(())
3774    }
3775}
3776
3777#[cfg(test)]
3778mod frame_signal_tests {
3779    use super::*;
3780
3781    #[cfg(feature = "profiler")]
3782    #[test]
3783    fn coalesced_signals_retain_the_earliest_until_drained() {
3784        for (first_source, later_source) in [
3785            (
3786                FrameRequestSource::NativeCallback,
3787                FrameRequestSource::LocalSchedule,
3788            ),
3789            (
3790                FrameRequestSource::LocalSchedule,
3791                FrameRequestSource::NativeCallback,
3792            ),
3793        ] {
3794            let signal = PlatformFrameSignal::new();
3795            let first =
3796                PlatformFrameSignal::capture(Instant::now).expect("profiling captures requests");
3797            assert_eq!(signal.take(), None);
3798            signal.record(first + Duration::from_millis(16), later_source);
3799            signal.record(first, first_source);
3800            signal.record(first + Duration::from_millis(32), later_source);
3801            assert_eq!(signal.take(), Some((first, first_source)));
3802            assert_eq!(signal.take(), None);
3803            let next = first + Duration::from_millis(48);
3804            signal.record(next, later_source);
3805            assert_eq!(signal.take(), Some((next, later_source)));
3806        }
3807    }
3808
3809    #[cfg(not(feature = "profiler"))]
3810    #[test]
3811    fn disabled_profiling_does_not_capture_or_store_timestamps() {
3812        let captured = PlatformFrameSignal::capture(|| {
3813            panic!("profiler-disabled builds must not evaluate the capture closure");
3814        });
3815        assert_eq!(captured, None);
3816        let signal = PlatformFrameSignal::new();
3817        signal.record(Instant::now(), FrameRequestSource::LocalSchedule);
3818        assert_eq!(signal.take(), None);
3819    }
3820}
3821
3822#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3823mod tests {
3824    use super::*;
3825    use std::collections::HashSet;
3826
3827    #[test]
3828    fn test_refresh_interval_from_hz() {
3829        assert_eq!(
3830            refresh_interval_from_hz(50.0),
3831            Some(Duration::from_millis(20))
3832        );
3833        assert_eq!(
3834            refresh_interval_from_hz(120.0),
3835            Some(Duration::from_secs(1) / 120)
3836        );
3837        for unknown in [0.0, -60.0, f64::NAN, f64::INFINITY] {
3838            assert_eq!(refresh_interval_from_hz(unknown), None);
3839        }
3840    }
3841
3842    #[test]
3843    fn test_window_button_layout_parse_standard() {
3844        let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3845        assert_eq!(
3846            layout.left,
3847            [
3848                Some(WindowButton::Close),
3849                Some(WindowButton::Minimize),
3850                None
3851            ]
3852        );
3853        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3854    }
3855
3856    #[test]
3857    fn test_window_button_layout_parse_right_only() {
3858        let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3859        assert_eq!(layout.left, [None, None, None]);
3860        assert_eq!(
3861            layout.right,
3862            [
3863                Some(WindowButton::Minimize),
3864                Some(WindowButton::Maximize),
3865                Some(WindowButton::Close)
3866            ]
3867        );
3868    }
3869
3870    #[test]
3871    fn test_window_button_layout_parse_left_only() {
3872        let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3873        assert_eq!(
3874            layout.left,
3875            [
3876                Some(WindowButton::Close),
3877                Some(WindowButton::Minimize),
3878                Some(WindowButton::Maximize)
3879            ]
3880        );
3881        assert_eq!(layout.right, [None, None, None]);
3882    }
3883
3884    #[test]
3885    fn test_window_button_layout_parse_with_whitespace() {
3886        let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3887        assert_eq!(
3888            layout.left,
3889            [
3890                Some(WindowButton::Close),
3891                Some(WindowButton::Minimize),
3892                None
3893            ]
3894        );
3895        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3896    }
3897
3898    #[test]
3899    fn test_window_button_layout_parse_empty() {
3900        let layout = WindowButtonLayout::parse("").unwrap();
3901        assert_eq!(layout.left, [None, None, None]);
3902        assert_eq!(layout.right, [None, None, None]);
3903    }
3904
3905    #[test]
3906    fn test_window_button_layout_parse_intentionally_empty() {
3907        let layout = WindowButtonLayout::parse(":").unwrap();
3908        assert_eq!(layout.left, [None, None, None]);
3909        assert_eq!(layout.right, [None, None, None]);
3910    }
3911
3912    #[test]
3913    fn test_window_button_layout_parse_invalid_buttons() {
3914        let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3915        assert_eq!(
3916            layout.left,
3917            [
3918                Some(WindowButton::Close),
3919                Some(WindowButton::Minimize),
3920                None
3921            ]
3922        );
3923        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3924    }
3925
3926    #[test]
3927    fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3928        let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3929        assert_eq!(
3930            layout.right,
3931            [
3932                Some(WindowButton::Close),
3933                Some(WindowButton::Minimize),
3934                None
3935            ]
3936        );
3937        assert_eq!(layout.format(), ":close,minimize");
3938    }
3939
3940    #[test]
3941    fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3942        let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3943        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3944        assert_eq!(
3945            layout.right,
3946            [
3947                Some(WindowButton::Maximize),
3948                Some(WindowButton::Minimize),
3949                None
3950            ]
3951        );
3952
3953        let button_ids: Vec<_> = layout
3954            .left
3955            .iter()
3956            .chain(layout.right.iter())
3957            .flatten()
3958            .map(WindowButton::id)
3959            .collect();
3960        let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3961        assert_eq!(unique_button_ids.len(), button_ids.len());
3962        assert_eq!(layout.format(), "close:maximize,minimize");
3963    }
3964
3965    #[test]
3966    fn test_window_button_layout_parse_gnome_style() {
3967        let layout = WindowButtonLayout::parse("close").unwrap();
3968        assert_eq!(layout.left, [None, None, None]);
3969        assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3970    }
3971
3972    #[test]
3973    fn test_window_button_layout_parse_elementary_style() {
3974        let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3975        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3976        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3977    }
3978
3979    #[test]
3980    fn test_window_button_layout_round_trip() {
3981        let cases = [
3982            "close:minimize,maximize",
3983            "minimize,maximize,close:",
3984            ":close",
3985            "close:",
3986            "close:maximize",
3987            ":",
3988        ];
3989
3990        for case in cases {
3991            let layout = WindowButtonLayout::parse(case).unwrap();
3992            assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3993        }
3994    }
3995
3996    #[test]
3997    fn test_window_button_layout_linux_default() {
3998        let layout = WindowButtonLayout::linux_default();
3999        assert_eq!(layout.left, [None, None, None]);
4000        assert_eq!(
4001            layout.right,
4002            [
4003                Some(WindowButton::Minimize),
4004                Some(WindowButton::Maximize),
4005                Some(WindowButton::Close)
4006            ]
4007        );
4008
4009        let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
4010        assert_eq!(round_tripped, layout);
4011    }
4012
4013    #[test]
4014    fn test_window_button_layout_parse_all_invalid() {
4015        assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
4016    }
4017}