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    /// How long the GPU has spent on this window's frames since it opened.
961    /// `None` where the renderer cannot measure it, which is every backend but
962    /// Metal.
963    fn gpu_time(&self) -> Option<Duration> {
964        None
965    }
966    fn schedule_frame(&self) {}
967    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
968    fn is_subpixel_rendering_supported(&self) -> bool;
969
970    // macOS specific methods
971    fn get_title(&self) -> String {
972        String::new()
973    }
974    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
975        None
976    }
977    fn tab_bar_visible(&self) -> bool {
978        false
979    }
980    fn set_edited(&mut self, _edited: bool) {}
981    fn set_document_path(&self, _path: Option<&std::path::Path>) {}
982    fn toggle_simple_fullscreen(&self) {}
983    fn is_simple_fullscreen(&self) -> bool {
984        false
985    }
986    #[cfg(target_os = "macos")]
987    fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
988    fn show_character_palette(&self) {}
989    fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
990    fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
991    fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
992    fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
993    fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
994    fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
995    fn merge_all_windows(&self) {}
996    fn move_tab_to_new_window(&self) {}
997    fn toggle_window_tab_overview(&self) {}
998    fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
999
1000    fn native_window_state(&self) -> Option<Vec<u8>> {
1001        None
1002    }
1003    fn restore_native_window_state(&self, _state: &[u8]) {}
1004
1005    #[cfg(target_os = "windows")]
1006    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
1007
1008    // Linux specific methods
1009    fn inner_window_bounds(&self) -> WindowBounds {
1010        self.window_bounds()
1011    }
1012    fn request_decorations(&self, _decorations: WindowDecorations) {}
1013    fn show_window_menu(&self, _position: Point<Pixels>) {}
1014    fn start_window_move(&self) {}
1015    fn can_start_external_drag(&self) -> bool {
1016        false
1017    }
1018    fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
1019        false
1020    }
1021    fn start_window_resize(&self, _edge: ResizeEdge) {}
1022    fn set_exclusive_zone(&self, _zone: Pixels) {}
1023    #[cfg(all(target_os = "linux", feature = "wayland"))]
1024    fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
1025    fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
1026    fn window_decorations(&self) -> Decorations {
1027        Decorations::Server
1028    }
1029    fn set_app_id(&mut self, _app_id: &str) {}
1030    fn map_window(&mut self) -> anyhow::Result<()> {
1031        Ok(())
1032    }
1033    fn window_controls(&self) -> WindowControls {
1034        WindowControls::default()
1035    }
1036    fn set_client_inset(&self, _inset: Pixels) {}
1037    fn gpu_specs(&self) -> Option<GpuSpecs>;
1038
1039    fn update_ime_position(&self, _bounds: Bounds<Pixels>);
1040
1041    // Mobile platform methods.
1042
1043    /// The regions of this window currently obscured or reserved by the
1044    /// system. Zero on platforms without such regions.
1045    fn insets(&self) -> WindowInsets {
1046        WindowInsets::default()
1047    }
1048
1049    /// Registers a callback invoked whenever [`Self::insets`] change.
1050    ///
1051    /// Contract: fires continuously during animated transitions (Android
1052    /// `WindowInsetsAnimation` progress; on iOS the platform interpolates
1053    /// the keyboard animation curve on frame ticks) and is exact at rest.
1054    fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
1055
1056    /// Sets the handler for the system back action (Android back
1057    /// button/gesture; no source on iOS or desktop).
1058    fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
1059
1060    /// Declares whether the application would currently handle the system
1061    /// back action (e.g. navigation depth > 0).
1062    fn set_back_enabled(&self, _enabled: bool) {}
1063
1064    /// Requests that the soft keyboard be shown.
1065    fn show_soft_keyboard(&self) {}
1066
1067    /// Requests that the soft keyboard be hidden.
1068    fn hide_soft_keyboard(&self) {}
1069
1070    /// Inform the operating system that the text input state has changed
1071    fn text_input_state_changed(&self, _change: TextInputStateChange) {}
1072
1073    fn play_system_bell(&self) {}
1074
1075    /// Initialize the accessibility adapter with callbacks.
1076    fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1077
1078    /// Provide a TreeUpdate to the accessibility adapter.
1079    fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1080
1081    /// Inform the adapter of updated window bounds.
1082    fn a11y_update_window_bounds(&self) {}
1083
1084    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1085    fn as_test(&mut self) -> Option<&mut TestWindow> {
1086        None
1087    }
1088
1089    /// Renders the given scene to a texture and returns the pixel data as an RGBA image.
1090    /// This does not present the frame to screen - useful for visual testing where we want
1091    /// to capture what would be rendered without displaying it or requiring the window to be visible.
1092    #[cfg(any(test, feature = "test-support"))]
1093    fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1094        anyhow::bail!("render_to_image not implemented for this platform")
1095    }
1096}
1097
1098/// A renderer for headless windows that can produce real rendered output.
1099#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1100pub trait PlatformHeadlessRenderer {
1101    /// Render a scene and return the result as an RGBA image.
1102    fn render_scene_to_image(
1103        &mut self,
1104        scene: &Scene,
1105        size: Size<DevicePixels>,
1106    ) -> Result<RgbaImage>;
1107
1108    /// Render a scene to an offscreen target without reading the result back.
1109    ///
1110    /// This is the headless analogue of presenting a frame: it performs the
1111    /// same CPU-side scene encoding and GPU submission as drawing to a real
1112    /// window, but doesn't block on GPU completion or copy pixels back.
1113    fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1114
1115    /// Returns the sprite atlas used by this renderer.
1116    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1117}
1118
1119/// Type alias for runnables with metadata.
1120/// Previously an enum with a single variant, now simplified to a direct type alias.
1121#[doc(hidden)]
1122pub type RunnableVariant = Runnable<RunnableMeta>;
1123
1124#[doc(hidden)]
1125pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1126
1127#[doc(hidden)]
1128pub enum TasksIncluded {
1129    OnlyCompleted,
1130    CompletedAndRunning,
1131}
1132
1133/// This type is public so that our test macro can generate and use it, but it should not
1134/// be considered part of our public API.
1135#[doc(hidden)]
1136pub trait PlatformDispatcher: Send + Sync {
1137    fn is_main_thread(&self) -> bool;
1138    fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1139    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1140    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1141
1142    fn dispatch_on_main_thread_when_idle(
1143        &self,
1144        runnable: RunnableVariant,
1145        timeout: Option<Duration>,
1146    ) {
1147        let _ = timeout;
1148        self.dispatch_on_main_thread(runnable, Priority::Low);
1149    }
1150
1151    fn idle_time_remaining(&self) -> Option<Duration> {
1152        None
1153    }
1154
1155    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1156
1157    fn now(&self) -> Instant {
1158        Instant::now()
1159    }
1160
1161    fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1162        gpui_util::defer(Box::new(|| {}))
1163    }
1164
1165    fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
1166        ActivityGuard::noop()
1167    }
1168
1169    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1170    fn as_test(&self) -> Option<&TestDispatcher> {
1171        None
1172    }
1173
1174    // This cfg must match the `threaded_dispatcher` module's, which implements
1175    // this method whenever it compiles.
1176    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1177    fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1178        None
1179    }
1180}
1181
1182#[expect(missing_docs)]
1183pub trait PlatformTextSystem: Send + Sync {
1184    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1185    /// Installs a nonblocking sink for unresolved grapheme clusters.
1186    fn set_missing_glyph_sink(&self, _sink: Option<Arc<dyn MissingGlyphSink>>) {}
1187    /// Get all available font names.
1188    fn all_font_names(&self) -> Vec<String>;
1189    /// Get the font ID for a font descriptor.
1190    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1191    /// Prewarm any system font caches needed to shape text.
1192    fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1193    /// Get metrics for a font.
1194    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1195    /// Get typographic bounds for a glyph.
1196    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1197    /// Get the advance width for a glyph.
1198    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1199    /// Get the glyph ID for a character.
1200    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1201    /// Get raster bounds for a glyph.
1202    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1203    /// Rasterize a glyph.
1204    fn rasterize_glyph(
1205        &self,
1206        params: &RenderGlyphParams,
1207        raster_bounds: Bounds<DevicePixels>,
1208    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1209    /// Layout a line of text with the given font runs.
1210    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1211    /// Returns the recommended text rendering mode for the given font and size.
1212    fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1213    -> TextRenderingMode;
1214    /// Returns the dilation level to use for a glyph painted in the given color.
1215    fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1216        0
1217    }
1218}
1219
1220#[expect(missing_docs)]
1221pub struct NoopTextSystem;
1222
1223#[expect(missing_docs)]
1224impl NoopTextSystem {
1225    #[allow(dead_code)]
1226    pub fn new() -> Self {
1227        Self
1228    }
1229}
1230
1231impl PlatformTextSystem for NoopTextSystem {
1232    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1233        Ok(())
1234    }
1235
1236    fn all_font_names(&self) -> Vec<String> {
1237        Vec::new()
1238    }
1239
1240    fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1241        Ok(FontId(1))
1242    }
1243
1244    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1245        FontMetrics {
1246            units_per_em: 1000,
1247            ascent: 1025.0,
1248            descent: -275.0,
1249            line_gap: 0.0,
1250            underline_position: -95.0,
1251            underline_thickness: 60.0,
1252            cap_height: 698.0,
1253            x_height: 516.0,
1254            bounding_box: Bounds {
1255                origin: Point {
1256                    x: -260.0,
1257                    y: -245.0,
1258                },
1259                size: Size {
1260                    width: 1501.0,
1261                    height: 1364.0,
1262                },
1263            },
1264        }
1265    }
1266
1267    fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1268        Ok(Bounds {
1269            origin: Point { x: 54.0, y: 0.0 },
1270            size: size(392.0, 528.0),
1271        })
1272    }
1273
1274    fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1275        Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1276    }
1277
1278    fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1279        Some(GlyphId(ch.len_utf16() as u32))
1280    }
1281
1282    fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1283        Ok(Default::default())
1284    }
1285
1286    fn rasterize_glyph(
1287        &self,
1288        _params: &RenderGlyphParams,
1289        raster_bounds: Bounds<DevicePixels>,
1290    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1291        Ok((raster_bounds.size, Vec::new()))
1292    }
1293
1294    fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1295        let mut position = px(0.);
1296        let metrics = self.font_metrics(FontId(0));
1297        let em_width = font_size
1298            * self
1299                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1300                .unwrap()
1301                .width
1302            / metrics.units_per_em as f32;
1303        let mut glyphs = Vec::new();
1304        for (ix, c) in text.char_indices() {
1305            if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1306                glyphs.push(ShapedGlyph {
1307                    id: glyph,
1308                    position: point(position, px(0.)),
1309                    index: ix,
1310                    is_emoji: glyph.0 == 2,
1311                });
1312                if glyph.0 == 2 {
1313                    position += em_width * 2.0;
1314                } else {
1315                    position += em_width;
1316                }
1317            } else {
1318                position += em_width
1319            }
1320        }
1321        let mut runs = Vec::default();
1322        if !glyphs.is_empty() {
1323            runs.push(ShapedRun {
1324                font_id: FontId(0),
1325                glyphs,
1326            });
1327        } else {
1328            position = px(0.);
1329        }
1330
1331        LineLayout {
1332            font_size,
1333            width: position,
1334            ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1335            descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1336            runs,
1337            len: text.len(),
1338        }
1339    }
1340
1341    fn recommended_rendering_mode(
1342        &self,
1343        _font_id: FontId,
1344        _font_size: Pixels,
1345    ) -> TextRenderingMode {
1346        TextRenderingMode::Grayscale
1347    }
1348}
1349
1350// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
1351// Copyright (c) Microsoft Corporation.
1352// Licensed under the MIT license.
1353/// Compute gamma correction ratios for subpixel text rendering.
1354#[allow(dead_code)]
1355pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1356    const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1357        [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
1358        [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
1359        [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
1360        [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
1361        [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
1362        [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
1363        [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
1364        [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
1365        [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
1366        [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
1367        [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
1368        [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
1369        [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
1370    ];
1371
1372    const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1373    const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1374
1375    let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1376    let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1377
1378    [
1379        ratios[0] * NORM13,
1380        ratios[1] * NORM24,
1381        ratios[2] * NORM13,
1382        ratios[3] * NORM24,
1383    ]
1384}
1385
1386#[derive(PartialEq, Eq, Hash, Clone)]
1387#[expect(missing_docs)]
1388pub enum AtlasKey {
1389    Glyph(RenderGlyphParams),
1390    Svg(RenderSvgParams),
1391    Image(RenderImageParams),
1392}
1393
1394impl AtlasKey {
1395    /// Returns the texture kind for this atlas key.
1396    pub fn texture_kind(&self) -> AtlasTextureKind {
1397        match self {
1398            AtlasKey::Glyph(params) => {
1399                if params.is_emoji {
1400                    AtlasTextureKind::Polychrome
1401                } else if params.subpixel_rendering {
1402                    AtlasTextureKind::Subpixel
1403                } else {
1404                    AtlasTextureKind::Monochrome
1405                }
1406            }
1407            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1408            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1409        }
1410    }
1411}
1412
1413impl From<RenderGlyphParams> for AtlasKey {
1414    fn from(params: RenderGlyphParams) -> Self {
1415        Self::Glyph(params)
1416    }
1417}
1418
1419impl From<RenderSvgParams> for AtlasKey {
1420    fn from(params: RenderSvgParams) -> Self {
1421        Self::Svg(params)
1422    }
1423}
1424
1425impl From<RenderImageParams> for AtlasKey {
1426    fn from(params: RenderImageParams) -> Self {
1427        Self::Image(params)
1428    }
1429}
1430
1431#[expect(missing_docs)]
1432pub trait PlatformAtlas {
1433    /// The builder runs with the atlas locked and must not re-enter the same atlas.
1434    fn get_or_insert_with<'a>(
1435        &self,
1436        key: AtlasKey,
1437        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1438    ) -> Result<Option<AtlasTile>>;
1439    fn remove(&self, key: &AtlasKey);
1440
1441    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1442    fn contains(&self, _key: &AtlasKey) -> bool {
1443        false
1444    }
1445}
1446
1447#[doc(hidden)]
1448pub trait AtlasBackend {
1449    fn insert(
1450        &mut self,
1451        kind: AtlasTextureKind,
1452        size: Size<DevicePixels>,
1453        bytes: &[u8],
1454    ) -> Result<AtlasTile>;
1455
1456    fn remove(&mut self, tile: AtlasTile);
1457}
1458
1459#[doc(hidden)]
1460pub struct AtlasState<Backend> {
1461    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
1462    pub backend: Backend,
1463}
1464
1465impl<Backend> AtlasState<Backend> {
1466    pub fn new(backend: Backend) -> Self {
1467        Self {
1468            tiles_by_key: FxHashMap::default(),
1469            backend,
1470        }
1471    }
1472
1473    pub fn contains(&self, key: &AtlasKey) -> bool {
1474        self.tiles_by_key.contains_key(key)
1475    }
1476
1477    pub fn clear(&mut self, reset_backend: impl FnOnce(&mut Backend)) {
1478        self.tiles_by_key.clear();
1479        reset_backend(&mut self.backend);
1480    }
1481}
1482
1483impl<Backend: Default> Default for AtlasState<Backend> {
1484    fn default() -> Self {
1485        Self::new(Backend::default())
1486    }
1487}
1488
1489impl<Backend: AtlasBackend> AtlasState<Backend> {
1490    pub fn get_or_insert_with<'a>(
1491        &mut self,
1492        key: AtlasKey,
1493        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1494    ) -> Result<Option<AtlasTile>> {
1495        match self.tiles_by_key.entry(key) {
1496            Entry::Occupied(entry) => Ok(Some(*entry.get())),
1497            Entry::Vacant(entry) => {
1498                profiling::scope!("new tile");
1499                let Some((size, bytes)) = build()? else {
1500                    return Ok(None);
1501                };
1502                let tile = self
1503                    .backend
1504                    .insert(entry.key().texture_kind(), size, &bytes)?;
1505                entry.insert(tile);
1506                Ok(Some(tile))
1507            }
1508        }
1509    }
1510
1511    pub fn remove(&mut self, key: &AtlasKey) {
1512        if let Some(tile) = self.tiles_by_key.remove(key) {
1513            self.backend.remove(tile);
1514        }
1515    }
1516}
1517
1518/// A sprite atlas for windows without a GPU. It hands out uniquely identified
1519/// tiles without uploading any pixels, so glyph, SVG, and image painting can
1520/// run to completion in tests and headless platforms.
1521#[derive(Default)]
1522pub struct HeadlessAtlas(parking_lot::Mutex<AtlasState<HeadlessAtlasBackend>>);
1523
1524#[doc(hidden)]
1525#[derive(Default)]
1526pub struct HeadlessAtlasBackend {
1527    next_id: u32,
1528}
1529
1530impl AtlasBackend for HeadlessAtlasBackend {
1531    fn insert(
1532        &mut self,
1533        kind: AtlasTextureKind,
1534        size: Size<DevicePixels>,
1535        _bytes: &[u8],
1536    ) -> Result<AtlasTile> {
1537        self.next_id += 1;
1538        let texture_id = self.next_id;
1539        self.next_id += 1;
1540        let tile_id = self.next_id;
1541        Ok(AtlasTile {
1542            texture_id: AtlasTextureId {
1543                index: texture_id,
1544                kind,
1545            },
1546            tile_id: TileId(tile_id),
1547            padding: 0,
1548            bounds: Bounds {
1549                origin: Point::default(),
1550                size,
1551            },
1552        })
1553    }
1554
1555    fn remove(&mut self, _tile: AtlasTile) {}
1556}
1557
1558impl PlatformAtlas for HeadlessAtlas {
1559    fn get_or_insert_with<'a>(
1560        &self,
1561        key: AtlasKey,
1562        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1563    ) -> Result<Option<AtlasTile>> {
1564        self.0.lock().get_or_insert_with(key, build)
1565    }
1566
1567    fn remove(&self, key: &AtlasKey) {
1568        self.0.lock().remove(key);
1569    }
1570
1571    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1572    fn contains(&self, key: &AtlasKey) -> bool {
1573        self.0.lock().contains(key)
1574    }
1575}
1576
1577#[doc(hidden)]
1578pub struct AtlasTextureList<T> {
1579    pub textures: Vec<Option<T>>,
1580    pub free_list: Vec<usize>,
1581}
1582
1583impl<T> Default for AtlasTextureList<T> {
1584    fn default() -> Self {
1585        Self {
1586            textures: Vec::default(),
1587            free_list: Vec::default(),
1588        }
1589    }
1590}
1591
1592impl<T> ops::Index<usize> for AtlasTextureList<T> {
1593    type Output = Option<T>;
1594
1595    fn index(&self, index: usize) -> &Self::Output {
1596        &self.textures[index]
1597    }
1598}
1599
1600impl<T> AtlasTextureList<T> {
1601    #[allow(unused)]
1602    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1603        self.free_list.clear();
1604        self.textures.drain(..)
1605    }
1606
1607    #[allow(dead_code)]
1608    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1609        self.textures.iter_mut().flatten()
1610    }
1611}
1612
1613#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1614#[repr(C)]
1615#[expect(missing_docs)]
1616pub struct AtlasTile {
1617    /// The texture this tile belongs to.
1618    pub texture_id: AtlasTextureId,
1619    /// The unique ID of this tile within its texture.
1620    pub tile_id: TileId,
1621    /// Padding around the tile content in pixels.
1622    pub padding: u32,
1623    /// The bounds of this tile within the texture.
1624    pub bounds: Bounds<DevicePixels>,
1625}
1626
1627#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1628#[repr(C)]
1629#[expect(missing_docs)]
1630pub struct AtlasTextureId {
1631    // We use u32 instead of usize for Metal Shader Language compatibility
1632    /// The index of this texture in the atlas.
1633    pub index: u32,
1634    /// The kind of content stored in this texture.
1635    pub kind: AtlasTextureKind,
1636}
1637
1638#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1639#[repr(C)]
1640#[cfg_attr(
1641    all(
1642        any(target_os = "linux", target_os = "freebsd"),
1643        not(any(feature = "x11", feature = "wayland"))
1644    ),
1645    allow(dead_code)
1646)]
1647#[expect(missing_docs)]
1648pub enum AtlasTextureKind {
1649    Monochrome = 0,
1650    Polychrome = 1,
1651    Subpixel = 2,
1652}
1653
1654#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1655#[repr(C)]
1656#[expect(missing_docs)]
1657pub struct TileId(pub u32);
1658
1659impl From<etagere::AllocId> for TileId {
1660    fn from(id: etagere::AllocId) -> Self {
1661        Self(id.serialize())
1662    }
1663}
1664
1665impl From<TileId> for etagere::AllocId {
1666    fn from(id: TileId) -> Self {
1667        Self::deserialize(id.0)
1668    }
1669}
1670
1671#[expect(missing_docs)]
1672pub struct PlatformInputHandler {
1673    cx: AsyncWindowContext,
1674    handler: Box<dyn InputHandler>,
1675}
1676
1677#[expect(missing_docs)]
1678#[cfg_attr(
1679    all(
1680        any(target_os = "linux", target_os = "freebsd"),
1681        not(any(feature = "x11", feature = "wayland"))
1682    ),
1683    allow(dead_code)
1684)]
1685impl PlatformInputHandler {
1686    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1687        Self { cx, handler }
1688    }
1689
1690    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1691        self.cx
1692            .update(|window, cx| {
1693                self.handler
1694                    .selected_text_range(ignore_disabled_input, window, cx)
1695            })
1696            .ok()
1697            .flatten()
1698    }
1699
1700    #[cfg_attr(target_os = "windows", allow(dead_code))]
1701    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1702        self.cx
1703            .update(|window, cx| self.handler.marked_text_range(window, cx))
1704            .ok()
1705            .flatten()
1706    }
1707
1708    #[cfg_attr(
1709        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1710        allow(dead_code)
1711    )]
1712    pub fn text_for_range(
1713        &mut self,
1714        range_utf16: Range<usize>,
1715        adjusted: &mut Option<Range<usize>>,
1716    ) -> Option<String> {
1717        self.cx
1718            .update(|window, cx| {
1719                self.handler
1720                    .text_for_range(range_utf16, adjusted, window, cx)
1721            })
1722            .ok()
1723            .flatten()
1724    }
1725
1726    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1727        self.cx
1728            .update(|window, cx| {
1729                self.handler
1730                    .replace_text_in_range(replacement_range, text, window, cx);
1731            })
1732            .ok();
1733    }
1734
1735    pub fn replace_and_mark_text_in_range(
1736        &mut self,
1737        range_utf16: Option<Range<usize>>,
1738        new_text: &str,
1739        new_selected_range: Option<Range<usize>>,
1740    ) {
1741        self.cx
1742            .update(|window, cx| {
1743                self.handler.replace_and_mark_text_in_range(
1744                    range_utf16,
1745                    new_text,
1746                    new_selected_range,
1747                    window,
1748                    cx,
1749                )
1750            })
1751            .ok();
1752    }
1753
1754    #[cfg_attr(target_os = "windows", allow(dead_code))]
1755    pub fn unmark_text(&mut self) {
1756        self.cx
1757            .update(|window, cx| self.handler.unmark_text(window, cx))
1758            .ok();
1759    }
1760
1761    pub fn paste(&mut self, item: ClipboardItem) {
1762        self.cx
1763            .update(|window, cx| self.handler.paste(item, window, cx))
1764            .ok();
1765    }
1766
1767    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1768        self.cx
1769            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1770            .ok()
1771            .flatten()
1772    }
1773
1774    #[allow(dead_code)]
1775    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1776        self.handler.apple_press_and_hold_enabled()
1777    }
1778
1779    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1780        self.handler.replace_text_in_range(None, input, window, cx);
1781    }
1782
1783    pub fn compute_ime_candidate_bounds(
1784        marked_range: Option<Range<usize>>,
1785        selection: &UTF16Selection,
1786        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1787    ) -> Option<Bounds<Pixels>> {
1788        if let Some(marked_range) = marked_range {
1789            // Default to the start of the marked (composing) range.
1790            let mut line_start = marked_range.start;
1791
1792            // Walk backward from the caret looking for a line break. A change in
1793            // the Y coordinate means we crossed into the previous visual line, so
1794            // the line start is one position after the break point.
1795            let caret = selection.range.end;
1796            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1797                for i in (marked_range.start..caret).rev() {
1798                    if let Some(b) = bounds_for_range(i..i) {
1799                        if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1800                            line_start = i + 1;
1801                            break;
1802                        }
1803                    }
1804                }
1805            }
1806            bounds_for_range(line_start..line_start)
1807        } else {
1808            // No active composition — use the selection endpoint.
1809            let offset = if selection.reversed {
1810                selection.range.start
1811            } else {
1812                selection.range.end
1813            };
1814            bounds_for_range(offset..offset)
1815        }
1816    }
1817
1818    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1819        let marked_range = self.handler.marked_text_range(window, cx);
1820        let selection = self.handler.selected_text_range(true, window, cx)?;
1821        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1822            self.handler.bounds_for_range(range, window, cx)
1823        })
1824    }
1825
1826    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1827        let marked_range = self.marked_text_range();
1828        let selection = self.selected_text_range(true)?;
1829        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1830            self.bounds_for_range(range)
1831        })
1832    }
1833
1834    #[allow(unused)]
1835    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1836        self.cx
1837            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1838            .ok()
1839            .flatten()
1840    }
1841
1842    /// See [`InputHandler::set_selected_text_range`].
1843    pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1844        self.cx
1845            .update(|window, cx| {
1846                self.handler
1847                    .set_selected_text_range(range_utf16, window, cx)
1848            })
1849            .ok();
1850    }
1851
1852    /// See [`InputHandler::element_bounds`].
1853    pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1854        self.cx
1855            .update(|window, cx| self.handler.element_bounds(window, cx))
1856            .ok()
1857            .flatten()
1858    }
1859
1860    /// See [`InputHandler::text_length_utf16`].
1861    pub fn text_length_utf16(&mut self) -> Option<usize> {
1862        self.cx
1863            .update(|window, cx| self.handler.text_length_utf16(window, cx))
1864            .ok()
1865            .flatten()
1866    }
1867
1868    #[allow(dead_code)]
1869    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1870        self.handler.accepts_text_input(window, cx)
1871    }
1872
1873    #[allow(dead_code)]
1874    pub fn query_accepts_text_input(&mut self) -> bool {
1875        self.cx
1876            .update(|window, cx| self.handler.accepts_text_input(window, cx))
1877            .unwrap_or(true)
1878    }
1879
1880    /// See [`InputHandler::prefers_ime_for_printable_keys`].
1881    ///
1882    /// This is not a pure delegation to the handler: while a multi-stroke binding is pending this
1883    /// returns `false` regardless of the handler's preference, because the next printable key may
1884    /// complete a binding whose prefix already bypassed the IME.
1885    pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1886        self.cx
1887            .update(|window, cx| {
1888                // The next printable key may complete a chord whose prefix bypassed the IME.
1889                !window.has_pending_keystrokes()
1890                    && self.handler.prefers_ime_for_printable_keys(window, cx)
1891            })
1892            .unwrap_or(false)
1893    }
1894
1895    /// See [`InputHandler::text_input_configuration`].
1896    pub fn text_input_configuration(
1897        &mut self,
1898        window: &mut Window,
1899        cx: &mut App,
1900    ) -> TextInputConfiguration {
1901        self.handler.text_input_configuration(window, cx)
1902    }
1903
1904    /// See [`InputHandler::text_input_editable_range`].
1905    pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
1906        self.cx
1907            .update(|window, cx| self.handler.text_input_editable_range(window, cx))
1908            .ok()
1909            .flatten()
1910    }
1911}
1912
1913/// A struct representing a selection in a text buffer, in UTF16 characters.
1914/// This is different from a range because the head may be before the tail.
1915#[derive(Debug)]
1916pub struct UTF16Selection {
1917    /// The range of text in the document this selection corresponds to
1918    /// in UTF16 characters.
1919    pub range: Range<usize>,
1920    /// Whether the head of this selection is at the start (true), or end (false)
1921    /// of the range
1922    pub reversed: bool,
1923}
1924
1925/// Zed's interface for handling text input from the platform's IME system
1926/// This is currently a 1:1 exposure of the NSTextInputClient API:
1927///
1928/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
1929pub trait InputHandler: 'static {
1930    /// Get the range of the user's currently selected text, if any
1931    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
1932    ///
1933    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1934    fn selected_text_range(
1935        &mut self,
1936        ignore_disabled_input: bool,
1937        window: &mut Window,
1938        cx: &mut App,
1939    ) -> Option<UTF16Selection>;
1940
1941    /// Get the range of the currently marked text, if any
1942    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
1943    ///
1944    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1945    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1946
1947    /// Get the text for the given document range in UTF-16 characters
1948    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
1949    ///
1950    /// range_utf16 is in terms of UTF-16 characters
1951    fn text_for_range(
1952        &mut self,
1953        range_utf16: Range<usize>,
1954        adjusted_range: &mut Option<Range<usize>>,
1955        window: &mut Window,
1956        cx: &mut App,
1957    ) -> Option<String>;
1958
1959    /// Replace the text in the given document range with the given text
1960    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
1961    ///
1962    /// replacement_range is in terms of UTF-16 characters
1963    fn replace_text_in_range(
1964        &mut self,
1965        replacement_range: Option<Range<usize>>,
1966        text: &str,
1967        window: &mut Window,
1968        cx: &mut App,
1969    );
1970
1971    /// Replace the text in the given document range with the given text,
1972    /// and mark the given text as part of an IME 'composing' state
1973    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
1974    ///
1975    /// range_utf16 is in terms of UTF-16 characters
1976    /// new_selected_range is in terms of UTF-16 characters
1977    fn replace_and_mark_text_in_range(
1978        &mut self,
1979        range_utf16: Option<Range<usize>>,
1980        new_text: &str,
1981        new_selected_range: Option<Range<usize>>,
1982        window: &mut Window,
1983        cx: &mut App,
1984    );
1985
1986    /// Remove the IME 'composing' state from the document
1987    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
1988    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1989
1990    /// Insert a platform-initiated paste at the current selection.
1991    ///
1992    /// Platforms that deliver paste as an input event rather than through an
1993    /// application-defined action (e.g. the DOM `paste` event on web) call
1994    /// this with the full clipboard contents. The default implementation
1995    /// inserts only the plain-text portion of the item.
1996    fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1997        if let Some(text) = item.text() {
1998            self.replace_text_in_range(None, &text, window, cx);
1999        }
2000    }
2001
2002    /// Get the bounds of the given document range in screen coordinates
2003    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
2004    ///
2005    /// This is used for positioning the IME candidate window
2006    fn bounds_for_range(
2007        &mut self,
2008        range_utf16: Range<usize>,
2009        window: &mut Window,
2010        cx: &mut App,
2011    ) -> Option<Bounds<Pixels>>;
2012
2013    /// Get the character offset for the given point in terms of UTF16 characters
2014    ///
2015    /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
2016    fn character_index_for_point(
2017        &mut self,
2018        point: Point<Pixels>,
2019        window: &mut Window,
2020        cx: &mut App,
2021    ) -> Option<usize>;
2022
2023    /// Set the range of the user's currently selected text.
2024    ///
2025    /// This is the reverse data-flow direction from [`Self::selected_text_range`]:
2026    /// platforms call it when the system text machinery moves the selection on the
2027    /// application's behalf — e.g. the user drags a system selection handle or
2028    /// invokes Select All from system UI (iOS `UITextInput setSelectedTextRange:`,
2029    /// Android `InputConnection.setSelection`).
2030    ///
2031    /// range_utf16 is in terms of UTF-16 characters, from 0 to the length of the document
2032    fn set_selected_text_range(
2033        &mut self,
2034        _range_utf16: Range<usize>,
2035        _window: &mut Window,
2036        _cx: &mut App,
2037    ) {
2038    }
2039
2040    /// Get the bounds of the focused text element in window coordinates, if known.
2041    ///
2042    /// This is the pull counterpart to the [`PlatformWindow::update_ime_position`]
2043    /// push: mobile platforms ask for the focused element's geometry when they
2044    /// need it (e.g. to frame system text-interaction UI overlaid on the focused
2045    /// element).
2046    fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
2047        None
2048    }
2049
2050    /// Get the length of the document in UTF-16 characters, if known.
2051    fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
2052        None
2053    }
2054
2055    /// Allows a given input context to opt into getting raw key repeats instead of
2056    /// sending these to the platform.
2057    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
2058    /// (which is how iTerm does it) but it doesn't seem to work for me.
2059    #[allow(dead_code)]
2060    fn apple_press_and_hold_enabled(&mut self) -> bool {
2061        true
2062    }
2063
2064    /// Returns whether this handler is accepting text input to be inserted.
2065    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2066        true
2067    }
2068
2069    /// The contiguous range of text, in UTF-16 code units, that platform text
2070    /// input may read and edit around the current selection.
2071    ///
2072    /// Platforms that mirror document text into an IME-editable buffer clamp
2073    /// the mirrored window to this range, so multi-step IME edit gestures
2074    /// (word deletion, autocorrect rewrites, suggestion picks) cannot reach
2075    /// content outside it. The range should contain the current selection;
2076    /// when it cannot (a selection spanning a region boundary), platforms
2077    /// degrade the mirrored IME context rather than widening the range.
2078    /// `None` places no bound.
2079    fn text_input_editable_range(
2080        &mut self,
2081        _window: &mut Window,
2082        _cx: &mut App,
2083    ) -> Option<Range<usize>> {
2084        None
2085    }
2086
2087    /// Returns whether printable keys should be routed to the IME before keybinding
2088    /// matching when a non-ASCII input source (e.g. Japanese, Korean, Chinese IME)
2089    /// is active. This prevents multi-stroke keybindings like `jj` from intercepting
2090    /// keys that the IME should compose.
2091    ///
2092    /// Defaults to `false`. The editor overrides this based on whether it expects
2093    /// character input (e.g. Vim insert mode returns `true`, normal mode returns `false`).
2094    /// The terminal keeps the default `false` so that raw keys reach the terminal process.
2095    fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2096        false
2097    }
2098
2099    /// Get this handler's preferences for platform text assistance.
2100    ///
2101    /// GPUI re-queries this every frame and forwards it to the platform window
2102    /// only when it changes, so implementations must be cheap and may vary the
2103    /// result with application state (e.g. with the cursor's position).
2104    fn text_input_configuration(
2105        &mut self,
2106        _window: &mut Window,
2107        _cx: &mut App,
2108    ) -> TextInputConfiguration {
2109        TextInputConfiguration::default()
2110    }
2111}
2112
2113/// Platform text-assistance preferences for the focused text region.
2114///
2115/// Returned by [`InputHandler::text_input_configuration`] and forwarded to the
2116/// platform whenever it changes; the platform maps the fields onto its native
2117/// input-session attributes (on web, DOM attributes of the hidden editable
2118/// element such as `autocorrect` and `enterkeyhint`).
2119///
2120/// The default disables all text assistance and requests no particular action
2121/// key presentation.
2122#[derive(Clone, Debug, Default, PartialEq, Eq)]
2123pub struct TextInputConfiguration {
2124    /// Whether the platform may automatically correct entered text.
2125    pub autocorrect: bool,
2126    /// How software keyboards automatically capitalize entered text.
2127    pub autocapitalize: Autocapitalize,
2128    /// Whether software keyboards may offer word suggestions and spellcheck.
2129    pub suggestions: bool,
2130    /// The action advertised on a software keyboard's confirm ("enter") key.
2131    pub input_action: TextInputAction,
2132}
2133
2134/// Automatic capitalization applied by software keyboards.
2135#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2136pub enum Autocapitalize {
2137    /// No automatic capitalization.
2138    #[default]
2139    None,
2140    /// Capitalize the first letter of each word.
2141    Words,
2142    /// Capitalize the first letter of each sentence.
2143    Sentences,
2144    /// Capitalize every letter.
2145    Characters,
2146}
2147
2148/// The action a software keyboard advertises on its confirm ("enter") key.
2149///
2150/// This affects only how the key is presented (icon or label); pressing it is
2151/// still delivered as ordinary input.
2152///
2153/// The variants are the HTML `enterkeyhint` attribute's value set
2154/// (<https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-enterkeyhint-attribute>),
2155/// which also maps onto Android's `IME_ACTION_*` constants and iOS's
2156/// `UIReturnKeyType`; [`TextInputAction::Unspecified`] means "emit no hint".
2157#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2158pub enum TextInputAction {
2159    /// Let the platform choose its default presentation.
2160    #[default]
2161    Unspecified,
2162    /// Inserting a line break.
2163    Enter,
2164    /// Committing the field's value.
2165    Done,
2166    /// Navigating to the typed target.
2167    Go,
2168    /// Moving to the next field.
2169    Next,
2170    /// Moving to the previous field.
2171    Previous,
2172    /// Executing a search.
2173    Search,
2174    /// Sending a message.
2175    Send,
2176}
2177
2178/// The variables that can be configured when creating a new window
2179#[derive(Debug)]
2180pub struct WindowOptions {
2181    /// Specifies the state and bounds of the window in screen coordinates.
2182    /// - `None`: Inherit the bounds.
2183    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
2184    pub window_bounds: Option<WindowBounds>,
2185
2186    /// The titlebar configuration of the window
2187    pub titlebar: Option<TitlebarOptions>,
2188
2189    /// Whether the window should be focused when created
2190    pub focus: bool,
2191
2192    /// Whether the window should be shown when created
2193    pub show: bool,
2194
2195    /// The kind of window to create
2196    pub kind: WindowKind,
2197
2198    /// Whether the window can be moved by the user. When `false`, the user cannot drag
2199    /// the window (on macOS this sets `NSWindow.isMovable`, which also disables the
2200    /// Window-menu tiling items); programmatic moves are still allowed.
2201    pub is_movable: bool,
2202
2203    /// Whether the application owns dragging of the (custom) titlebar, rather than
2204    /// AppKit. Only has an effect on macOS.
2205    ///
2206    /// Set this to `true` for windows that draw their own titlebar and move the window
2207    /// themselves via [`Window::start_window_move`]. It marks the whole content view as
2208    /// app-owned titlebar content, so AppKit neither drags the window from the titlebar
2209    /// nor delays titlebar clicks while disambiguating double-clicks (a delay first
2210    /// observed on macOS 27). It is independent of `is_movable`, so such windows stay
2211    /// user-movable (via their own drag) and keep the Window-menu tiling items enabled.
2212    ///
2213    /// Leave this `false` for windows that rely on AppKit's native titlebar dragging.
2214    pub app_owns_titlebar_drag: bool,
2215
2216    /// The minimum interval between animation frames while the window is inactive.
2217    ///
2218    /// Set to `None` to disable inactive-window animation frame throttling.
2219    pub inactive_frame_interval: Option<Duration>,
2220
2221    /// Whether the window should be resizable by the user
2222    pub is_resizable: bool,
2223
2224    /// Whether the window should be minimized by the user
2225    pub is_minimizable: bool,
2226
2227    /// The display to create the window on, if this is None,
2228    /// the window will be created on the main display
2229    pub display_id: Option<DisplayId>,
2230
2231    /// The appearance of the window background.
2232    pub window_background: WindowBackgroundAppearance,
2233
2234    /// Application identifier of the window. Can by used by desktop environments to group applications together.
2235    pub app_id: Option<String>,
2236
2237    /// Window minimum size
2238    pub window_min_size: Option<Size<Pixels>>,
2239
2240    /// Whether to use client or server-side decorations on X11 and Wayland.
2241    /// The platform may ignore requests it cannot satisfy.
2242    pub window_decorations: Option<WindowDecorations>,
2243
2244    /// Icon image (X11 only)
2245    pub icon: Option<Arc<image::RgbaImage>>,
2246
2247    /// 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.
2248    pub tabbing_identifier: Option<String>,
2249}
2250
2251/// The variables that can be configured when creating a new window
2252#[derive(Debug)]
2253#[cfg_attr(
2254    all(
2255        any(target_os = "linux", target_os = "freebsd"),
2256        not(any(feature = "x11", feature = "wayland"))
2257    ),
2258    allow(dead_code)
2259)]
2260#[allow(missing_docs)]
2261pub struct WindowParams {
2262    pub bounds: Bounds<Pixels>,
2263
2264    /// The titlebar configuration of the window
2265    #[cfg_attr(feature = "wayland", allow(dead_code))]
2266    pub titlebar: Option<TitlebarOptions>,
2267
2268    /// The kind of window to create
2269    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2270    pub kind: WindowKind,
2271
2272    /// Whether the window should be movable by the user
2273    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2274    pub is_movable: bool,
2275
2276    /// Whether the application owns dragging of the (custom) titlebar (macOS only)
2277    #[cfg_attr(
2278        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2279        allow(dead_code)
2280    )]
2281    pub app_owns_titlebar_drag: bool,
2282
2283    /// Whether the window should be resizable by the user
2284    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2285    pub is_resizable: bool,
2286
2287    /// Whether the window should be minimized by the user
2288    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2289    pub is_minimizable: bool,
2290
2291    #[cfg_attr(
2292        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2293        allow(dead_code)
2294    )]
2295    pub focus: bool,
2296
2297    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2298    pub show: bool,
2299
2300    /// An image to set as the window icon (x11 only)
2301    #[cfg_attr(feature = "wayland", allow(dead_code))]
2302    pub icon: Option<Arc<image::RgbaImage>>,
2303
2304    #[cfg_attr(feature = "wayland", allow(dead_code))]
2305    pub display_id: Option<DisplayId>,
2306
2307    #[cfg_attr(feature = "wayland", allow(dead_code))]
2308    pub app_id: Option<String>,
2309
2310    pub window_min_size: Option<Size<Pixels>>,
2311
2312    #[cfg(target_os = "macos")]
2313    pub tabbing_identifier: Option<String>,
2314}
2315
2316/// Represents the status of how a window should be opened.
2317#[derive(Debug, Copy, Clone, PartialEq)]
2318pub enum WindowBounds {
2319    /// Indicates that the window should open in a windowed state with the given bounds.
2320    Windowed(Bounds<Pixels>),
2321    /// Indicates that the window should open in a maximized state.
2322    /// The bounds provided here represent the restore size of the window.
2323    Maximized(Bounds<Pixels>),
2324    /// Indicates that the window should open in fullscreen mode.
2325    /// The bounds provided here represent the restore size of the window.
2326    Fullscreen(Bounds<Pixels>),
2327}
2328
2329impl Default for WindowBounds {
2330    fn default() -> Self {
2331        WindowBounds::Windowed(Bounds::default())
2332    }
2333}
2334
2335impl WindowBounds {
2336    /// Retrieve the inner bounds
2337    pub fn get_bounds(&self) -> Bounds<Pixels> {
2338        match self {
2339            WindowBounds::Windowed(bounds) => *bounds,
2340            WindowBounds::Maximized(bounds) => *bounds,
2341            WindowBounds::Fullscreen(bounds) => *bounds,
2342        }
2343    }
2344
2345    /// Creates a new window bounds that centers the window on the screen.
2346    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2347        WindowBounds::Windowed(Bounds::centered(None, size, cx))
2348    }
2349}
2350
2351impl Default for WindowOptions {
2352    fn default() -> Self {
2353        Self {
2354            window_bounds: None,
2355            titlebar: Some(TitlebarOptions {
2356                title: Default::default(),
2357                appears_transparent: Default::default(),
2358                traffic_light_position: Default::default(),
2359            }),
2360            focus: true,
2361            show: true,
2362            kind: WindowKind::Normal,
2363            is_movable: true,
2364            app_owns_titlebar_drag: false,
2365            inactive_frame_interval: Some(Duration::from_micros(33_333)),
2366            is_resizable: true,
2367            is_minimizable: true,
2368            display_id: None,
2369            window_background: WindowBackgroundAppearance::default(),
2370            icon: None,
2371            app_id: None,
2372            window_min_size: None,
2373            window_decorations: None,
2374            tabbing_identifier: None,
2375        }
2376    }
2377}
2378
2379/// The options that can be configured for a window's titlebar
2380#[derive(Debug, Default)]
2381pub struct TitlebarOptions {
2382    /// The initial title of the window
2383    pub title: Option<SharedString>,
2384
2385    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
2386    /// Refer to [`WindowOptions::window_decorations`] on Linux
2387    pub appears_transparent: bool,
2388
2389    /// The position of the macOS traffic light buttons
2390    pub traffic_light_position: Option<Point<Pixels>>,
2391}
2392
2393/// The kind of window to create
2394#[derive(Clone, Debug, PartialEq, Eq)]
2395pub enum WindowKind {
2396    /// A normal application window
2397    Normal,
2398
2399    /// A window that appears above all other windows, usually used for alerts or popups
2400    /// use sparingly!
2401    PopUp,
2402
2403    /// A parent-anchored, platform-native popup window for menus, comboboxes, context menus and
2404    /// tooltips. Unlike [`WindowKind::PopUp`], it is positioned relative to a parent window.
2405    ///
2406    /// The popup's size comes from [`WindowOptions::window_bounds`], whose origin is ignored.
2407    /// See [`popup::PopupOptions`] for the placement options. Platforms without a native
2408    /// implementation reject it with [`popup::PopupNotSupportedError`].
2409    AnchoredPopup(popup::PopupOptions),
2410
2411    /// A floating window that appears on top of its parent window
2412    Floating,
2413
2414    /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
2415    /// docks, notifications or wallpapers.
2416    #[cfg(all(target_os = "linux", feature = "wayland"))]
2417    LayerShell(layer_shell::LayerShellOptions),
2418
2419    /// A window that appears on top of its parent window and blocks interaction with it
2420    /// until the modal window is closed
2421    Dialog,
2422}
2423
2424/// The appearance of the window, as defined by the operating system.
2425///
2426/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
2427/// values.
2428#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2429pub enum WindowAppearance {
2430    /// A light appearance.
2431    ///
2432    /// On macOS, this corresponds to the `aqua` appearance.
2433    #[default]
2434    Light,
2435
2436    /// A light appearance with vibrant colors.
2437    ///
2438    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
2439    VibrantLight,
2440
2441    /// A dark appearance.
2442    ///
2443    /// On macOS, this corresponds to the `darkAqua` appearance.
2444    Dark,
2445
2446    /// A dark appearance with vibrant colors.
2447    ///
2448    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
2449    VibrantDark,
2450}
2451
2452/// The appearance of the background of the window itself, when there is
2453/// no content or the content is transparent.
2454#[derive(Copy, Clone, Debug, Default, PartialEq)]
2455pub enum WindowBackgroundAppearance {
2456    /// Opaque.
2457    ///
2458    /// This lets the window manager know that content behind this
2459    /// window does not need to be drawn.
2460    ///
2461    /// Actual color depends on the system and themes should define a fully
2462    /// opaque background color instead.
2463    #[default]
2464    Opaque,
2465    /// Plain alpha transparency.
2466    Transparent,
2467    /// Transparency, but the contents behind the window are blurred.
2468    ///
2469    /// Not always supported.
2470    Blurred,
2471    /// The Mica backdrop material, supported on Windows 11.
2472    MicaBackdrop,
2473    /// The Mica Alt backdrop material, supported on Windows 11.
2474    MicaAltBackdrop,
2475}
2476
2477/// The text rendering mode to use for drawing glyphs.
2478#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2479pub enum TextRenderingMode {
2480    /// Use the platform's default text rendering mode.
2481    #[default]
2482    PlatformDefault,
2483    /// Use subpixel (ClearType-style) text rendering.
2484    Subpixel,
2485    /// Use grayscale text rendering.
2486    Grayscale,
2487}
2488
2489/// The options that can be configured for a file dialog prompt
2490#[derive(Clone, Debug)]
2491pub struct PathPromptOptions {
2492    /// Should the prompt allow files to be selected?
2493    pub files: bool,
2494    /// Should the prompt allow directories to be selected?
2495    pub directories: bool,
2496    /// Should the prompt allow multiple files to be selected?
2497    pub multiple: bool,
2498    /// The prompt to show to a user when selecting a path
2499    pub prompt: Option<SharedString>,
2500}
2501
2502/// What kind of prompt styling to show
2503#[derive(Copy, Clone, Debug, PartialEq)]
2504pub enum PromptLevel {
2505    /// A prompt that is shown when the user should be notified of something
2506    Info,
2507
2508    /// A prompt that is shown when the user needs to be warned of a potential problem
2509    Warning,
2510
2511    /// A prompt that is shown when a critical problem has occurred
2512    Critical,
2513}
2514
2515/// Prompt Button
2516#[derive(Clone, Debug, PartialEq)]
2517pub enum PromptButton {
2518    /// Ok button
2519    Ok(SharedString),
2520    /// Cancel button
2521    Cancel(SharedString),
2522    /// Other button
2523    Other(SharedString),
2524}
2525
2526impl PromptButton {
2527    /// Create a button with label
2528    pub fn new(label: impl Into<SharedString>) -> Self {
2529        PromptButton::Other(label.into())
2530    }
2531
2532    /// Create an Ok button
2533    pub fn ok(label: impl Into<SharedString>) -> Self {
2534        PromptButton::Ok(label.into())
2535    }
2536
2537    /// Create a Cancel button
2538    pub fn cancel(label: impl Into<SharedString>) -> Self {
2539        PromptButton::Cancel(label.into())
2540    }
2541
2542    /// Returns true if this button is a cancel button.
2543    #[allow(dead_code)]
2544    pub fn is_cancel(&self) -> bool {
2545        matches!(self, PromptButton::Cancel(_))
2546    }
2547
2548    /// Returns the label of the button
2549    pub fn label(&self) -> &SharedString {
2550        match self {
2551            PromptButton::Ok(label) => label,
2552            PromptButton::Cancel(label) => label,
2553            PromptButton::Other(label) => label,
2554        }
2555    }
2556}
2557
2558impl From<&str> for PromptButton {
2559    fn from(value: &str) -> Self {
2560        match value.to_lowercase().as_str() {
2561            "ok" => PromptButton::Ok("OK".into()),
2562            "cancel" => PromptButton::Cancel("Cancel".into()),
2563            _ => PromptButton::Other(SharedString::from(value.to_owned())),
2564        }
2565    }
2566}
2567
2568/// The style of the cursor (pointer)
2569#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2570pub enum CursorStyle {
2571    /// The default cursor
2572    #[default]
2573    Arrow,
2574
2575    /// A text input cursor
2576    /// corresponds to the CSS cursor value `text`
2577    IBeam,
2578
2579    /// A crosshair cursor
2580    /// corresponds to the CSS cursor value `crosshair`
2581    Crosshair,
2582
2583    /// A closed hand cursor
2584    /// corresponds to the CSS cursor value `grabbing`
2585    ClosedHand,
2586
2587    /// An open hand cursor
2588    /// corresponds to the CSS cursor value `grab`
2589    OpenHand,
2590
2591    /// A pointing hand cursor
2592    /// corresponds to the CSS cursor value `pointer`
2593    PointingHand,
2594
2595    /// A resize left cursor
2596    /// corresponds to the CSS cursor value `w-resize`
2597    ResizeLeft,
2598
2599    /// A resize right cursor
2600    /// corresponds to the CSS cursor value `e-resize`
2601    ResizeRight,
2602
2603    /// A resize cursor to the left and right
2604    /// corresponds to the CSS cursor value `ew-resize`
2605    ResizeLeftRight,
2606
2607    /// A resize up cursor
2608    /// corresponds to the CSS cursor value `n-resize`
2609    ResizeUp,
2610
2611    /// A resize down cursor
2612    /// corresponds to the CSS cursor value `s-resize`
2613    ResizeDown,
2614
2615    /// A resize cursor directing up and down
2616    /// corresponds to the CSS cursor value `ns-resize`
2617    ResizeUpDown,
2618
2619    /// A resize cursor directing up-left and down-right
2620    /// corresponds to the CSS cursor value `nesw-resize`
2621    ResizeUpLeftDownRight,
2622
2623    /// A resize cursor directing up-right and down-left
2624    /// corresponds to the CSS cursor value `nwse-resize`
2625    ResizeUpRightDownLeft,
2626
2627    /// A cursor indicating that the item/column can be resized horizontally.
2628    /// corresponds to the CSS cursor value `col-resize`
2629    ResizeColumn,
2630
2631    /// A cursor indicating that the item/row can be resized vertically.
2632    /// corresponds to the CSS cursor value `row-resize`
2633    ResizeRow,
2634
2635    /// A text input cursor for vertical layout
2636    /// corresponds to the CSS cursor value `vertical-text`
2637    IBeamCursorForVerticalLayout,
2638
2639    /// A cursor indicating that the operation is not allowed
2640    /// corresponds to the CSS cursor value `not-allowed`
2641    OperationNotAllowed,
2642
2643    /// A cursor indicating that the operation will result in a link
2644    /// corresponds to the CSS cursor value `alias`
2645    DragLink,
2646
2647    /// A cursor indicating that the operation will result in a copy
2648    /// corresponds to the CSS cursor value `copy`
2649    DragCopy,
2650
2651    /// A cursor indicating that the operation will result in a context menu
2652    /// corresponds to the CSS cursor value `context-menu`
2653    ContextualMenu,
2654}
2655
2656/// A clipboard item that should be copied to the clipboard
2657#[derive(Clone, Debug, Eq, PartialEq)]
2658pub struct ClipboardItem {
2659    /// The entries in this clipboard item.
2660    pub entries: Vec<ClipboardEntry>,
2661}
2662
2663/// An error produced by [`Platform::read_from_clipboard_async`].
2664///
2665/// Callers surface these failures to users, so the variants distinguish
2666/// conditions that call for different user-facing guidance.
2667#[derive(Clone, Debug, PartialEq, Eq)]
2668pub enum ClipboardReadError {
2669    /// The platform clipboard is not available in this context, e.g. the
2670    /// browser does not expose the async clipboard API or the page is not a
2671    /// secure context.
2672    Unavailable,
2673    /// The platform refused access, e.g. the user declined the browser's
2674    /// clipboard permission prompt or paste confirmation.
2675    Denied(String),
2676    /// The clipboard contents could not be converted into a
2677    /// [`ClipboardItem`].
2678    UnsupportedContent,
2679}
2680
2681impl std::fmt::Display for ClipboardReadError {
2682    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2683        match self {
2684            Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2685            Self::Denied(message) => {
2686                write!(formatter, "clipboard access was denied: {message}")
2687            }
2688            Self::UnsupportedContent => {
2689                formatter.write_str("the clipboard contents are unsupported")
2690            }
2691        }
2692    }
2693}
2694
2695impl std::error::Error for ClipboardReadError {}
2696
2697/// Either a ClipboardString or a ClipboardImage
2698#[derive(Clone, Debug, Eq, PartialEq)]
2699pub enum ClipboardEntry {
2700    /// A string entry
2701    String(ClipboardString),
2702    /// An image entry
2703    Image(Image),
2704    /// A file entry
2705    ExternalPaths(crate::ExternalPaths),
2706}
2707
2708impl ClipboardItem {
2709    /// Create a new ClipboardItem::String with no associated metadata
2710    pub fn new_string(text: String) -> Self {
2711        Self {
2712            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2713        }
2714    }
2715
2716    /// Create a new ClipboardItem::String with the given text and associated metadata
2717    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2718        Self {
2719            entries: vec![ClipboardEntry::String(ClipboardString {
2720                text,
2721                metadata: Some(metadata),
2722            })],
2723        }
2724    }
2725
2726    /// Create a new ClipboardItem::String with the given text and associated metadata
2727    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2728        Self {
2729            entries: vec![ClipboardEntry::String(
2730                ClipboardString::new(text).with_json_metadata(metadata),
2731            )],
2732        }
2733    }
2734
2735    /// Create a new ClipboardItem::Image with the given image with no associated metadata
2736    pub fn new_image(image: &Image) -> Self {
2737        Self {
2738            entries: vec![ClipboardEntry::Image(image.clone())],
2739        }
2740    }
2741
2742    /// Concatenates together all the ClipboardString entries in the item.
2743    /// Returns None if there were no ClipboardString entries.
2744    pub fn text(&self) -> Option<String> {
2745        let mut answer = String::new();
2746
2747        for entry in self.entries.iter() {
2748            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2749                answer.push_str(text);
2750            }
2751        }
2752
2753        if answer.is_empty() {
2754            for entry in self.entries.iter() {
2755                if let ClipboardEntry::ExternalPaths(paths) = entry {
2756                    for path in &paths.0 {
2757                        use std::fmt::Write as _;
2758                        _ = write!(answer, "{}", path.display());
2759                    }
2760                }
2761            }
2762        }
2763
2764        if !answer.is_empty() {
2765            Some(answer)
2766        } else {
2767            None
2768        }
2769    }
2770
2771    /// If this item is one ClipboardEntry::String, returns its metadata.
2772    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2773    pub fn metadata(&self) -> Option<&String> {
2774        match self.entries().first() {
2775            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2776                clipboard_string.metadata.as_ref()
2777            }
2778            _ => None,
2779        }
2780    }
2781
2782    /// Get the item's entries
2783    pub fn entries(&self) -> &[ClipboardEntry] {
2784        &self.entries
2785    }
2786
2787    /// Get owned versions of the item's entries
2788    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2789        self.entries.into_iter()
2790    }
2791}
2792
2793impl From<ClipboardString> for ClipboardEntry {
2794    fn from(value: ClipboardString) -> Self {
2795        Self::String(value)
2796    }
2797}
2798
2799impl From<String> for ClipboardEntry {
2800    fn from(value: String) -> Self {
2801        Self::from(ClipboardString::from(value))
2802    }
2803}
2804
2805impl From<Image> for ClipboardEntry {
2806    fn from(value: Image) -> Self {
2807        Self::Image(value)
2808    }
2809}
2810
2811impl From<ClipboardEntry> for ClipboardItem {
2812    fn from(value: ClipboardEntry) -> Self {
2813        Self {
2814            entries: vec![value],
2815        }
2816    }
2817}
2818
2819impl From<String> for ClipboardItem {
2820    fn from(value: String) -> Self {
2821        Self::from(ClipboardEntry::from(value))
2822    }
2823}
2824
2825impl From<Image> for ClipboardItem {
2826    fn from(value: Image) -> Self {
2827        Self::from(ClipboardEntry::from(value))
2828    }
2829}
2830
2831/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
2832#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2833pub enum ImageFormat {
2834    // Sorted from most to least likely to be pasted into an editor,
2835    // which matters when we iterate through them trying to see if
2836    // clipboard content matches them.
2837    /// .png
2838    Png,
2839    /// .jpeg or .jpg
2840    Jpeg,
2841    /// .webp
2842    Webp,
2843    /// .gif
2844    Gif,
2845    /// .svg
2846    Svg,
2847    /// .bmp
2848    Bmp,
2849    /// .tif or .tiff
2850    Tiff,
2851    /// .ico
2852    Ico,
2853    /// Netpbm image formats (.pbm, .ppm, .pgm).
2854    Pnm,
2855}
2856
2857impl ImageFormat {
2858    /// Returns the mime type for the ImageFormat
2859    pub const fn mime_type(self) -> &'static str {
2860        match self {
2861            ImageFormat::Png => "image/png",
2862            ImageFormat::Jpeg => "image/jpeg",
2863            ImageFormat::Webp => "image/webp",
2864            ImageFormat::Gif => "image/gif",
2865            ImageFormat::Svg => "image/svg+xml",
2866            ImageFormat::Bmp => "image/bmp",
2867            ImageFormat::Tiff => "image/tiff",
2868            ImageFormat::Ico => "image/ico",
2869            ImageFormat::Pnm => "image/x-portable-anymap",
2870        }
2871    }
2872
2873    /// Returns the file extension for this image format (without leading dot).
2874    pub const fn extension(self) -> &'static str {
2875        match self {
2876            ImageFormat::Png => "png",
2877            ImageFormat::Jpeg => "jpg",
2878            ImageFormat::Webp => "webp",
2879            ImageFormat::Gif => "gif",
2880            ImageFormat::Svg => "svg",
2881            ImageFormat::Bmp => "bmp",
2882            ImageFormat::Tiff => "tiff",
2883            ImageFormat::Ico => "ico",
2884            ImageFormat::Pnm => "pnm",
2885        }
2886    }
2887
2888    /// Returns the ImageFormat for the given mime type, including known aliases.
2889    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2890        use strum::IntoEnumIterator;
2891        Self::iter()
2892            .find(|format| format.mime_type() == mime_type)
2893            .or_else(|| Self::from_mime_type_alias(mime_type))
2894    }
2895
2896    /// Non-canonical mime types that some producers use in the wild.
2897    /// Unlike `mime_type()` which returns the single canonical form,
2898    /// these are legacy or shortened variants we still need to recognize.
2899    fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2900        match mime_type {
2901            "image/jpg" => Some(Self::Jpeg),
2902            "image/tif" => Some(Self::Tiff),
2903            _ => None,
2904        }
2905    }
2906}
2907
2908/// An image, with a format and certain bytes
2909#[derive(Clone, Debug, PartialEq, Eq)]
2910pub struct Image {
2911    /// The image format the bytes represent (e.g. PNG)
2912    pub format: ImageFormat,
2913    /// The raw image bytes
2914    pub bytes: Vec<u8>,
2915    /// The unique ID for the image
2916    pub id: u64,
2917}
2918
2919pub(crate) fn decode_static_image(
2920    bytes: &[u8],
2921    format: image::ImageFormat,
2922) -> Result<SmallVec<[Frame; 1]>> {
2923    let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2924        .into_decoder()
2925        .context("creating image decoder")?;
2926    decode_static_image_from_decoder(decoder)
2927}
2928
2929pub(crate) fn decode_static_image_from_decoder(
2930    mut decoder: impl image::ImageDecoder,
2931) -> Result<SmallVec<[Frame; 1]>> {
2932    let orientation = decoder
2933        .orientation()
2934        .context("reading decoder's orientation")?;
2935    let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2936    image.apply_orientation(orientation);
2937
2938    let mut data = image.into_rgba8();
2939    for pixel in data.chunks_exact_mut(4) {
2940        pixel.swap(0, 2);
2941    }
2942
2943    Ok(SmallVec::from_elem(Frame::new(data), 1))
2944}
2945
2946impl Hash for Image {
2947    fn hash<H: Hasher>(&self, state: &mut H) {
2948        state.write_u64(self.id);
2949    }
2950}
2951
2952impl Image {
2953    /// An empty image containing no data
2954    pub fn empty() -> Self {
2955        Self::from_bytes(ImageFormat::Png, Vec::new())
2956    }
2957
2958    /// Create an image from a format and bytes
2959    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2960        Self {
2961            id: hash(&bytes),
2962            format,
2963            bytes,
2964        }
2965    }
2966
2967    /// Get this image's ID
2968    pub fn id(&self) -> u64 {
2969        self.id
2970    }
2971
2972    /// Use the GPUI `use_asset` API to make this image renderable
2973    pub fn use_render_image(
2974        self: Arc<Self>,
2975        window: &mut Window,
2976        cx: &mut App,
2977    ) -> Option<Arc<RenderImage>> {
2978        ImageSource::Image(self)
2979            .use_data(None, window, cx)
2980            .and_then(|result| result.ok())
2981    }
2982
2983    /// Use the GPUI `get_asset` API to make this image renderable
2984    pub fn get_render_image(
2985        self: Arc<Self>,
2986        window: &mut Window,
2987        cx: &mut App,
2988    ) -> Option<Arc<RenderImage>> {
2989        ImageSource::Image(self)
2990            .get_data(None, window, cx)
2991            .and_then(|result| result.ok())
2992    }
2993
2994    /// Use the GPUI `remove_asset` API to drop this image, if possible.
2995    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2996        ImageSource::Image(self).remove_asset(cx);
2997    }
2998
2999    /// Check whether this image is present in GPUI's asset cache (loading or
3000    /// loaded), without fetching it.
3001    #[cfg(any(test, feature = "test-support"))]
3002    pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
3003        ImageSource::Image(self.clone()).is_asset_cached(cx)
3004    }
3005
3006    /// Convert the clipboard image to an `ImageData` object.
3007    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
3008        let frames = match self.format {
3009            ImageFormat::Gif => {
3010                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
3011                let mut frames = SmallVec::new();
3012
3013                for frame in decoder.into_frames() {
3014                    match frame {
3015                        Ok(mut frame) => {
3016                            // Convert from RGBA to BGRA.
3017                            for pixel in frame.buffer_mut().chunks_exact_mut(4) {
3018                                pixel.swap(0, 2);
3019                            }
3020                            frames.push(frame);
3021                        }
3022                        Err(err) => {
3023                            log::debug!("Skipping GIF frame due to decode error: {err}");
3024                        }
3025                    }
3026                }
3027
3028                if frames.is_empty() {
3029                    anyhow::bail!("GIF could not be decoded: all frames failed");
3030                }
3031
3032                frames
3033            }
3034            ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
3035            ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
3036            ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
3037            ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
3038            ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
3039            ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
3040            ImageFormat::Svg => {
3041                return svg_renderer
3042                    .render_single_frame(&self.bytes, 1.0)
3043                    .map_err(Into::into);
3044            }
3045            ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
3046        };
3047
3048        Ok(Arc::new(RenderImage::new(frames)))
3049    }
3050
3051    /// Get the format of the clipboard image
3052    pub fn format(&self) -> ImageFormat {
3053        self.format
3054    }
3055
3056    /// Get the raw bytes of the clipboard image
3057    pub fn bytes(&self) -> &[u8] {
3058        self.bytes.as_slice()
3059    }
3060}
3061
3062/// A clipboard item that should be copied to the clipboard
3063#[derive(Clone, Debug, Eq, PartialEq)]
3064pub struct ClipboardString {
3065    /// The text content.
3066    pub text: String,
3067    /// Optional metadata associated with this clipboard string.
3068    pub metadata: Option<String>,
3069}
3070
3071impl ClipboardString {
3072    /// Create a new clipboard string with the given text
3073    pub fn new(text: String) -> Self {
3074        Self {
3075            text,
3076            metadata: None,
3077        }
3078    }
3079
3080    /// Return a new clipboard item with the metadata replaced by the given metadata,
3081    /// after serializing it as JSON.
3082    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
3083        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
3084        self
3085    }
3086
3087    /// Get the text of the clipboard string
3088    pub fn text(&self) -> &String {
3089        &self.text
3090    }
3091
3092    /// Get the owned text of the clipboard string
3093    pub fn into_text(self) -> String {
3094        self.text
3095    }
3096
3097    /// Get the metadata of the clipboard string, formatted as JSON
3098    pub fn metadata_json<T>(&self) -> Option<T>
3099    where
3100        T: for<'a> Deserialize<'a>,
3101    {
3102        self.metadata
3103            .as_ref()
3104            .and_then(|m| serde_json::from_str(m).ok())
3105    }
3106
3107    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3108    /// Compute a hash of the given text for clipboard change detection.
3109    pub fn text_hash(text: &str) -> u64 {
3110        let mut hasher = SeaHasher::new();
3111        text.hash(&mut hasher);
3112        hasher.finish()
3113    }
3114}
3115
3116impl From<String> for ClipboardString {
3117    fn from(value: String) -> Self {
3118        Self {
3119            text: value,
3120            metadata: None,
3121        }
3122    }
3123}
3124
3125#[cfg(test)]
3126mod image_tests {
3127    use super::*;
3128    use std::sync::Arc;
3129
3130    #[test]
3131    fn test_image_to_image_data_applies_exif_orientation() {
3132        let image = Image::from_bytes(
3133            ImageFormat::Jpeg,
3134            include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
3135        );
3136
3137        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3138
3139        assert_eq!(render_image.size(0), size(16.into(), 32.into()));
3140
3141        let bytes = render_image.as_bytes(0).unwrap();
3142        assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
3143        assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
3144    }
3145
3146    #[test]
3147    fn test_svg_image_to_image_data_converts_to_bgra() {
3148        let image = Image::from_bytes(
3149            ImageFormat::Svg,
3150            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3151<rect width="1" height="1" fill="#38BDF8"/>
3152</svg>"##
3153                .to_vec(),
3154        );
3155
3156        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3157        let bytes = render_image.as_bytes(0).unwrap();
3158
3159        for pixel in bytes.chunks_exact(4) {
3160            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3161        }
3162    }
3163}
3164
3165#[cfg(test)]
3166mod atlas_tests {
3167    use super::*;
3168
3169    const TILE_SIZE: Size<DevicePixels> = Size {
3170        width: DevicePixels(1),
3171        height: DevicePixels(1),
3172    };
3173
3174    #[derive(Default)]
3175    struct RecordingAtlasBackend {
3176        insert_calls: u32,
3177        fail_next_insert: bool,
3178        removed_tiles: Vec<AtlasTile>,
3179    }
3180
3181    impl AtlasBackend for RecordingAtlasBackend {
3182        fn insert(
3183            &mut self,
3184            kind: AtlasTextureKind,
3185            size: Size<DevicePixels>,
3186            _bytes: &[u8],
3187        ) -> Result<AtlasTile> {
3188            self.insert_calls += 1;
3189            if std::mem::take(&mut self.fail_next_insert) {
3190                anyhow::bail!("backend failed");
3191            }
3192            Ok(AtlasTile {
3193                texture_id: AtlasTextureId { index: 0, kind },
3194                tile_id: TileId(self.insert_calls),
3195                padding: 0,
3196                bounds: Bounds {
3197                    origin: Point::default(),
3198                    size,
3199                },
3200            })
3201        }
3202
3203        fn remove(&mut self, tile: AtlasTile) {
3204            self.removed_tiles.push(tile);
3205        }
3206    }
3207
3208    fn image_key(image_id: usize) -> AtlasKey {
3209        AtlasKey::Image(RenderImageParams {
3210            image_id: crate::ImageId(image_id),
3211            frame_index: 0,
3212        })
3213    }
3214
3215    fn build_tile() -> Result<Option<(Size<DevicePixels>, Cow<'static, [u8]>)>> {
3216        Ok(Some((TILE_SIZE, Cow::Borrowed(&[0, 0, 0, 255]))))
3217    }
3218
3219    #[test]
3220    fn only_successful_inserts_are_cached() -> Result<()> {
3221        let mut state = AtlasState::new(RecordingAtlasBackend::default());
3222        let key = image_key(1);
3223
3224        assert_eq!(
3225            state.get_or_insert_with(key.clone(), &mut || Ok(None))?,
3226            None
3227        );
3228        state
3229            .get_or_insert_with(key.clone(), &mut || anyhow::bail!("builder failed"))
3230            .expect_err("builder error should propagate");
3231        assert!(!state.contains(&key));
3232        assert_eq!(state.backend.insert_calls, 0);
3233
3234        state.backend.fail_next_insert = true;
3235        state
3236            .get_or_insert_with(key.clone(), &mut build_tile)
3237            .expect_err("backend error should propagate");
3238        assert!(!state.contains(&key));
3239        assert_eq!(state.backend.insert_calls, 1);
3240
3241        let tile = state
3242            .get_or_insert_with(key.clone(), &mut build_tile)?
3243            .context("builder should produce a tile")?;
3244        assert_eq!(tile.texture_id.kind, key.texture_kind());
3245        assert_eq!(
3246            state.get_or_insert_with(key.clone(), &mut || {
3247                anyhow::bail!("cache hit must not call the builder")
3248            })?,
3249            Some(tile)
3250        );
3251        assert!(state.contains(&key));
3252        assert_eq!(state.backend.insert_calls, 2);
3253        Ok(())
3254    }
3255
3256    #[test]
3257    fn remove_and_clear_invalidate_keys() -> Result<()> {
3258        let mut state = AtlasState::new(RecordingAtlasBackend::default());
3259        let key = image_key(1);
3260        let other_key = image_key(2);
3261        let tile = state
3262            .get_or_insert_with(key.clone(), &mut build_tile)?
3263            .context("builder should produce a tile")?;
3264        state
3265            .get_or_insert_with(other_key.clone(), &mut build_tile)?
3266            .context("builder should produce another tile")?;
3267
3268        state.remove(&key);
3269        state.remove(&key);
3270        assert!(!state.contains(&key));
3271        assert!(state.contains(&other_key));
3272        assert_eq!(state.backend.removed_tiles, vec![tile]);
3273
3274        let mut reset_calls = 0;
3275        state.clear(|_| reset_calls += 1);
3276        assert_eq!(reset_calls, 1);
3277        assert!(!state.contains(&other_key));
3278        assert_eq!(state.backend.removed_tiles, vec![tile]);
3279        Ok(())
3280    }
3281}
3282
3283#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3284mod tests {
3285    use super::*;
3286    use std::collections::HashSet;
3287
3288    #[test]
3289    fn test_window_button_layout_parse_standard() {
3290        let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3291        assert_eq!(
3292            layout.left,
3293            [
3294                Some(WindowButton::Close),
3295                Some(WindowButton::Minimize),
3296                None
3297            ]
3298        );
3299        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3300    }
3301
3302    #[test]
3303    fn test_window_button_layout_parse_right_only() {
3304        let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3305        assert_eq!(layout.left, [None, None, None]);
3306        assert_eq!(
3307            layout.right,
3308            [
3309                Some(WindowButton::Minimize),
3310                Some(WindowButton::Maximize),
3311                Some(WindowButton::Close)
3312            ]
3313        );
3314    }
3315
3316    #[test]
3317    fn test_window_button_layout_parse_left_only() {
3318        let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3319        assert_eq!(
3320            layout.left,
3321            [
3322                Some(WindowButton::Close),
3323                Some(WindowButton::Minimize),
3324                Some(WindowButton::Maximize)
3325            ]
3326        );
3327        assert_eq!(layout.right, [None, None, None]);
3328    }
3329
3330    #[test]
3331    fn test_window_button_layout_parse_with_whitespace() {
3332        let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3333        assert_eq!(
3334            layout.left,
3335            [
3336                Some(WindowButton::Close),
3337                Some(WindowButton::Minimize),
3338                None
3339            ]
3340        );
3341        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3342    }
3343
3344    #[test]
3345    fn test_window_button_layout_parse_empty() {
3346        let layout = WindowButtonLayout::parse("").unwrap();
3347        assert_eq!(layout.left, [None, None, None]);
3348        assert_eq!(layout.right, [None, None, None]);
3349    }
3350
3351    #[test]
3352    fn test_window_button_layout_parse_intentionally_empty() {
3353        let layout = WindowButtonLayout::parse(":").unwrap();
3354        assert_eq!(layout.left, [None, None, None]);
3355        assert_eq!(layout.right, [None, None, None]);
3356    }
3357
3358    #[test]
3359    fn test_window_button_layout_parse_invalid_buttons() {
3360        let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3361        assert_eq!(
3362            layout.left,
3363            [
3364                Some(WindowButton::Close),
3365                Some(WindowButton::Minimize),
3366                None
3367            ]
3368        );
3369        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3370    }
3371
3372    #[test]
3373    fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3374        let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3375        assert_eq!(
3376            layout.right,
3377            [
3378                Some(WindowButton::Close),
3379                Some(WindowButton::Minimize),
3380                None
3381            ]
3382        );
3383        assert_eq!(layout.format(), ":close,minimize");
3384    }
3385
3386    #[test]
3387    fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3388        let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3389        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3390        assert_eq!(
3391            layout.right,
3392            [
3393                Some(WindowButton::Maximize),
3394                Some(WindowButton::Minimize),
3395                None
3396            ]
3397        );
3398
3399        let button_ids: Vec<_> = layout
3400            .left
3401            .iter()
3402            .chain(layout.right.iter())
3403            .flatten()
3404            .map(WindowButton::id)
3405            .collect();
3406        let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3407        assert_eq!(unique_button_ids.len(), button_ids.len());
3408        assert_eq!(layout.format(), "close:maximize,minimize");
3409    }
3410
3411    #[test]
3412    fn test_window_button_layout_parse_gnome_style() {
3413        let layout = WindowButtonLayout::parse("close").unwrap();
3414        assert_eq!(layout.left, [None, None, None]);
3415        assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3416    }
3417
3418    #[test]
3419    fn test_window_button_layout_parse_elementary_style() {
3420        let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3421        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3422        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3423    }
3424
3425    #[test]
3426    fn test_window_button_layout_round_trip() {
3427        let cases = [
3428            "close:minimize,maximize",
3429            "minimize,maximize,close:",
3430            ":close",
3431            "close:",
3432            "close:maximize",
3433            ":",
3434        ];
3435
3436        for case in cases {
3437            let layout = WindowButtonLayout::parse(case).unwrap();
3438            assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3439        }
3440    }
3441
3442    #[test]
3443    fn test_window_button_layout_linux_default() {
3444        let layout = WindowButtonLayout::linux_default();
3445        assert_eq!(layout.left, [None, None, None]);
3446        assert_eq!(
3447            layout.right,
3448            [
3449                Some(WindowButton::Minimize),
3450                Some(WindowButton::Maximize),
3451                Some(WindowButton::Close)
3452            ]
3453        );
3454
3455        let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3456        assert_eq!(round_tripped, layout);
3457    }
3458
3459    #[test]
3460    fn test_window_button_layout_parse_all_invalid() {
3461        assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3462    }
3463}