Skip to main content

gpui/
platform.rs

1mod app_menu;
2mod keyboard;
3mod keystroke;
4
5#[cfg(all(target_os = "linux", feature = "wayland"))]
6#[expect(missing_docs)]
7pub mod layer_shell;
8
9/// Types for configuring parent-anchored popup windows such as menus, dropdowns and tooltips.
10pub mod popup;
11
12#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
13mod threaded_dispatcher;
14
15#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
16mod test;
17
18#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
19mod visual_test;
20
21#[cfg(all(feature = "screen-capture", target_os = "windows"))]
22pub mod screen_capture;
23#[cfg(target_os = "windows")]
24mod windows_screen_capture;
25#[cfg(target_os = "windows")]
26pub use windows_screen_capture::WindowsScreenCaptureFrame;
27
28#[cfg(all(target_os = "windows", feature = "screen-capture"))]
29pub(crate) type PlatformScreenCaptureFrame = WindowsScreenCaptureFrame;
30#[cfg(not(feature = "screen-capture"))]
31pub(crate) type PlatformScreenCaptureFrame = ();
32#[cfg(all(target_os = "macos", feature = "screen-capture"))]
33pub(crate) type PlatformScreenCaptureFrame =
34    objc2_core_foundation::CFRetained<objc2_core_video::CVImageBuffer>;
35#[cfg(all(
36    feature = "screen-capture",
37    not(any(target_os = "macos", target_os = "windows"))
38))]
39// Screen capture currently has native frame representations only on macOS and Windows. Keep the
40// cross-platform API well-formed for enabled-but-unsupported targets; source enumeration simply
41// yields no platform sources there.
42pub(crate) type PlatformScreenCaptureFrame = ();
43
44use crate::util::FluentBuilder;
45use crate::{
46    Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
47    DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Edges, ExternalDragPayload, Font,
48    FontId, FontMetrics, ForegroundExecutor, GlyphAtlasEntry, GlyphId, GpuSpecs, ImageSource,
49    InlineLayout, InlineLayoutRequest, Keymap, LineLayout, Pixels, PlatformGestures, PlatformInput,
50    Point, PreparedRasterStyle, Priority, RasterStyleRequest, RasterizedGlyph,
51    RasterizedGlyphFormat, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams,
52    Scene, SharedString, Size, SvgRenderer, SystemWindowTab, Task, TextLayoutRequest,
53    ValidatedRasterizedGlyph, Window, WindowControlArea, hash, point, px,
54};
55#[cfg(any(target_os = "linux", target_os = "freebsd"))]
56use anyhow::bail;
57use anyhow::{Context as _, Result};
58use async_task::Runnable;
59use collections::FxHashMap;
60use futures::channel::oneshot;
61#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
62use image::RgbaImage;
63use image::codecs::gif::GifDecoder;
64use image::{AnimationDecoder as _, DynamicImage, Frame};
65use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
66use scheduler::Instant;
67pub use scheduler::{LocalTaskScope, RunnableMeta};
68use schemars::JsonSchema;
69use seahash::SeaHasher;
70use serde::{Deserialize, Serialize};
71use smallvec::SmallVec;
72use std::borrow::Cow;
73use std::collections::hash_map::Entry;
74use std::hash::{Hash, Hasher};
75use std::io::Cursor;
76use std::ops;
77use std::time::Duration;
78use std::{
79    ffi::OsString,
80    fmt::{self, Debug},
81    ops::Range,
82    path::{Path, PathBuf},
83    rc::Rc,
84    sync::Arc,
85};
86use strum::EnumIter;
87use uuid::Uuid;
88
89pub use app_menu::*;
90pub use keyboard::*;
91pub use keystroke::*;
92
93/// Whether the platform is presenting a window's frames.
94///
95/// This is about presentation, not the window's shown/hidden state: a shown
96/// window that is fully covered by other windows, minimized, on another
97/// Space or virtual desktop, or on a display that is asleep is `Hidden`. A
98/// window only partly covered by other windows is `Visible`.
99///
100/// Each platform reports from a single source, and what that source can see
101/// differs:
102///
103/// * macOS: `NSWindow.occlusionState`. Covers all of the cases above.
104/// * Windows: `WS_VISIBLE` and the minimized state. Windows keeps compositing
105///   covered windows for thumbnails and Alt-Tab and offers no occlusion
106///   notification, so a fully covered window stays `Visible`. Display sleep is
107///   not reported either.
108/// * Wayland: the `xdg_toplevel` `suspended` state (xdg-shell v6). Compositors
109///   that don't support it never report `Hidden`.
110/// * X11: mapped state plus `VisibilityNotify`. Compositing window managers
111///   generally never report a window as fully obscured, so covering is only
112///   detected without compositing.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum WindowVisibility {
115    /// At least part of the window is being presented; frames drawn for it
116    /// will be shown.
117    Visible,
118    /// No part of the window is being presented. The platform will not
119    /// request frames for it until it becomes visible again.
120    Hidden,
121}
122
123impl WindowVisibility {
124    /// Whether frames drawn for the window will be shown.
125    pub fn is_visible(self) -> bool {
126        self == Self::Visible
127    }
128}
129
130/// A change to the connected displays, reported through
131/// [`App::observe_displays`](crate::App::observe_displays).
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
133#[non_exhaustive]
134pub enum DisplayEvent {
135    /// A display was connected.
136    Added(DisplayId),
137    /// A display was disconnected. Windows on it are moved to another
138    /// display by the platform.
139    Removed(DisplayId),
140    /// A connected display's properties changed.
141    Changed {
142        /// The display that changed.
143        id: DisplayId,
144        /// Which of its properties changed.
145        changes: DisplayChanges,
146    },
147}
148
149bitflags::bitflags! {
150    /// The properties of a display that a [`DisplayEvent::Changed`] reports.
151    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
152    pub struct DisplayChanges: u8 {
153        /// [`PlatformDisplay::bounds`] changed.
154        const BOUNDS = 1 << 0;
155        /// [`PlatformDisplay::refresh_interval`] changed.
156        const REFRESH_INTERVAL = 1 << 1;
157    }
158}
159
160/// Converts a refresh rate in hertz to the time between refreshes, rejecting
161/// the zero, negative, and non-finite rates platforms use to mean "unknown".
162pub fn refresh_interval_from_hz(hertz: f64) -> Option<Duration> {
163    (hertz.is_finite() && hertz > 0.0).then(|| Duration::from_secs_f64(1.0 / hertz))
164}
165
166/// Controls whether the application participates in the system's foreground UI.
167///
168/// Only has an effect on macOS; other platforms ignore this setting. There,
169/// [`App::request_windowing`] sets it: `Accessory` while headless and `Regular` while windowed.
170/// Set it directly with [`App::set_activation_policy`] for the one case that doesn't cover: an
171/// accessory app that shows windows, such as a menu bar utility.
172#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
173pub enum ActivationPolicy {
174    /// Participate in foreground application UI, such as the Dock and menu bar on macOS.
175    #[default]
176    Regular,
177    /// Run without foreground application UI while retaining the ability to open windows.
178    Accessory,
179    /// Run in the background without windows; the app cannot be activated.
180    Prohibited,
181}
182
183#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
184pub(crate) use test::*;
185
186#[cfg(any(test, feature = "test-support"))]
187pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
188
189#[cfg(any(test, feature = "test-support"))]
190pub use tests::TestTextSystem;
191
192#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
193pub use threaded_dispatcher::ThreadedDispatcher;
194
195#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
196pub use visual_test::VisualTestPlatform;
197
198/// Keeps an operating system activity, such as an idle sleep inhibitor, alive until dropped.
199pub struct ActivityGuard {
200    _release: gpui_util::Deferred<Box<dyn FnOnce() + Send>>,
201}
202
203impl ActivityGuard {
204    /// Runs `release` when the guard is dropped.
205    pub fn new(release: impl FnOnce() + Send + 'static) -> Self {
206        Self {
207            _release: gpui_util::defer(Box::new(release)),
208        }
209    }
210
211    /// A guard for platforms without a corresponding activity.
212    pub fn noop() -> Self {
213        Self::new(|| {})
214    }
215}
216
217// TODO(jk): return an enum instead of a string
218/// Return which compositor we're guessing we'll use.
219/// Does not attempt to connect to the given compositor.
220#[cfg(any(target_os = "linux", target_os = "freebsd"))]
221#[inline]
222pub fn guess_compositor() -> &'static str {
223    if std::env::var_os("ZED_HEADLESS").is_some() {
224        return "Headless";
225    }
226    GraphicalEnvironment::detect().guess_compositor()
227}
228
229/// The graphical session to connect to: the variables that locate its display server, as some
230/// process sees them.
231///
232/// A long-running process can outlive the graphical session it was started in, so a platform
233/// that attaches to a display server later can be given a fresher environment than its own.
234/// While connected, programs the platform launches (for example to open a URL) get these
235/// variables instead of the ones this process started with. Apply
236/// [`App::graphical_environment`] to the programs an app launches, for the same reason.
237#[cfg(any(target_os = "linux", target_os = "freebsd"))]
238#[derive(Clone, Debug, Default)]
239pub struct GraphicalEnvironment {
240    /// `WAYLAND_DISPLAY`: a socket name relative to `xdg_runtime_dir`, or an absolute path.
241    pub wayland_display: Option<OsString>,
242    /// `DISPLAY`: the X11 display name.
243    pub x11_display: Option<OsString>,
244    /// `XDG_RUNTIME_DIR`: the directory containing Wayland sockets.
245    pub xdg_runtime_dir: Option<OsString>,
246    /// `XDG_ACTIVATION_TOKEN`: lets the first window take focus on Wayland.
247    ///
248    /// A platform takes this process's token from its environment when it's created, and uses
249    /// it if it starts windowed. Pass one here to switch to windowed mode later, for example
250    /// the token of the process that asked for a window.
251    pub activation_token: Option<String>,
252}
253
254/// The graphical session to connect to: the Windows session whose desktop windows appear on.
255///
256/// A process can only show windows in its own session, so switching to windowed mode fails if
257/// this names another one, for example when a process started over SSH is asked to show a
258/// window on the desktop.
259#[cfg(target_os = "windows")]
260#[derive(Clone, Debug, Default)]
261pub struct GraphicalEnvironment {
262    /// The session ID, as `ProcessIdToSessionId` reports it. `None` means this process's own
263    /// session.
264    pub session_id: Option<u32>,
265}
266
267#[cfg(target_os = "windows")]
268impl GraphicalEnvironment {
269    /// Returns the environment of this process's session.
270    pub fn detect() -> Self {
271        let mut session_id = 0;
272        // SAFETY: `session_id` is a valid pointer for the call's duration.
273        let result = unsafe {
274            windows::Win32::System::RemoteDesktop::ProcessIdToSessionId(
275                windows::Win32::System::Threading::GetCurrentProcessId(),
276                &mut session_id,
277            )
278        };
279        Self {
280            session_id: result.is_ok().then_some(session_id),
281        }
282    }
283
284    /// Does nothing on this platform: programs inherit the session of the process that
285    /// starts them.
286    pub fn apply_to(&self, _command: &mut std::process::Command) {}
287}
288
289/// The graphical session to connect to. Carries nothing yet on this platform.
290#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")))]
291#[derive(Clone, Debug, Default)]
292pub struct GraphicalEnvironment;
293
294#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")))]
295impl GraphicalEnvironment {
296    /// Returns the environment of this process's graphical session.
297    pub fn detect() -> Self {
298        Self
299    }
300
301    /// Does nothing on this platform.
302    pub fn apply_to(&self, _command: &mut std::process::Command) {}
303}
304
305/// A display mode for [`App::request_windowing`] to switch to.
306#[derive(Clone, Debug)]
307pub enum WindowingRequest {
308    /// No display server. Windows lay out and handle input but draw nothing.
309    Headless,
310    /// Connected to the display server that the environment names.
311    Windowed(GraphicalEnvironment),
312}
313
314#[cfg(any(target_os = "linux", target_os = "freebsd"))]
315impl GraphicalEnvironment {
316    /// Reads the display variables from this process's environment.
317    ///
318    /// This only reads environment variables: whether they name a reachable display server is
319    /// checked when a platform connects. Leaves `activation_token` unset: see its documentation.
320    pub fn detect() -> Self {
321        Self {
322            wayland_display: std::env::var_os("WAYLAND_DISPLAY"),
323            x11_display: std::env::var_os("DISPLAY"),
324            xdg_runtime_dir: std::env::var_os("XDG_RUNTIME_DIR"),
325            activation_token: None,
326        }
327    }
328
329    /// Sets this environment's display variables on `command`, and removes the ones it doesn't
330    /// set, so the program connects to this graphical session rather than the one this process
331    /// started in. Leaves `XDG_ACTIVATION_TOKEN` alone.
332    pub fn apply_to(&self, command: &mut std::process::Command) {
333        for (name, value) in [
334            ("WAYLAND_DISPLAY", &self.wayland_display),
335            ("DISPLAY", &self.x11_display),
336            ("XDG_RUNTIME_DIR", &self.xdg_runtime_dir),
337        ] {
338            match value {
339                Some(value) => command.env(name, value),
340                None => command.env_remove(name),
341            };
342        }
343    }
344
345    /// Returns the compositor this environment selects: Wayland, then X11, then headless.
346    ///
347    /// Does not attempt to connect to the compositor.
348    pub fn guess_compositor(&self) -> &'static str {
349        let is_set =
350            |value: &Option<OsString>| value.as_ref().is_some_and(|value| !value.is_empty());
351        if cfg!(feature = "wayland") && is_set(&self.wayland_display) {
352            "Wayland"
353        } else if cfg!(feature = "x11") && is_set(&self.x11_display) {
354            "X11"
355        } else {
356            "Headless"
357        }
358    }
359}
360
361#[cfg(any(target_os = "linux", target_os = "freebsd"))]
362bitflags::bitflags! {
363    /// The windowing modes a platform may start in or switch to.
364    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
365    pub struct WindowingModes: u8 {
366        /// Connected to a Wayland compositor.
367        const WAYLAND = 1 << 0;
368        /// Connected to an X server.
369        const X11 = 1 << 1;
370        /// No display server. Windows lay out and handle input but draw nothing.
371        const HEADLESS = 1 << 2;
372    }
373}
374
375/// The activation policy for a macOS application.
376#[deprecated(note = "renamed to `ActivationPolicy`")]
377pub type MacActivationPolicy = ActivationPolicy;
378
379/// The operating system or browser that GPUI runs on, as seen by the [`App`].
380///
381/// Each backend crate implements this trait once: `gpui_macos`, `gpui_windows`,
382/// `gpui_linux` and `gpui_web`. Application code doesn't call most of these methods
383/// directly. It calls the [`App`] methods that wrap them. Use this trait to
384/// implement a new backend, or to reach a platform service that [`App`] doesn't
385/// expose.
386///
387/// Methods without a default body are required. Methods with a default body are
388/// optional platform services: the default supplies the behavior for a backend
389/// that does not provide the service. The [platforms](crate::_documentation::platforms)
390/// table lists the backends that override each default.
391pub trait Platform: 'static {
392    /// Returns the executor for work that runs off the main thread.
393    fn background_executor(&self) -> BackgroundExecutor;
394    /// Returns the executor that runs tasks on the main thread, the thread that owns windows.
395    fn foreground_executor(&self) -> ForegroundExecutor;
396    /// Returns the platform's text system, which shapes and rasterizes glyphs.
397    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
398    /// Starts the event loop and calls `on_finish_launching` once the application is ready.
399    ///
400    /// This method blocks until the application exits. Create windows inside
401    /// `on_finish_launching`.
402    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
403    /// Asks the application to quit.
404    ///
405    /// On macOS the request is processed asynchronously, so the event loop keeps
406    /// running briefly. The web backend only logs a warning, because a page can't quit
407    /// itself.
408    fn quit(&self);
409    /// Sets the windowing mode the platform starts in. Called before run. See
410    /// Application::with_windowing.
411    fn set_initial_windowing(&self, _request: WindowingRequest) {}
412    /// Requests a switch between headless and windowed modes while the application is running.
413    ///
414    /// The returned task resolves when the platform has applied the change. Platforms that do
415    /// not support switching return an error from the default implementation.
416    fn request_windowing(&self, _request: WindowingRequest) -> Task<anyhow::Result<()>> {
417        Task::ready(Err(anyhow::anyhow!(
418            "this platform cannot switch between headless and windowed modes"
419        )))
420    }
421    /// Restarts the application by launching a new process and quitting this one.
422    ///
423    /// `binary_path` defaults to the current executable when `None`, and `arguments`
424    /// are passed to the new process. The web backend does nothing.
425    fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
426    /// Brings the application to the front.
427    ///
428    /// When `ignoring_other_apps` is `true`, the application takes focus even if
429    /// another application is active. The web backend ignores the call.
430    fn activate(&self, ignoring_other_apps: bool);
431    /// Sets the initial or current activation policy. Has no effect outside macOS.
432    fn set_activation_policy(&self, _policy: ActivationPolicy) {}
433    /// Hides all of the application's windows. macOS hides them. Windows does nothing,
434    /// and Linux logs a message and ignores the call.
435    fn hide(&self);
436    /// Hides all other running applications. macOS does this. Linux and the web do nothing.
437    /// Windows panics, because its implementation is `unimplemented!()`.
438    fn hide_other_apps(&self);
439    /// Shows the applications hidden by [`Platform::hide_other_apps`]. macOS does this.
440    /// Linux and the web do nothing.
441    fn unhide_other_apps(&self);
442
443    /// Returns every display currently connected, in no particular order.
444    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
445    /// Returns the display that new windows open on by default, or `None` if there is none.
446    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
447    /// Registers the callback invoked when displays may have been added or
448    /// removed, or a display's properties may have changed. GPUI rereads
449    /// [`Self::displays`] to find out what changed, so calls may be spurious.
450    /// The callback runs on the main thread outside of any window update.
451    fn on_displays_changed(&self, callback: Box<dyn FnMut()>);
452    /// Returns the handle of the window that currently has focus, if any.
453    fn active_window(&self) -> Option<AnyWindowHandle>;
454    /// Returns the open windows from front to back, or `None` if the platform doesn't track stacking order.
455    ///
456    /// Implemented on macOS and Linux. The default returns `None`.
457    fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
458        None
459    }
460
461    /// Returns `true` if this platform can capture the screen.
462    ///
463    /// Returns `true` on macOS 12.3 or later and on Windows. Other platforms return
464    /// `false`.
465    fn is_screen_capture_supported(&self) -> bool {
466        false
467    }
468
469    /// Lists the displays and windows that can be captured.
470    ///
471    /// The result arrives on the returned channel once enumeration finishes. The
472    /// default sends an error, so backends must override it to list sources.
473    fn screen_capture_sources(
474        &self,
475    ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
476        let (sources_tx, sources_rx) = oneshot::channel();
477        sources_tx
478            .send(Err(anyhow::anyhow!(
479                "gpui was compiled without the screen-capture feature"
480            )))
481            .ok();
482        sources_rx
483    }
484
485    /// Creates a native window for `handle` with the given `options`.
486    ///
487    /// [`App::open_window`] calls this. The returned [`PlatformWindow`] represents the
488    /// native window. Errors are returned for unsupported options, such as a second
489    /// window on the web.
490    fn open_window(
491        &self,
492        handle: AnyWindowHandle,
493        options: WindowParams,
494    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
495
496    /// Returns the appearance of the application's windows.
497    fn window_appearance(&self) -> WindowAppearance;
498
499    /// Overrides the appearance (light/dark) applied to the app's windows, independent
500    /// of the OS-wide setting. Pass `None` to clear the override and follow the system
501    /// again. The override is reflected by [`Platform::window_appearance`].
502    ///
503    /// Only macOS implements this, by setting `NSApplication.appearance`. This keeps
504    /// the native window chrome matching a dark app theme when the system is light, or
505    /// the reverse. Other platforms ignore the call.
506    fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
507
508    /// Returns the order of window control buttons, or `None` if the platform
509    /// doesn't use the layout. Only Linux returns a value. macOS, Windows and the web return `None`.
510    fn button_layout(&self) -> Option<WindowButtonLayout> {
511        None
512    }
513
514    /// Opens `url` in the default handler for its scheme, such as the browser for `https`.
515    fn open_url(&self, url: &str);
516    /// Registers the callback that receives URLs the operating system asks this application to open.
517    ///
518    /// Registering replaces any earlier callback. The web backend stores the callback
519    /// but never calls it.
520    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
521    /// Registers this application as the handler for `url`'s scheme, such as `myapp://`.
522    ///
523    /// The task resolves once registration finishes. The web backend resolves it at once and does nothing.
524    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
525
526    /// Shows a file or folder picker and resolves to the chosen paths.
527    ///
528    /// The receiver yields `Ok(None)` when the user cancels. The options control whether
529    /// files, directories or several items can be picked. The web backend doesn't
530    /// support this, so the receiver resolves to an error.
531    fn prompt_for_paths(
532        &self,
533        options: PathPromptOptions,
534    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
535    /// Shows a save dialog in `directory` and resolves to the chosen path.
536    ///
537    /// `suggested_name` pre-fills the file name. The receiver yields `Ok(None)` when
538    /// the user cancels. The web backend doesn't support this.
539    fn prompt_for_new_path(
540        &self,
541        directory: &Path,
542        suggested_name: Option<&str>,
543    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
544    /// Returns whether a single file picker can select both files and directories.
545    ///
546    /// Only macOS returns `true`. Other platforms return `false` and need separate
547    /// pickers for files and directories.
548    fn can_select_mixed_files_and_dirs(&self) -> bool;
549    /// Shows `path` selected in the file manager, such as Finder or Explorer.
550    fn reveal_path(&self, path: &Path);
551    /// Opens `path` with the application the system associates with its file type.
552    fn open_with_system(&self, path: &Path);
553
554    /// Registers the callback the platform runs when the application is asked to quit.
555    ///
556    /// The callback returns `true` when shutdown has completed, and `false` when it
557    /// couldn't shut down synchronously. macOS calls it while terminating and ignores
558    /// the return value.
559    fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
560    /// Registers the callback run when the user activates the running application again, such as clicking its Dock icon on macOS.
561    fn on_reopen(&self, callback: Box<dyn FnMut()>);
562    /// Registers the callback invoked when the system is about to sleep.
563    fn on_system_sleep(&self, callback: Box<dyn FnMut()>);
564    /// Registers the callback run after the system wakes from sleep.
565    fn on_system_wake(&self, callback: Box<dyn FnMut()>);
566
567    // Mobile platform methods. On mobile the OS owns the application
568    // lifecycle: apps are backgrounded, foregrounded, and killed at the
569    // system's discretion, and must react rather than decide.
570
571    /// Registers a callback invoked whenever the application's lifecycle
572    /// phase changes. See [`AppLifecyclePhase`] for the phase vocabulary and
573    /// its mapping onto iOS and Android.
574    ///
575    /// Desktop platforms never invoke this.
576    fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
577
578    /// Registers a callback invoked when the OS signals memory pressure
579    /// (iOS `didReceiveMemoryWarning`, Android `onTrimMemory`).
580    ///
581    /// Desktop platforms never invoke this.
582    fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
583
584    /// Returns the platform's gesture recognizers, or `None` to use GPUI's portable ones.
585    /// See [`PlatformGestures`].
586    fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
587        None
588    }
589
590    /// Replaces the application menu with `menus`, and uses `keymap` to show each item's shortcut.
591    ///
592    /// Windows stores the menus but doesn't draw them. Linux and the web don't show a
593    /// menu bar.
594    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
595    /// Returns the menus last set with [`Platform::set_menus`], or `None` if the platform doesn't keep them.
596    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
597        None
598    }
599
600    /// Sets the menu shown when the user right-clicks the app's Dock icon (macOS) or taskbar entry (Windows, as a jump list).
601    fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
602    /// Runs the dock menu item at index `_action`. Windows implements this.
603    fn perform_dock_menu_action(&self, _action: usize) {}
604    /// Adds `path` to the system's recent documents list. Only macOS implements this.
605    fn add_recent_document(&self, _path: &Path) {}
606    /// Updates the Windows jump list with `_menus` and the recent-file `_entries`, and returns the entries that were kept.
607    ///
608    /// Other backends return an empty list.
609    fn update_jump_list(
610        &self,
611        _menus: Vec<MenuItem>,
612        _entries: Vec<SmallVec<[PathBuf; 2]>>,
613    ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
614        Task::ready(Vec::new())
615    }
616    /// Registers the callback that receives actions chosen from the application menu.
617    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
618    /// Registers the callback run just before a menu opens, so the app can update its menu items.
619    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
620    /// Registers the callback that decides whether a menu item is enabled. Return `true` to enable the action.
621    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
622
623    /// Returns the device's current thermal state. See [`ThermalState`].
624    fn thermal_state(&self) -> ThermalState;
625    /// Registers the callback run when [`Platform::thermal_state`] changes. Read the new state from the platform inside the callback.
626    fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
627    /// Requests that the operating system keep the device awake while the returned guard lives.
628    ///
629    /// The reason explains the activity to the platform and may be shown in system UI. Dropping
630    /// the ActivityGuard releases the request. The task resolves with the guard or an error if
631    /// the platform cannot create the activity request.
632    fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
633
634    /// Sets the application's identifier and user-visible name.
635    ///
636    /// Windows sets `identifier` as the AppUserModelID, unless the process already has
637    /// package identity. Linux stores `name`. macOS and the web ignore both values.
638    ///
639    /// Call it once early in startup, before opening windows or posting notifications.
640    fn set_app_identity(&self, identifier: &str, name: &str) {
641        _ = (identifier, name);
642    }
643
644    /// Posts `notification` to the operating system's notification center.
645    ///
646    /// Posting a notification whose [`SystemNotification::tag`] matches an
647    /// earlier one replaces that notification where the platform supports it.
648    /// No-op on platforms without notification support, or when delivery is
649    /// unavailable (e.g. authorization was denied).
650    fn show_system_notification(&self, notification: SystemNotification) {
651        _ = notification;
652    }
653
654    /// Removes the delivered or pending notification with this tag.
655    ///
656    /// Best-effort: some platforms cannot retract a notification once shown,
657    /// in which case it ages out of the notification center on its own.
658    fn dismiss_system_notification(&self, tag: &str) {
659        _ = tag;
660    }
661
662    /// Registers the callback invoked when the user activates a system
663    /// notification, either by clicking its body or one of its action
664    /// buttons.
665    ///
666    /// Implementations must invoke the callback on the main thread.
667    fn on_system_notification_response(
668        &self,
669        callback: Box<dyn FnMut(SystemNotificationResponse)>,
670    ) {
671        _ = callback;
672    }
673
674    /// Returns the name of the display server or compositor, such as `"Wayland"`, `"X11"` or `"Headless"`.
675    ///
676    /// Returns an empty string by default.
677    fn compositor_name(&self) -> &'static str {
678        ""
679    }
680    /// The environment of the display server a platform that can switch windowing modes is
681    /// connected to. See [`App::graphical_environment`].
682    fn graphical_environment(&self) -> Option<GraphicalEnvironment> {
683        None
684    }
685    /// Returns the path of the running application's executable.
686    fn app_path(&self) -> Result<PathBuf>;
687    /// Returns the path of a helper executable named `name` that ships alongside the application.
688    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
689
690    /// Shows the mouse cursor `style` over this application's windows.
691    fn set_cursor_style(&self, style: CursorStyle);
692
693    /// Hides the mouse cursor until the user moves the mouse over one of
694    /// this application's windows.
695    fn hide_cursor_until_mouse_moves(&self);
696
697    /// Returns whether the mouse cursor is currently visible.
698    fn is_cursor_visible(&self) -> bool;
699
700    /// Returns `true` when scrollbars should hide until the user scrolls. macOS follows
701    /// the overlay-scroller setting, Windows and Linux read their UI settings, and the
702    /// web returns `true`.
703    fn should_auto_hide_scrollbars(&self) -> bool;
704
705    /// Returns the clipboard's contents synchronously, or `None` if it's empty.
706    ///
707    /// The web backend can't read the clipboard synchronously and always returns
708    /// `None`. Use [`Platform::read_from_clipboard_async`] there.
709    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
710    /// Replaces the clipboard's contents with `item`.
711    fn write_to_clipboard(&self, item: ClipboardItem);
712
713    /// Reads the clipboard, resolving once its contents are available.
714    ///
715    /// Most platforms read synchronously and return a ready task. A platform with
716    /// inherently asynchronous or permission-gated access, such as the browser's
717    /// Async Clipboard API, overrides this method. On those platforms,
718    /// [`Platform::read_from_clipboard`] cannot return the contents; callers that
719    /// can await should use this method.
720    fn read_from_clipboard_async(&self) -> Task<Result<Option<ClipboardItem>, ClipboardReadError>> {
721        Task::ready(Ok(self.read_from_clipboard()))
722    }
723
724    /// Returns the contents of the X11 or Wayland primary selection, the text selected with the mouse.
725    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
726    fn read_from_primary(&self) -> Option<ClipboardItem>;
727    /// Sets the primary selection to `item`. The selection is the text highlighted with the mouse, pasted with middle-click.
728    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
729    fn write_to_primary(&self, item: ClipboardItem);
730
731    /// Returns the contents of the macOS find pasteboard, which the find bar shares across apps.
732    #[cfg(target_os = "macos")]
733    fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
734    /// Sets the macOS find pasteboard to `item`.
735    #[cfg(target_os = "macos")]
736    fn write_to_find_pasteboard(&self, item: ClipboardItem);
737
738    /// Stores `username` and `password` for `url` in the system credential store.
739    ///
740    /// The task resolves once the credentials are saved. Use [`Platform::read_credentials`] to read them back.
741    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
742    /// Reads the username and password stored for `url`, resolving to `None` if there are none.
743    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
744    /// Deletes the credentials stored for `url`.
745    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
746
747    /// Returns the current keyboard layout.
748    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
749    /// Returns the mapper that converts key codes to the characters the current layout produces.
750    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
751    /// Registers the callback run when the keyboard layout changes.
752    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
753
754    /// Registers extra GPU device requirements, such as features and limits, before the first window is opened.
755    ///
756    /// The value must be a `gpui_wgpu::WgpuDeviceRequirements`. macOS and Linux use
757    /// it. Other backends ignore it.
758    fn set_gpu_requirements(&self, _requirements: Box<dyn std::any::Any>) {}
759
760    /// Sets the label applied to credentials stored in the system keyring.
761    /// Only Linux/FreeBSD use this label.
762    fn set_keyring_label(&self, _label: SharedString) {}
763
764    /// Returns `true` if [`Platform::play_haptic_feedback`] has an effect on this platform.
765    ///
766    /// Only macOS returns `true`, for trackpads with Force Touch.
767    fn supports_haptic_feedback(&self) -> bool {
768        false
769    }
770
771    /// Plays haptic feedback of the given style.
772    ///
773    /// No-op on platforms that don't support haptic feedback.
774    fn play_haptic_feedback(&self, _style: HapticFeedbackStyle) {}
775}
776
777/// A display, such as a monitor or a laptop screen, as the platform reports it.
778///
779/// Obtain one from [`App::displays`] or [`App::primary_display`]. Its bounds are
780/// in logical pixels.
781pub trait PlatformDisplay: Debug {
782    /// Returns the display's id for the current session. To persist a display across restarts, use [`PlatformDisplay::uuid`].
783    fn id(&self) -> DisplayId;
784
785    /// Returns a stable identifier for this display that survives restarts.
786    ///
787    /// Returns an error if the platform can't provide one.
788    fn uuid(&self) -> Result<Uuid>;
789
790    /// Returns the display's full bounds in logical pixels, including areas such as the taskbar.
791    fn bounds(&self) -> Bounds<Pixels>;
792
793    /// The time between the display's refreshes. Variable refresh rate
794    /// displays report their maximum rate. `None` when the platform doesn't
795    /// report it.
796    ///
797    /// May query the operating system; GPUI caches it per display, see
798    /// [`Window::refresh_interval`](crate::Window::refresh_interval).
799    fn refresh_interval(&self) -> Option<Duration>;
800
801    /// Returns the usable bounds, excluding the menu bar, Dock and taskbar.
802    ///
803    /// Defaults to [`PlatformDisplay::bounds`] when a backend doesn't override it.
804    fn visible_bounds(&self) -> Bounds<Pixels> {
805        self.bounds()
806    }
807
808    /// Returns the bounds a new window opens at on this display: the default window size, centered and clipped to the display.
809    fn default_bounds(&self) -> Bounds<Pixels> {
810        let bounds = self.bounds();
811        let center = bounds.center();
812        let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
813
814        let offset = clipped_window_size / 2.0;
815        let origin = point(center.x - offset.width, center.y - offset.height);
816        Bounds::new(origin, clipped_window_size)
817    }
818}
819
820/// A notification for the operating system's notification center, rather than
821/// in-app UI.
822///
823/// Post one with [`App::show_system_notification`]. Responses arrive as a
824/// [`SystemNotificationResponse`] with the same `tag`.
825#[derive(Clone, Debug, PartialEq, Eq)]
826pub struct SystemNotification {
827    /// Stable identity for the notification. Posting a new notification with
828    /// the same tag replaces the previous one where the platform supports it,
829    /// and responses carry the tag back to the application.
830    pub tag: SharedString,
831    /// The notification's headline.
832    pub title: SharedString,
833    /// Additional text displayed below the title.
834    pub body: SharedString,
835    /// Buttons shown with the notification. Platforms may show fewer than given.
836    pub actions: Vec<SystemNotificationAction>,
837}
838
839/// A button on a [`SystemNotification`].
840#[derive(Clone, Debug, PartialEq, Eq, Hash)]
841pub struct SystemNotificationAction {
842    /// Identifies the button in a [`SystemNotificationResponse::action_id`].
843    pub id: SharedString,
844    /// The text shown on the button.
845    pub label: SharedString,
846}
847
848/// The user's response to a [`SystemNotification`].
849#[derive(Clone, Debug, PartialEq, Eq)]
850pub struct SystemNotificationResponse {
851    /// The [`SystemNotification::tag`] of the notification the user activated.
852    pub tag: SharedString,
853    /// The [`SystemNotificationAction::id`] of the button clicked, or `None` when the body was clicked.
854    pub action_id: Option<SharedString>,
855}
856
857/// The device's thermal pressure, from least to most constrained.
858///
859/// Read it with [`App::thermal_state`]. When it changes, the callback from
860/// [`App::on_thermal_state_change`] runs. Reduce discretionary work, such as
861/// animation frames or background indexing, as the state rises.
862#[derive(Debug, Clone, Copy, PartialEq, Eq)]
863pub enum ThermalState {
864    /// The system has no thermal constraints.
865    Nominal,
866    /// The system is slightly constrained. Reduce discretionary work.
867    Fair,
868    /// The system is moderately constrained. Reduce CPU and GPU intensive work.
869    Serious,
870    /// The system is critically constrained. Minimize resource use.
871    Critical,
872}
873
874/// A kind of haptic feedback to play with [`App::play_haptic_feedback`].
875///
876/// The styles map to `NSHapticFeedbackPattern` on macOS. Other platforms do nothing.
877#[derive(Debug, Clone, Copy, PartialEq, Eq)]
878pub enum HapticFeedbackStyle {
879    /// A generic tap, for most interactions.
880    Generic,
881    /// A sharp snap, for alignment guides, detents and snapping.
882    Alignment,
883    /// A distinct click, for slider steps, toggles and discrete state changes.
884    LevelChange,
885}
886
887/// Describes a screen or window that can be captured.
888///
889/// Returned by [`ScreenCaptureSource::metadata`] and [`ScreenCaptureStream::metadata`].
890#[derive(Clone)]
891pub struct SourceMetadata {
892    /// An opaque identifier for the source. It is stable only within one session.
893    pub id: u64,
894    /// A name to show in a picker, or `None` when the platform gives none.
895    pub label: Option<SharedString>,
896    /// Whether this is the main display, or `None` when unknown.
897    pub is_main: Option<bool>,
898    /// The size of the captured video, in device pixels.
899    pub resolution: Size<DevicePixels>,
900}
901
902/// A screen or window that can be captured.
903///
904/// Get sources from [`Platform::screen_capture_sources`]. Call
905/// [`ScreenCaptureSource::stream`] to start receiving frames.
906pub trait ScreenCaptureSource {
907    /// Returns a description of this source.
908    fn metadata(&self) -> Result<SourceMetadata>;
909
910    /// Starts capturing this source and calls `frame_callback` with each frame.
911    ///
912    /// The returned receiver resolves to the running [`ScreenCaptureStream`], or to an
913    /// error if capture can't start. The platform chooses the thread that runs
914    /// `frame_callback`, so the callback must be `Send`. Drop the stream to stop capture.
915    fn stream(
916        &self,
917        foreground_executor: &ForegroundExecutor,
918        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
919    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
920}
921
922/// A running capture of a [`ScreenCaptureSource`].
923pub trait ScreenCaptureStream {
924    /// Returns a description of the captured source.
925    fn metadata(&self) -> Result<SourceMetadata>;
926}
927
928/// One frame of captured video.
929///
930/// The wrapped value is platform-specific. On macOS it is a `CVImageBuffer`, and on
931/// Windows it is a `WindowsScreenCaptureFrame`. Without the `screen-capture`
932/// feature it is `()`.
933pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
934
935/// An identifier for a display that is valid for the current session.
936#[derive(PartialEq, Eq, Hash, Copy, Clone)]
937pub struct DisplayId(pub(crate) u64);
938
939impl DisplayId {
940    /// Creates a `DisplayId` from a raw platform display identifier.
941    pub fn new(id: u64) -> Self {
942        Self(id)
943    }
944}
945
946impl From<u64> for DisplayId {
947    fn from(id: u64) -> Self {
948        Self(id)
949    }
950}
951
952impl From<DisplayId> for u64 {
953    fn from(id: DisplayId) -> Self {
954        id.0
955    }
956}
957
958impl Debug for DisplayId {
959    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
960        write!(f, "DisplayId({})", self.0)
961    }
962}
963
964/// The edge or corner of a window that a user drags to resize it.
965#[derive(Debug, Clone, Copy, PartialEq, Eq)]
966pub enum ResizeEdge {
967    /// The top edge
968    Top,
969    /// The top right corner
970    TopRight,
971    /// The right edge
972    Right,
973    /// The bottom right corner
974    BottomRight,
975    /// The bottom edge
976    Bottom,
977    /// The bottom left corner
978    BottomLeft,
979    /// The left edge
980    Left,
981    /// The top left corner
982    TopLeft,
983}
984
985impl From<ResizeEdge> for CursorStyle {
986    fn from(edge: ResizeEdge) -> Self {
987        match edge {
988            ResizeEdge::Top => Self::ResizeUp,
989            ResizeEdge::Right => Self::ResizeRight,
990            ResizeEdge::Bottom => Self::ResizeDown,
991            ResizeEdge::Left => Self::ResizeLeft,
992            ResizeEdge::TopLeft => Self::ResizeUpLeft,
993            ResizeEdge::TopRight => Self::ResizeUpRight,
994            ResizeEdge::BottomLeft => Self::ResizeDownLeft,
995            ResizeEdge::BottomRight => Self::ResizeDownRight,
996        }
997    }
998}
999
1000/// Whether a window draws its own titlebar and borders, or the window server does.
1001///
1002/// Set this with [`WindowOptions::window_decorations`]. Linux's Wayland and X11
1003/// backends honor it. The other backends ignore it.
1004#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
1005pub enum WindowDecorations {
1006    /// The window server draws the titlebar and borders.
1007    #[default]
1008    Server,
1009    /// GPUI draws the titlebar and borders.
1010    Client,
1011}
1012
1013/// The decorations a window is currently using.
1014///
1015/// Read it with [`Window::window_decorations`] to decide whether to draw resize handles or
1016/// window controls yourself.
1017#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
1018pub enum Decorations {
1019    /// The window server draws the decorations.
1020    #[default]
1021    Server,
1022    /// GPUI draws the decorations. `tiling` says which edges are snapped to the screen.
1023    Client {
1024        /// Which edges of the window are tiled.
1025        tiling: Tiling,
1026    },
1027}
1028
1029/// The window controls a platform supports.
1030///
1031/// The default assumes that every control is available.
1032#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1033pub struct WindowControls {
1034    /// Whether the window can enter fullscreen.
1035    pub fullscreen: bool,
1036    /// Whether the window can be maximized.
1037    pub maximize: bool,
1038    /// Whether the window can be minimized.
1039    pub minimize: bool,
1040    /// Whether the window has a window menu.
1041    pub window_menu: bool,
1042}
1043
1044impl Default for WindowControls {
1045    fn default() -> Self {
1046        // Assume that we can do anything, unless told otherwise
1047        Self {
1048            fullscreen: true,
1049            maximize: true,
1050            minimize: true,
1051            window_menu: true,
1052        }
1053    }
1054}
1055
1056/// A window control button, used in [`WindowButtonLayout`].
1057#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1058pub enum WindowButton {
1059    /// The minimize button
1060    Minimize,
1061    /// The maximize button
1062    Maximize,
1063    /// The close button
1064    Close,
1065}
1066
1067impl WindowButton {
1068    /// Returns a stable element id for rendering this button.
1069    ///
1070    /// Use the id to find the button's element in a titlebar. The ids are `"minimize"`,
1071    /// `"maximize"` and `"close"`.
1072    pub fn id(&self) -> &'static str {
1073        match self {
1074            WindowButton::Minimize => "minimize",
1075            WindowButton::Maximize => "maximize",
1076            WindowButton::Close => "close",
1077        }
1078    }
1079
1080    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
1081    fn index(&self) -> usize {
1082        match self {
1083            WindowButton::Minimize => 0,
1084            WindowButton::Maximize => 1,
1085            WindowButton::Close => 2,
1086        }
1087    }
1088}
1089
1090/// The largest number of [`WindowButton`]s on one side of the titlebar.
1091pub const MAX_BUTTONS_PER_SIDE: usize = 3;
1092
1093/// Which [`WindowButton`]s appear on each side of the titlebar, in order.
1094///
1095/// On Linux, this is read from the desktop environment's configuration
1096/// (the `button-layout` key of `org.gnome.desktop.wm.preferences`) via `WindowButtonLayout::parse`.
1097/// Empty slots are `None`.
1098#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1099pub struct WindowButtonLayout {
1100    /// The buttons on the left side of the titlebar, from the edge inwards.
1101    pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
1102    /// The buttons on the right side of the titlebar, from the edge inwards.
1103    pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
1104}
1105
1106#[cfg(any(target_os = "linux", target_os = "freebsd"))]
1107impl WindowButtonLayout {
1108    /// Returns GPUI's fallback layout for Linux titlebars: minimize, maximize and close on the right.
1109    pub fn linux_default() -> Self {
1110        Self {
1111            left: [None; MAX_BUTTONS_PER_SIDE],
1112            right: [
1113                Some(WindowButton::Minimize),
1114                Some(WindowButton::Maximize),
1115                Some(WindowButton::Close),
1116            ],
1117        }
1118    }
1119
1120    /// Parses a GNOME-style `button-layout` string such as `"close,minimize:maximize"`.
1121    ///
1122    /// The text before the `:` is the left side and the text after it is the right side.
1123    /// Names are `minimize`, `maximize` and `close`. Unknown names and repeated buttons
1124    /// are skipped, and each side keeps at most [`MAX_BUTTONS_PER_SIDE`] buttons.
1125    ///
1126    /// # Errors
1127    ///
1128    /// Returns an error when the string names no valid buttons and at least one name is unknown.
1129    pub fn parse(layout_string: &str) -> Result<Self> {
1130        fn parse_side(
1131            s: &str,
1132            seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
1133            unrecognized: &mut Vec<String>,
1134        ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
1135            let mut result = [None; MAX_BUTTONS_PER_SIDE];
1136            let mut i = 0;
1137            for name in s.split(',') {
1138                let trimmed = name.trim();
1139                if trimmed.is_empty() {
1140                    continue;
1141                }
1142                let button = match trimmed {
1143                    "minimize" => Some(WindowButton::Minimize),
1144                    "maximize" => Some(WindowButton::Maximize),
1145                    "close" => Some(WindowButton::Close),
1146                    other => {
1147                        unrecognized.push(other.to_string());
1148                        None
1149                    }
1150                };
1151                if let Some(button) = button {
1152                    if seen_buttons[button.index()] {
1153                        continue;
1154                    }
1155                    if let Some(slot) = result.get_mut(i) {
1156                        *slot = Some(button);
1157                        seen_buttons[button.index()] = true;
1158                        i += 1;
1159                    }
1160                }
1161            }
1162            result
1163        }
1164
1165        let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
1166        let mut unrecognized = Vec::new();
1167        let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
1168        let layout = Self {
1169            left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
1170            right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
1171        };
1172
1173        if !unrecognized.is_empty()
1174            && layout.left.iter().all(Option::is_none)
1175            && layout.right.iter().all(Option::is_none)
1176        {
1177            bail!(
1178                "button layout string {:?} contains no valid buttons (unrecognized: {})",
1179                layout_string,
1180                unrecognized.join(", ")
1181            );
1182        }
1183
1184        Ok(layout)
1185    }
1186
1187    /// Formats the layout back into a GNOME-style `button-layout` string.
1188    #[cfg(test)]
1189    pub fn format(&self) -> String {
1190        fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
1191            buttons
1192                .iter()
1193                .flatten()
1194                .map(|button| match button {
1195                    WindowButton::Minimize => "minimize",
1196                    WindowButton::Maximize => "maximize",
1197                    WindowButton::Close => "close",
1198                })
1199                .collect::<Vec<_>>()
1200                .join(",")
1201        }
1202
1203        format!("{}:{}", format_side(&self.left), format_side(&self.right))
1204    }
1205}
1206
1207/// Which edges of a window are tiled against the screen edge.
1208#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
1209pub struct Tiling {
1210    /// Whether the top edge is tiled.
1211    pub top: bool,
1212    /// Whether the left edge is tiled.
1213    pub left: bool,
1214    /// Whether the right edge is tiled.
1215    pub right: bool,
1216    /// Whether the bottom edge is tiled.
1217    pub bottom: bool,
1218}
1219
1220impl Tiling {
1221    /// Returns a [`Tiling`] with every edge tiled.
1222    pub fn tiled() -> Self {
1223        Self {
1224            top: true,
1225            left: true,
1226            right: true,
1227            bottom: true,
1228        }
1229    }
1230
1231    /// Returns `true` if any edge is tiled.
1232    pub fn is_tiled(&self) -> bool {
1233        self.top || self.left || self.right || self.bottom
1234    }
1235}
1236
1237/// The callbacks an accessibility adapter uses to talk to the application.
1238///
1239/// The platform's accessibility adapter calls these when a screen reader connects,
1240/// disconnects, or asks for an action. Register them with
1241/// [`PlatformWindow::a11y_init`].
1242pub struct A11yCallbacks {
1243    /// Returns the accessibility tree to send when a screen reader connects.
1244    pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
1245    /// Handles an action, such as focusing or activating an element, that a screen reader requested.
1246    pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
1247    /// Runs when the screen reader disconnects.
1248    pub deactivation: Box<dyn Fn() + Send + 'static>,
1249}
1250
1251/// The source of a platform frame request's timestamp.
1252#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
1253pub enum FrameRequestSource {
1254    /// An OS callback or compositor-paced frame request.
1255    #[default]
1256    NativeCallback,
1257    /// A refresh timer, retry, queued wakeup, or fallback sleep paced by the app.
1258    LocalSchedule,
1259}
1260
1261/// Options for a frame the platform asks GPUI to draw. The platform passes them to
1262/// the callback registered with [`PlatformWindow::on_request_frame`].
1263#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
1264pub struct RequestFrameOptions {
1265    /// Whether the frame must be presented to the screen, not only drawn.
1266    pub require_presentation: bool,
1267    /// Whether to redraw everything, ignoring cached rendering state.
1268    pub force_render: bool,
1269    /// When the platform first requested this frame, before main-thread dispatch.
1270    ///
1271    /// `None` means the captured request time is unavailable.
1272    /// Coalesced requests carry their first request time, not their delivery time.
1273    /// Built-in backends collect timestamps only with the `profiler` feature.
1274    pub signal_at: Option<Instant>,
1275    /// Distinguishes native callbacks from local scheduling requests.
1276    pub signal_source: FrameRequestSource,
1277}
1278
1279/// Preserves the first platform frame request time across coalesced notifications.
1280///
1281/// Producers may record from a platform thread without waiting for the UI thread.
1282/// The consumer drains the timestamp before dispatching the request on the UI thread.
1283/// The timestamp offset and source share one atomic word so coalescing cannot
1284/// mix a request's time with another request's source. The low bit encodes the
1285/// source; the remaining bits encode nanoseconds from `origin`. `u64::MAX` is empty.
1286/// Without the `profiler` feature, this accumulator has no timestamp storage.
1287pub struct PlatformFrameSignal {
1288    #[cfg(feature = "profiler")]
1289    origin: Instant,
1290    #[cfg(feature = "profiler")]
1291    first_signal: std::sync::atomic::AtomicU64,
1292}
1293
1294impl Default for PlatformFrameSignal {
1295    fn default() -> Self {
1296        Self::new()
1297    }
1298}
1299
1300impl PlatformFrameSignal {
1301    /// Creates an empty signal accumulator.
1302    pub fn new() -> Self {
1303        Self {
1304            #[cfg(feature = "profiler")]
1305            origin: Instant::now(),
1306            #[cfg(feature = "profiler")]
1307            first_signal: std::sync::atomic::AtomicU64::new(u64::MAX),
1308        }
1309    }
1310
1311    /// Captures a platform request timestamp only when profiling is enabled.
1312    ///
1313    /// The closure is not invoked in profiler-disabled builds.
1314    #[inline]
1315    pub fn capture(capture: impl FnOnce() -> Instant) -> Option<Instant> {
1316        if cfg!(feature = "profiler") {
1317            Some(capture())
1318        } else {
1319            None
1320        }
1321    }
1322
1323    /// Records a platform frame request, retaining the first undrained timestamp.
1324    #[inline]
1325    #[cfg_attr(not(feature = "profiler"), expect(unused_variables))]
1326    pub fn record(&self, at: Instant, source: FrameRequestSource) {
1327        #[cfg(feature = "profiler")]
1328        {
1329            let nanoseconds = at
1330                .saturating_duration_since(self.origin)
1331                .as_nanos()
1332                .min(u128::from((u64::MAX >> 1) - 1)) as u64;
1333            let source_bit = match source {
1334                FrameRequestSource::NativeCallback => 0,
1335                FrameRequestSource::LocalSchedule => 1,
1336            };
1337            self.first_signal.fetch_min(
1338                (nanoseconds << 1) | source_bit,
1339                std::sync::atomic::Ordering::Relaxed,
1340            );
1341        }
1342    }
1343
1344    /// Drains the first platform frame request time and source, leaving the accumulator empty.
1345    #[inline]
1346    pub fn take(&self) -> Option<(Instant, FrameRequestSource)> {
1347        #[cfg(feature = "profiler")]
1348        {
1349            let signal = self
1350                .first_signal
1351                .swap(u64::MAX, std::sync::atomic::Ordering::Relaxed);
1352            (signal != u64::MAX).then(|| {
1353                let source = match signal & 1 {
1354                    0 => FrameRequestSource::NativeCallback,
1355                    _ => FrameRequestSource::LocalSchedule,
1356                };
1357                (self.origin + Duration::from_nanos(signal >> 1), source)
1358            })
1359        }
1360        #[cfg(not(feature = "profiler"))]
1361        {
1362            None
1363        }
1364    }
1365}
1366
1367/// The application's lifecycle phase, as owned and reported by a mobile OS.
1368///
1369/// `Inactive` means visible but not receiving input (a system dialog on
1370/// top), while `Background` means not visible at all, with process death
1371/// possible at any time thereafter.
1372///
1373/// | Phase        | iOS                          | Android      |
1374/// |--------------|------------------------------|--------------|
1375/// | `Active`     | `didBecomeActive`            | `onResume`   |
1376/// | `Inactive`   | `willResignActive`           | `onPause`    |
1377/// | `Background` | `didEnterBackground`         | `onStop`     |
1378/// | `Foreground` | `willEnterForeground`        | `onStart`    |
1379#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1380pub enum AppLifecyclePhase {
1381    /// Foreground and receiving input.
1382    Active,
1383    /// Foreground (visible) but not receiving input.
1384    Inactive,
1385    /// Not visible. The GPU surface may be destroyed while backgrounded and
1386    /// the process may be killed without further notice.
1387    Background,
1388    /// Becoming visible again, before input is restored.
1389    Foreground,
1390    /// The native scene has disconnected. Its windows may reconnect later.
1391    /// This is not a process-termination notification or a guaranteed final
1392    /// opportunity to save state.
1393    Disconnected,
1394}
1395
1396/// Regions of a window that are obscured or reserved by the system.
1397///
1398/// Mobile applications often share space in their window with system-specific
1399/// geometry, from keyboards to camera notches. In GPUI, all this is abstracted
1400/// into a single "inset" which should be overlaid on the window's bounds.
1401/// It is up to the application develop to determine how to handle these cases.
1402#[derive(Debug, Clone, Default, PartialEq)]
1403pub struct WindowInsets {
1404    /// Regions covered by system UI or hardware: status bar, display
1405    /// cutouts/notch, home indicator, navigation bars.
1406    /// (iOS: `safeAreaInsets`. Android: `WindowInsets` of types
1407    /// `systemBars() | displayCutout()`.)
1408    pub safe_area: Edges<Pixels>,
1409    /// The region covered by the keyboard, when present.
1410    /// (iOS: derived from `keyboardWillShow`/frame-change notifications.
1411    /// Android: `WindowInsets.Type.ime()`.)
1412    pub ime: Edges<Pixels>,
1413}
1414
1415impl WindowInsets {
1416    /// Returns, on each side, the larger of the system inset and the keyboard inset.
1417    ///
1418    /// Content should stay clear of this area.
1419    pub fn effective(&self) -> Edges<Pixels> {
1420        Edges {
1421            top: self.safe_area.top.max(self.ime.top),
1422            right: self.safe_area.right.max(self.ime.right),
1423            bottom: self.safe_area.bottom.max(self.ime.bottom),
1424            left: self.safe_area.left.max(self.ime.left),
1425        }
1426    }
1427}
1428
1429/// A change in the state of the focused text input.
1430#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1431pub enum TextInputStateChange {
1432    /// The window changed from having no active text input to having one.
1433    FocusGained,
1434    /// The window no longer has an active text input.
1435    FocusLost,
1436    /// The selection or caret moved
1437    SelectionChanged,
1438    /// The document content changed outside of platform-initiated edits.
1439    ContentChanged,
1440}
1441
1442/// The native window that GPUI draws into, implemented by each backend.
1443///
1444/// [`App::open_window`] creates one through [`Platform::open_window`] for each
1445/// [`Window`]. Application code doesn't call these methods directly. [`Window`] and
1446/// the [`Platform`] wrap them. Each backend implements the trait for its windowing
1447/// system. Methods with a default body are optional, and the default is the behavior
1448/// for a platform that lacks the feature.
1449///
1450/// Methods that a platform doesn't support are commented with the platform that
1451/// does.
1452pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
1453    /// Returns the window's frame in screen coordinates, in logical pixels.
1454    fn bounds(&self) -> Bounds<Pixels>;
1455    /// Returns whether the window is maximized.
1456    fn is_maximized(&self) -> bool;
1457    /// Returns the window's state, and the bounds to restore when it leaves that state.
1458    fn window_bounds(&self) -> WindowBounds;
1459    /// Returns the drawable size, excluding the titlebar, in logical pixels.
1460    fn content_size(&self) -> Size<Pixels>;
1461    /// Returns the visible viewport in logical pixels relative to the content origin.
1462    ///
1463    /// This may be smaller or offset when a keyboard or zoom obscures content;
1464    /// it must not change the full layout size returned by `content_size`.
1465    /// Implementations should return a frame snapshot, not query platform layout here.
1466    fn visual_viewport_bounds(&self) -> Bounds<Pixels> {
1467        Bounds::new(Point::default(), self.content_size())
1468    }
1469    /// Registers a callback when visible geometry may have changed.
1470    ///
1471    /// This requests a frame; backends can sample the new viewport and safe-area
1472    /// geometry in `prepare_frame` rather than updating it inside the callback.
1473    fn on_visual_viewport_changed(&self, _callback: Box<dyn FnMut()>) {}
1474    /// Samples platform geometry before a draw, returning whether view caches must be invalidated.
1475    ///
1476    /// Geometry getters must remain consistent throughout the ensuing draw.
1477    /// Do not invoke callbacks here: GPUI is already updating this window.
1478    fn prepare_frame(&self) -> bool {
1479        false
1480    }
1481    /// Resizes the window's content area to `size`, in logical pixels.
1482    fn resize(&mut self, size: Size<Pixels>);
1483    /// Returns the number of device pixels per logical pixel.
1484    fn scale_factor(&self) -> f32;
1485    /// Returns the window's light or dark appearance.
1486    fn appearance(&self) -> WindowAppearance;
1487    /// Returns the display the window is on, or `None` if the platform can't tell.
1488    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
1489    /// Returns the mouse position in window coordinates, in logical pixels.
1490    fn mouse_position(&self) -> Point<Pixels>;
1491    /// Returns the modifier keys currently held down.
1492    fn modifiers(&self) -> Modifiers;
1493    /// Returns the state of the Caps Lock key.
1494    fn capslock(&self) -> Capslock;
1495    /// Installs the handler that receives text input for the focused element.
1496    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
1497    /// Removes and returns the installed input handler.
1498    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
1499    /// Apply the focused text region's [`TextInputConfiguration`] to the
1500    /// platform's text input session (e.g. attributes of the hidden editable
1501    /// element on web). Called only when the configuration changes, because
1502    /// reconfiguring a live input session can restart the IME connection.
1503    fn set_text_input_configuration(&mut self, _configuration: TextInputConfiguration) {}
1504    /// Shows a native alert or prompt with `answers` as its buttons.
1505    ///
1506    /// The returned receiver yields the index in `answers` of the button the user
1507    /// chose. Returns `None` when the platform can't show a prompt. The web backend
1508    /// always returns `None`.
1509    fn prompt(
1510        &self,
1511        level: PromptLevel,
1512        msg: &str,
1513        detail: Option<&str>,
1514        answers: &[PromptButton],
1515    ) -> Option<oneshot::Receiver<usize>>;
1516    /// Request activation, optionally using an externally supplied token.
1517    /// Returns whether a request was sent, not whether focus was granted.
1518    fn activate(&self, token: Option<&str>) -> bool;
1519    /// Requests that the operating system draw attention to this window.
1520    fn request_attention(&self) {}
1521    /// Returns whether this window has keyboard focus.
1522    fn is_active(&self) -> bool;
1523    /// The current [`WindowVisibility`]. Read once when the window is created;
1524    /// afterwards changes arrive through [`Self::on_visibility_change`].
1525    fn visibility(&self) -> WindowVisibility;
1526    /// Returns whether the mouse is over this window.
1527    fn is_hovered(&self) -> bool;
1528    /// Returns how the window background is drawn.
1529    fn background_appearance(&self) -> WindowBackgroundAppearance;
1530    /// Sets the window title.
1531    fn set_title(&mut self, title: &str);
1532    /// Sets how the window background is drawn: opaque, transparent or blurred.
1533    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
1534    /// Show or hide the window without explicitly requesting focus.
1535    ///
1536    /// The default implementation does nothing for platforms that do not support
1537    /// changing window visibility at runtime.
1538    fn set_visible(&self, _visible: bool) {}
1539    /// Minimizes the window. The web backend logs a warning and does nothing.
1540    fn minimize(&self);
1541    /// Zooms the window with the platform's zoom behavior. The web backend logs a
1542    /// warning and does nothing.
1543    fn zoom(&self);
1544    /// Enters fullscreen, or leaves it if the window is already fullscreen.
1545    fn toggle_fullscreen(&self);
1546    /// Returns whether the window is fullscreen.
1547    fn is_fullscreen(&self) -> bool;
1548    /// Returns a function that wakes the event loop to draw a frame, or `None` if the
1549    /// platform doesn't provide one.
1550    fn frame_waker(&self) -> Option<Rc<dyn Fn()>> {
1551        None
1552    }
1553    /// Registers the callback that draws a frame when the platform asks for one.
1554    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
1555    /// Registers the callback that receives input events. Its return value tells the
1556    /// platform whether GPUI handled the event.
1557    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
1558    /// Registers the callback run when the window gains or loses focus. It receives `true` when the window is active.
1559    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
1560    /// Registers the callback invoked when [`Self::visibility`] changes. Only
1561    /// transitions are reported; the callback runs on the main thread outside
1562    /// of any window update.
1563    fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
1564    /// Registers the callback run when the mouse enters or leaves the window. It receives `true` on enter.
1565    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
1566    /// Registers the callback run when the content size or scale factor changes. It
1567    /// receives the new size and the scale factor.
1568    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
1569    /// Registers the callback run when the window moves.
1570    fn on_moved(&self, callback: Box<dyn FnMut()>);
1571    /// Registers the callback invoked when the window moves to another
1572    /// display. Calls may be spurious. The callback runs on the main thread
1573    /// outside of any window update.
1574    fn on_display_changed(&self, callback: Box<dyn FnMut()>);
1575    /// Registers the callback run when the user asks to close the window. Return `false` to keep it open.
1576    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
1577    /// Registers the callback the platform calls to find out which titlebar control, if any, the mouse is over.
1578    fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
1579    /// Registers the callback run once the window has closed.
1580    fn on_close(&self, callback: Box<dyn FnOnce()>);
1581    /// Registers the callback run when the window's appearance changes.
1582    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
1583    /// Registers the callback run when the titlebar button layout changes. The default does nothing.
1584    fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
1585    /// Draws `scene` into the window. GPUI calls this once per frame.
1586    fn draw(&self, scene: &Scene);
1587    /// Asks the platform to request another frame soon. The default does nothing.
1588    fn schedule_frame(&self) {}
1589    /// Returns the atlas that stores the glyph and image textures for this window.
1590    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1591    /// Returns whether the renderer supports subpixel (LCD) text antialiasing. macOS returns `false`.
1592    fn is_subpixel_rendering_supported(&self) -> bool;
1593
1594    // macOS specific methods
1595    /// Returns the window title. macOS and the Linux headless window override this. The default returns an empty string.
1596    fn get_title(&self) -> String {
1597        String::new()
1598    }
1599    /// Returns the windows in this window's native tab group, or `None` if the window isn't tabbed. macOS only.
1600    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
1601        None
1602    }
1603    /// Returns whether the native tab bar is visible. macOS only.
1604    fn tab_bar_visible(&self) -> bool {
1605        false
1606    }
1607    /// Marks the window's document as edited or unedited. macOS only.
1608    fn set_edited(&mut self, _edited: bool) {}
1609    /// Sets the document path that the titlebar shows for the window. macOS only.
1610    fn set_document_path(&self, _path: Option<&std::path::Path>) {}
1611    /// Toggles simple fullscreen, which fills the screen without creating a new Space. macOS only.
1612    fn toggle_simple_fullscreen(&self) {}
1613    /// Returns whether the window is in simple fullscreen. macOS only.
1614    fn is_simple_fullscreen(&self) -> bool {
1615        false
1616    }
1617    /// Moves the traffic-light buttons to `position`, in logical pixels. macOS only.
1618    #[cfg(target_os = "macos")]
1619    fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
1620    /// Show the platform character palette.
1621    ///
1622    /// The default implementation logs a warning when the backend does not
1623    /// support showing a character palette.
1624    fn show_character_palette(&self) {
1625        log::warn!("show_character_palette is not implemented by this platform backend");
1626    }
1627    /// Runs the titlebar double-click action. macOS only, and the default does nothing.
1628    fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
1629    /// Registers the callback for the Move Tab to New Window action. macOS only.
1630    fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
1631    /// Registers the callback for the Merge All Windows action. macOS only.
1632    fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
1633    /// Registers the callback for selecting the previous tab. macOS only.
1634    fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
1635    /// Registers the callback for selecting the next tab. macOS only.
1636    fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
1637    /// Registers the callback for showing or hiding the tab bar. macOS only.
1638    fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
1639    /// Merges all windows into one tab group. macOS only.
1640    fn merge_all_windows(&self) {}
1641    /// Moves this window's tab into a new window. macOS only.
1642    fn move_tab_to_new_window(&self) {}
1643    /// Shows or hides the tab overview. macOS only.
1644    fn toggle_window_tab_overview(&self) {}
1645    /// Sets the identifier that groups windows into one native tab group. macOS only.
1646    fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
1647
1648    /// Captures opaque, platform-specific state for restoring this native window later.
1649    ///
1650    /// Returns None when the backend has no restorable state. The bytes are only meaningful to
1651    /// the backend that produced them; the default implementation returns None.
1652    fn native_window_state(&self) -> Option<Vec<u8>> {
1653        None
1654    }
1655    /// Restores state previously returned by PlatformWindow::native_window_state.
1656    ///
1657    /// The default implementation does nothing on platforms without native state restoration.
1658    fn restore_native_window_state(&self, _state: &[u8]) {}
1659
1660    /// Returns the window's Win32 `HWND`. Windows only.
1661    #[cfg(target_os = "windows")]
1662    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
1663
1664    // Linux specific methods
1665    /// Returns the window's state and bounds, excluding decorations. Defaults to [`PlatformWindow::window_bounds`].
1666    fn inner_window_bounds(&self) -> WindowBounds {
1667        self.window_bounds()
1668    }
1669    /// Asks for server-side or client-side decorations. Linux only.
1670    fn request_decorations(&self, _decorations: WindowDecorations) {}
1671    /// Shows the window menu at `position`, in logical pixels. Linux only.
1672    fn show_window_menu(&self, _position: Point<Pixels>) {}
1673    /// Starts an interactive move, as if the user dragged the titlebar. Implemented on macOS, Windows and Linux; the web backend does nothing.
1674    fn start_window_move(&self) {}
1675    /// Returns whether this window can start a drag to another application.
1676    fn can_start_external_drag(&self) -> bool {
1677        false
1678    }
1679    /// Starts a drag of `payload` to another application. Returns `true` if the drag started.
1680    fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
1681        false
1682    }
1683    /// Starts an interactive resize from `edge`. Linux only.
1684    fn start_window_resize(&self, _edge: ResizeEdge) {}
1685    /// Reserves `zone` logical pixels at the screen edge for a layer-shell surface. Wayland only.
1686    fn set_exclusive_zone(&self, _zone: Pixels) {}
1687    /// Sets the screen edge that a layer-shell exclusive zone uses. Wayland only.
1688    #[cfg(all(target_os = "linux", feature = "wayland"))]
1689    fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
1690    /// Limits input to `region`, in logical pixels. `None` lets the whole window take input. Linux only.
1691    fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
1692    /// Returns the decorations the window is using. The default is [`Decorations::Server`].
1693    fn window_decorations(&self) -> Decorations {
1694        Decorations::Server
1695    }
1696    /// Sets the application id that the window manager uses to group windows and find the desktop entry. Linux only.
1697    fn set_app_id(&mut self, _app_id: &str) {}
1698    /// Maps the window so the platform shows it. The default does nothing and returns `Ok(())`.
1699    fn map_window(&mut self) -> anyhow::Result<()> {
1700        Ok(())
1701    }
1702    /// Returns which window controls the platform supports.
1703    fn window_controls(&self) -> WindowControls {
1704        WindowControls::default()
1705    }
1706    /// Sets the width of the client-side shadow that surrounds the window, in logical pixels. Linux only.
1707    fn set_client_inset(&self, _inset: Pixels) {}
1708    /// Returns the GPU's name and driver details, or `None` if they're unavailable.
1709    fn gpu_specs(&self) -> Option<GpuSpecs>;
1710
1711    /// Returns the GPU context for this window's renderer.
1712    /// The returned `Box` contains `(Arc<wgpu::Device>, Arc<wgpu::Queue>)`.
1713    #[cfg(any(
1714        target_os = "linux",
1715        target_os = "freebsd",
1716        target_os = "windows",
1717        target_os = "macos"
1718    ))]
1719    fn gpu_context(&self) -> Option<Box<dyn std::any::Any>> {
1720        None
1721    }
1722
1723    /// Returns typed backend-specific GPU context information for custom
1724    /// controls. The value is intentionally type-erased in this crate so the
1725    /// core UI crate does not depend on a rendering backend.
1726    #[cfg(any(
1727        target_family = "wasm",
1728        target_os = "linux",
1729        target_os = "freebsd",
1730        target_os = "macos"
1731    ))]
1732    fn gpu_context_info(&self) -> Option<Box<dyn std::any::Any>> {
1733        None
1734    }
1735
1736    /// Whether this window's GPU device has been lost (the platform renderer
1737    /// recovers it on a subsequent draw). `None` when the backend cannot
1738    /// know. Safe to call mid-recovery, unlike `gpu_context`. Embedders that
1739    /// captured the device from `gpu_context` should stop submitting while
1740    /// this is `Some(true)` and re-acquire the device once it reads
1741    /// `Some(false)` again.
1742    #[cfg(any(
1743        target_os = "linux",
1744        target_os = "freebsd",
1745        target_os = "windows",
1746        target_os = "macos"
1747    ))]
1748    fn gpu_device_lost(&self) -> Option<bool> {
1749        None
1750    }
1751
1752    /// Moves the input method's candidate window next to `bounds`, in window coordinates.
1753    fn update_ime_position(&self, _bounds: Bounds<Pixels>);
1754
1755    // Mobile platform methods.
1756
1757    /// The regions of this window currently obscured or reserved by the
1758    /// system. Zero on platforms without such regions.
1759    fn insets(&self) -> WindowInsets {
1760        WindowInsets::default()
1761    }
1762
1763    /// Registers a callback invoked whenever [`Self::insets`] change.
1764    ///
1765    /// Contract: fires continuously during animated transitions (Android
1766    /// `WindowInsetsAnimation` progress; on iOS the platform interpolates
1767    /// the keyboard animation curve on frame ticks) and is exact at rest.
1768    fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
1769
1770    /// Sets the handler for the system back action (Android back
1771    /// button/gesture; no source on iOS or desktop).
1772    fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
1773
1774    /// Declares whether the application would currently handle the system
1775    /// back action (e.g. navigation depth > 0).
1776    fn set_back_enabled(&self, _enabled: bool) {}
1777
1778    /// Requests that the soft keyboard be shown. Mobile only.
1779    fn show_soft_keyboard(&self) {}
1780
1781    /// Requests that the soft keyboard be hidden. Mobile only.
1782    fn hide_soft_keyboard(&self) {}
1783
1784    /// Tells the platform that the focused text input changed.
1785    fn text_input_state_changed(&self, _change: TextInputStateChange) {}
1786
1787    /// Plays the system alert sound.
1788    fn play_system_bell(&self) {}
1789
1790    /// Initialize the accessibility adapter with callbacks.
1791    fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1792
1793    /// Provide a TreeUpdate to the accessibility adapter.
1794    fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1795
1796    /// Inform the adapter of updated window bounds.
1797    fn a11y_update_window_bounds(&self) {}
1798
1799    /// Returns this window as a `TestWindow`, if it is one. Only the test platform's windows are.
1800    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1801    fn as_test(&mut self) -> Option<&mut TestWindow> {
1802        None
1803    }
1804
1805    /// Renders the given scene to a texture and returns the pixel data as an RGBA image.
1806    /// This does not present the frame to screen - useful for visual testing where we want
1807    /// to capture what would be rendered without displaying it or requiring the window to be visible.
1808    #[cfg(any(test, feature = "test-support"))]
1809    fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1810        anyhow::bail!("render_to_image not implemented for this platform")
1811    }
1812}
1813
1814/// Renders scenes offscreen for windows that have no display, such as in tests and benchmarks.
1815#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1816pub trait PlatformHeadlessRenderer {
1817    /// Renders `scene` at `size` and returns the pixels as an RGBA image.
1818    fn render_scene_to_image(
1819        &mut self,
1820        scene: &Scene,
1821        size: Size<DevicePixels>,
1822    ) -> Result<RgbaImage>;
1823
1824    /// Render a scene to an offscreen target without reading the result back.
1825    ///
1826    /// This is the headless analogue of presenting a frame: it performs the
1827    /// same CPU-side scene encoding and GPU submission as drawing to a real
1828    /// window, but doesn't block on GPU completion or copy pixels back.
1829    fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1830
1831    /// Returns the sprite atlas used by this renderer.
1832    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1833}
1834
1835/// Type alias for runnables with metadata.
1836/// Previously an enum with a single variant, now simplified to a direct type alias.
1837#[doc(hidden)]
1838pub type RunnableVariant = Runnable<RunnableMeta>;
1839
1840#[doc(hidden)]
1841pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1842
1843#[doc(hidden)]
1844pub enum TasksIncluded {
1845    OnlyCompleted,
1846    CompletedAndRunning,
1847}
1848
1849/// This type is public so that our test macro can generate and use it, but it should not
1850/// be considered part of our public API.
1851#[doc(hidden)]
1852pub trait PlatformDispatcher: Send + Sync {
1853    fn is_main_thread(&self) -> bool;
1854    fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1855    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1856    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1857
1858    fn dispatch_on_main_thread_when_idle(
1859        &self,
1860        runnable: RunnableVariant,
1861        timeout: Option<Duration>,
1862    ) {
1863        let _ = timeout;
1864        self.dispatch_on_main_thread(runnable, Priority::Low);
1865    }
1866
1867    /// Creates an owner-thread local-task scope for platforms that retire event loops.
1868    #[doc(hidden)]
1869    fn create_local_task_scope(&self) -> Option<Arc<LocalTaskScope>> {
1870        None
1871    }
1872
1873    /// Dispatches a scoped runnable through the normal owner-thread queue.
1874    #[doc(hidden)]
1875    fn dispatch_local_task(&self, _scope: Arc<LocalTaskScope>, runnable: RunnableVariant) {
1876        self.dispatch_on_main_thread(runnable, Priority::default());
1877    }
1878
1879    /// Dispatches a scoped runnable through the owner's idle queue.
1880    #[doc(hidden)]
1881    fn dispatch_local_task_when_idle(
1882        &self,
1883        _scope: Arc<LocalTaskScope>,
1884        runnable: RunnableVariant,
1885        timeout: Option<Duration>,
1886    ) {
1887        self.dispatch_on_main_thread_when_idle(runnable, timeout);
1888    }
1889
1890    /// Enqueues a scoped runnable for owner-thread destruction without polling.
1891    #[doc(hidden)]
1892    fn dispatch_local_task_cancellation(
1893        &self,
1894        _scope: Arc<LocalTaskScope>,
1895        runnable: RunnableVariant,
1896    ) {
1897        self.dispatch_on_main_thread(runnable, Priority::High);
1898    }
1899
1900    /// Begins retiring a scope after switching its queue to cancellation mode.
1901    #[doc(hidden)]
1902    fn begin_local_task_retirement(&self, scope: &Arc<LocalTaskScope>) {
1903        scope.begin_retirement();
1904    }
1905
1906    /// Drops one batch of queued cancellation runnables on the owner thread.
1907    #[doc(hidden)]
1908    fn drain_local_task_retirements(&self, _scope: &Arc<LocalTaskScope>) {}
1909
1910    /// Finishes retirement only after all scoped queues and tasks are empty.
1911    #[doc(hidden)]
1912    fn finish_local_task_retirement(&self, scope: &Arc<LocalTaskScope>) -> bool {
1913        scope.seal_if_quiescent()
1914    }
1915
1916    fn idle_time_remaining(&self) -> Option<Duration> {
1917        None
1918    }
1919
1920    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1921
1922    fn now(&self) -> Instant {
1923        Instant::now()
1924    }
1925
1926    fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1927        gpui_util::defer(Box::new(|| {}))
1928    }
1929
1930    fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
1931        ActivityGuard::noop()
1932    }
1933
1934    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1935    fn as_test(&self) -> Option<&TestDispatcher> {
1936        None
1937    }
1938
1939    // This cfg must match the `threaded_dispatcher` module's, which implements
1940    // this method whenever it compiles.
1941    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1942    fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1943        None
1944    }
1945}
1946
1947/// Loads fonts and lays out text for one platform. Each backend implements this trait.
1948///
1949/// Application code uses [`TextSystem`](crate::TextSystem), which calls these methods and caches the
1950/// results. Glyph and layout methods run on whatever thread the caller uses, so
1951/// implementations must be `Send + Sync`.
1952pub trait PlatformTextSystem: Send + Sync {
1953    /// Adds fonts from their file bytes, such as TTF or OTF data, so layout can find them by family name.
1954    ///
1955    /// Returns an error if any of the bytes can't be parsed as a font.
1956    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1957    /// Returns the names of every font family available to layout.
1958    fn all_font_names(&self) -> Vec<String>;
1959    /// Returns a number that changes whenever the font collection changes, so caches can detect stale entries.
1960    ///
1961    /// The default returns `0`, which means the collection never changes.
1962    fn font_generation(&self) -> u64 {
1963        0
1964    }
1965    /// Returns the id of the font that best matches `descriptor`.
1966    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1967    /// Loads the caches that shaping needs for `font_ids`, so the first layout with them is fast.
1968    /// The default does nothing.
1969    fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1970    /// Returns the metrics, such as ascent, descent and line gap, for `font_id`.
1971    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1972    /// Returns the glyph's bounding box, relative to its origin on the baseline.
1973    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1974    /// Returns the distance the pen moves after drawing this glyph.
1975    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1976    /// Returns the glyph that `font_id` uses for `ch`, or `None` if the font has no glyph for it.
1977    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1978    /// Rasterizes a glyph, including its atlas placement and pixels.
1979    fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<RasterizedGlyph>;
1980    /// Returns the raster style to use for `request`, normalized so that cached glyph
1981    /// pixels can be shared. The default uses the requested mode unchanged.
1982    fn prepare_raster_style(&self, request: RasterStyleRequest) -> PreparedRasterStyle {
1983        PreparedRasterStyle::independent(request.requested_mode)
1984    }
1985    /// Lays out one complete text document, including hard breaks and optional wrapping.
1986    fn layout_text(&self, request: TextLayoutRequest<'_>) -> LineLayout;
1987    /// Lays out one complete text document that contains atomic element boxes, which
1988    /// keep their size and sit on the text line.
1989    fn layout_inline(&self, request: InlineLayoutRequest<'_>) -> InlineLayout;
1990    /// Returns the text rendering mode the platform recommends for `font_id` at `font_size`.
1991    ///
1992    /// The default returns [`TextRenderingMode::Subpixel`].
1993    fn recommended_rendering_mode(
1994        &self,
1995        _font_id: FontId,
1996        _font_size: Pixels,
1997    ) -> TextRenderingMode {
1998        TextRenderingMode::Subpixel
1999    }
2000}
2001
2002#[cfg(any(test, feature = "test-support"))]
2003mod tests {
2004    use crate::{
2005        Bounds, CaretAffinity, CaretMovement, CaretPosition, Font, FontId, FontMetrics, GlyphId,
2006        InlineLayout, InlineLayoutRequest, InlineVisualLine, LineLayout, PaintFragment, PaintStyle,
2007        Pixels, PlatformTextLayout, PlatformTextSystem, Point, PositionedInlineBox,
2008        RasterizedGlyph, RasterizedGlyphFormat, RenderGlyphParams, ResolvedDirection, ShapedGlyph,
2009        Size, TextAlign, TextBoundary, TextDirection, TextLayoutRequest, TextMovement,
2010        TextRenderingMode, TextSelectionKind, VisualDirection, VisualLine, align_inline_boxes,
2011        point, px, size,
2012    };
2013    use anyhow::Result;
2014    use std::{borrow::Cow, ops::Range, sync::Arc};
2015
2016    #[expect(missing_docs)]
2017    pub struct TestTextSystem;
2018
2019    #[derive(Debug)]
2020    /// A deterministic test layout with exactly one visual and hard line.
2021    struct TestPlatformTextLayout {
2022        /// Source text.
2023        text: String,
2024        /// Ordered (byte index, x position) caret stops.
2025        stops: Vec<(usize, Pixels)>,
2026        /// Size of the single visual line.
2027        size: Size<Pixels>,
2028    }
2029
2030    impl TestPlatformTextLayout {
2031        fn caret_stop(&self, byte_offset: usize) -> (usize, Pixels) {
2032            let stop_index = self
2033                .stops
2034                .partition_point(|(index, _position)| *index <= byte_offset)
2035                .saturating_sub(1);
2036
2037            self.stops[stop_index]
2038        }
2039    }
2040
2041    impl PlatformTextLayout for TestPlatformTextLayout {
2042        fn len(&self) -> usize {
2043            self.stops.last().map_or(0, |(index, _)| *index)
2044        }
2045
2046        fn line_count(&self) -> usize {
2047            // This test layout deliberately reports its single visual line.
2048            1
2049        }
2050
2051        fn size(&self) -> Size<Pixels> {
2052            self.size
2053        }
2054
2055        fn byte_index_from_pixel_point(
2056            &self,
2057            pixel_point: Point<Pixels>,
2058            line_height: Pixels,
2059        ) -> Result<usize, usize> {
2060            let closest = self
2061                .caret_from_pixel_point(pixel_point, line_height)
2062                .unwrap_or_else(|caret| caret)
2063                .index;
2064            self.stops
2065                .windows(2)
2066                .find_map(|stops| {
2067                    let [(start_index, start_x), (_end_index, end_x)] = stops else {
2068                        return None;
2069                    };
2070
2071                    (pixel_point.x >= *start_x && pixel_point.x < *end_x)
2072                        .then_some(Ok(*start_index))
2073                })
2074                .unwrap_or(Err(closest))
2075        }
2076
2077        fn caret_from_pixel_point(
2078            &self,
2079            pixel_point: Point<Pixels>,
2080            line_height: Pixels,
2081        ) -> Result<CaretPosition, CaretPosition> {
2082            let index = self
2083                .stops
2084                .iter()
2085                .min_by(|(_, left), (_, right)| {
2086                    (f32::from(*left) - f32::from(pixel_point.x))
2087                        .abs()
2088                        .total_cmp(&(f32::from(*right) - f32::from(pixel_point.x)).abs())
2089                })
2090                .map_or(0, |(index, _)| *index);
2091            let caret = self.normalized_caret(CaretPosition {
2092                index,
2093                affinity: CaretAffinity::Downstream,
2094            });
2095
2096            if pixel_point.y >= Pixels::ZERO
2097                && pixel_point.y < line_height
2098                && pixel_point.x >= Pixels::ZERO
2099                && pixel_point.x < self.size.width
2100            {
2101                Ok(caret)
2102            } else {
2103                Err(caret)
2104            }
2105        }
2106
2107        fn caret_bounds(
2108            &self,
2109            caret: CaretPosition,
2110            line_height: Pixels,
2111        ) -> Option<Bounds<Pixels>> {
2112            let caret = self.normalized_caret(caret);
2113            let (_index, position) = self.caret_stop(caret.index);
2114
2115            Some(Bounds::new(
2116                point(position, Pixels::ZERO),
2117                size(Pixels::ZERO, line_height),
2118            ))
2119        }
2120
2121        fn normalized_caret(&self, caret: CaretPosition) -> CaretPosition {
2122            let (index, _position) = self.caret_stop(caret.index);
2123            let affinity = if index == self.len() && index != 0 {
2124                CaretAffinity::Upstream
2125            } else {
2126                caret.affinity
2127            };
2128
2129            CaretPosition { index, affinity }
2130        }
2131
2132        fn adjacent_visual_caret(
2133            &self,
2134            caret: CaretPosition,
2135            direction: VisualDirection,
2136        ) -> Option<CaretPosition> {
2137            let caret = self.normalized_caret(caret);
2138            let position = self
2139                .stops
2140                .iter()
2141                .position(|(index, _)| *index == caret.index)?;
2142            let position = match direction {
2143                VisualDirection::Left => position.checked_sub(1)?,
2144                VisualDirection::Right => position.checked_add(1)?,
2145            };
2146
2147            let index = self.stops.get(position)?.0;
2148            Some(self.normalized_caret(CaretPosition {
2149                index,
2150                affinity: CaretAffinity::Downstream,
2151            }))
2152        }
2153
2154        fn selection_bounds(
2155            &self,
2156            byte_range: Range<usize>,
2157            line_height: Pixels,
2158        ) -> Vec<Bounds<Pixels>> {
2159            if byte_range.is_empty() {
2160                return Vec::new();
2161            }
2162
2163            let (_start_index, start) = self.caret_stop(byte_range.start);
2164            let (_end_index, end) = self.caret_stop(byte_range.end);
2165
2166            // The platform contract returns one bound per visual line. Since this test
2167            // layout has exactly one visual line, every non-empty selection has one bound.
2168            vec![Bounds::from_corners(
2169                point(start.min(end), Pixels::ZERO),
2170                point(start.max(end), line_height),
2171            )]
2172        }
2173
2174        fn logical_cluster_before(&self, caret: CaretPosition) -> Option<Range<usize>> {
2175            self.stops
2176                .windows(2)
2177                .rev()
2178                .find(|stops| stops[1].0 <= caret.index)
2179                .map(|stops| stops[0].0..stops[1].0)
2180        }
2181
2182        fn logical_cluster_after(&self, caret: CaretPosition) -> Option<Range<usize>> {
2183            self.stops
2184                .windows(2)
2185                .find(|stops| stops[0].0 >= caret.index)
2186                .map(|stops| stops[0].0..stops[1].0)
2187        }
2188
2189        fn caret_movement(
2190            &self,
2191            caret: CaretPosition,
2192            movement: TextMovement,
2193            vertical_navigation_x: Option<Pixels>,
2194        ) -> CaretMovement {
2195            use TextBoundary::*;
2196            use TextDirection::*;
2197
2198            let index = match (movement.direction, movement.boundary) {
2199                (Left, Cluster) => {
2200                    self.adjacent_visual_caret(caret, VisualDirection::Left)
2201                        .unwrap_or(caret)
2202                        .index
2203                }
2204                (Right, Cluster) => {
2205                    self.adjacent_visual_caret(caret, VisualDirection::Right)
2206                        .unwrap_or(caret)
2207                        .index
2208                }
2209                (Left, Word) => {
2210                    let prefix = &self.text[..caret.index.min(self.text.len())];
2211                    let trimmed = prefix.trim_end_matches(char::is_whitespace);
2212                    trimmed.rfind(char::is_whitespace).map_or(0, |index| {
2213                        index + trimmed[index..].chars().next().unwrap().len_utf8()
2214                    })
2215                }
2216                (Right, Word) => {
2217                    let start = caret.index.min(self.text.len());
2218                    let suffix = &self.text[start..];
2219                    let word_end = suffix.find(char::is_whitespace).unwrap_or(suffix.len());
2220                    let rest = &suffix[word_end..];
2221                    start
2222                        + word_end
2223                        + rest
2224                            .find(|character: char| !character.is_whitespace())
2225                            .unwrap_or(rest.len())
2226                }
2227                // This test layout has one visual and hard line. Without wrapping or
2228                // additional hard lines, these movements resolve to the document endpoints.
2229                (Up | Start, VisualLine) | (Start, HardLine | Document) => 0,
2230                (Down | End, VisualLine) | (End, HardLine | Document) => self.len(),
2231                _ => caret.index,
2232            };
2233
2234            let vertical_navigation_x =
2235                matches!(movement.direction, TextDirection::Up | TextDirection::Down).then(|| {
2236                    vertical_navigation_x.unwrap_or_else(|| {
2237                        self.caret_bounds(caret, self.size.height)
2238                            .map_or(Pixels::ZERO, |bounds| bounds.origin.x)
2239                    })
2240                });
2241
2242            CaretMovement {
2243                result: self.normalized_caret(CaretPosition {
2244                    index,
2245                    affinity: CaretAffinity::Downstream,
2246                }),
2247                vertical_navigation_x,
2248            }
2249        }
2250
2251        fn selection_from_pixel_point(
2252            &self,
2253            pixel_point: Point<Pixels>,
2254            line_height: Pixels,
2255            kind: TextSelectionKind,
2256        ) -> Range<usize> {
2257            if !matches!(kind, TextSelectionKind::Word) {
2258                return 0..self.len();
2259            }
2260
2261            let index = self
2262                .caret_from_pixel_point(pixel_point, line_height)
2263                .unwrap_or_else(|caret| caret)
2264                .index
2265                .min(self.text.len());
2266            let start = self.text[..index]
2267                .rfind(char::is_whitespace)
2268                .map_or(0, |offset| {
2269                    offset + self.text[offset..].chars().next().unwrap().len_utf8()
2270                });
2271            let end = self.text[index..]
2272                .find(char::is_whitespace)
2273                .map_or(self.text.len(), |offset| index + offset);
2274            start..end
2275        }
2276    }
2277
2278    #[expect(missing_docs)]
2279    impl TestTextSystem {
2280        #[allow(dead_code)]
2281        pub fn new() -> Self {
2282            Self
2283        }
2284
2285        fn em_width(&self, font_size: Pixels) -> Pixels {
2286            let metrics = self.font_metrics(FontId(0));
2287            let advance = self
2288                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
2289                .unwrap();
2290
2291            font_size * advance.width / metrics.units_per_em as f32
2292        }
2293    }
2294
2295    fn position_test_inline_boxes(
2296        request: InlineLayoutRequest<'_>,
2297        em_width: Pixels,
2298        baseline: Pixels,
2299    ) -> Vec<PositionedInlineBox> {
2300        let mut preceding_width = Pixels::ZERO;
2301        request
2302            .boxes
2303            .iter()
2304            .map(|inline_box| {
2305                let text_width = em_width
2306                    * request.text[..inline_box.index]
2307                        .chars()
2308                        .map(|character| character.len_utf16() as f32)
2309                        .sum::<f32>();
2310                let positioned = PositionedInlineBox {
2311                    id: inline_box.id,
2312                    line_index: 0,
2313                    bounds: Bounds::new(
2314                        point(
2315                            text_width + preceding_width,
2316                            baseline - inline_box.size.height,
2317                        ),
2318                        inline_box.size,
2319                    ),
2320                };
2321
2322                preceding_width += inline_box.size.width;
2323                positioned
2324            })
2325            .collect()
2326    }
2327
2328    fn add_test_inline_box_advances(layout: &mut LineLayout, request: InlineLayoutRequest<'_>) {
2329        for fragment in &mut layout.paint_fragments {
2330            // This layout owns its freshly built glyphs. If they become shared,
2331            // make_mut copies the slice before changing their positions.
2332            for (glyph, (index, _)) in Arc::make_mut(&mut fragment.glyphs)
2333                .iter_mut()
2334                .zip(request.text.char_indices())
2335            {
2336                glyph.position.x += request
2337                    .boxes
2338                    .iter()
2339                    .filter(|inline_box| inline_box.index <= index)
2340                    .map(|inline_box| inline_box.size.width)
2341                    .sum::<Pixels>();
2342            }
2343        }
2344
2345        let box_width = request
2346            .boxes
2347            .iter()
2348            .map(|inline_box| inline_box.size.width)
2349            .sum::<Pixels>();
2350        for fragment in &mut layout.paint_fragments {
2351            fragment.x_range.end += box_width;
2352        }
2353
2354        layout.width += box_width;
2355
2356        if let Some(line) = layout.visual_lines.first_mut() {
2357            line.advance_width += box_width;
2358        }
2359    }
2360
2361    impl PlatformTextSystem for TestTextSystem {
2362        fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
2363            Ok(())
2364        }
2365
2366        fn all_font_names(&self) -> Vec<String> {
2367            Vec::new()
2368        }
2369
2370        fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
2371            Ok(FontId(1))
2372        }
2373
2374        fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
2375            FontMetrics {
2376                units_per_em: 1000,
2377                ascent: 1025.0,
2378                descent: -275.0,
2379                line_gap: 0.0,
2380                underline_position: -95.0,
2381                underline_thickness: 60.0,
2382                cap_height: 698.0,
2383                x_height: 516.0,
2384                bounding_box: Bounds {
2385                    origin: Point {
2386                        x: -260.0,
2387                        y: -245.0,
2388                    },
2389                    size: Size {
2390                        width: 1501.0,
2391                        height: 1364.0,
2392                    },
2393                },
2394            }
2395        }
2396
2397        fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
2398            Ok(Bounds {
2399                origin: Point { x: 54.0, y: 0.0 },
2400                size: size(392.0, 528.0),
2401            })
2402        }
2403
2404        fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
2405            Ok(size(600.0 * glyph_id.0 as f32, 0.0))
2406        }
2407
2408        fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
2409            Some(GlyphId(ch.len_utf16() as u32))
2410        }
2411
2412        fn rasterize_glyph(&self, _params: &RenderGlyphParams) -> Result<RasterizedGlyph> {
2413            Ok(RasterizedGlyph {
2414                bounds: Default::default(),
2415                size: Default::default(),
2416                format: RasterizedGlyphFormat::AlphaMask,
2417                pixels: Vec::new(),
2418            })
2419        }
2420
2421        fn layout_text(&self, request: TextLayoutRequest<'_>) -> LineLayout {
2422            let text = request.text;
2423            let font_size = request.font_size;
2424            let shaping_runs = request.runs;
2425            let mut position = px(0.);
2426            let metrics = self.font_metrics(FontId(0));
2427            let em_width = self.em_width(font_size);
2428            let mut glyphs = Vec::new();
2429            let mut stops = vec![(0, Pixels::ZERO)];
2430
2431            for (idx, character) in text.char_indices() {
2432                let glyph_id = GlyphId(character.len_utf16() as u32);
2433                glyphs.push(ShapedGlyph {
2434                    id: glyph_id,
2435                    position: point(position, Pixels::ZERO),
2436                    is_emoji: glyph_id.0 == 2,
2437                });
2438
2439                position += em_width * glyph_id.0 as f32;
2440                stops.push((idx + character.len_utf8(), position));
2441            }
2442
2443            let mut tracking = px(0.);
2444            let mut tracking_covered = 0usize;
2445            for run in shaping_runs {
2446                let start = tracking_covered.min(text.len());
2447                let end = start.saturating_add(run.len).min(text.len()).max(start);
2448                let slice = text.get(start..end).unwrap_or("");
2449                let n = slice.chars().count();
2450                if n > 1 {
2451                    if let Some(spacing) = run.letter_spacing {
2452                        tracking += spacing * (n - 1) as f32;
2453                    }
2454                }
2455                tracking_covered = end;
2456            }
2457
2458            let direction = match request.options.direction {
2459                crate::ParagraphDirection::LeftToRight => ResolvedDirection::LeftToRight,
2460                crate::ParagraphDirection::RightToLeft => ResolvedDirection::RightToLeft,
2461                crate::ParagraphDirection::Auto => {
2462                    ResolvedDirection::from_first_strong(text).unwrap_or_default()
2463                }
2464            };
2465            let advance = position + tracking;
2466            let alignment_width = request.options.alignment_width.unwrap_or(advance);
2467            let offset = match request.options.text_align {
2468                TextAlign::Start if direction.is_rtl() => alignment_width - advance,
2469                TextAlign::Start | TextAlign::Left => Pixels::ZERO,
2470                TextAlign::Center => (alignment_width - advance) / 2.0,
2471                TextAlign::End if direction.is_rtl() => Pixels::ZERO,
2472                TextAlign::End | TextAlign::Right => alignment_width - advance,
2473            };
2474
2475            for (_idx, position) in &mut stops {
2476                *position += offset;
2477            }
2478
2479            let visual_lines = [VisualLine {
2480                text_range: 0..text.len(),
2481                paint_fragment_range: 0..usize::from(!glyphs.is_empty()),
2482                advance_width: advance,
2483                offset,
2484                direction,
2485            }]
2486            .into_iter()
2487            .collect();
2488            let paint_fragments = (!glyphs.is_empty())
2489                .then(|| PaintFragment {
2490                    source_run: 0,
2491                    font_id: FontId(0),
2492                    font_size,
2493                    glyphs: glyphs.into(),
2494                    x_range: Pixels::ZERO..advance,
2495                    style: shaping_runs
2496                        .first()
2497                        .map_or_else(PaintStyle::default, PaintStyle::from),
2498                    underline_offset: Some(font_size * 0.1),
2499                    strikethrough_offset: Some(-font_size * 0.3),
2500                })
2501                .into_iter()
2502                .collect();
2503            LineLayout {
2504                font_size,
2505                width: advance,
2506                ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
2507                descent: font_size * (metrics.descent / metrics.units_per_em as f32),
2508                visual_lines,
2509                paint_fragments,
2510                len: text.len(),
2511                platform_layout: Arc::new(TestPlatformTextLayout {
2512                    text: text.to_owned(),
2513                    stops,
2514                    size: size(advance, font_size),
2515                }),
2516            }
2517        }
2518
2519        fn layout_inline(&self, request: InlineLayoutRequest<'_>) -> InlineLayout {
2520            let mut layout = self.layout_text(TextLayoutRequest {
2521                text: request.text,
2522                font_size: request.font_size,
2523                runs: request.runs,
2524                options: request.options,
2525            });
2526
2527            let em_width = self.em_width(request.font_size);
2528            let baseline = request
2529                .boxes
2530                .iter()
2531                .map(|inline_box| inline_box.size.height)
2532                .fold(request.line_height, Pixels::max);
2533            let positioned_boxes = position_test_inline_boxes(request, em_width, baseline);
2534            add_test_inline_box_advances(&mut layout, request);
2535            let line_width = request
2536                .options
2537                .wrap_width
2538                .unwrap_or(Pixels::MAX)
2539                .min(layout.width);
2540            let line_offset = layout.visual_lines[0].offset;
2541            let mut inline = InlineLayout {
2542                size: size(layout.width, baseline),
2543                layout: Arc::new(layout),
2544                lines: [InlineVisualLine {
2545                    origin: point(line_offset, Pixels::ZERO),
2546                    size: size(line_width, baseline),
2547                    baseline,
2548                }]
2549                .into_iter()
2550                .collect(),
2551                boxes: positioned_boxes,
2552                alignment_offset: Pixels::ZERO,
2553            };
2554
2555            align_inline_boxes(
2556                &mut inline.lines,
2557                &mut inline.boxes,
2558                &mut inline.size,
2559                request.boxes,
2560                &[request.text_metrics],
2561                &[],
2562                request.text_metrics,
2563                request.line_height,
2564            );
2565            inline
2566        }
2567
2568        fn recommended_rendering_mode(
2569            &self,
2570            _font_id: FontId,
2571            _font_size: Pixels,
2572        ) -> TextRenderingMode {
2573            TextRenderingMode::Grayscale
2574        }
2575    }
2576}
2577
2578// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
2579// Copyright (c) Microsoft Corporation.
2580// Licensed under the MIT license.
2581/// Returns the gamma correction ratios for subpixel text at `gamma`.
2582///
2583/// `gamma` is rounded to one decimal place and clamped to the range `1.0` to `2.2`.
2584#[allow(dead_code)]
2585pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
2586    const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
2587        [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
2588        [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
2589        [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
2590        [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
2591        [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
2592        [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
2593        [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
2594        [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
2595        [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
2596        [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
2597        [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
2598        [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
2599        [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
2600    ];
2601
2602    const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
2603    const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
2604
2605    let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
2606    let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
2607
2608    [
2609        ratios[0] * NORM13,
2610        ratios[1] * NORM24,
2611        ratios[2] * NORM13,
2612        ratios[3] * NORM24,
2613    ]
2614}
2615
2616/// Identifies one entry in a [`PlatformAtlas`]: a glyph, an SVG or a raster image.
2617#[derive(PartialEq, Eq, Hash, Clone)]
2618pub enum AtlasKey {
2619    /// A rasterized glyph, with the format it was rasterized in.
2620    Glyph {
2621        /// The glyph and how it is rendered.
2622        params: RenderGlyphParams,
2623        /// The pixel format it was rasterized in.
2624        format: RasterizedGlyphFormat,
2625    },
2626    /// An SVG rendered at specific parameters.
2627    Svg(RenderSvgParams),
2628    /// A raster image, identified by its render parameters.
2629    Image(RenderImageParams),
2630}
2631
2632impl AtlasKey {
2633    /// Returns the texture kind for this atlas key.
2634    pub fn texture_kind(&self) -> AtlasTextureKind {
2635        match self {
2636            AtlasKey::Glyph { format, .. } => match format {
2637                RasterizedGlyphFormat::AlphaMask => AtlasTextureKind::Monochrome,
2638                RasterizedGlyphFormat::BgraSubpixelMask => AtlasTextureKind::Subpixel,
2639                RasterizedGlyphFormat::BgraColor => AtlasTextureKind::Polychrome,
2640            },
2641            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
2642            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
2643        }
2644    }
2645}
2646
2647impl From<(RenderGlyphParams, RasterizedGlyphFormat)> for AtlasKey {
2648    fn from((params, format): (RenderGlyphParams, RasterizedGlyphFormat)) -> Self {
2649        Self::Glyph { params, format }
2650    }
2651}
2652
2653impl From<RenderSvgParams> for AtlasKey {
2654    fn from(params: RenderSvgParams) -> Self {
2655        Self::Svg(params)
2656    }
2657}
2658
2659impl From<RenderImageParams> for AtlasKey {
2660    fn from(params: RenderImageParams) -> Self {
2661        Self::Image(params)
2662    }
2663}
2664
2665#[doc(hidden)]
2666#[derive(Default)]
2667pub struct GlyphAtlasCache {
2668    entries: FxHashMap<RenderGlyphParams, GlyphAtlasEntry>,
2669}
2670
2671impl GlyphAtlasCache {
2672    pub fn get(&self, params: &RenderGlyphParams) -> Option<GlyphAtlasEntry> {
2673        self.entries.get(params).copied()
2674    }
2675
2676    pub fn insert(
2677        &mut self,
2678        params: &RenderGlyphParams,
2679        glyph: &RasterizedGlyph,
2680        tile: Option<AtlasTile>,
2681    ) -> GlyphAtlasEntry {
2682        let entry = GlyphAtlasEntry {
2683            tile,
2684            bounds: glyph.bounds,
2685            format: glyph.format,
2686        };
2687        self.entries.insert(params.clone(), entry);
2688
2689        entry
2690    }
2691
2692    pub fn remove(&mut self, key: &AtlasKey) {
2693        let AtlasKey::Glyph { params, format } = key else {
2694            return;
2695        };
2696
2697        if self
2698            .entries
2699            .get(params)
2700            .is_some_and(|entry| entry.format == *format)
2701        {
2702            self.entries.remove(params);
2703        }
2704    }
2705
2706    pub fn clear(&mut self) {
2707        self.entries.clear();
2708    }
2709}
2710
2711/// A texture atlas that packs glyphs, SVGs and images into shared GPU textures.
2712///
2713/// Each window's renderer owns one atlas. Methods that insert tiles build the pixels
2714/// only on a cache miss, so callers don't pay for rasterizing a tile they already
2715/// have.
2716pub trait PlatformAtlas {
2717    /// Returns the tile for `key`, calling `build` on a cache miss to produce the pixels.
2718    ///
2719    /// `build` returns `None` when there is nothing to draw, and then this returns
2720    /// `Ok(None)`. Returns an error if the pixels fail validation or the atlas is full.
2721    /// The builder runs with the atlas locked and must not re-enter the same atlas.
2722    fn get_or_insert_with<'a>(
2723        &self,
2724        key: AtlasKey,
2725        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
2726    ) -> Result<Option<AtlasTile>>;
2727    /// Returns the atlas entry for the glyph `params`, calling `build` on a cache miss.
2728    ///
2729    /// `build` returns a [`ValidatedRasterizedGlyph`], which is inserted into the atlas.
2730    fn get_or_insert_glyph_with(
2731        &self,
2732        params: &RenderGlyphParams,
2733        build: &mut dyn FnMut() -> Result<ValidatedRasterizedGlyph>,
2734    ) -> Result<GlyphAtlasEntry>;
2735    /// Removes the tile for `key`, freeing its space in the atlas.
2736    fn remove(&self, key: &AtlasKey);
2737
2738    /// Returns `true` if the atlas has a tile for `key`. Atlases that don't track this return `false`.
2739    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
2740    fn contains(&self, _key: &AtlasKey) -> bool {
2741        false
2742    }
2743}
2744
2745#[doc(hidden)]
2746pub trait AtlasBackend {
2747    fn insert(
2748        &mut self,
2749        kind: AtlasTextureKind,
2750        size: Size<DevicePixels>,
2751        bytes: &[u8],
2752    ) -> Result<AtlasTile>;
2753
2754    fn remove(&mut self, tile: AtlasTile);
2755}
2756
2757#[doc(hidden)]
2758pub struct AtlasState<Backend> {
2759    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
2760    pub backend: Backend,
2761}
2762
2763impl<Backend> AtlasState<Backend> {
2764    pub fn new(backend: Backend) -> Self {
2765        Self {
2766            tiles_by_key: FxHashMap::default(),
2767            backend,
2768        }
2769    }
2770
2771    pub fn contains(&self, key: &AtlasKey) -> bool {
2772        self.tiles_by_key.contains_key(key)
2773    }
2774
2775    pub fn clear(&mut self, reset_backend: impl FnOnce(&mut Backend)) {
2776        self.tiles_by_key.clear();
2777        reset_backend(&mut self.backend);
2778    }
2779}
2780
2781impl<Backend: Default> Default for AtlasState<Backend> {
2782    fn default() -> Self {
2783        Self::new(Backend::default())
2784    }
2785}
2786
2787impl<Backend: AtlasBackend> AtlasState<Backend> {
2788    pub fn get_or_insert_with<'a>(
2789        &mut self,
2790        key: AtlasKey,
2791        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
2792    ) -> Result<Option<AtlasTile>> {
2793        match self.tiles_by_key.entry(key) {
2794            Entry::Occupied(entry) => Ok(Some(*entry.get())),
2795            Entry::Vacant(entry) => {
2796                profiling::scope!("new tile");
2797                let Some((size, bytes)) = build()? else {
2798                    return Ok(None);
2799                };
2800                let tile = self
2801                    .backend
2802                    .insert(entry.key().texture_kind(), size, &bytes)?;
2803                entry.insert(tile);
2804                Ok(Some(tile))
2805            }
2806        }
2807    }
2808
2809    pub fn remove(&mut self, key: &AtlasKey) {
2810        if let Some(tile) = self.tiles_by_key.remove(key) {
2811            self.backend.remove(tile);
2812        }
2813    }
2814}
2815
2816/// A sprite atlas for windows without a GPU. It hands out uniquely identified
2817/// tiles without uploading any pixels, so glyph, SVG, and image painting can
2818/// run to completion in tests and headless platforms.
2819#[derive(Default)]
2820pub struct HeadlessAtlas(parking_lot::Mutex<HeadlessAtlasState>);
2821
2822#[derive(Default)]
2823struct HeadlessAtlasState {
2824    atlas: AtlasState<HeadlessAtlasBackend>,
2825    glyph_cache: GlyphAtlasCache,
2826}
2827
2828#[doc(hidden)]
2829#[derive(Default)]
2830pub struct HeadlessAtlasBackend {
2831    next_id: u32,
2832}
2833
2834impl AtlasBackend for HeadlessAtlasBackend {
2835    fn insert(
2836        &mut self,
2837        kind: AtlasTextureKind,
2838        size: Size<DevicePixels>,
2839        _bytes: &[u8],
2840    ) -> Result<AtlasTile> {
2841        self.next_id += 1;
2842        let texture_id = self.next_id;
2843        self.next_id += 1;
2844        let tile_id = self.next_id;
2845        Ok(AtlasTile {
2846            texture_id: AtlasTextureId {
2847                index: texture_id,
2848                kind,
2849            },
2850            tile_id: TileId(tile_id),
2851            padding: 0,
2852            bounds: Bounds {
2853                origin: Point::default(),
2854                size,
2855            },
2856        })
2857    }
2858
2859    fn remove(&mut self, _tile: AtlasTile) {}
2860}
2861
2862impl PlatformAtlas for HeadlessAtlas {
2863    fn get_or_insert_with<'a>(
2864        &self,
2865        key: AtlasKey,
2866        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
2867    ) -> Result<Option<AtlasTile>> {
2868        self.0.lock().atlas.get_or_insert_with(key, build)
2869    }
2870
2871    fn get_or_insert_glyph_with(
2872        &self,
2873        params: &RenderGlyphParams,
2874        build: &mut dyn FnMut() -> Result<ValidatedRasterizedGlyph>,
2875    ) -> Result<GlyphAtlasEntry> {
2876        let mut state = self.0.lock();
2877        if let Some(entry) = state.glyph_cache.get(params) {
2878            return Ok(entry);
2879        }
2880
2881        let glyph = build()?;
2882        let tile = if glyph.size == Size::default() {
2883            None
2884        } else {
2885            let key = AtlasKey::from((params.clone(), glyph.format));
2886            let mut pixels = || Ok(Some((glyph.size, Cow::Borrowed(glyph.pixels.as_slice()))));
2887            state.atlas.get_or_insert_with(key, &mut pixels)?
2888        };
2889        Ok(state.glyph_cache.insert(params, &glyph, tile))
2890    }
2891
2892    fn remove(&self, key: &AtlasKey) {
2893        let mut state = self.0.lock();
2894        state.glyph_cache.remove(key);
2895        state.atlas.remove(key);
2896    }
2897
2898    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
2899    fn contains(&self, key: &AtlasKey) -> bool {
2900        self.0.lock().atlas.contains(key)
2901    }
2902}
2903
2904#[doc(hidden)]
2905pub struct AtlasTextureList<T> {
2906    pub textures: Vec<Option<T>>,
2907    pub free_list: Vec<usize>,
2908}
2909
2910impl<T> Default for AtlasTextureList<T> {
2911    fn default() -> Self {
2912        Self {
2913            textures: Vec::default(),
2914            free_list: Vec::default(),
2915        }
2916    }
2917}
2918
2919impl<T> ops::Index<usize> for AtlasTextureList<T> {
2920    type Output = Option<T>;
2921
2922    fn index(&self, index: usize) -> &Self::Output {
2923        &self.textures[index]
2924    }
2925}
2926
2927impl<T> AtlasTextureList<T> {
2928    #[allow(unused)]
2929    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
2930        self.free_list.clear();
2931        self.textures.drain(..)
2932    }
2933
2934    #[allow(dead_code)]
2935    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
2936        self.textures.iter_mut().flatten()
2937    }
2938}
2939
2940/// Where a glyph, SVG or image sits in an atlas texture.
2941#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2942#[repr(C)]
2943pub struct AtlasTile {
2944    /// The texture this tile belongs to.
2945    pub texture_id: AtlasTextureId,
2946    /// The unique ID of this tile within its texture.
2947    pub tile_id: TileId,
2948    /// Padding around the tile content in pixels.
2949    pub padding: u32,
2950    /// The bounds of this tile within the texture.
2951    pub bounds: Bounds<DevicePixels>,
2952}
2953
2954/// Identifies one texture in an atlas.
2955#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2956#[repr(C)]
2957pub struct AtlasTextureId {
2958    // We use u32 instead of usize for Metal Shader Language compatibility
2959    /// The index of this texture in the atlas.
2960    pub index: u32,
2961    /// The kind of content stored in this texture.
2962    pub kind: AtlasTextureKind,
2963}
2964
2965#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2966#[repr(C)]
2967#[cfg_attr(
2968    all(
2969        any(target_os = "linux", target_os = "freebsd"),
2970        not(any(feature = "x11", feature = "wayland"))
2971    ),
2972    allow(dead_code)
2973)]
2974/// The kind of pixels a texture in an atlas stores.
2975pub enum AtlasTextureKind {
2976    /// One coverage byte per pixel, used for alpha-masked glyphs and SVGs.
2977    Monochrome = 0,
2978    /// Four bytes per pixel, used for full-color images and emoji.
2979    Polychrome = 1,
2980    /// Four bytes per pixel, used for subpixel (LCD) antialiased glyphs.
2981    Subpixel = 2,
2982}
2983
2984impl AtlasTextureKind {
2985    /// Validates a tightly packed bitmap before an atlas allocates or caches its tile.
2986    /// Monochrome tiles contain one coverage byte; color and LCD tiles contain four bytes.
2987    pub fn validate_upload(self, size: Size<DevicePixels>, bytes: &[u8]) -> Result<()> {
2988        anyhow::ensure!(
2989            size.width.0 > 0 && size.height.0 > 0,
2990            "{self:?} atlas upload requires positive dimensions, got {size:?}"
2991        );
2992        let channels = match self {
2993            Self::Monochrome => 1,
2994            Self::Polychrome | Self::Subpixel => 4,
2995        };
2996        let expected = (size.width.0 as usize)
2997            .checked_mul(size.height.0 as usize)
2998            .and_then(|pixels| pixels.checked_mul(channels))
2999            .ok_or_else(|| anyhow::anyhow!("atlas upload byte count overflow for {size:?}"))?;
3000        anyhow::ensure!(
3001            bytes.len() == expected,
3002            "{self:?} atlas upload for {size:?} requires {expected} bytes, got {}",
3003            bytes.len()
3004        );
3005        Ok(())
3006    }
3007}
3008
3009/// Identifies a tile within its atlas texture.
3010#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
3011#[repr(C)]
3012pub struct TileId(pub u32);
3013
3014impl From<etagere::AllocId> for TileId {
3015    fn from(id: etagere::AllocId) -> Self {
3016        Self(id.serialize())
3017    }
3018}
3019
3020impl From<TileId> for etagere::AllocId {
3021    fn from(id: TileId) -> Self {
3022        Self::deserialize(id.0)
3023    }
3024}
3025
3026/// Connects the platform's input method (IME) to the focused [`InputHandler`].
3027///
3028/// GPUI creates one for the focused element while it's focused. Each method
3029/// runs the matching [`InputHandler`] method inside an update of the window. If the
3030/// window has closed, the methods return `None` or do nothing.
3031pub struct PlatformInputHandler {
3032    cx: AsyncWindowContext,
3033    handler: Box<dyn InputHandler>,
3034    focus_id: Option<crate::FocusId>,
3035}
3036
3037#[cfg_attr(
3038    all(
3039        any(target_os = "linux", target_os = "freebsd"),
3040        not(any(feature = "x11", feature = "wayland"))
3041    ),
3042    allow(dead_code)
3043)]
3044impl PlatformInputHandler {
3045    /// Wraps `handler` so the platform can call it through the window context `cx`.
3046    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
3047        Self {
3048            cx,
3049            handler,
3050            focus_id: None,
3051        }
3052    }
3053
3054    pub(crate) fn with_focus(mut self, focus_id: crate::FocusId) -> Self {
3055        self.focus_id = Some(focus_id);
3056        self
3057    }
3058
3059    /// Identifies the input field registered through [`Window::handle_input`].
3060    ///
3061    /// This identity survives adapter replacement during repaint. Backends can
3062    /// use it to detect a field change without restarting the keyboard session.
3063    pub fn focus_id(&self) -> Option<crate::FocusId> {
3064        self.focus_id
3065    }
3066
3067    /// Returns the selection, in UTF-16 units, or `None` if the handler has no selection.
3068    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
3069        self.cx
3070            .update(|window, cx| {
3071                self.handler
3072                    .selected_text_range(ignore_disabled_input, window, cx)
3073            })
3074            .ok()
3075            .flatten()
3076    }
3077
3078    /// Returns the range of composing (marked) text in UTF-16 units, or `None` when no text is being composed.
3079    #[cfg_attr(target_os = "windows", allow(dead_code))]
3080    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
3081        self.cx
3082            .update(|window, cx| self.handler.marked_text_range(window, cx))
3083            .ok()
3084            .flatten()
3085    }
3086
3087    #[cfg_attr(
3088        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
3089        allow(dead_code)
3090    )]
3091    /// Returns the document text in `range_utf16`, or `None` if the handler has none.
3092    ///
3093    /// `adjusted` receives the range the handler actually used, which may differ from the request.
3094    pub fn text_for_range(
3095        &mut self,
3096        range_utf16: Range<usize>,
3097        adjusted: &mut Option<Range<usize>>,
3098    ) -> Option<String> {
3099        self.cx
3100            .update(|window, cx| {
3101                self.handler
3102                    .text_for_range(range_utf16, adjusted, window, cx)
3103            })
3104            .ok()
3105            .flatten()
3106    }
3107
3108    /// Replaces `replacement_range`, in UTF-16 units, with `text`. `None` replaces the selection.
3109    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
3110        self.cx
3111            .update(|window, cx| {
3112                self.handler
3113                    .replace_text_in_range(replacement_range, text, window, cx);
3114            })
3115            .ok();
3116    }
3117
3118    /// Replaces `range_utf16` with `new_text` and marks the new text as composing, as an
3119    /// IME does while the user types. `new_selected_range` sets the selection inside the composing text.
3120    pub fn replace_and_mark_text_in_range(
3121        &mut self,
3122        range_utf16: Option<Range<usize>>,
3123        new_text: &str,
3124        new_selected_range: Option<Range<usize>>,
3125    ) {
3126        self.cx
3127            .update(|window, cx| {
3128                self.handler.replace_and_mark_text_in_range(
3129                    range_utf16,
3130                    new_text,
3131                    new_selected_range,
3132                    window,
3133                    cx,
3134                )
3135            })
3136            .ok();
3137    }
3138
3139    /// Ends the composition and keeps the marked text.
3140    #[cfg_attr(target_os = "windows", allow(dead_code))]
3141    pub fn unmark_text(&mut self) {
3142        self.cx
3143            .update(|window, cx| self.handler.unmark_text(window, cx))
3144            .ok();
3145    }
3146
3147    /// Pastes `item` at the selection through [`InputHandler::paste`].
3148    pub fn paste(&mut self, item: ClipboardItem) {
3149        self.cx
3150            .update(|window, cx| self.handler.paste(item, window, cx))
3151            .ok();
3152    }
3153
3154    /// Returns the screen bounds of `range_utf16`, in logical pixels. The platform uses these to place the IME candidate window.
3155    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
3156        self.cx
3157            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
3158            .ok()
3159            .flatten()
3160    }
3161
3162    /// Returns selection rectangles for a range of UTF-16 code units in document order.
3163    ///
3164    /// The platform uses these logical-pixel bounds to position selection UI. A backend may
3165    /// return an empty vector when selection geometry is unavailable; IME candidate placement
3166    /// can still use bounds_for_range. If the window has closed, this also returns an empty vector.
3167    pub fn selection_bounds_for_range(&mut self, range_utf16: Range<usize>) -> Vec<Bounds<Pixels>> {
3168        self.cx
3169            .update(|window, cx| {
3170                self.handler
3171                    .selection_bounds_for_range(range_utf16, window, cx)
3172            })
3173            .unwrap_or_default()
3174    }
3175
3176    /// Returns whether macOS press-and-hold should stay enabled for this handler.
3177    #[allow(dead_code)]
3178    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
3179        self.handler.apple_press_and_hold_enabled()
3180    }
3181
3182    /// Replaces the selection with `input`, using the handler's [`InputHandler::replace_text_in_range`].
3183    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
3184        self.handler.replace_text_in_range(None, input, window, cx);
3185    }
3186
3187    /// Returns where the IME candidate window should appear.
3188    ///
3189    /// While text is composing, this is the start of the composing text's visual line,
3190    /// found by walking back from the caret until the line changes. Otherwise it's the
3191    /// caret position. `bounds_for_range` returns the bounds of each position it's asked for.
3192    pub fn compute_ime_candidate_bounds(
3193        marked_range: Option<Range<usize>>,
3194        selection: &UTF16Selection,
3195        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
3196    ) -> Option<Bounds<Pixels>> {
3197        // The active end of the selection, which is `start` when it is reversed.
3198        let caret = if selection.reversed {
3199            selection.range.start
3200        } else {
3201            selection.range.end
3202        };
3203
3204        if let Some(marked_range) = marked_range {
3205            // Default to the start of the marked (composing) range.
3206            let mut line_start = marked_range.start;
3207
3208            // Walk backward from the caret looking for a line break. A change in
3209            // the Y coordinate means we crossed into the previous visual line, so
3210            // the line start is one position after the break point.
3211            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
3212                for i in (marked_range.start..caret).rev() {
3213                    if let Some(b) = bounds_for_range(i..i) {
3214                        if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
3215                            line_start = i + 1;
3216                            break;
3217                        }
3218                    }
3219                }
3220            }
3221            bounds_for_range(line_start..line_start)
3222        } else {
3223            // No active composition — use the selection endpoint.
3224            bounds_for_range(caret..caret)
3225        }
3226    }
3227
3228    /// Returns the IME candidate position for the current selection.
3229    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
3230        let marked_range = self.handler.marked_text_range(window, cx);
3231        let selection = self.handler.selected_text_range(true, window, cx)?;
3232        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
3233            self.handler.bounds_for_range(range, window, cx)
3234        })
3235    }
3236
3237    /// Returns the IME candidate position for the current selection, through the window context.
3238    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
3239        let marked_range = self.marked_text_range();
3240        let selection = self.selected_text_range(true)?;
3241        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
3242            self.bounds_for_range(range)
3243        })
3244    }
3245
3246    /// Returns the UTF-16 index of the character at `point`, in window coordinates, or `None` if there is none.
3247    #[allow(unused)]
3248    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
3249        self.cx
3250            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
3251            .ok()
3252            .flatten()
3253    }
3254
3255    /// See [`InputHandler::set_selected_text_range`].
3256    pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
3257        self.cx
3258            .update(|window, cx| {
3259                self.handler
3260                    .set_selected_text_range(range_utf16, window, cx)
3261            })
3262            .ok();
3263    }
3264
3265    /// See [`InputHandler::element_bounds`].
3266    pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
3267        self.cx
3268            .update(|window, cx| self.handler.element_bounds(window, cx))
3269            .ok()
3270            .flatten()
3271    }
3272
3273    /// See [`InputHandler::text_length_utf16`].
3274    pub fn text_length_utf16(&mut self) -> Option<usize> {
3275        self.cx
3276            .update(|window, cx| self.handler.text_length_utf16(window, cx))
3277            .ok()
3278            .flatten()
3279    }
3280
3281    /// Returns whether the handler accepts text input right now.
3282    #[allow(dead_code)]
3283    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
3284        self.handler.accepts_text_input(window, cx)
3285    }
3286
3287    /// Returns whether the handler accepts text input. Returns `true` if the window is gone.
3288    #[allow(dead_code)]
3289    pub fn query_accepts_text_input(&mut self) -> bool {
3290        self.cx
3291            .update(|window, cx| self.handler.accepts_text_input(window, cx))
3292            .unwrap_or(true)
3293    }
3294
3295    /// Checks eligibility against the current focus, including before the next frame is drawn.
3296    ///
3297    /// Unlike the legacy acceptance query, this fails closed when the window cannot
3298    /// be updated and requires registration through `Window::handle_input`.
3299    pub fn query_accepts_focused_text_input(&mut self) -> bool {
3300        let focus_id = self.focus_id;
3301        self.cx
3302            .update(|window, cx| {
3303                focus_id.is_some()
3304                    && window.focus == focus_id
3305                    && self.handler.accepts_text_input(window, cx)
3306            })
3307            .unwrap_or(false)
3308    }
3309
3310    /// See [`InputHandler::prefers_ime_for_printable_keys`].
3311    ///
3312    /// This is not a pure delegation to the handler: while a multi-stroke binding is pending this
3313    /// returns `false` regardless of the handler's preference, because the next printable key may
3314    /// complete a binding whose prefix already bypassed the IME.
3315    pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
3316        self.cx
3317            .update(|window, cx| {
3318                // The next printable key may complete a chord whose prefix bypassed the IME.
3319                !window.has_pending_keystrokes()
3320                    && self.handler.prefers_ime_for_printable_keys(window, cx)
3321            })
3322            .unwrap_or(false)
3323    }
3324
3325    /// Returns the text input configuration the handler requests.
3326    pub fn text_input_configuration(
3327        &mut self,
3328        window: &mut Window,
3329        cx: &mut App,
3330    ) -> TextInputConfiguration {
3331        self.handler.text_input_configuration(window, cx)
3332    }
3333
3334    /// See [`InputHandler::text_input_editable_range`].
3335    pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
3336        self.cx
3337            .update(|window, cx| self.handler.text_input_editable_range(window, cx))
3338            .ok()
3339            .flatten()
3340    }
3341}
3342
3343/// A struct representing a selection in a text buffer, in UTF16 characters.
3344/// This is different from a range because the head may be before the tail.
3345#[derive(Debug)]
3346pub struct UTF16Selection {
3347    /// The range of text in the document this selection corresponds to
3348    /// in UTF16 characters.
3349    pub range: Range<usize>,
3350    /// Whether the head of this selection is at the start (true), or end (false)
3351    /// of the range
3352    pub reversed: bool,
3353}
3354
3355/// Receives text from the platform's input method (IME) on behalf of a focused element.
3356///
3357/// Implement this on the element that accepts text, and return it from
3358/// [`Window::handle_input`]. The methods mirror the macOS `NSTextInputClient`
3359/// protocol. All ranges are in UTF-16 code units, because that is what platforms
3360/// use, not bytes or characters.
3361///
3362/// Methods without a default body must be implemented. The others have defaults, so
3363/// override only what your text needs. See [text input](crate::_documentation::guides::text_input).
3364pub trait InputHandler: 'static {
3365    /// Get the range of the user's currently selected text, if any
3366    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
3367    ///
3368    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
3369    fn selected_text_range(
3370        &mut self,
3371        ignore_disabled_input: bool,
3372        window: &mut Window,
3373        cx: &mut App,
3374    ) -> Option<UTF16Selection>;
3375
3376    /// Get the range of the currently marked text, if any
3377    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
3378    ///
3379    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
3380    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
3381
3382    /// Get the text for the given document range in UTF-16 characters
3383    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
3384    ///
3385    /// range_utf16 is in terms of UTF-16 characters
3386    fn text_for_range(
3387        &mut self,
3388        range_utf16: Range<usize>,
3389        adjusted_range: &mut Option<Range<usize>>,
3390        window: &mut Window,
3391        cx: &mut App,
3392    ) -> Option<String>;
3393
3394    /// Replace the text in the given document range with the given text
3395    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
3396    ///
3397    /// replacement_range is in terms of UTF-16 characters
3398    fn replace_text_in_range(
3399        &mut self,
3400        replacement_range: Option<Range<usize>>,
3401        text: &str,
3402        window: &mut Window,
3403        cx: &mut App,
3404    );
3405
3406    /// Replace the text in the given document range with the given text,
3407    /// and mark the given text as part of an IME 'composing' state
3408    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
3409    ///
3410    /// range_utf16 is in terms of UTF-16 characters
3411    /// new_selected_range is in terms of UTF-16 characters
3412    fn replace_and_mark_text_in_range(
3413        &mut self,
3414        range_utf16: Option<Range<usize>>,
3415        new_text: &str,
3416        new_selected_range: Option<Range<usize>>,
3417        window: &mut Window,
3418        cx: &mut App,
3419    );
3420
3421    /// Remove the IME 'composing' state from the document
3422    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
3423    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
3424
3425    /// Insert a platform-initiated paste at the current selection.
3426    ///
3427    /// Platforms that deliver paste as an input event rather than through an
3428    /// application-defined action (e.g. the DOM `paste` event on web) call
3429    /// this with the full clipboard contents. The default implementation
3430    /// inserts only the plain-text portion of the item.
3431    fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
3432        if let Some(text) = item.text() {
3433            self.replace_text_in_range(None, &text, window, cx);
3434        }
3435    }
3436
3437    /// Get the bounds of the given document range in window coordinates.
3438    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
3439    ///
3440    /// This is used for positioning the IME candidate window
3441    fn bounds_for_range(
3442        &mut self,
3443        range_utf16: Range<usize>,
3444        window: &mut Window,
3445        cx: &mut App,
3446    ) -> Option<Bounds<Pixels>>;
3447
3448    /// Returns selection rectangles in document order, in window coordinates.
3449    ///
3450    /// Return line or shaped-run fragments, not one rectangle per character. The
3451    /// first and last rectangles contain the range's start and end respectively.
3452    /// Empty means native selection geometry is unavailable; caret/IME geometry can
3453    /// still be supplied by `bounds_for_range`.
3454    fn selection_bounds_for_range(
3455        &mut self,
3456        _range_utf16: Range<usize>,
3457        _window: &mut Window,
3458        _cx: &mut App,
3459    ) -> Vec<Bounds<Pixels>> {
3460        Vec::new()
3461    }
3462
3463    /// Get the character offset for the given point in terms of UTF16 characters
3464    ///
3465    /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
3466    fn character_index_for_point(
3467        &mut self,
3468        point: Point<Pixels>,
3469        window: &mut Window,
3470        cx: &mut App,
3471    ) -> Option<usize>;
3472
3473    /// Set the range of the user's currently selected text.
3474    ///
3475    /// This is the reverse data-flow direction from [`Self::selected_text_range`]:
3476    /// platforms call it when the system text machinery moves the selection on the
3477    /// application's behalf — e.g. the user drags a system selection handle or
3478    /// invokes Select All from system UI (iOS `UITextInput setSelectedTextRange:`,
3479    /// Android `InputConnection.setSelection`).
3480    ///
3481    /// range_utf16 is in terms of UTF-16 characters, from 0 to the length of the document
3482    fn set_selected_text_range(
3483        &mut self,
3484        _range_utf16: Range<usize>,
3485        _window: &mut Window,
3486        _cx: &mut App,
3487    ) {
3488    }
3489
3490    /// Get the bounds of the focused text element in window coordinates, if known.
3491    ///
3492    /// This is the pull counterpart to the [`PlatformWindow::update_ime_position`]
3493    /// push: mobile platforms ask for the focused element's geometry when they
3494    /// need it (e.g. to frame system text-interaction UI overlaid on the focused
3495    /// element).
3496    fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
3497        None
3498    }
3499
3500    /// Get the length of the document in UTF-16 characters, if known.
3501    fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
3502        None
3503    }
3504
3505    /// Allows a given input context to opt into getting raw key repeats instead of
3506    /// sending these to the platform.
3507    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
3508    /// (which is how iTerm does it) but it doesn't seem to work for me.
3509    #[allow(dead_code)]
3510    fn apple_press_and_hold_enabled(&mut self) -> bool {
3511        true
3512    }
3513
3514    /// Returns whether this handler is accepting text input to be inserted.
3515    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
3516        true
3517    }
3518
3519    /// The contiguous range of text, in UTF-16 code units, that platform text
3520    /// input may read and edit around the current selection.
3521    ///
3522    /// Platforms that mirror document text into an IME-editable buffer clamp
3523    /// the mirrored window to this range, so multi-step IME edit gestures
3524    /// (word deletion, autocorrect rewrites, suggestion picks) cannot reach
3525    /// content outside it. The range should contain the current selection;
3526    /// when it cannot (a selection spanning a region boundary), platforms
3527    /// degrade the mirrored IME context rather than widening the range.
3528    /// `None` places no bound.
3529    fn text_input_editable_range(
3530        &mut self,
3531        _window: &mut Window,
3532        _cx: &mut App,
3533    ) -> Option<Range<usize>> {
3534        None
3535    }
3536
3537    /// Returns whether printable keys should be routed to the IME before keybinding
3538    /// matching when a non-ASCII input source (e.g. Japanese, Korean, Chinese IME)
3539    /// is active. This prevents multi-stroke keybindings like `jj` from intercepting
3540    /// keys that the IME should compose.
3541    ///
3542    /// Defaults to `false`. The editor overrides this based on whether it expects
3543    /// character input (e.g. Vim insert mode returns `true`, normal mode returns `false`).
3544    /// The terminal keeps the default `false` so that raw keys reach the terminal process.
3545    fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
3546        false
3547    }
3548
3549    /// Get this handler's preferences for platform text assistance.
3550    ///
3551    /// GPUI re-queries this every frame and forwards it to the platform window
3552    /// only when it changes, so implementations must be cheap and may vary the
3553    /// result with application state (e.g. with the cursor's position).
3554    fn text_input_configuration(
3555        &mut self,
3556        _window: &mut Window,
3557        _cx: &mut App,
3558    ) -> TextInputConfiguration {
3559        TextInputConfiguration::default()
3560    }
3561}
3562
3563/// Platform text-assistance preferences for the focused text region.
3564///
3565/// Returned by [`InputHandler::text_input_configuration`] and forwarded to the
3566/// platform whenever it changes; the platform maps the fields onto its native
3567/// input-session attributes (on web, DOM attributes of the hidden editable
3568/// element such as `autocorrect` and `enterkeyhint`).
3569///
3570/// The default disables all text assistance and requests no particular action
3571/// key presentation.
3572#[derive(Clone, Debug, Default, PartialEq, Eq)]
3573pub struct TextInputConfiguration {
3574    /// Whether the platform may automatically correct entered text.
3575    pub autocorrect: bool,
3576    /// How software keyboards automatically capitalize entered text.
3577    pub autocapitalize: Autocapitalize,
3578    /// Whether software keyboards may offer word suggestions and spellcheck.
3579    pub suggestions: bool,
3580    /// The action advertised on a software keyboard's confirm ("enter") key.
3581    pub input_action: TextInputAction,
3582}
3583
3584/// Automatic capitalization applied by software keyboards.
3585#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
3586pub enum Autocapitalize {
3587    /// No automatic capitalization.
3588    #[default]
3589    None,
3590    /// Capitalize the first letter of each word.
3591    Words,
3592    /// Capitalize the first letter of each sentence.
3593    Sentences,
3594    /// Capitalize every letter.
3595    Characters,
3596}
3597
3598/// The action a software keyboard advertises on its confirm ("enter") key.
3599///
3600/// This affects only how the key is presented (icon or label); pressing it is
3601/// still delivered as ordinary input.
3602///
3603/// The variants are the HTML `enterkeyhint` attribute's value set
3604/// (<https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-enterkeyhint-attribute>),
3605/// which also maps onto Android's `IME_ACTION_*` constants and iOS's
3606/// `UIReturnKeyType`; [`TextInputAction::Unspecified`] means "emit no hint".
3607#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
3608pub enum TextInputAction {
3609    /// Let the platform choose its default presentation.
3610    #[default]
3611    Unspecified,
3612    /// Inserting a line break.
3613    Enter,
3614    /// Committing the field's value.
3615    Done,
3616    /// Navigating to the typed target.
3617    Go,
3618    /// Moving to the next field.
3619    Next,
3620    /// Moving to the previous field.
3621    Previous,
3622    /// Executing a search.
3623    Search,
3624    /// Sending a message.
3625    Send,
3626}
3627
3628/// Options for creating a window, passed to [`App::open_window`].
3629///
3630/// Start from [`WindowOptions::default`] (or [`WindowOptions::new`], which is the same)
3631/// and chain setters for the fields you want to change. Each setter takes the value
3632/// itself, or an `Option` to clear the field. The defaults give a focused, shown,
3633/// resizable, movable normal window with a titlebar, at the platform's default size
3634/// and position.
3635///
3636/// Not every platform reads every field. The web backend uses [`WindowOptions::kind`],
3637/// [`WindowOptions::show`], and `web_window_background`. The page's canvas is
3638/// the window, so bounds, the titlebar, the minimum size and the resize options are ignored.
3639/// Each field's docs say which
3640/// platforms use it. See [platforms](crate::_documentation::platforms).
3641///
3642/// # Examples
3643///
3644/// Start from the defaults and change only what you need:
3645///
3646/// ```
3647/// use gpui::{Bounds, WindowBounds, WindowOptions, point, px, size};
3648///
3649/// let bounds = Bounds::new(point(px(100.), px(100.)), size(px(800.), px(600.)));
3650/// let options = WindowOptions::default()
3651///     .window_bounds(WindowBounds::Windowed(bounds))
3652///     .is_resizable(false)
3653///     .titlebar(None);
3654///
3655/// assert_eq!(options.window_bounds, Some(WindowBounds::Windowed(bounds)));
3656/// assert!(!options.is_resizable);
3657/// assert!(options.titlebar.is_none());
3658/// assert!(options.focus);
3659/// ```
3660///
3661/// Create a window that doesn't take focus and opens hidden, so you can show it later:
3662///
3663/// ```
3664/// use gpui::WindowOptions;
3665///
3666/// let options = WindowOptions::new().focus(false).show(false);
3667///
3668/// assert!(!options.focus);
3669/// assert!(!options.show);
3670/// ```
3671#[derive(Debug, derive_setters::Setters)]
3672#[setters(into)]
3673pub struct WindowOptions {
3674    /// The window's state and bounds in screen coordinates, in logical pixels.
3675    ///
3676    /// `None` inherits the bounds from the platform, and `Some` opens the window in the
3677    /// given state. A `Windowed` window opens at the rectangle, and `Maximized` and
3678    /// `Fullscreen` restore to the rectangle when left. Ignored on the web.
3679    pub window_bounds: Option<WindowBounds>,
3680
3681    /// The titlebar, or `None` for a window without one. Ignored on the web.
3682    ///
3683    /// The default is [`TitlebarOptions::default`], which shows the platform's titlebar with no title.
3684    pub titlebar: Option<TitlebarOptions>,
3685
3686    /// Whether the window takes keyboard focus when it opens. Defaults to `true`.
3687    pub focus: bool,
3688
3689    /// Whether the window appears when it opens. Set it to `false` to show the window later
3690    /// with [`PlatformWindow::set_visible`].
3691    pub show: bool,
3692
3693    /// The kind of window to create. Defaults to [`WindowKind::Normal`]. On the web, only `Normal` works.
3694    pub kind: WindowKind,
3695
3696    /// Whether the user can drag the window. Defaults to `true`. macOS and Windows use this
3697    /// value. Linux and the web ignore it. Programmatic moves still work.
3698    pub is_movable: bool,
3699
3700    /// Whether the application owns dragging of the (custom) titlebar, rather than
3701    /// AppKit. Only has an effect on macOS.
3702    ///
3703    /// Set this to `true` for windows that draw their own titlebar and move the window
3704    /// themselves via [`Window::start_window_move`]. It marks the whole content view as
3705    /// app-owned titlebar content, so AppKit neither drags the window from the titlebar
3706    /// nor delays titlebar clicks while disambiguating double-clicks (a delay first
3707    /// observed on macOS 27). It is independent of `is_movable`, so such windows stay
3708    /// user-movable (via their own drag) and keep the Window-menu tiling items enabled.
3709    ///
3710    /// Leave this `false` for windows that rely on AppKit's native titlebar dragging.
3711    pub app_owns_titlebar_drag: bool,
3712
3713    /// The minimum time between animation frames while the window is inactive.
3714    ///
3715    /// Defaults to about 30 frames per second. Set `None` to draw inactive windows at the
3716    /// full frame rate.
3717    pub inactive_frame_interval: Option<Duration>,
3718
3719    /// Whether the user can resize the window. Defaults to `true`. Ignored on the web.
3720    pub is_resizable: bool,
3721
3722    /// Whether the user can minimize the window. Defaults to `true`. Ignored on the web.
3723    pub is_minimizable: bool,
3724
3725    /// The display to open the window on, or `None` for the primary display. Ignored on the web.
3726    pub display_id: Option<DisplayId>,
3727
3728    /// The background appearance of a macOS window.
3729    #[cfg(target_os = "macos")]
3730    pub macos_window_background: MacosWindowBackground,
3731
3732    /// The background appearance of a Windows window.
3733    #[cfg(target_os = "windows")]
3734    pub windows_window_background: WindowsWindowBackground,
3735
3736    /// The background appearance of a Linux window.
3737    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3738    pub linux_window_background: LinuxWindowBackground,
3739
3740    /// The background appearance of a web window.
3741    #[cfg(target_family = "wasm")]
3742    pub web_window_background: WebWindowBackground,
3743
3744    /// The application id that desktop environments use to group windows and find the desktop entry.
3745    ///
3746    /// Linux only. Other platforms ignore it.
3747    pub app_id: Option<String>,
3748
3749    /// The smallest size the user can resize the window to, in logical pixels. `None` sets no minimum.
3750    /// Ignored on the web.
3751    pub window_min_size: Option<Size<Pixels>>,
3752
3753    /// Whether the window draws its own decorations (client-side) or the window server does (server-side).
3754    ///
3755    /// `None` uses the server's default. Linux's Wayland and X11 backends honor it, and
3756    /// the other backends ignore it. The platform may ignore requests it can't satisfy.
3757    pub window_decorations: Option<WindowDecorations>,
3758
3759    /// The window's icon, from an RGBA image. Linux only, and Wayland needs the
3760    /// `xdg-toplevel-icon` protocol to show it.
3761    pub icon: Option<Arc<image::RgbaImage>>,
3762
3763    /// The name of the native tab group. Windows with the same identifier open as tabs of one window.
3764    /// macOS only.
3765    pub tabbing_identifier: Option<String>,
3766
3767    /// Whether to render the window using the Display P3 color space on macOS.
3768    pub use_display_p3: bool,
3769}
3770
3771impl FluentBuilder for WindowOptions {}
3772
3773/// The variables that can be configured when creating a new window
3774#[derive(Debug)]
3775#[cfg_attr(
3776    all(
3777        any(target_os = "linux", target_os = "freebsd"),
3778        not(any(feature = "x11", feature = "wayland"))
3779    ),
3780    allow(dead_code)
3781)]
3782/// The platform-level options for one window, passed to [`Platform::open_window`].
3783///
3784/// [`WindowOptions`] is the public form. GPUI resolves its fields into these parameters,
3785/// with `bounds` already computed.
3786#[allow(missing_docs)]
3787pub struct WindowParams {
3788    /// The window's initial bounds, in logical pixels.
3789    pub bounds: Bounds<Pixels>,
3790
3791    /// The titlebar configuration of the window
3792    #[cfg_attr(feature = "wayland", allow(dead_code))]
3793    pub titlebar: Option<TitlebarOptions>,
3794
3795    /// The kind of window to create
3796    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3797    pub kind: WindowKind,
3798
3799    /// Whether the window should be movable by the user
3800    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3801    pub is_movable: bool,
3802
3803    /// Whether the application owns dragging of the (custom) titlebar (macOS only)
3804    #[cfg_attr(
3805        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
3806        allow(dead_code)
3807    )]
3808    /// Whether the application owns dragging of the custom titlebar. macOS only. See [`WindowOptions::app_owns_titlebar_drag`].
3809    pub app_owns_titlebar_drag: bool,
3810
3811    /// Whether the window should be resizable by the user
3812    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3813    pub is_resizable: bool,
3814
3815    /// Whether the window should be minimized by the user
3816    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3817    pub is_minimizable: bool,
3818
3819    #[cfg_attr(
3820        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
3821        allow(dead_code)
3822    )]
3823    /// Whether the window takes keyboard focus when it opens.
3824    pub focus: bool,
3825
3826    /// Whether the window appears when it opens.
3827    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3828    pub show: bool,
3829
3830    /// An image to set as the window icon (X11 and Wayland only)
3831    /// Wayland requires the xdg-toplevel-icon protocol to be available
3832    #[cfg_attr(feature = "wayland", allow(dead_code))]
3833    pub icon: Option<Arc<image::RgbaImage>>,
3834
3835    /// The display to open the window on, or `None` for the primary display.
3836    #[cfg_attr(feature = "wayland", allow(dead_code))]
3837    pub display_id: Option<DisplayId>,
3838
3839    /// The application id for desktop environments to group windows. Linux only.
3840    #[cfg_attr(feature = "wayland", allow(dead_code))]
3841    pub app_id: Option<String>,
3842
3843    /// The smallest size the user can resize the window to, in logical pixels.
3844    pub window_min_size: Option<Size<Pixels>>,
3845
3846    /// The name of the native tab group. macOS only.
3847    #[cfg(target_os = "macos")]
3848    pub tabbing_identifier: Option<String>,
3849
3850    #[cfg(target_os = "macos")]
3851    pub use_display_p3: bool,
3852}
3853
3854/// The state a window opens in, with the bounds it uses for that state.
3855///
3856/// Set it with [`WindowOptions::window_bounds`]. The bounds are in screen coordinates.
3857#[derive(Debug, Copy, Clone, PartialEq)]
3858pub enum WindowBounds {
3859    /// Indicates that the window should open in a windowed state with the given bounds.
3860    Windowed(Bounds<Pixels>),
3861    /// Indicates that the window should open in a maximized state.
3862    /// The bounds provided here represent the restore size of the window.
3863    Maximized(Bounds<Pixels>),
3864    /// Indicates that the window should open in fullscreen mode.
3865    /// The bounds provided here represent the restore size of the window.
3866    Fullscreen(Bounds<Pixels>),
3867}
3868
3869impl Default for WindowBounds {
3870    fn default() -> Self {
3871        WindowBounds::Windowed(Bounds::default())
3872    }
3873}
3874
3875impl WindowBounds {
3876    /// Returns the bounds, whatever the state. For a maximized or fullscreen window these are the restore bounds.
3877    pub fn get_bounds(&self) -> Bounds<Pixels> {
3878        match self {
3879            WindowBounds::Windowed(bounds) => *bounds,
3880            WindowBounds::Maximized(bounds) => *bounds,
3881            WindowBounds::Fullscreen(bounds) => *bounds,
3882        }
3883    }
3884
3885    /// Returns `WindowBounds::Windowed` with `size`, centered on the primary display.
3886    ///
3887    /// Use [`Bounds::centered`] to center on another display.
3888    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
3889        WindowBounds::Windowed(Bounds::centered(None, size, cx))
3890    }
3891}
3892
3893impl Default for WindowOptions {
3894    fn default() -> Self {
3895        Self {
3896            window_bounds: None,
3897            titlebar: Some(TitlebarOptions {
3898                title: Default::default(),
3899                appears_transparent: Default::default(),
3900                traffic_light_position: Default::default(),
3901            }),
3902            focus: true,
3903            show: true,
3904            kind: WindowKind::Normal,
3905            is_movable: true,
3906            app_owns_titlebar_drag: false,
3907            inactive_frame_interval: Some(Duration::from_micros(33_333)),
3908            is_resizable: true,
3909            is_minimizable: true,
3910            display_id: None,
3911            #[cfg(target_os = "macos")]
3912            macos_window_background: MacosWindowBackground::default(),
3913            #[cfg(target_os = "windows")]
3914            windows_window_background: WindowsWindowBackground::default(),
3915            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3916            linux_window_background: LinuxWindowBackground::default(),
3917            #[cfg(target_family = "wasm")]
3918            web_window_background: WebWindowBackground::default(),
3919            icon: None,
3920            app_id: None,
3921            window_min_size: None,
3922            window_decorations: None,
3923            tabbing_identifier: None,
3924            use_display_p3: false,
3925        }
3926    }
3927}
3928
3929impl WindowOptions {
3930    /// Returns window options with the platform's defaults: a focused, shown,
3931    /// resizable, minimizable, movable normal window with a titlebar.
3932    pub fn new() -> Self {
3933        Self::default()
3934    }
3935
3936    /// Returns the window's background appearance for the current platform,
3937    /// erased into the renderer-facing [`WindowBackgroundAppearance`].
3938    pub fn background_appearance(&self) -> WindowBackgroundAppearance {
3939        #[cfg(target_os = "macos")]
3940        {
3941            self.macos_window_background.into()
3942        }
3943        #[cfg(target_os = "windows")]
3944        {
3945            self.windows_window_background.into()
3946        }
3947        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3948        {
3949            self.linux_window_background.into()
3950        }
3951        #[cfg(target_family = "wasm")]
3952        {
3953            self.web_window_background.into()
3954        }
3955        #[cfg(not(any(
3956            target_os = "macos",
3957            target_os = "windows",
3958            any(target_os = "linux", target_os = "freebsd"),
3959            target_family = "wasm",
3960        )))]
3961        {
3962            WindowBackgroundAppearance::default()
3963        }
3964    }
3965}
3966
3967/// Options for a window's titlebar.
3968///
3969/// Chain setters on [`TitlebarOptions::default`] and pass the result to
3970/// [`WindowOptions::titlebar`]. Pass `None` to clear the title or traffic light position.
3971///
3972/// ```
3973/// use gpui::{SharedString, TitlebarOptions, point, px};
3974///
3975/// let titlebar = TitlebarOptions::default()
3976///     .title(SharedString::from("My app"))
3977///     .appears_transparent(true)
3978///     .traffic_light_position(point(px(16.0), px(16.0)));
3979///
3980/// assert_eq!(titlebar.title, Some(SharedString::from("My app")));
3981/// assert!(titlebar.appears_transparent);
3982/// ```
3983#[derive(Debug, Default, derive_setters::Setters)]
3984#[setters(into)]
3985pub struct TitlebarOptions {
3986    /// The window's initial title. The web backend ignores it.
3987    pub title: Option<SharedString>,
3988
3989    /// Whether to hide the system titlebar so you can draw your own. macOS and Windows only.
3990    /// On Linux, use [`WindowOptions::window_decorations`] instead.
3991    pub appears_transparent: bool,
3992
3993    /// The position of the macOS traffic-light buttons, in logical pixels. macOS only.
3994    pub traffic_light_position: Option<Point<Pixels>>,
3995}
3996
3997impl FluentBuilder for TitlebarOptions {}
3998
3999/// The kind of window to create, set with [`WindowOptions::kind`].
4000///
4001/// On the web, only `Normal` works. The other kinds return an error from
4002/// [`App::open_window`].
4003#[derive(Clone, Debug, PartialEq, Eq)]
4004pub enum WindowKind {
4005    /// A normal application window
4006    Normal,
4007
4008    /// A window that appears above all other windows, usually used for alerts or popups.
4009    /// Use it sparingly.
4010    PopUp,
4011
4012    /// A parent-anchored, platform-native popup window for menus, comboboxes, context menus and
4013    /// tooltips. Unlike [`WindowKind::PopUp`], it is positioned relative to a parent window.
4014    ///
4015    /// The popup's size comes from [`WindowOptions::window_bounds`], whose origin is ignored.
4016    /// See [`popup::PopupOptions`] for the placement options. Platforms without a native
4017    /// implementation reject it with [`popup::PopupNotSupportedError`].
4018    AnchoredPopup(popup::PopupOptions),
4019
4020    /// A floating window that appears on top of its parent window.
4021    Floating,
4022
4023    /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
4024    /// docks, notifications or wallpapers.
4025    #[cfg(all(target_os = "linux", feature = "wayland"))]
4026    LayerShell(layer_shell::LayerShellOptions),
4027
4028    /// A window that appears on top of its parent window and blocks interaction with it
4029    /// until the modal window is closed.
4030    Dialog,
4031}
4032
4033/// The light or dark appearance of a window, as the operating system defines it.
4034///
4035/// Read it with [`Window::appearance`]. [`Platform::set_window_appearance`] can override the system's setting on macOS.
4036#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
4037pub enum WindowAppearance {
4038    /// A light appearance.
4039    ///
4040    /// On macOS, this corresponds to the `aqua` appearance.
4041    #[default]
4042    Light,
4043
4044    /// A light appearance with vibrant colors.
4045    ///
4046    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
4047    VibrantLight,
4048
4049    /// A dark appearance.
4050    ///
4051    /// On macOS, this corresponds to the `darkAqua` appearance.
4052    Dark,
4053
4054    /// A dark appearance with vibrant colors.
4055    ///
4056    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
4057    VibrantDark,
4058}
4059
4060/// How a window's background is drawn when there is no content or the content is transparent.
4061///
4062/// The variants a platform supports depend on the target. The Mica backdrop materials
4063/// are supported only on Windows 11, and are ignored elsewhere. Set the appearance
4064/// with the `macos_window_background`, `windows_window_background`,
4065/// `linux_window_background` or `web_window_background` field of [`WindowOptions`].
4066#[derive(Copy, Clone, Debug, Default, PartialEq)]
4067pub enum WindowBackgroundAppearance {
4068    /// Opaque.
4069    ///
4070    /// This lets the window manager know that content behind this
4071    /// window does not need to be drawn.
4072    ///
4073    /// Actual color depends on the system and themes should define a fully
4074    /// opaque background color instead.
4075    #[default]
4076    Opaque,
4077    /// Plain alpha transparency.
4078    Transparent,
4079    /// Transparency, but the contents behind the window are blurred.
4080    ///
4081    /// Not always supported.
4082    Blurred,
4083    /// The Mica backdrop material, supported on Windows 11.
4084    MicaBackdrop,
4085    /// The Mica Alt backdrop material, supported on Windows 11.
4086    MicaAltBackdrop,
4087}
4088
4089impl WindowBackgroundAppearance {
4090    /// Whether the window's background hides everything behind the window,
4091    /// letting the platform skip compositing the content it covers.
4092    pub fn is_opaque(&self) -> bool {
4093        matches!(self, Self::Opaque)
4094    }
4095
4096    /// Whether the window's background lets content behind the window show
4097    /// through, either directly, blurred, or via a system backdrop material.
4098    pub fn is_transparent(&self) -> bool {
4099        !self.is_opaque()
4100    }
4101}
4102
4103#[cfg(test)]
4104mod window_background_appearance_tests {
4105    use super::WindowBackgroundAppearance;
4106
4107    #[test]
4108    fn only_opaque_hides_the_content_behind_the_window() {
4109        assert!(WindowBackgroundAppearance::Opaque.is_opaque());
4110        assert!(!WindowBackgroundAppearance::Opaque.is_transparent());
4111
4112        for appearance in [
4113            WindowBackgroundAppearance::Transparent,
4114            WindowBackgroundAppearance::Blurred,
4115            WindowBackgroundAppearance::MicaBackdrop,
4116            WindowBackgroundAppearance::MicaAltBackdrop,
4117        ] {
4118            assert!(appearance.is_transparent());
4119            assert!(!appearance.is_opaque());
4120        }
4121    }
4122}
4123
4124/// The background appearance of a macOS window, set through
4125/// [`WindowOptions::macos_window_background`].
4126#[cfg(target_os = "macos")]
4127#[derive(Copy, Clone, Debug, Default, PartialEq)]
4128pub enum MacosWindowBackground {
4129    /// Hides everything behind the window. Themes should define a fully
4130    /// opaque background color instead of relying on the system's.
4131    #[default]
4132    Opaque,
4133    /// Plain alpha transparency.
4134    Transparent,
4135    /// Transparency with the contents behind the window blurred, via the
4136    /// system's vibrancy materials.
4137    Blurred,
4138}
4139
4140/// The background appearance of a Windows window, set through
4141/// [`WindowOptions::windows_window_background`].
4142#[cfg(target_os = "windows")]
4143#[derive(Copy, Clone, Debug, Default, PartialEq)]
4144pub enum WindowsWindowBackground {
4145    /// Hides everything behind the window. Themes should define a fully
4146    /// opaque background color instead of relying on the system's.
4147    #[default]
4148    Opaque,
4149    /// Plain alpha transparency.
4150    Transparent,
4151    /// Transparency with the contents behind the window blurred.
4152    Blurred,
4153    /// The Mica backdrop material, supported on Windows 11.
4154    MicaBackdrop,
4155    /// The Mica Alt backdrop material, supported on Windows 11.
4156    MicaAltBackdrop,
4157}
4158
4159/// The background appearance of a Linux window, set through
4160/// [`WindowOptions::linux_window_background`]. Whether blurring is honored
4161/// depends on the compositor; X11 treats it as plain transparency.
4162#[cfg(any(target_os = "linux", target_os = "freebsd"))]
4163#[derive(Copy, Clone, Debug, Default, PartialEq)]
4164pub enum LinuxWindowBackground {
4165    /// Hides everything behind the window. Themes should define a fully
4166    /// opaque background color instead of relying on the system's.
4167    #[default]
4168    Opaque,
4169    /// Plain alpha transparency.
4170    Transparent,
4171    /// Transparency with the contents behind the window blurred, when the
4172    /// compositor supports it.
4173    Blurred,
4174}
4175
4176/// The background appearance of a web window, set through
4177/// `web_window_background`. Web windows are always opaque at
4178/// the platform level.
4179#[cfg(target_family = "wasm")]
4180#[derive(Copy, Clone, Debug, Default, PartialEq)]
4181pub enum WebWindowBackground {
4182    /// The window background is always opaque.
4183    #[default]
4184    Opaque,
4185}
4186
4187#[cfg(target_os = "macos")]
4188impl From<MacosWindowBackground> for WindowBackgroundAppearance {
4189    fn from(background: MacosWindowBackground) -> Self {
4190        match background {
4191            MacosWindowBackground::Opaque => Self::Opaque,
4192            MacosWindowBackground::Transparent => Self::Transparent,
4193            MacosWindowBackground::Blurred => Self::Blurred,
4194        }
4195    }
4196}
4197
4198#[cfg(target_os = "windows")]
4199impl From<WindowsWindowBackground> for WindowBackgroundAppearance {
4200    fn from(background: WindowsWindowBackground) -> Self {
4201        match background {
4202            WindowsWindowBackground::Opaque => Self::Opaque,
4203            WindowsWindowBackground::Transparent => Self::Transparent,
4204            WindowsWindowBackground::Blurred => Self::Blurred,
4205            WindowsWindowBackground::MicaBackdrop => Self::MicaBackdrop,
4206            WindowsWindowBackground::MicaAltBackdrop => Self::MicaAltBackdrop,
4207        }
4208    }
4209}
4210
4211#[cfg(any(target_os = "linux", target_os = "freebsd"))]
4212impl From<LinuxWindowBackground> for WindowBackgroundAppearance {
4213    fn from(background: LinuxWindowBackground) -> Self {
4214        match background {
4215            LinuxWindowBackground::Opaque => Self::Opaque,
4216            LinuxWindowBackground::Transparent => Self::Transparent,
4217            LinuxWindowBackground::Blurred => Self::Blurred,
4218        }
4219    }
4220}
4221
4222#[cfg(target_family = "wasm")]
4223impl From<WebWindowBackground> for WindowBackgroundAppearance {
4224    fn from(background: WebWindowBackground) -> Self {
4225        match background {
4226            WebWindowBackground::Opaque => Self::Opaque,
4227        }
4228    }
4229}
4230
4231#[cfg(test)]
4232mod platform_window_background_tests {
4233    use super::WindowBackgroundAppearance;
4234
4235    #[cfg(target_os = "macos")]
4236    #[test]
4237    fn macos_backgrounds_map_to_appearances() {
4238        use super::MacosWindowBackground;
4239        assert_eq!(
4240            WindowBackgroundAppearance::from(MacosWindowBackground::Opaque),
4241            WindowBackgroundAppearance::Opaque
4242        );
4243        assert_eq!(
4244            WindowBackgroundAppearance::from(MacosWindowBackground::Blurred),
4245            WindowBackgroundAppearance::Blurred
4246        );
4247    }
4248
4249    #[cfg(target_os = "windows")]
4250    #[test]
4251    fn windows_backgrounds_map_to_appearances() {
4252        use super::WindowsWindowBackground;
4253        assert_eq!(
4254            WindowBackgroundAppearance::from(WindowsWindowBackground::MicaAltBackdrop),
4255            WindowBackgroundAppearance::MicaAltBackdrop
4256        );
4257        assert_eq!(
4258            WindowBackgroundAppearance::from(WindowsWindowBackground::default()),
4259            WindowBackgroundAppearance::Opaque
4260        );
4261    }
4262
4263    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
4264    #[test]
4265    fn linux_backgrounds_map_to_appearances() {
4266        use super::LinuxWindowBackground;
4267        assert_eq!(
4268            WindowBackgroundAppearance::from(LinuxWindowBackground::Transparent),
4269            WindowBackgroundAppearance::Transparent
4270        );
4271    }
4272
4273    #[cfg(target_family = "wasm")]
4274    #[test]
4275    fn web_backgrounds_map_to_appearances() {
4276        use super::WebWindowBackground;
4277        assert_eq!(
4278            WindowBackgroundAppearance::from(WebWindowBackground::Opaque),
4279            WindowBackgroundAppearance::Opaque
4280        );
4281    }
4282}
4283
4284/// The text rendering mode for glyphs.
4285///
4286/// [`PlatformTextSystem::recommended_rendering_mode`] returns the platform's choice.
4287#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
4288pub enum TextRenderingMode {
4289    /// Use the platform's default text rendering mode.
4290    #[default]
4291    PlatformDefault,
4292    /// Use subpixel (ClearType-style) text rendering.
4293    Subpixel,
4294    /// Use grayscale text rendering.
4295    Grayscale,
4296}
4297
4298/// Options for [`Platform::prompt_for_paths`], which shows a file or folder picker.
4299#[derive(Clone, Debug)]
4300pub struct PathPromptOptions {
4301    /// Whether the user can pick files.
4302    pub files: bool,
4303    /// Whether the user can pick directories.
4304    pub directories: bool,
4305    /// Whether the user can pick more than one item.
4306    pub multiple: bool,
4307    /// Text that explains the request in the picker, on platforms that display it.
4308    pub prompt: Option<SharedString>,
4309}
4310
4311/// How serious a prompt is. The platform uses it to choose the alert's icon or style.
4312#[derive(Copy, Clone, Debug, PartialEq)]
4313pub enum PromptLevel {
4314    /// A prompt that is shown when the user should be notified of something
4315    Info,
4316
4317    /// A prompt that is shown when the user needs to be warned of a potential problem
4318    Warning,
4319
4320    /// A prompt that is shown when a critical problem has occurred
4321    Critical,
4322}
4323
4324/// A button on a prompt shown with [`PlatformWindow::prompt`].
4325///
4326/// The variant tells the platform the button's role, such as the default or cancel action.
4327/// Use [`PromptButton::from`] to get a role from a label: `"ok"` and `"cancel"`,
4328/// in any case, become `Ok` and `Cancel`, and any other text becomes `Other`.
4329///
4330/// ```
4331/// use gpui::PromptButton;
4332///
4333/// assert_eq!(PromptButton::from("ok"), PromptButton::Ok("OK".into()));
4334/// assert_eq!(PromptButton::from("Delete"), PromptButton::Other("Delete".into()));
4335/// ```
4336#[derive(Clone, Debug, PartialEq)]
4337pub enum PromptButton {
4338    /// Ok button
4339    Ok(SharedString),
4340    /// Cancel button
4341    Cancel(SharedString),
4342    /// Other button
4343    Other(SharedString),
4344}
4345
4346impl PromptButton {
4347    /// Creates a button with `label` and no special role.
4348    pub fn new(label: impl Into<SharedString>) -> Self {
4349        PromptButton::Other(label.into())
4350    }
4351
4352    /// Creates a button with the [`PromptButton::Ok`] role.
4353    pub fn ok(label: impl Into<SharedString>) -> Self {
4354        PromptButton::Ok(label.into())
4355    }
4356
4357    /// Creates a button with the [`PromptButton::Cancel`] role.
4358    pub fn cancel(label: impl Into<SharedString>) -> Self {
4359        PromptButton::Cancel(label.into())
4360    }
4361
4362    /// Returns `true` if this is the cancel button.
4363    #[allow(dead_code)]
4364    pub fn is_cancel(&self) -> bool {
4365        matches!(self, PromptButton::Cancel(_))
4366    }
4367
4368    /// Returns the button's label.
4369    pub fn label(&self) -> &SharedString {
4370        match self {
4371            PromptButton::Ok(label) => label,
4372            PromptButton::Cancel(label) => label,
4373            PromptButton::Other(label) => label,
4374        }
4375    }
4376}
4377
4378impl From<&str> for PromptButton {
4379    fn from(value: &str) -> Self {
4380        match value.to_lowercase().as_str() {
4381            "ok" => PromptButton::Ok("OK".into()),
4382            "cancel" => PromptButton::Cancel("Cancel".into()),
4383            _ => PromptButton::Other(SharedString::from(value.to_owned())),
4384        }
4385    }
4386}
4387
4388/// The shape of the mouse cursor. Each variant names the CSS cursor it maps to on the web.
4389#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
4390pub enum CursorStyle {
4391    /// The default cursor, corresponds to the CSS cursor value `default`
4392    #[default]
4393    Arrow,
4394
4395    /// A text input cursor
4396    /// corresponds to the CSS cursor value `text`
4397    IBeam,
4398
4399    /// A crosshair cursor
4400    /// corresponds to the CSS cursor value `crosshair`
4401    Crosshair,
4402
4403    /// A closed hand cursor
4404    /// corresponds to the CSS cursor value `grabbing`
4405    ClosedHand,
4406
4407    /// An open hand cursor
4408    /// corresponds to the CSS cursor value `grab`
4409    OpenHand,
4410
4411    /// A pointing hand cursor
4412    /// corresponds to the CSS cursor value `pointer`
4413    PointingHand,
4414
4415    /// A resize left cursor
4416    /// corresponds to the CSS cursor value `ew-resize`
4417    ResizeLeft,
4418
4419    /// A resize right cursor
4420    /// corresponds to the CSS cursor value `ew-resize`
4421    ResizeRight,
4422
4423    /// A resize cursor to the left and right
4424    /// corresponds to the CSS cursor value `ew-resize`
4425    ResizeLeftRight,
4426
4427    /// A resize up cursor
4428    /// corresponds to the CSS cursor value `ns-resize`
4429    ResizeUp,
4430
4431    /// A resize down cursor
4432    /// corresponds to the CSS cursor value `ns-resize`
4433    ResizeDown,
4434
4435    /// A resize cursor directing up and down
4436    /// corresponds to the CSS cursor value `ns-resize`
4437    ResizeUpDown,
4438
4439    /// A cursor for the upper-left resize edge (`nw-resize`).
4440    ResizeUpLeft,
4441
4442    /// A cursor for the upper-right resize edge (`ne-resize`).
4443    ResizeUpRight,
4444
4445    /// A cursor for the lower-left resize edge (`sw-resize`).
4446    ResizeDownLeft,
4447
4448    /// A cursor for the lower-right resize edge (`se-resize`).
4449    ResizeDownRight,
4450
4451    /// A resize cursor directing up-left and down-right
4452    /// corresponds to the CSS cursor value `nwse-resize`
4453    ResizeUpLeftDownRight,
4454
4455    /// A resize cursor directing up-right and down-left
4456    /// corresponds to the CSS cursor value `nesw-resize`
4457    ResizeUpRightDownLeft,
4458
4459    /// A cursor indicating that the item/column can be resized horizontally.
4460    /// corresponds to the CSS cursor value `col-resize`
4461    ResizeColumn,
4462
4463    /// A cursor indicating that the item/row can be resized vertically.
4464    /// corresponds to the CSS cursor value `row-resize`
4465    ResizeRow,
4466
4467    /// A text input cursor for vertical layout
4468    /// corresponds to the CSS cursor value `vertical-text`
4469    IBeamCursorForVerticalLayout,
4470
4471    /// A cursor indicating that the operation is not allowed
4472    /// corresponds to the CSS cursor value `not-allowed`
4473    OperationNotAllowed,
4474
4475    /// A cursor indicating that the operation will result in a link
4476    /// corresponds to the CSS cursor value `alias`
4477    DragLink,
4478
4479    /// A cursor indicating that the operation will result in a copy
4480    /// corresponds to the CSS cursor value `copy`
4481    DragCopy,
4482
4483    /// A cursor indicating that the operation will result in a context menu
4484    /// corresponds to the CSS cursor value `context-menu`
4485    ContextualMenu,
4486}
4487
4488/// Something to put on the clipboard, or something read from it.
4489///
4490/// An item holds one or more [`ClipboardEntry`] values, such as text, an image or file
4491/// paths. Use [`App::write_to_clipboard`] to copy an item, and
4492/// [`App::read_from_clipboard`] to read one.
4493///
4494/// # Examples
4495///
4496/// ```
4497/// use gpui::{ClipboardEntry, ClipboardItem};
4498///
4499/// let item = ClipboardItem::new_string("hello".to_string());
4500/// assert_eq!(item.text().as_deref(), Some("hello"));
4501/// assert_eq!(item.metadata(), None);
4502///
4503/// let item = ClipboardItem::new_string_with_json_metadata("1, 2, 3".to_string(), vec![1, 2, 3]);
4504/// assert_eq!(item.metadata().map(String::as_str), Some("[1,2,3]"));
4505///
4506/// let ClipboardEntry::String(entry) = &item.entries()[0] else {
4507///     panic!("expected a string entry");
4508/// };
4509/// assert_eq!(entry.metadata_json::<Vec<i32>>(), Some(vec![1, 2, 3]));
4510/// ```
4511#[derive(Clone, Debug, Eq, PartialEq)]
4512pub struct ClipboardItem {
4513    /// The entries in this clipboard item.
4514    pub entries: Vec<ClipboardEntry>,
4515}
4516
4517/// An error from [`Platform::read_from_clipboard_async`]. Each variant calls for different guidance to the user.
4518#[derive(Clone, Debug, PartialEq, Eq)]
4519pub enum ClipboardReadError {
4520    /// The platform clipboard is not available in this context, e.g. the
4521    /// browser does not expose the async clipboard API or the page is not a
4522    /// secure context.
4523    Unavailable,
4524    /// The platform refused access, e.g. the user declined the browser's
4525    /// clipboard permission prompt or paste confirmation.
4526    Denied(String),
4527    /// The clipboard contents could not be converted into a
4528    /// [`ClipboardItem`].
4529    UnsupportedContent,
4530}
4531
4532impl std::fmt::Display for ClipboardReadError {
4533    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4534        match self {
4535            Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
4536            Self::Denied(message) => {
4537                write!(formatter, "clipboard access was denied: {message}")
4538            }
4539            Self::UnsupportedContent => {
4540                formatter.write_str("the clipboard contents are unsupported")
4541            }
4542        }
4543    }
4544}
4545
4546impl std::error::Error for ClipboardReadError {}
4547
4548/// One part of a [`ClipboardItem`]: a string, an image or a list of file paths.
4549#[derive(Clone, Debug, Eq, PartialEq)]
4550pub enum ClipboardEntry {
4551    /// A string entry
4552    String(ClipboardString),
4553    /// An image entry
4554    Image(Image),
4555    /// A file entry
4556    ExternalPaths(crate::ExternalPaths),
4557}
4558
4559impl ClipboardItem {
4560    /// Creates an item holding `text`, with no metadata.
4561    pub fn new_string(text: String) -> Self {
4562        Self {
4563            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
4564        }
4565    }
4566
4567    /// Creates an item holding `text` and a raw `metadata` string.
4568    ///
4569    /// Use [`ClipboardItem::new_string_with_json_metadata`] to store structured data.
4570    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
4571        Self {
4572            entries: vec![ClipboardEntry::String(ClipboardString {
4573                text,
4574                metadata: Some(metadata),
4575            })],
4576        }
4577    }
4578
4579    /// Creates an item holding `text` and `metadata`, serialized as JSON.
4580    ///
4581    /// Read the metadata back with [`ClipboardString::metadata_json`].
4582    ///
4583    /// # Panics
4584    ///
4585    /// Panics if `metadata` can't be serialized to JSON, such as a map with non-string keys.
4586    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
4587        Self {
4588            entries: vec![ClipboardEntry::String(
4589                ClipboardString::new(text).with_json_metadata(metadata),
4590            )],
4591        }
4592    }
4593
4594    /// Creates an item holding a copy of `image`.
4595    pub fn new_image(image: &Image) -> Self {
4596        Self {
4597            entries: vec![ClipboardEntry::Image(image.clone())],
4598        }
4599    }
4600
4601    /// Returns the text of all string entries joined together, or `None` if the item has no text.
4602    ///
4603    /// If the string entries contain no text, the file paths of any [`ClipboardEntry::ExternalPaths`]
4604    /// entries are joined with no separator instead. The result is `None` if there are no paths either.
4605    pub fn text(&self) -> Option<String> {
4606        let mut answer = String::new();
4607
4608        for entry in self.entries.iter() {
4609            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
4610                answer.push_str(text);
4611            }
4612        }
4613
4614        if answer.is_empty() {
4615            for entry in self.entries.iter() {
4616                if let ClipboardEntry::ExternalPaths(paths) = entry {
4617                    for path in &paths.0 {
4618                        use std::fmt::Write as _;
4619                        _ = write!(answer, "{}", path.display());
4620                    }
4621                }
4622            }
4623        }
4624
4625        if !answer.is_empty() {
4626            Some(answer)
4627        } else {
4628            None
4629        }
4630    }
4631
4632    /// Returns the metadata of the item's string entry, or `None`.
4633    ///
4634    /// Returns `None` unless the item has exactly one entry and that entry is a string.
4635    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
4636    pub fn metadata(&self) -> Option<&String> {
4637        match self.entries().first() {
4638            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
4639                clipboard_string.metadata.as_ref()
4640            }
4641            _ => None,
4642        }
4643    }
4644
4645    /// Returns the item's entries.
4646    pub fn entries(&self) -> &[ClipboardEntry] {
4647        &self.entries
4648    }
4649
4650    /// Consumes the item and returns its entries.
4651    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
4652        self.entries.into_iter()
4653    }
4654}
4655
4656impl From<ClipboardString> for ClipboardEntry {
4657    fn from(value: ClipboardString) -> Self {
4658        Self::String(value)
4659    }
4660}
4661
4662impl From<String> for ClipboardEntry {
4663    fn from(value: String) -> Self {
4664        Self::from(ClipboardString::from(value))
4665    }
4666}
4667
4668impl From<Image> for ClipboardEntry {
4669    fn from(value: Image) -> Self {
4670        Self::Image(value)
4671    }
4672}
4673
4674impl From<ClipboardEntry> for ClipboardItem {
4675    fn from(value: ClipboardEntry) -> Self {
4676        Self {
4677            entries: vec![value],
4678        }
4679    }
4680}
4681
4682impl From<String> for ClipboardItem {
4683    fn from(value: String) -> Self {
4684        Self::from(ClipboardEntry::from(value))
4685    }
4686}
4687
4688impl From<Image> for ClipboardItem {
4689    fn from(value: Image) -> Self {
4690        Self::from(ClipboardEntry::from(value))
4691    }
4692}
4693
4694/// An image format GPUI recognizes in clipboard data and in [`Image`].
4695#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
4696pub enum ImageFormat {
4697    // Sorted from most to least likely to be pasted into an editor,
4698    // which matters when we iterate through them trying to see if
4699    // clipboard content matches them.
4700    /// .png
4701    Png,
4702    /// .jpeg or .jpg
4703    Jpeg,
4704    /// .webp
4705    Webp,
4706    /// .gif
4707    Gif,
4708    /// .svg
4709    Svg,
4710    /// .bmp
4711    Bmp,
4712    /// .tif or .tiff
4713    Tiff,
4714    /// .ico
4715    Ico,
4716    /// Netpbm image formats (.pbm, .ppm, .pgm).
4717    Pnm,
4718}
4719
4720impl ImageFormat {
4721    /// Returns the canonical MIME type, such as `image/png`.
4722    pub const fn mime_type(self) -> &'static str {
4723        match self {
4724            ImageFormat::Png => "image/png",
4725            ImageFormat::Jpeg => "image/jpeg",
4726            ImageFormat::Webp => "image/webp",
4727            ImageFormat::Gif => "image/gif",
4728            ImageFormat::Svg => "image/svg+xml",
4729            ImageFormat::Bmp => "image/bmp",
4730            ImageFormat::Tiff => "image/tiff",
4731            ImageFormat::Ico => "image/ico",
4732            ImageFormat::Pnm => "image/x-portable-anymap",
4733        }
4734    }
4735
4736    /// Returns the file extension for this image format (without leading dot).
4737    pub const fn extension(self) -> &'static str {
4738        match self {
4739            ImageFormat::Png => "png",
4740            ImageFormat::Jpeg => "jpg",
4741            ImageFormat::Webp => "webp",
4742            ImageFormat::Gif => "gif",
4743            ImageFormat::Svg => "svg",
4744            ImageFormat::Bmp => "bmp",
4745            ImageFormat::Tiff => "tiff",
4746            ImageFormat::Ico => "ico",
4747            ImageFormat::Pnm => "pnm",
4748        }
4749    }
4750
4751    /// Returns the format for `mime_type`, or `None` if the type is unknown.
4752    ///
4753    /// The aliases `image/jpg` and `image/tif` map to [`ImageFormat::Jpeg`] and [`ImageFormat::Tiff`].
4754    ///
4755    /// ```
4756    /// use gpui::ImageFormat;
4757    ///
4758    /// assert_eq!(ImageFormat::from_mime_type("image/jpg"), Some(ImageFormat::Jpeg));
4759    /// assert_eq!(ImageFormat::Png.extension(), "png");
4760    /// assert_eq!(ImageFormat::from_mime_type("text/plain"), None);
4761    /// ```
4762    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
4763        use strum::IntoEnumIterator;
4764        Self::iter()
4765            .find(|format| format.mime_type() == mime_type)
4766            .or_else(|| Self::from_mime_type_alias(mime_type))
4767    }
4768
4769    /// Non-canonical mime types that some producers use in the wild.
4770    /// Unlike `mime_type()` which returns the single canonical form,
4771    /// these are legacy or shortened variants we still need to recognize.
4772    fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
4773        match mime_type {
4774            "image/jpg" => Some(Self::Jpeg),
4775            "image/tif" => Some(Self::Tiff),
4776            _ => None,
4777        }
4778    }
4779}
4780
4781/// Encoded image data and the format it is encoded in.
4782///
4783/// Use [`Image::from_bytes`] to create one. GPUI decodes it for display through
4784/// [`Image::to_image_data`] or the asset APIs.
4785#[derive(Clone, Debug, PartialEq, Eq)]
4786pub struct Image {
4787    /// The format the bytes are encoded in, such as PNG.
4788    pub format: ImageFormat,
4789    /// The encoded image bytes.
4790    pub bytes: Vec<u8>,
4791    /// A key for the image in GPUI's asset cache. [`Image::from_bytes`] sets it to a hash of `bytes`.
4792    pub id: u64,
4793}
4794
4795pub(crate) fn decode_static_image(
4796    bytes: &[u8],
4797    format: image::ImageFormat,
4798) -> Result<SmallVec<[Frame; 1]>> {
4799    let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
4800        .into_decoder()
4801        .context("creating image decoder")?;
4802    decode_static_image_from_decoder(decoder)
4803}
4804
4805pub(crate) fn decode_static_image_from_decoder(
4806    mut decoder: impl image::ImageDecoder,
4807) -> Result<SmallVec<[Frame; 1]>> {
4808    let orientation = decoder
4809        .orientation()
4810        .context("reading decoder's orientation")?;
4811    let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
4812    image.apply_orientation(orientation);
4813
4814    let mut data = image.into_rgba8();
4815    for pixel in data.chunks_exact_mut(4) {
4816        pixel.swap(0, 2);
4817    }
4818
4819    Ok(SmallVec::from_elem(Frame::new(data), 1))
4820}
4821
4822impl Hash for Image {
4823    fn hash<H: Hasher>(&self, state: &mut H) {
4824        state.write_u64(self.id);
4825    }
4826}
4827
4828impl Image {
4829    /// Returns a PNG image with no bytes. It can't be decoded.
4830    pub fn empty() -> Self {
4831        Self::from_bytes(ImageFormat::Png, Vec::new())
4832    }
4833
4834    /// Creates an image from `format` and encoded `bytes`.
4835    ///
4836    /// The [`Image::id`] is a hash of `bytes` alone, so two images with the same bytes but different formats share an id.
4837    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
4838        Self {
4839            id: hash(&bytes),
4840            format,
4841            bytes,
4842        }
4843    }
4844
4845    /// Returns the image's cache key. [`Image::from_bytes`] sets it from a hash of the bytes.
4846    pub fn id(&self) -> u64 {
4847        self.id
4848    }
4849
4850    /// Returns the decoded image, starting the load if it isn't cached yet.
4851    ///
4852    /// Returns `None` while the image is loading, or if it can't be decoded. Call it from a render method, so the window redraws when the load finishes.
4853    pub fn use_render_image(
4854        self: Arc<Self>,
4855        window: &mut Window,
4856        cx: &mut App,
4857    ) -> Option<Arc<RenderImage>> {
4858        ImageSource::Image(self)
4859            .use_data(None, window, cx)
4860            .and_then(|result| result.ok())
4861    }
4862
4863    /// Returns the decoded image if it's already cached. Doesn't start a load.
4864    pub fn get_render_image(
4865        self: Arc<Self>,
4866        window: &mut Window,
4867        cx: &mut App,
4868    ) -> Option<Arc<RenderImage>> {
4869        ImageSource::Image(self)
4870            .get_data(None, window, cx)
4871            .and_then(|result| result.ok())
4872    }
4873
4874    /// Removes the decoded image from GPUI's asset cache.
4875    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
4876        ImageSource::Image(self).remove_asset(cx);
4877    }
4878
4879    /// Check whether this image is present in GPUI's asset cache (loading or
4880    /// loaded), without fetching it.
4881    #[cfg(any(test, feature = "test-support"))]
4882    pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
4883        ImageSource::Image(self.clone()).is_asset_cached(cx)
4884    }
4885
4886    /// Decodes the image into a [`RenderImage`] that you can paint.
4887    ///
4888    /// SVG images are rendered at scale `1.0`.
4889    ///
4890    /// # Errors
4891    ///
4892    /// Returns an error if the bytes can't be decoded. For a GIF, frames that fail to
4893    /// decode are skipped and logged, and an error is returned only when every frame fails.
4894    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
4895        let frames = match self.format {
4896            ImageFormat::Gif => {
4897                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
4898                let mut frames = SmallVec::new();
4899
4900                for frame in decoder.into_frames() {
4901                    match frame {
4902                        Ok(mut frame) => {
4903                            // Convert from RGBA to BGRA.
4904                            for pixel in frame.buffer_mut().chunks_exact_mut(4) {
4905                                pixel.swap(0, 2);
4906                            }
4907                            frames.push(frame);
4908                        }
4909                        Err(err) => {
4910                            log::debug!("Skipping GIF frame due to decode error: {err}");
4911                        }
4912                    }
4913                }
4914
4915                if frames.is_empty() {
4916                    anyhow::bail!("GIF could not be decoded: all frames failed");
4917                }
4918
4919                frames
4920            }
4921            ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
4922            ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
4923            ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
4924            ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
4925            ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
4926            ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
4927            ImageFormat::Svg => {
4928                return svg_renderer
4929                    .render_single_frame(&self.bytes, 1.0)
4930                    .map_err(Into::into);
4931            }
4932            ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
4933        };
4934
4935        Ok(Arc::new(RenderImage::new(frames)))
4936    }
4937
4938    /// Returns the image's format.
4939    pub fn format(&self) -> ImageFormat {
4940        self.format
4941    }
4942
4943    /// Returns the encoded image bytes.
4944    pub fn bytes(&self) -> &[u8] {
4945        self.bytes.as_slice()
4946    }
4947}
4948
4949/// A string on the clipboard, with optional metadata.
4950///
4951/// Metadata lets an application record more about copied text, such as where it came
4952/// from. The metadata is stored with the text and read back with [`ClipboardString::metadata_json`].
4953#[derive(Clone, Debug, Eq, PartialEq)]
4954pub struct ClipboardString {
4955    /// The text content.
4956    pub text: String,
4957    /// Optional metadata associated with this clipboard string.
4958    pub metadata: Option<String>,
4959}
4960
4961impl ClipboardString {
4962    /// Creates a clipboard string with `text` and no metadata.
4963    pub fn new(text: String) -> Self {
4964        Self {
4965            text,
4966            metadata: None,
4967        }
4968    }
4969
4970    /// Sets the metadata to `metadata`, serialized as JSON. Returns the updated string.
4971    ///
4972    /// # Panics
4973    ///
4974    /// Panics if `metadata` can't be serialized to JSON.
4975    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
4976        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
4977        self
4978    }
4979
4980    /// Returns the text.
4981    pub fn text(&self) -> &String {
4982        &self.text
4983    }
4984
4985    /// Consumes the string and returns its text.
4986    pub fn into_text(self) -> String {
4987        self.text
4988    }
4989
4990    /// Returns the metadata deserialized as `T`.
4991    ///
4992    /// Returns `None` if there is no metadata or it doesn't deserialize as `T`.
4993    pub fn metadata_json<T>(&self) -> Option<T>
4994    where
4995        T: for<'a> Deserialize<'a>,
4996    {
4997        self.metadata
4998            .as_ref()
4999            .and_then(|m| serde_json::from_str(m).ok())
5000    }
5001
5002    /// Returns a hash of `text`. Use it to detect whether the clipboard text has changed.
5003    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
5004    pub fn text_hash(text: &str) -> u64 {
5005        let mut hasher = SeaHasher::new();
5006        text.hash(&mut hasher);
5007        hasher.finish()
5008    }
5009}
5010
5011impl From<String> for ClipboardString {
5012    fn from(value: String) -> Self {
5013        Self {
5014            text: value,
5015            metadata: None,
5016        }
5017    }
5018}
5019
5020#[cfg(test)]
5021mod ime_candidate_bounds_tests {
5022    use super::*;
5023    use crate::size;
5024
5025    // Offsets 0..3 sit on the first visual line and the rest wrap onto the second.
5026    fn bounds_at(offset: usize) -> Bounds<Pixels> {
5027        let line = if offset < 3 { 0. } else { 20. };
5028        Bounds::new(point(px(offset as f32), px(line)), size(px(1.), px(20.)))
5029    }
5030
5031    fn candidate_bounds(
5032        marked_range: Option<Range<usize>>,
5033        selection: UTF16Selection,
5034    ) -> Option<Bounds<Pixels>> {
5035        PlatformInputHandler::compute_ime_candidate_bounds(marked_range, &selection, |range| {
5036            Some(bounds_at(range.start))
5037        })
5038    }
5039
5040    #[test]
5041    fn composition_follows_the_active_end_of_a_reversed_selection() {
5042        // The caret is at offset 2 on the first line, so the candidate window
5043        // stays at the start of the composition instead of the wrapped line.
5044        let bounds = candidate_bounds(
5045            Some(0..8),
5046            UTF16Selection {
5047                range: 2..7,
5048                reversed: true,
5049            },
5050        );
5051        assert_eq!(bounds, Some(bounds_at(0)));
5052    }
5053
5054    #[test]
5055    fn composition_uses_the_start_of_the_caret_line() {
5056        let bounds = candidate_bounds(
5057            Some(0..8),
5058            UTF16Selection {
5059                range: 2..7,
5060                reversed: false,
5061            },
5062        );
5063        assert_eq!(bounds, Some(bounds_at(3)));
5064    }
5065
5066    #[test]
5067    fn without_composition_uses_the_active_end() {
5068        let bounds = candidate_bounds(
5069            None,
5070            UTF16Selection {
5071                range: 2..7,
5072                reversed: true,
5073            },
5074        );
5075        assert_eq!(bounds, Some(bounds_at(2)));
5076    }
5077}
5078
5079#[cfg(test)]
5080mod image_tests {
5081    use crate::{AssetRegistry, size};
5082
5083    use super::*;
5084    use std::sync::Arc;
5085
5086    #[test]
5087    fn test_image_to_image_data_applies_exif_orientation() {
5088        let image = Image::from_bytes(
5089            ImageFormat::Jpeg,
5090            include_bytes!("../tests/fixtures/exif-orientation-rotate-180.jpg").to_vec(),
5091        );
5092
5093        let render_image = image
5094            .to_image_data(SvgRenderer::new(Arc::new(AssetRegistry::default())))
5095            .unwrap();
5096
5097        assert_eq!(render_image.size(0), size(16.into(), 32.into()));
5098
5099        let bytes = render_image.as_bytes(0).unwrap();
5100        assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
5101        assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
5102    }
5103
5104    #[test]
5105    fn test_svg_image_to_image_data_converts_to_bgra() {
5106        let image = Image::from_bytes(
5107            ImageFormat::Svg,
5108            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
5109<rect width="1" height="1" fill="#38BDF8"/>
5110</svg>"##
5111                .to_vec(),
5112        );
5113
5114        let render_image = image
5115            .to_image_data(SvgRenderer::new(Arc::new(AssetRegistry::default())))
5116            .unwrap();
5117        let bytes = render_image.as_bytes(0).unwrap();
5118
5119        for pixel in bytes.chunks_exact(4) {
5120            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
5121        }
5122    }
5123}
5124
5125#[cfg(test)]
5126mod atlas_tests {
5127    use super::*;
5128
5129    const TILE_SIZE: Size<DevicePixels> = Size {
5130        width: DevicePixels(1),
5131        height: DevicePixels(1),
5132    };
5133
5134    #[derive(Default)]
5135    struct RecordingAtlasBackend {
5136        insert_calls: u32,
5137        fail_next_insert: bool,
5138        removed_tiles: Vec<AtlasTile>,
5139    }
5140
5141    impl AtlasBackend for RecordingAtlasBackend {
5142        fn insert(
5143            &mut self,
5144            kind: AtlasTextureKind,
5145            size: Size<DevicePixels>,
5146            _bytes: &[u8],
5147        ) -> Result<AtlasTile> {
5148            self.insert_calls += 1;
5149            if std::mem::take(&mut self.fail_next_insert) {
5150                anyhow::bail!("backend failed");
5151            }
5152            Ok(AtlasTile {
5153                texture_id: AtlasTextureId { index: 0, kind },
5154                tile_id: TileId(self.insert_calls),
5155                padding: 0,
5156                bounds: Bounds {
5157                    origin: Point::default(),
5158                    size,
5159                },
5160            })
5161        }
5162
5163        fn remove(&mut self, tile: AtlasTile) {
5164            self.removed_tiles.push(tile);
5165        }
5166    }
5167
5168    fn image_key(image_id: usize) -> AtlasKey {
5169        AtlasKey::Image(RenderImageParams {
5170            image_id: crate::ImageId(image_id),
5171            frame_index: 0,
5172        })
5173    }
5174
5175    fn build_tile() -> Result<Option<(Size<DevicePixels>, Cow<'static, [u8]>)>> {
5176        Ok(Some((TILE_SIZE, Cow::Borrowed(&[0, 0, 0, 255]))))
5177    }
5178
5179    #[test]
5180    fn only_successful_inserts_are_cached() -> Result<()> {
5181        let mut state = AtlasState::new(RecordingAtlasBackend::default());
5182        let key = image_key(1);
5183
5184        assert_eq!(
5185            state.get_or_insert_with(key.clone(), &mut || Ok(None))?,
5186            None
5187        );
5188        state
5189            .get_or_insert_with(key.clone(), &mut || anyhow::bail!("builder failed"))
5190            .expect_err("builder error should propagate");
5191        assert!(!state.contains(&key));
5192        assert_eq!(state.backend.insert_calls, 0);
5193
5194        state.backend.fail_next_insert = true;
5195        state
5196            .get_or_insert_with(key.clone(), &mut build_tile)
5197            .expect_err("backend error should propagate");
5198        assert!(!state.contains(&key));
5199        assert_eq!(state.backend.insert_calls, 1);
5200
5201        let tile = state
5202            .get_or_insert_with(key.clone(), &mut build_tile)?
5203            .context("builder should produce a tile")?;
5204        assert_eq!(tile.texture_id.kind, key.texture_kind());
5205        assert_eq!(
5206            state.get_or_insert_with(key.clone(), &mut || {
5207                anyhow::bail!("cache hit must not call the builder")
5208            })?,
5209            Some(tile)
5210        );
5211        assert!(state.contains(&key));
5212        assert_eq!(state.backend.insert_calls, 2);
5213        Ok(())
5214    }
5215
5216    #[test]
5217    fn remove_and_clear_invalidate_keys() -> Result<()> {
5218        let mut state = AtlasState::new(RecordingAtlasBackend::default());
5219        let key = image_key(1);
5220        let other_key = image_key(2);
5221        let tile = state
5222            .get_or_insert_with(key.clone(), &mut build_tile)?
5223            .context("builder should produce a tile")?;
5224        state
5225            .get_or_insert_with(other_key.clone(), &mut build_tile)?
5226            .context("builder should produce another tile")?;
5227
5228        state.remove(&key);
5229        state.remove(&key);
5230        assert!(!state.contains(&key));
5231        assert!(state.contains(&other_key));
5232        assert_eq!(state.backend.removed_tiles, vec![tile]);
5233
5234        let mut reset_calls = 0;
5235        state.clear(|_| reset_calls += 1);
5236        assert_eq!(reset_calls, 1);
5237        assert!(!state.contains(&other_key));
5238        assert_eq!(state.backend.removed_tiles, vec![tile]);
5239        Ok(())
5240    }
5241}
5242
5243#[cfg(test)]
5244mod frame_signal_tests {
5245    use super::*;
5246
5247    #[cfg(feature = "profiler")]
5248    #[test]
5249    fn coalesced_signals_retain_the_earliest_until_drained() {
5250        for (first_source, later_source) in [
5251            (
5252                FrameRequestSource::NativeCallback,
5253                FrameRequestSource::LocalSchedule,
5254            ),
5255            (
5256                FrameRequestSource::LocalSchedule,
5257                FrameRequestSource::NativeCallback,
5258            ),
5259        ] {
5260            let signal = PlatformFrameSignal::new();
5261            let first =
5262                PlatformFrameSignal::capture(Instant::now).expect("profiling captures requests");
5263            assert_eq!(signal.take(), None);
5264            signal.record(first + Duration::from_millis(16), later_source);
5265            signal.record(first, first_source);
5266            signal.record(first + Duration::from_millis(32), later_source);
5267            assert_eq!(signal.take(), Some((first, first_source)));
5268            assert_eq!(signal.take(), None);
5269            let next = first + Duration::from_millis(48);
5270            signal.record(next, later_source);
5271            assert_eq!(signal.take(), Some((next, later_source)));
5272        }
5273    }
5274
5275    #[cfg(not(feature = "profiler"))]
5276    #[test]
5277    fn disabled_profiling_does_not_capture_or_store_timestamps() {
5278        let captured = PlatformFrameSignal::capture(|| {
5279            panic!("profiler-disabled builds must not evaluate the capture closure");
5280        });
5281        assert_eq!(captured, None);
5282        let signal = PlatformFrameSignal::new();
5283        signal.record(Instant::now(), FrameRequestSource::LocalSchedule);
5284        assert_eq!(signal.take(), None);
5285    }
5286}
5287
5288#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
5289mod window_button_layout_tests {
5290    use super::*;
5291    use std::collections::HashSet;
5292
5293    #[test]
5294    fn test_refresh_interval_from_hz() {
5295        assert_eq!(
5296            refresh_interval_from_hz(50.0),
5297            Some(Duration::from_millis(20))
5298        );
5299        assert_eq!(
5300            refresh_interval_from_hz(120.0),
5301            Some(Duration::from_secs(1) / 120)
5302        );
5303        for unknown in [0.0, -60.0, f64::NAN, f64::INFINITY] {
5304            assert_eq!(refresh_interval_from_hz(unknown), None);
5305        }
5306    }
5307
5308    #[test]
5309    fn test_window_button_layout_parse_standard() {
5310        let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
5311        assert_eq!(
5312            layout.left,
5313            [
5314                Some(WindowButton::Close),
5315                Some(WindowButton::Minimize),
5316                None
5317            ]
5318        );
5319        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
5320    }
5321
5322    #[test]
5323    fn test_window_button_layout_parse_right_only() {
5324        let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
5325        assert_eq!(layout.left, [None, None, None]);
5326        assert_eq!(
5327            layout.right,
5328            [
5329                Some(WindowButton::Minimize),
5330                Some(WindowButton::Maximize),
5331                Some(WindowButton::Close)
5332            ]
5333        );
5334    }
5335
5336    #[test]
5337    fn test_window_button_layout_parse_left_only() {
5338        let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
5339        assert_eq!(
5340            layout.left,
5341            [
5342                Some(WindowButton::Close),
5343                Some(WindowButton::Minimize),
5344                Some(WindowButton::Maximize)
5345            ]
5346        );
5347        assert_eq!(layout.right, [None, None, None]);
5348    }
5349
5350    #[test]
5351    fn test_window_button_layout_parse_with_whitespace() {
5352        let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
5353        assert_eq!(
5354            layout.left,
5355            [
5356                Some(WindowButton::Close),
5357                Some(WindowButton::Minimize),
5358                None
5359            ]
5360        );
5361        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
5362    }
5363
5364    #[test]
5365    fn test_window_button_layout_parse_empty() {
5366        let layout = WindowButtonLayout::parse("").unwrap();
5367        assert_eq!(layout.left, [None, None, None]);
5368        assert_eq!(layout.right, [None, None, None]);
5369    }
5370
5371    #[test]
5372    fn test_window_button_layout_parse_intentionally_empty() {
5373        let layout = WindowButtonLayout::parse(":").unwrap();
5374        assert_eq!(layout.left, [None, None, None]);
5375        assert_eq!(layout.right, [None, None, None]);
5376    }
5377
5378    #[test]
5379    fn test_window_button_layout_parse_invalid_buttons() {
5380        let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
5381        assert_eq!(
5382            layout.left,
5383            [
5384                Some(WindowButton::Close),
5385                Some(WindowButton::Minimize),
5386                None
5387            ]
5388        );
5389        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
5390    }
5391
5392    #[test]
5393    fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
5394        let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
5395        assert_eq!(
5396            layout.right,
5397            [
5398                Some(WindowButton::Close),
5399                Some(WindowButton::Minimize),
5400                None
5401            ]
5402        );
5403        assert_eq!(layout.format(), ":close,minimize");
5404    }
5405
5406    #[test]
5407    fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
5408        let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
5409        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
5410        assert_eq!(
5411            layout.right,
5412            [
5413                Some(WindowButton::Maximize),
5414                Some(WindowButton::Minimize),
5415                None
5416            ]
5417        );
5418
5419        let button_ids: Vec<_> = layout
5420            .left
5421            .iter()
5422            .chain(layout.right.iter())
5423            .flatten()
5424            .map(WindowButton::id)
5425            .collect();
5426        let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
5427        assert_eq!(unique_button_ids.len(), button_ids.len());
5428        assert_eq!(layout.format(), "close:maximize,minimize");
5429    }
5430
5431    #[test]
5432    fn test_window_button_layout_parse_gnome_style() {
5433        let layout = WindowButtonLayout::parse("close").unwrap();
5434        assert_eq!(layout.left, [None, None, None]);
5435        assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
5436    }
5437
5438    #[test]
5439    fn test_window_button_layout_parse_elementary_style() {
5440        let layout = WindowButtonLayout::parse("close:maximize").unwrap();
5441        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
5442        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
5443    }
5444
5445    #[test]
5446    fn test_window_button_layout_round_trip() {
5447        let cases = [
5448            "close:minimize,maximize",
5449            "minimize,maximize,close:",
5450            ":close",
5451            "close:",
5452            "close:maximize",
5453            ":",
5454        ];
5455
5456        for case in cases {
5457            let layout = WindowButtonLayout::parse(case).unwrap();
5458            assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
5459        }
5460    }
5461
5462    #[test]
5463    fn test_window_button_layout_linux_default() {
5464        let layout = WindowButtonLayout::linux_default();
5465        assert_eq!(layout.left, [None, None, None]);
5466        assert_eq!(
5467            layout.right,
5468            [
5469                Some(WindowButton::Minimize),
5470                Some(WindowButton::Maximize),
5471                Some(WindowButton::Close)
5472            ]
5473        );
5474
5475        let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
5476        assert_eq!(round_tripped, layout);
5477    }
5478
5479    #[test]
5480    fn test_window_button_layout_parse_all_invalid() {
5481        assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
5482    }
5483}