Skip to main content

gpui/
platform.rs

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