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