Skip to main content

gpui/
platform.rs

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