Skip to main content

gpui/
platform.rs

1mod app_menu;
2mod keyboard;
3mod keystroke;
4
5#[cfg(all(target_os = "linux", feature = "wayland"))]
6#[expect(missing_docs)]
7pub mod layer_shell;
8
9/// Types for configuring parent-anchored popup windows such as menus, dropdowns and tooltips.
10pub mod popup;
11
12#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
13mod threaded_dispatcher;
14
15#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
16mod test;
17
18#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
19mod visual_test;
20
21#[cfg(all(
22    feature = "screen-capture",
23    any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
24))]
25pub mod scap_screen_capture;
26
27#[cfg(all(
28    any(target_os = "windows", target_os = "linux"),
29    feature = "screen-capture"
30))]
31pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
32#[cfg(not(feature = "screen-capture"))]
33pub(crate) type PlatformScreenCaptureFrame = ();
34#[cfg(all(target_os = "macos", feature = "screen-capture"))]
35pub(crate) type PlatformScreenCaptureFrame = 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", feature = "bench-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", feature = "bench-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", feature = "bench-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() -> bool>);
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 schedule_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", feature = "bench-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", feature = "bench-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", feature = "bench-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", feature = "bench-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    /// Prewarm any system font caches needed to shape text.
1079    fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1080    /// Get metrics for a font.
1081    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1082    /// Get typographic bounds for a glyph.
1083    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1084    /// Get the advance width for a glyph.
1085    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1086    /// Get the glyph ID for a character.
1087    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1088    /// Get raster bounds for a glyph.
1089    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1090    /// Rasterize a glyph.
1091    fn rasterize_glyph(
1092        &self,
1093        params: &RenderGlyphParams,
1094        raster_bounds: Bounds<DevicePixels>,
1095    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1096    /// Layout a line of text with the given font runs.
1097    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1098    /// Returns the recommended text rendering mode for the given font and size.
1099    fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1100    -> TextRenderingMode;
1101    /// Returns the dilation level to use for a glyph painted in the given color.
1102    fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1103        0
1104    }
1105}
1106
1107#[expect(missing_docs)]
1108pub struct NoopTextSystem;
1109
1110#[expect(missing_docs)]
1111impl NoopTextSystem {
1112    #[allow(dead_code)]
1113    pub fn new() -> Self {
1114        Self
1115    }
1116}
1117
1118impl PlatformTextSystem for NoopTextSystem {
1119    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1120        Ok(())
1121    }
1122
1123    fn all_font_names(&self) -> Vec<String> {
1124        Vec::new()
1125    }
1126
1127    fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1128        Ok(FontId(1))
1129    }
1130
1131    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1132        FontMetrics {
1133            units_per_em: 1000,
1134            ascent: 1025.0,
1135            descent: -275.0,
1136            line_gap: 0.0,
1137            underline_position: -95.0,
1138            underline_thickness: 60.0,
1139            cap_height: 698.0,
1140            x_height: 516.0,
1141            bounding_box: Bounds {
1142                origin: Point {
1143                    x: -260.0,
1144                    y: -245.0,
1145                },
1146                size: Size {
1147                    width: 1501.0,
1148                    height: 1364.0,
1149                },
1150            },
1151        }
1152    }
1153
1154    fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1155        Ok(Bounds {
1156            origin: Point { x: 54.0, y: 0.0 },
1157            size: size(392.0, 528.0),
1158        })
1159    }
1160
1161    fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1162        Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1163    }
1164
1165    fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1166        Some(GlyphId(ch.len_utf16() as u32))
1167    }
1168
1169    fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1170        Ok(Default::default())
1171    }
1172
1173    fn rasterize_glyph(
1174        &self,
1175        _params: &RenderGlyphParams,
1176        raster_bounds: Bounds<DevicePixels>,
1177    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1178        Ok((raster_bounds.size, Vec::new()))
1179    }
1180
1181    fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1182        let mut position = px(0.);
1183        let metrics = self.font_metrics(FontId(0));
1184        let em_width = font_size
1185            * self
1186                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1187                .unwrap()
1188                .width
1189            / metrics.units_per_em as f32;
1190        let mut glyphs = Vec::new();
1191        for (ix, c) in text.char_indices() {
1192            if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1193                glyphs.push(ShapedGlyph {
1194                    id: glyph,
1195                    position: point(position, px(0.)),
1196                    index: ix,
1197                    is_emoji: glyph.0 == 2,
1198                });
1199                if glyph.0 == 2 {
1200                    position += em_width * 2.0;
1201                } else {
1202                    position += em_width;
1203                }
1204            } else {
1205                position += em_width
1206            }
1207        }
1208        let mut runs = Vec::default();
1209        if !glyphs.is_empty() {
1210            runs.push(ShapedRun {
1211                font_id: FontId(0),
1212                glyphs,
1213            });
1214        } else {
1215            position = px(0.);
1216        }
1217
1218        LineLayout {
1219            font_size,
1220            width: position,
1221            ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1222            descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1223            runs,
1224            len: text.len(),
1225        }
1226    }
1227
1228    fn recommended_rendering_mode(
1229        &self,
1230        _font_id: FontId,
1231        _font_size: Pixels,
1232    ) -> TextRenderingMode {
1233        TextRenderingMode::Grayscale
1234    }
1235}
1236
1237// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
1238// Copyright (c) Microsoft Corporation.
1239// Licensed under the MIT license.
1240/// Compute gamma correction ratios for subpixel text rendering.
1241#[allow(dead_code)]
1242pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1243    const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1244        [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
1245        [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
1246        [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
1247        [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
1248        [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
1249        [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
1250        [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
1251        [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
1252        [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
1253        [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
1254        [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
1255        [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
1256        [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
1257    ];
1258
1259    const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1260    const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1261
1262    let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1263    let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1264
1265    [
1266        ratios[0] * NORM13,
1267        ratios[1] * NORM24,
1268        ratios[2] * NORM13,
1269        ratios[3] * NORM24,
1270    ]
1271}
1272
1273#[derive(PartialEq, Eq, Hash, Clone)]
1274#[expect(missing_docs)]
1275pub enum AtlasKey {
1276    Glyph(RenderGlyphParams),
1277    Svg(RenderSvgParams),
1278    Image(RenderImageParams),
1279}
1280
1281impl AtlasKey {
1282    #[cfg_attr(
1283        all(
1284            any(target_os = "linux", target_os = "freebsd"),
1285            not(any(feature = "x11", feature = "wayland"))
1286        ),
1287        allow(dead_code)
1288    )]
1289    /// Returns the texture kind for this atlas key.
1290    pub fn texture_kind(&self) -> AtlasTextureKind {
1291        match self {
1292            AtlasKey::Glyph(params) => {
1293                if params.is_emoji {
1294                    AtlasTextureKind::Polychrome
1295                } else if params.subpixel_rendering {
1296                    AtlasTextureKind::Subpixel
1297                } else {
1298                    AtlasTextureKind::Monochrome
1299                }
1300            }
1301            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1302            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1303        }
1304    }
1305}
1306
1307impl From<RenderGlyphParams> for AtlasKey {
1308    fn from(params: RenderGlyphParams) -> Self {
1309        Self::Glyph(params)
1310    }
1311}
1312
1313impl From<RenderSvgParams> for AtlasKey {
1314    fn from(params: RenderSvgParams) -> Self {
1315        Self::Svg(params)
1316    }
1317}
1318
1319impl From<RenderImageParams> for AtlasKey {
1320    fn from(params: RenderImageParams) -> Self {
1321        Self::Image(params)
1322    }
1323}
1324
1325#[expect(missing_docs)]
1326pub trait PlatformAtlas {
1327    fn get_or_insert_with<'a>(
1328        &self,
1329        key: &AtlasKey,
1330        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1331    ) -> Result<Option<AtlasTile>>;
1332    fn remove(&self, key: &AtlasKey);
1333
1334    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1335    fn contains(&self, _key: &AtlasKey) -> bool {
1336        false
1337    }
1338}
1339
1340#[doc(hidden)]
1341pub struct AtlasTextureList<T> {
1342    pub textures: Vec<Option<T>>,
1343    pub free_list: Vec<usize>,
1344}
1345
1346impl<T> Default for AtlasTextureList<T> {
1347    fn default() -> Self {
1348        Self {
1349            textures: Vec::default(),
1350            free_list: Vec::default(),
1351        }
1352    }
1353}
1354
1355impl<T> ops::Index<usize> for AtlasTextureList<T> {
1356    type Output = Option<T>;
1357
1358    fn index(&self, index: usize) -> &Self::Output {
1359        &self.textures[index]
1360    }
1361}
1362
1363impl<T> AtlasTextureList<T> {
1364    #[allow(unused)]
1365    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1366        self.free_list.clear();
1367        self.textures.drain(..)
1368    }
1369
1370    #[allow(dead_code)]
1371    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1372        self.textures.iter_mut().flatten()
1373    }
1374}
1375
1376#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1377#[repr(C)]
1378#[expect(missing_docs)]
1379pub struct AtlasTile {
1380    /// The texture this tile belongs to.
1381    pub texture_id: AtlasTextureId,
1382    /// The unique ID of this tile within its texture.
1383    pub tile_id: TileId,
1384    /// Padding around the tile content in pixels.
1385    pub padding: u32,
1386    /// The bounds of this tile within the texture.
1387    pub bounds: Bounds<DevicePixels>,
1388}
1389
1390#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1391#[repr(C)]
1392#[expect(missing_docs)]
1393pub struct AtlasTextureId {
1394    // We use u32 instead of usize for Metal Shader Language compatibility
1395    /// The index of this texture in the atlas.
1396    pub index: u32,
1397    /// The kind of content stored in this texture.
1398    pub kind: AtlasTextureKind,
1399}
1400
1401#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1402#[repr(C)]
1403#[cfg_attr(
1404    all(
1405        any(target_os = "linux", target_os = "freebsd"),
1406        not(any(feature = "x11", feature = "wayland"))
1407    ),
1408    allow(dead_code)
1409)]
1410#[expect(missing_docs)]
1411pub enum AtlasTextureKind {
1412    Monochrome = 0,
1413    Polychrome = 1,
1414    Subpixel = 2,
1415}
1416
1417#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1418#[repr(C)]
1419#[expect(missing_docs)]
1420pub struct TileId(pub u32);
1421
1422impl From<etagere::AllocId> for TileId {
1423    fn from(id: etagere::AllocId) -> Self {
1424        Self(id.serialize())
1425    }
1426}
1427
1428impl From<TileId> for etagere::AllocId {
1429    fn from(id: TileId) -> Self {
1430        Self::deserialize(id.0)
1431    }
1432}
1433
1434#[expect(missing_docs)]
1435pub struct PlatformInputHandler {
1436    cx: AsyncWindowContext,
1437    handler: Box<dyn InputHandler>,
1438}
1439
1440#[expect(missing_docs)]
1441#[cfg_attr(
1442    all(
1443        any(target_os = "linux", target_os = "freebsd"),
1444        not(any(feature = "x11", feature = "wayland"))
1445    ),
1446    allow(dead_code)
1447)]
1448impl PlatformInputHandler {
1449    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1450        Self { cx, handler }
1451    }
1452
1453    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1454        self.cx
1455            .update(|window, cx| {
1456                self.handler
1457                    .selected_text_range(ignore_disabled_input, window, cx)
1458            })
1459            .ok()
1460            .flatten()
1461    }
1462
1463    #[cfg_attr(target_os = "windows", allow(dead_code))]
1464    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1465        self.cx
1466            .update(|window, cx| self.handler.marked_text_range(window, cx))
1467            .ok()
1468            .flatten()
1469    }
1470
1471    #[cfg_attr(
1472        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1473        allow(dead_code)
1474    )]
1475    pub fn text_for_range(
1476        &mut self,
1477        range_utf16: Range<usize>,
1478        adjusted: &mut Option<Range<usize>>,
1479    ) -> Option<String> {
1480        self.cx
1481            .update(|window, cx| {
1482                self.handler
1483                    .text_for_range(range_utf16, adjusted, window, cx)
1484            })
1485            .ok()
1486            .flatten()
1487    }
1488
1489    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1490        self.cx
1491            .update(|window, cx| {
1492                self.handler
1493                    .replace_text_in_range(replacement_range, text, window, cx);
1494            })
1495            .ok();
1496    }
1497
1498    pub fn replace_and_mark_text_in_range(
1499        &mut self,
1500        range_utf16: Option<Range<usize>>,
1501        new_text: &str,
1502        new_selected_range: Option<Range<usize>>,
1503    ) {
1504        self.cx
1505            .update(|window, cx| {
1506                self.handler.replace_and_mark_text_in_range(
1507                    range_utf16,
1508                    new_text,
1509                    new_selected_range,
1510                    window,
1511                    cx,
1512                )
1513            })
1514            .ok();
1515    }
1516
1517    #[cfg_attr(target_os = "windows", allow(dead_code))]
1518    pub fn unmark_text(&mut self) {
1519        self.cx
1520            .update(|window, cx| self.handler.unmark_text(window, cx))
1521            .ok();
1522    }
1523
1524    pub fn paste(&mut self, item: ClipboardItem) {
1525        self.cx
1526            .update(|window, cx| self.handler.paste(item, window, cx))
1527            .ok();
1528    }
1529
1530    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1531        self.cx
1532            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1533            .ok()
1534            .flatten()
1535    }
1536
1537    #[allow(dead_code)]
1538    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1539        self.handler.apple_press_and_hold_enabled()
1540    }
1541
1542    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1543        self.handler.replace_text_in_range(None, input, window, cx);
1544    }
1545
1546    pub fn compute_ime_candidate_bounds(
1547        marked_range: Option<Range<usize>>,
1548        selection: &UTF16Selection,
1549        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1550    ) -> Option<Bounds<Pixels>> {
1551        if let Some(marked_range) = marked_range {
1552            // Default to the start of the marked (composing) range.
1553            let mut line_start = marked_range.start;
1554
1555            // Walk backward from the caret looking for a line break. A change in
1556            // the Y coordinate means we crossed into the previous visual line, so
1557            // the line start is one position after the break point.
1558            let caret = selection.range.end;
1559            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1560                for i in (marked_range.start..caret).rev() {
1561                    if let Some(b) = bounds_for_range(i..i) {
1562                        if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1563                            line_start = i + 1;
1564                            break;
1565                        }
1566                    }
1567                }
1568            }
1569            bounds_for_range(line_start..line_start)
1570        } else {
1571            // No active composition — use the selection endpoint.
1572            let offset = if selection.reversed {
1573                selection.range.start
1574            } else {
1575                selection.range.end
1576            };
1577            bounds_for_range(offset..offset)
1578        }
1579    }
1580
1581    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1582        let marked_range = self.handler.marked_text_range(window, cx);
1583        let selection = self.handler.selected_text_range(true, window, cx)?;
1584        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1585            self.handler.bounds_for_range(range, window, cx)
1586        })
1587    }
1588
1589    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1590        let marked_range = self.marked_text_range();
1591        let selection = self.selected_text_range(true)?;
1592        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1593            self.bounds_for_range(range)
1594        })
1595    }
1596
1597    #[allow(unused)]
1598    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1599        self.cx
1600            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1601            .ok()
1602            .flatten()
1603    }
1604
1605    /// See [`InputHandler::set_selected_text_range`].
1606    pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1607        self.cx
1608            .update(|window, cx| {
1609                self.handler
1610                    .set_selected_text_range(range_utf16, window, cx)
1611            })
1612            .ok();
1613    }
1614
1615    /// See [`InputHandler::element_bounds`].
1616    pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1617        self.cx
1618            .update(|window, cx| self.handler.element_bounds(window, cx))
1619            .ok()
1620            .flatten()
1621    }
1622
1623    /// See [`InputHandler::text_length_utf16`].
1624    pub fn text_length_utf16(&mut self) -> Option<usize> {
1625        self.cx
1626            .update(|window, cx| self.handler.text_length_utf16(window, cx))
1627            .ok()
1628            .flatten()
1629    }
1630
1631    #[allow(dead_code)]
1632    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1633        self.handler.accepts_text_input(window, cx)
1634    }
1635
1636    #[allow(dead_code)]
1637    pub fn query_accepts_text_input(&mut self) -> bool {
1638        self.cx
1639            .update(|window, cx| self.handler.accepts_text_input(window, cx))
1640            .unwrap_or(true)
1641    }
1642
1643    /// See [`InputHandler::prefers_ime_for_printable_keys`].
1644    ///
1645    /// This is not a pure delegation to the handler: while a multi-stroke binding is pending this
1646    /// returns `false` regardless of the handler's preference, because the next printable key may
1647    /// complete a binding whose prefix already bypassed the IME.
1648    pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1649        self.cx
1650            .update(|window, cx| {
1651                // The next printable key may complete a chord whose prefix bypassed the IME.
1652                !window.has_pending_keystrokes()
1653                    && self.handler.prefers_ime_for_printable_keys(window, cx)
1654            })
1655            .unwrap_or(false)
1656    }
1657}
1658
1659/// A struct representing a selection in a text buffer, in UTF16 characters.
1660/// This is different from a range because the head may be before the tail.
1661#[derive(Debug)]
1662pub struct UTF16Selection {
1663    /// The range of text in the document this selection corresponds to
1664    /// in UTF16 characters.
1665    pub range: Range<usize>,
1666    /// Whether the head of this selection is at the start (true), or end (false)
1667    /// of the range
1668    pub reversed: bool,
1669}
1670
1671/// Zed's interface for handling text input from the platform's IME system
1672/// This is currently a 1:1 exposure of the NSTextInputClient API:
1673///
1674/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
1675pub trait InputHandler: 'static {
1676    /// Get the range of the user's currently selected text, if any
1677    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
1678    ///
1679    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1680    fn selected_text_range(
1681        &mut self,
1682        ignore_disabled_input: bool,
1683        window: &mut Window,
1684        cx: &mut App,
1685    ) -> Option<UTF16Selection>;
1686
1687    /// Get the range of the currently marked text, if any
1688    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
1689    ///
1690    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1691    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1692
1693    /// Get the text for the given document range in UTF-16 characters
1694    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
1695    ///
1696    /// range_utf16 is in terms of UTF-16 characters
1697    fn text_for_range(
1698        &mut self,
1699        range_utf16: Range<usize>,
1700        adjusted_range: &mut Option<Range<usize>>,
1701        window: &mut Window,
1702        cx: &mut App,
1703    ) -> Option<String>;
1704
1705    /// Replace the text in the given document range with the given text
1706    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
1707    ///
1708    /// replacement_range is in terms of UTF-16 characters
1709    fn replace_text_in_range(
1710        &mut self,
1711        replacement_range: Option<Range<usize>>,
1712        text: &str,
1713        window: &mut Window,
1714        cx: &mut App,
1715    );
1716
1717    /// Replace the text in the given document range with the given text,
1718    /// and mark the given text as part of an IME 'composing' state
1719    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
1720    ///
1721    /// range_utf16 is in terms of UTF-16 characters
1722    /// new_selected_range is in terms of UTF-16 characters
1723    fn replace_and_mark_text_in_range(
1724        &mut self,
1725        range_utf16: Option<Range<usize>>,
1726        new_text: &str,
1727        new_selected_range: Option<Range<usize>>,
1728        window: &mut Window,
1729        cx: &mut App,
1730    );
1731
1732    /// Remove the IME 'composing' state from the document
1733    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
1734    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1735
1736    /// Insert a platform-initiated paste at the current selection.
1737    ///
1738    /// Platforms that deliver paste as an input event rather than through an
1739    /// application-defined action (e.g. the DOM `paste` event on web) call
1740    /// this with the full clipboard contents. The default implementation
1741    /// inserts only the plain-text portion of the item.
1742    fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1743        if let Some(text) = item.text() {
1744            self.replace_text_in_range(None, &text, window, cx);
1745        }
1746    }
1747
1748    /// Get the bounds of the given document range in screen coordinates
1749    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
1750    ///
1751    /// This is used for positioning the IME candidate window
1752    fn bounds_for_range(
1753        &mut self,
1754        range_utf16: Range<usize>,
1755        window: &mut Window,
1756        cx: &mut App,
1757    ) -> Option<Bounds<Pixels>>;
1758
1759    /// Get the character offset for the given point in terms of UTF16 characters
1760    ///
1761    /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
1762    fn character_index_for_point(
1763        &mut self,
1764        point: Point<Pixels>,
1765        window: &mut Window,
1766        cx: &mut App,
1767    ) -> Option<usize>;
1768
1769    /// Set the range of the user's currently selected text.
1770    ///
1771    /// This is the reverse data-flow direction from [`Self::selected_text_range`]:
1772    /// platforms call it when the system text machinery moves the selection on the
1773    /// application's behalf — e.g. the user drags a system selection handle or
1774    /// invokes Select All from system UI (iOS `UITextInput setSelectedTextRange:`,
1775    /// Android `InputConnection.setSelection`).
1776    ///
1777    /// range_utf16 is in terms of UTF-16 characters, from 0 to the length of the document
1778    fn set_selected_text_range(
1779        &mut self,
1780        _range_utf16: Range<usize>,
1781        _window: &mut Window,
1782        _cx: &mut App,
1783    ) {
1784    }
1785
1786    /// Get the bounds of the focused text element in window coordinates, if known.
1787    ///
1788    /// This is the pull counterpart to the [`PlatformWindow::update_ime_position`]
1789    /// push: mobile platforms ask for the focused element's geometry when they
1790    /// need it (e.g. to frame system text-interaction UI overlaid on the focused
1791    /// element).
1792    fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
1793        None
1794    }
1795
1796    /// Get the length of the document in UTF-16 characters, if known.
1797    fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
1798        None
1799    }
1800
1801    /// Allows a given input context to opt into getting raw key repeats instead of
1802    /// sending these to the platform.
1803    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
1804    /// (which is how iTerm does it) but it doesn't seem to work for me.
1805    #[allow(dead_code)]
1806    fn apple_press_and_hold_enabled(&mut self) -> bool {
1807        true
1808    }
1809
1810    /// Returns whether this handler is accepting text input to be inserted.
1811    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1812        true
1813    }
1814
1815    /// Returns whether printable keys should be routed to the IME before keybinding
1816    /// matching when a non-ASCII input source (e.g. Japanese, Korean, Chinese IME)
1817    /// is active. This prevents multi-stroke keybindings like `jj` from intercepting
1818    /// keys that the IME should compose.
1819    ///
1820    /// Defaults to `false`. The editor overrides this based on whether it expects
1821    /// character input (e.g. Vim insert mode returns `true`, normal mode returns `false`).
1822    /// The terminal keeps the default `false` so that raw keys reach the terminal process.
1823    fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1824        false
1825    }
1826}
1827
1828/// The variables that can be configured when creating a new window
1829#[derive(Debug)]
1830pub struct WindowOptions {
1831    /// Specifies the state and bounds of the window in screen coordinates.
1832    /// - `None`: Inherit the bounds.
1833    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
1834    pub window_bounds: Option<WindowBounds>,
1835
1836    /// The titlebar configuration of the window
1837    pub titlebar: Option<TitlebarOptions>,
1838
1839    /// Whether the window should be focused when created
1840    pub focus: bool,
1841
1842    /// Whether the window should be shown when created
1843    pub show: bool,
1844
1845    /// The kind of window to create
1846    pub kind: WindowKind,
1847
1848    /// Whether the window can be moved by the user. When `false`, the user cannot drag
1849    /// the window (on macOS this sets `NSWindow.isMovable`, which also disables the
1850    /// Window-menu tiling items); programmatic moves are still allowed.
1851    pub is_movable: bool,
1852
1853    /// Whether the application owns dragging of the (custom) titlebar, rather than
1854    /// AppKit. Only has an effect on macOS.
1855    ///
1856    /// Set this to `true` for windows that draw their own titlebar and move the window
1857    /// themselves via [`Window::start_window_move`]. It marks the whole content view as
1858    /// app-owned titlebar content, so AppKit neither drags the window from the titlebar
1859    /// nor delays titlebar clicks while disambiguating double-clicks (a delay first
1860    /// observed on macOS 27). It is independent of `is_movable`, so such windows stay
1861    /// user-movable (via their own drag) and keep the Window-menu tiling items enabled.
1862    ///
1863    /// Leave this `false` for windows that rely on AppKit's native titlebar dragging.
1864    pub app_owns_titlebar_drag: bool,
1865
1866    /// The minimum interval between animation frames while the window is inactive.
1867    ///
1868    /// Set to `None` to disable inactive-window animation frame throttling.
1869    pub inactive_frame_interval: Option<Duration>,
1870
1871    /// Whether the window should be resizable by the user
1872    pub is_resizable: bool,
1873
1874    /// Whether the window should be minimized by the user
1875    pub is_minimizable: bool,
1876
1877    /// The display to create the window on, if this is None,
1878    /// the window will be created on the main display
1879    pub display_id: Option<DisplayId>,
1880
1881    /// The appearance of the window background.
1882    pub window_background: WindowBackgroundAppearance,
1883
1884    /// Application identifier of the window. Can by used by desktop environments to group applications together.
1885    pub app_id: Option<String>,
1886
1887    /// Window minimum size
1888    pub window_min_size: Option<Size<Pixels>>,
1889
1890    /// Whether to use client or server-side decorations on X11 and Wayland.
1891    /// The platform may ignore requests it cannot satisfy.
1892    pub window_decorations: Option<WindowDecorations>,
1893
1894    /// Icon image (X11 only)
1895    pub icon: Option<Arc<image::RgbaImage>>,
1896
1897    /// 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.
1898    pub tabbing_identifier: Option<String>,
1899}
1900
1901/// The variables that can be configured when creating a new window
1902#[derive(Debug)]
1903#[cfg_attr(
1904    all(
1905        any(target_os = "linux", target_os = "freebsd"),
1906        not(any(feature = "x11", feature = "wayland"))
1907    ),
1908    allow(dead_code)
1909)]
1910#[allow(missing_docs)]
1911pub struct WindowParams {
1912    pub bounds: Bounds<Pixels>,
1913
1914    /// The titlebar configuration of the window
1915    #[cfg_attr(feature = "wayland", allow(dead_code))]
1916    pub titlebar: Option<TitlebarOptions>,
1917
1918    /// The kind of window to create
1919    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1920    pub kind: WindowKind,
1921
1922    /// Whether the window should be movable by the user
1923    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1924    pub is_movable: bool,
1925
1926    /// Whether the application owns dragging of the (custom) titlebar (macOS only)
1927    #[cfg_attr(
1928        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1929        allow(dead_code)
1930    )]
1931    pub app_owns_titlebar_drag: bool,
1932
1933    /// Whether the window should be resizable by the user
1934    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1935    pub is_resizable: bool,
1936
1937    /// Whether the window should be minimized by the user
1938    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1939    pub is_minimizable: bool,
1940
1941    #[cfg_attr(
1942        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1943        allow(dead_code)
1944    )]
1945    pub focus: bool,
1946
1947    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1948    pub show: bool,
1949
1950    /// An image to set as the window icon (x11 only)
1951    #[cfg_attr(feature = "wayland", allow(dead_code))]
1952    pub icon: Option<Arc<image::RgbaImage>>,
1953
1954    #[cfg_attr(feature = "wayland", allow(dead_code))]
1955    pub display_id: Option<DisplayId>,
1956
1957    #[cfg_attr(feature = "wayland", allow(dead_code))]
1958    pub app_id: Option<String>,
1959
1960    pub window_min_size: Option<Size<Pixels>>,
1961
1962    #[cfg(target_os = "macos")]
1963    pub tabbing_identifier: Option<String>,
1964}
1965
1966/// Represents the status of how a window should be opened.
1967#[derive(Debug, Copy, Clone, PartialEq)]
1968pub enum WindowBounds {
1969    /// Indicates that the window should open in a windowed state with the given bounds.
1970    Windowed(Bounds<Pixels>),
1971    /// Indicates that the window should open in a maximized state.
1972    /// The bounds provided here represent the restore size of the window.
1973    Maximized(Bounds<Pixels>),
1974    /// Indicates that the window should open in fullscreen mode.
1975    /// The bounds provided here represent the restore size of the window.
1976    Fullscreen(Bounds<Pixels>),
1977}
1978
1979impl Default for WindowBounds {
1980    fn default() -> Self {
1981        WindowBounds::Windowed(Bounds::default())
1982    }
1983}
1984
1985impl WindowBounds {
1986    /// Retrieve the inner bounds
1987    pub fn get_bounds(&self) -> Bounds<Pixels> {
1988        match self {
1989            WindowBounds::Windowed(bounds) => *bounds,
1990            WindowBounds::Maximized(bounds) => *bounds,
1991            WindowBounds::Fullscreen(bounds) => *bounds,
1992        }
1993    }
1994
1995    /// Creates a new window bounds that centers the window on the screen.
1996    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1997        WindowBounds::Windowed(Bounds::centered(None, size, cx))
1998    }
1999}
2000
2001impl Default for WindowOptions {
2002    fn default() -> Self {
2003        Self {
2004            window_bounds: None,
2005            titlebar: Some(TitlebarOptions {
2006                title: Default::default(),
2007                appears_transparent: Default::default(),
2008                traffic_light_position: Default::default(),
2009            }),
2010            focus: true,
2011            show: true,
2012            kind: WindowKind::Normal,
2013            is_movable: true,
2014            app_owns_titlebar_drag: false,
2015            inactive_frame_interval: Some(Duration::from_micros(33_333)),
2016            is_resizable: true,
2017            is_minimizable: true,
2018            display_id: None,
2019            window_background: WindowBackgroundAppearance::default(),
2020            icon: None,
2021            app_id: None,
2022            window_min_size: None,
2023            window_decorations: None,
2024            tabbing_identifier: None,
2025        }
2026    }
2027}
2028
2029/// The options that can be configured for a window's titlebar
2030#[derive(Debug, Default)]
2031pub struct TitlebarOptions {
2032    /// The initial title of the window
2033    pub title: Option<SharedString>,
2034
2035    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
2036    /// Refer to [`WindowOptions::window_decorations`] on Linux
2037    pub appears_transparent: bool,
2038
2039    /// The position of the macOS traffic light buttons
2040    pub traffic_light_position: Option<Point<Pixels>>,
2041}
2042
2043/// The kind of window to create
2044#[derive(Clone, Debug, PartialEq, Eq)]
2045pub enum WindowKind {
2046    /// A normal application window
2047    Normal,
2048
2049    /// A window that appears above all other windows, usually used for alerts or popups
2050    /// use sparingly!
2051    PopUp,
2052
2053    /// A parent-anchored, platform-native popup window for menus, comboboxes, context menus and
2054    /// tooltips. Unlike [`WindowKind::PopUp`], it is positioned relative to a parent window.
2055    ///
2056    /// The popup's size comes from [`WindowOptions::window_bounds`], whose origin is ignored.
2057    /// See [`popup::PopupOptions`] for the placement options. Platforms without a native
2058    /// implementation reject it with [`popup::PopupNotSupportedError`].
2059    AnchoredPopup(popup::PopupOptions),
2060
2061    /// A floating window that appears on top of its parent window
2062    Floating,
2063
2064    /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
2065    /// docks, notifications or wallpapers.
2066    #[cfg(all(target_os = "linux", feature = "wayland"))]
2067    LayerShell(layer_shell::LayerShellOptions),
2068
2069    /// A window that appears on top of its parent window and blocks interaction with it
2070    /// until the modal window is closed
2071    Dialog,
2072}
2073
2074/// The appearance of the window, as defined by the operating system.
2075///
2076/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
2077/// values.
2078#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2079pub enum WindowAppearance {
2080    /// A light appearance.
2081    ///
2082    /// On macOS, this corresponds to the `aqua` appearance.
2083    #[default]
2084    Light,
2085
2086    /// A light appearance with vibrant colors.
2087    ///
2088    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
2089    VibrantLight,
2090
2091    /// A dark appearance.
2092    ///
2093    /// On macOS, this corresponds to the `darkAqua` appearance.
2094    Dark,
2095
2096    /// A dark appearance with vibrant colors.
2097    ///
2098    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
2099    VibrantDark,
2100}
2101
2102/// The appearance of the background of the window itself, when there is
2103/// no content or the content is transparent.
2104#[derive(Copy, Clone, Debug, Default, PartialEq)]
2105pub enum WindowBackgroundAppearance {
2106    /// Opaque.
2107    ///
2108    /// This lets the window manager know that content behind this
2109    /// window does not need to be drawn.
2110    ///
2111    /// Actual color depends on the system and themes should define a fully
2112    /// opaque background color instead.
2113    #[default]
2114    Opaque,
2115    /// Plain alpha transparency.
2116    Transparent,
2117    /// Transparency, but the contents behind the window are blurred.
2118    ///
2119    /// Not always supported.
2120    Blurred,
2121    /// The Mica backdrop material, supported on Windows 11.
2122    MicaBackdrop,
2123    /// The Mica Alt backdrop material, supported on Windows 11.
2124    MicaAltBackdrop,
2125}
2126
2127/// The text rendering mode to use for drawing glyphs.
2128#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2129pub enum TextRenderingMode {
2130    /// Use the platform's default text rendering mode.
2131    #[default]
2132    PlatformDefault,
2133    /// Use subpixel (ClearType-style) text rendering.
2134    Subpixel,
2135    /// Use grayscale text rendering.
2136    Grayscale,
2137}
2138
2139/// The options that can be configured for a file dialog prompt
2140#[derive(Clone, Debug)]
2141pub struct PathPromptOptions {
2142    /// Should the prompt allow files to be selected?
2143    pub files: bool,
2144    /// Should the prompt allow directories to be selected?
2145    pub directories: bool,
2146    /// Should the prompt allow multiple files to be selected?
2147    pub multiple: bool,
2148    /// The prompt to show to a user when selecting a path
2149    pub prompt: Option<SharedString>,
2150}
2151
2152/// What kind of prompt styling to show
2153#[derive(Copy, Clone, Debug, PartialEq)]
2154pub enum PromptLevel {
2155    /// A prompt that is shown when the user should be notified of something
2156    Info,
2157
2158    /// A prompt that is shown when the user needs to be warned of a potential problem
2159    Warning,
2160
2161    /// A prompt that is shown when a critical problem has occurred
2162    Critical,
2163}
2164
2165/// Prompt Button
2166#[derive(Clone, Debug, PartialEq)]
2167pub enum PromptButton {
2168    /// Ok button
2169    Ok(SharedString),
2170    /// Cancel button
2171    Cancel(SharedString),
2172    /// Other button
2173    Other(SharedString),
2174}
2175
2176impl PromptButton {
2177    /// Create a button with label
2178    pub fn new(label: impl Into<SharedString>) -> Self {
2179        PromptButton::Other(label.into())
2180    }
2181
2182    /// Create an Ok button
2183    pub fn ok(label: impl Into<SharedString>) -> Self {
2184        PromptButton::Ok(label.into())
2185    }
2186
2187    /// Create a Cancel button
2188    pub fn cancel(label: impl Into<SharedString>) -> Self {
2189        PromptButton::Cancel(label.into())
2190    }
2191
2192    /// Returns true if this button is a cancel button.
2193    #[allow(dead_code)]
2194    pub fn is_cancel(&self) -> bool {
2195        matches!(self, PromptButton::Cancel(_))
2196    }
2197
2198    /// Returns the label of the button
2199    pub fn label(&self) -> &SharedString {
2200        match self {
2201            PromptButton::Ok(label) => label,
2202            PromptButton::Cancel(label) => label,
2203            PromptButton::Other(label) => label,
2204        }
2205    }
2206}
2207
2208impl From<&str> for PromptButton {
2209    fn from(value: &str) -> Self {
2210        match value.to_lowercase().as_str() {
2211            "ok" => PromptButton::Ok("OK".into()),
2212            "cancel" => PromptButton::Cancel("Cancel".into()),
2213            _ => PromptButton::Other(SharedString::from(value.to_owned())),
2214        }
2215    }
2216}
2217
2218/// The style of the cursor (pointer)
2219#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2220pub enum CursorStyle {
2221    /// The default cursor
2222    #[default]
2223    Arrow,
2224
2225    /// A text input cursor
2226    /// corresponds to the CSS cursor value `text`
2227    IBeam,
2228
2229    /// A crosshair cursor
2230    /// corresponds to the CSS cursor value `crosshair`
2231    Crosshair,
2232
2233    /// A closed hand cursor
2234    /// corresponds to the CSS cursor value `grabbing`
2235    ClosedHand,
2236
2237    /// An open hand cursor
2238    /// corresponds to the CSS cursor value `grab`
2239    OpenHand,
2240
2241    /// A pointing hand cursor
2242    /// corresponds to the CSS cursor value `pointer`
2243    PointingHand,
2244
2245    /// A resize left cursor
2246    /// corresponds to the CSS cursor value `w-resize`
2247    ResizeLeft,
2248
2249    /// A resize right cursor
2250    /// corresponds to the CSS cursor value `e-resize`
2251    ResizeRight,
2252
2253    /// A resize cursor to the left and right
2254    /// corresponds to the CSS cursor value `ew-resize`
2255    ResizeLeftRight,
2256
2257    /// A resize up cursor
2258    /// corresponds to the CSS cursor value `n-resize`
2259    ResizeUp,
2260
2261    /// A resize down cursor
2262    /// corresponds to the CSS cursor value `s-resize`
2263    ResizeDown,
2264
2265    /// A resize cursor directing up and down
2266    /// corresponds to the CSS cursor value `ns-resize`
2267    ResizeUpDown,
2268
2269    /// A resize cursor directing up-left and down-right
2270    /// corresponds to the CSS cursor value `nesw-resize`
2271    ResizeUpLeftDownRight,
2272
2273    /// A resize cursor directing up-right and down-left
2274    /// corresponds to the CSS cursor value `nwse-resize`
2275    ResizeUpRightDownLeft,
2276
2277    /// A cursor indicating that the item/column can be resized horizontally.
2278    /// corresponds to the CSS cursor value `col-resize`
2279    ResizeColumn,
2280
2281    /// A cursor indicating that the item/row can be resized vertically.
2282    /// corresponds to the CSS cursor value `row-resize`
2283    ResizeRow,
2284
2285    /// A text input cursor for vertical layout
2286    /// corresponds to the CSS cursor value `vertical-text`
2287    IBeamCursorForVerticalLayout,
2288
2289    /// A cursor indicating that the operation is not allowed
2290    /// corresponds to the CSS cursor value `not-allowed`
2291    OperationNotAllowed,
2292
2293    /// A cursor indicating that the operation will result in a link
2294    /// corresponds to the CSS cursor value `alias`
2295    DragLink,
2296
2297    /// A cursor indicating that the operation will result in a copy
2298    /// corresponds to the CSS cursor value `copy`
2299    DragCopy,
2300
2301    /// A cursor indicating that the operation will result in a context menu
2302    /// corresponds to the CSS cursor value `context-menu`
2303    ContextualMenu,
2304}
2305
2306/// A clipboard item that should be copied to the clipboard
2307#[derive(Clone, Debug, Eq, PartialEq)]
2308pub struct ClipboardItem {
2309    /// The entries in this clipboard item.
2310    pub entries: Vec<ClipboardEntry>,
2311}
2312
2313/// An error produced by [`Platform::read_from_clipboard_async`].
2314///
2315/// Callers surface these failures to users, so the variants distinguish
2316/// conditions that call for different user-facing guidance.
2317#[derive(Clone, Debug, PartialEq, Eq)]
2318pub enum ClipboardReadError {
2319    /// The platform clipboard is not available in this context, e.g. the
2320    /// browser does not expose the async clipboard API or the page is not a
2321    /// secure context.
2322    Unavailable,
2323    /// The platform refused access, e.g. the user declined the browser's
2324    /// clipboard permission prompt or paste confirmation.
2325    Denied(String),
2326    /// The clipboard contents could not be converted into a
2327    /// [`ClipboardItem`].
2328    UnsupportedContent,
2329}
2330
2331impl std::fmt::Display for ClipboardReadError {
2332    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2333        match self {
2334            Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2335            Self::Denied(message) => {
2336                write!(formatter, "clipboard access was denied: {message}")
2337            }
2338            Self::UnsupportedContent => {
2339                formatter.write_str("the clipboard contents are unsupported")
2340            }
2341        }
2342    }
2343}
2344
2345impl std::error::Error for ClipboardReadError {}
2346
2347/// Either a ClipboardString or a ClipboardImage
2348#[derive(Clone, Debug, Eq, PartialEq)]
2349pub enum ClipboardEntry {
2350    /// A string entry
2351    String(ClipboardString),
2352    /// An image entry
2353    Image(Image),
2354    /// A file entry
2355    ExternalPaths(crate::ExternalPaths),
2356}
2357
2358impl ClipboardItem {
2359    /// Create a new ClipboardItem::String with no associated metadata
2360    pub fn new_string(text: String) -> Self {
2361        Self {
2362            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2363        }
2364    }
2365
2366    /// Create a new ClipboardItem::String with the given text and associated metadata
2367    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2368        Self {
2369            entries: vec![ClipboardEntry::String(ClipboardString {
2370                text,
2371                metadata: Some(metadata),
2372            })],
2373        }
2374    }
2375
2376    /// Create a new ClipboardItem::String with the given text and associated metadata
2377    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2378        Self {
2379            entries: vec![ClipboardEntry::String(
2380                ClipboardString::new(text).with_json_metadata(metadata),
2381            )],
2382        }
2383    }
2384
2385    /// Create a new ClipboardItem::Image with the given image with no associated metadata
2386    pub fn new_image(image: &Image) -> Self {
2387        Self {
2388            entries: vec![ClipboardEntry::Image(image.clone())],
2389        }
2390    }
2391
2392    /// Concatenates together all the ClipboardString entries in the item.
2393    /// Returns None if there were no ClipboardString entries.
2394    pub fn text(&self) -> Option<String> {
2395        let mut answer = String::new();
2396
2397        for entry in self.entries.iter() {
2398            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2399                answer.push_str(text);
2400            }
2401        }
2402
2403        if answer.is_empty() {
2404            for entry in self.entries.iter() {
2405                if let ClipboardEntry::ExternalPaths(paths) = entry {
2406                    for path in &paths.0 {
2407                        use std::fmt::Write as _;
2408                        _ = write!(answer, "{}", path.display());
2409                    }
2410                }
2411            }
2412        }
2413
2414        if !answer.is_empty() {
2415            Some(answer)
2416        } else {
2417            None
2418        }
2419    }
2420
2421    /// If this item is one ClipboardEntry::String, returns its metadata.
2422    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2423    pub fn metadata(&self) -> Option<&String> {
2424        match self.entries().first() {
2425            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2426                clipboard_string.metadata.as_ref()
2427            }
2428            _ => None,
2429        }
2430    }
2431
2432    /// Get the item's entries
2433    pub fn entries(&self) -> &[ClipboardEntry] {
2434        &self.entries
2435    }
2436
2437    /// Get owned versions of the item's entries
2438    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2439        self.entries.into_iter()
2440    }
2441}
2442
2443impl From<ClipboardString> for ClipboardEntry {
2444    fn from(value: ClipboardString) -> Self {
2445        Self::String(value)
2446    }
2447}
2448
2449impl From<String> for ClipboardEntry {
2450    fn from(value: String) -> Self {
2451        Self::from(ClipboardString::from(value))
2452    }
2453}
2454
2455impl From<Image> for ClipboardEntry {
2456    fn from(value: Image) -> Self {
2457        Self::Image(value)
2458    }
2459}
2460
2461impl From<ClipboardEntry> for ClipboardItem {
2462    fn from(value: ClipboardEntry) -> Self {
2463        Self {
2464            entries: vec![value],
2465        }
2466    }
2467}
2468
2469impl From<String> for ClipboardItem {
2470    fn from(value: String) -> Self {
2471        Self::from(ClipboardEntry::from(value))
2472    }
2473}
2474
2475impl From<Image> for ClipboardItem {
2476    fn from(value: Image) -> Self {
2477        Self::from(ClipboardEntry::from(value))
2478    }
2479}
2480
2481/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
2482#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2483pub enum ImageFormat {
2484    // Sorted from most to least likely to be pasted into an editor,
2485    // which matters when we iterate through them trying to see if
2486    // clipboard content matches them.
2487    /// .png
2488    Png,
2489    /// .jpeg or .jpg
2490    Jpeg,
2491    /// .webp
2492    Webp,
2493    /// .gif
2494    Gif,
2495    /// .svg
2496    Svg,
2497    /// .bmp
2498    Bmp,
2499    /// .tif or .tiff
2500    Tiff,
2501    /// .ico
2502    Ico,
2503    /// Netpbm image formats (.pbm, .ppm, .pgm).
2504    Pnm,
2505}
2506
2507impl ImageFormat {
2508    /// Returns the mime type for the ImageFormat
2509    pub const fn mime_type(self) -> &'static str {
2510        match self {
2511            ImageFormat::Png => "image/png",
2512            ImageFormat::Jpeg => "image/jpeg",
2513            ImageFormat::Webp => "image/webp",
2514            ImageFormat::Gif => "image/gif",
2515            ImageFormat::Svg => "image/svg+xml",
2516            ImageFormat::Bmp => "image/bmp",
2517            ImageFormat::Tiff => "image/tiff",
2518            ImageFormat::Ico => "image/ico",
2519            ImageFormat::Pnm => "image/x-portable-anymap",
2520        }
2521    }
2522
2523    /// Returns the file extension for this image format (without leading dot).
2524    pub const fn extension(self) -> &'static str {
2525        match self {
2526            ImageFormat::Png => "png",
2527            ImageFormat::Jpeg => "jpg",
2528            ImageFormat::Webp => "webp",
2529            ImageFormat::Gif => "gif",
2530            ImageFormat::Svg => "svg",
2531            ImageFormat::Bmp => "bmp",
2532            ImageFormat::Tiff => "tiff",
2533            ImageFormat::Ico => "ico",
2534            ImageFormat::Pnm => "pnm",
2535        }
2536    }
2537
2538    /// Returns the ImageFormat for the given mime type, including known aliases.
2539    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2540        use strum::IntoEnumIterator;
2541        Self::iter()
2542            .find(|format| format.mime_type() == mime_type)
2543            .or_else(|| Self::from_mime_type_alias(mime_type))
2544    }
2545
2546    /// Non-canonical mime types that some producers use in the wild.
2547    /// Unlike `mime_type()` which returns the single canonical form,
2548    /// these are legacy or shortened variants we still need to recognize.
2549    fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2550        match mime_type {
2551            "image/jpg" => Some(Self::Jpeg),
2552            "image/tif" => Some(Self::Tiff),
2553            _ => None,
2554        }
2555    }
2556}
2557
2558/// An image, with a format and certain bytes
2559#[derive(Clone, Debug, PartialEq, Eq)]
2560pub struct Image {
2561    /// The image format the bytes represent (e.g. PNG)
2562    pub format: ImageFormat,
2563    /// The raw image bytes
2564    pub bytes: Vec<u8>,
2565    /// The unique ID for the image
2566    pub id: u64,
2567}
2568
2569pub(crate) fn decode_static_image(
2570    bytes: &[u8],
2571    format: image::ImageFormat,
2572) -> Result<SmallVec<[Frame; 1]>> {
2573    let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2574        .into_decoder()
2575        .context("creating image decoder")?;
2576    decode_static_image_from_decoder(decoder)
2577}
2578
2579pub(crate) fn decode_static_image_from_decoder(
2580    mut decoder: impl image::ImageDecoder,
2581) -> Result<SmallVec<[Frame; 1]>> {
2582    let orientation = decoder
2583        .orientation()
2584        .context("reading decoder's orientation")?;
2585    let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2586    image.apply_orientation(orientation);
2587
2588    let mut data = image.into_rgba8();
2589    for pixel in data.chunks_exact_mut(4) {
2590        pixel.swap(0, 2);
2591    }
2592
2593    Ok(SmallVec::from_elem(Frame::new(data), 1))
2594}
2595
2596impl Hash for Image {
2597    fn hash<H: Hasher>(&self, state: &mut H) {
2598        state.write_u64(self.id);
2599    }
2600}
2601
2602impl Image {
2603    /// An empty image containing no data
2604    pub fn empty() -> Self {
2605        Self::from_bytes(ImageFormat::Png, Vec::new())
2606    }
2607
2608    /// Create an image from a format and bytes
2609    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2610        Self {
2611            id: hash(&bytes),
2612            format,
2613            bytes,
2614        }
2615    }
2616
2617    /// Get this image's ID
2618    pub fn id(&self) -> u64 {
2619        self.id
2620    }
2621
2622    /// Use the GPUI `use_asset` API to make this image renderable
2623    pub fn use_render_image(
2624        self: Arc<Self>,
2625        window: &mut Window,
2626        cx: &mut App,
2627    ) -> Option<Arc<RenderImage>> {
2628        ImageSource::Image(self)
2629            .use_data(None, window, cx)
2630            .and_then(|result| result.ok())
2631    }
2632
2633    /// Use the GPUI `get_asset` API to make this image renderable
2634    pub fn get_render_image(
2635        self: Arc<Self>,
2636        window: &mut Window,
2637        cx: &mut App,
2638    ) -> Option<Arc<RenderImage>> {
2639        ImageSource::Image(self)
2640            .get_data(None, window, cx)
2641            .and_then(|result| result.ok())
2642    }
2643
2644    /// Use the GPUI `remove_asset` API to drop this image, if possible.
2645    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2646        ImageSource::Image(self).remove_asset(cx);
2647    }
2648
2649    /// Check whether this image is present in GPUI's asset cache (loading or
2650    /// loaded), without fetching it.
2651    #[cfg(any(test, feature = "test-support"))]
2652    pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
2653        ImageSource::Image(self.clone()).is_asset_cached(cx)
2654    }
2655
2656    /// Convert the clipboard image to an `ImageData` object.
2657    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2658        let frames = match self.format {
2659            ImageFormat::Gif => {
2660                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2661                let mut frames = SmallVec::new();
2662
2663                for frame in decoder.into_frames() {
2664                    match frame {
2665                        Ok(mut frame) => {
2666                            // Convert from RGBA to BGRA.
2667                            for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2668                                pixel.swap(0, 2);
2669                            }
2670                            frames.push(frame);
2671                        }
2672                        Err(err) => {
2673                            log::debug!("Skipping GIF frame due to decode error: {err}");
2674                        }
2675                    }
2676                }
2677
2678                if frames.is_empty() {
2679                    anyhow::bail!("GIF could not be decoded: all frames failed");
2680                }
2681
2682                frames
2683            }
2684            ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2685            ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2686            ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2687            ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2688            ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2689            ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
2690            ImageFormat::Svg => {
2691                return svg_renderer
2692                    .render_single_frame(&self.bytes, 1.0)
2693                    .map_err(Into::into);
2694            }
2695            ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
2696        };
2697
2698        Ok(Arc::new(RenderImage::new(frames)))
2699    }
2700
2701    /// Get the format of the clipboard image
2702    pub fn format(&self) -> ImageFormat {
2703        self.format
2704    }
2705
2706    /// Get the raw bytes of the clipboard image
2707    pub fn bytes(&self) -> &[u8] {
2708        self.bytes.as_slice()
2709    }
2710}
2711
2712/// A clipboard item that should be copied to the clipboard
2713#[derive(Clone, Debug, Eq, PartialEq)]
2714pub struct ClipboardString {
2715    /// The text content.
2716    pub text: String,
2717    /// Optional metadata associated with this clipboard string.
2718    pub metadata: Option<String>,
2719}
2720
2721impl ClipboardString {
2722    /// Create a new clipboard string with the given text
2723    pub fn new(text: String) -> Self {
2724        Self {
2725            text,
2726            metadata: None,
2727        }
2728    }
2729
2730    /// Return a new clipboard item with the metadata replaced by the given metadata,
2731    /// after serializing it as JSON.
2732    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2733        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2734        self
2735    }
2736
2737    /// Get the text of the clipboard string
2738    pub fn text(&self) -> &String {
2739        &self.text
2740    }
2741
2742    /// Get the owned text of the clipboard string
2743    pub fn into_text(self) -> String {
2744        self.text
2745    }
2746
2747    /// Get the metadata of the clipboard string, formatted as JSON
2748    pub fn metadata_json<T>(&self) -> Option<T>
2749    where
2750        T: for<'a> Deserialize<'a>,
2751    {
2752        self.metadata
2753            .as_ref()
2754            .and_then(|m| serde_json::from_str(m).ok())
2755    }
2756
2757    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2758    /// Compute a hash of the given text for clipboard change detection.
2759    pub fn text_hash(text: &str) -> u64 {
2760        let mut hasher = SeaHasher::new();
2761        text.hash(&mut hasher);
2762        hasher.finish()
2763    }
2764}
2765
2766impl From<String> for ClipboardString {
2767    fn from(value: String) -> Self {
2768        Self {
2769            text: value,
2770            metadata: None,
2771        }
2772    }
2773}
2774
2775#[cfg(test)]
2776mod image_tests {
2777    use super::*;
2778    use std::sync::Arc;
2779
2780    #[test]
2781    fn test_image_to_image_data_applies_exif_orientation() {
2782        let image = Image::from_bytes(
2783            ImageFormat::Jpeg,
2784            include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
2785        );
2786
2787        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2788
2789        assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2790
2791        let bytes = render_image.as_bytes(0).unwrap();
2792        assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2793        assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2794    }
2795
2796    #[test]
2797    fn test_svg_image_to_image_data_converts_to_bgra() {
2798        let image = Image::from_bytes(
2799            ImageFormat::Svg,
2800            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
2801<rect width="1" height="1" fill="#38BDF8"/>
2802</svg>"##
2803                .to_vec(),
2804        );
2805
2806        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2807        let bytes = render_image.as_bytes(0).unwrap();
2808
2809        for pixel in bytes.chunks_exact(4) {
2810            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
2811        }
2812    }
2813}
2814
2815#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
2816mod tests {
2817    use super::*;
2818    use std::collections::HashSet;
2819
2820    #[test]
2821    fn test_window_button_layout_parse_standard() {
2822        let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
2823        assert_eq!(
2824            layout.left,
2825            [
2826                Some(WindowButton::Close),
2827                Some(WindowButton::Minimize),
2828                None
2829            ]
2830        );
2831        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2832    }
2833
2834    #[test]
2835    fn test_window_button_layout_parse_right_only() {
2836        let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
2837        assert_eq!(layout.left, [None, None, None]);
2838        assert_eq!(
2839            layout.right,
2840            [
2841                Some(WindowButton::Minimize),
2842                Some(WindowButton::Maximize),
2843                Some(WindowButton::Close)
2844            ]
2845        );
2846    }
2847
2848    #[test]
2849    fn test_window_button_layout_parse_left_only() {
2850        let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
2851        assert_eq!(
2852            layout.left,
2853            [
2854                Some(WindowButton::Close),
2855                Some(WindowButton::Minimize),
2856                Some(WindowButton::Maximize)
2857            ]
2858        );
2859        assert_eq!(layout.right, [None, None, None]);
2860    }
2861
2862    #[test]
2863    fn test_window_button_layout_parse_with_whitespace() {
2864        let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
2865        assert_eq!(
2866            layout.left,
2867            [
2868                Some(WindowButton::Close),
2869                Some(WindowButton::Minimize),
2870                None
2871            ]
2872        );
2873        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2874    }
2875
2876    #[test]
2877    fn test_window_button_layout_parse_empty() {
2878        let layout = WindowButtonLayout::parse("").unwrap();
2879        assert_eq!(layout.left, [None, None, None]);
2880        assert_eq!(layout.right, [None, None, None]);
2881    }
2882
2883    #[test]
2884    fn test_window_button_layout_parse_intentionally_empty() {
2885        let layout = WindowButtonLayout::parse(":").unwrap();
2886        assert_eq!(layout.left, [None, None, None]);
2887        assert_eq!(layout.right, [None, None, None]);
2888    }
2889
2890    #[test]
2891    fn test_window_button_layout_parse_invalid_buttons() {
2892        let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
2893        assert_eq!(
2894            layout.left,
2895            [
2896                Some(WindowButton::Close),
2897                Some(WindowButton::Minimize),
2898                None
2899            ]
2900        );
2901        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2902    }
2903
2904    #[test]
2905    fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
2906        let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
2907        assert_eq!(
2908            layout.right,
2909            [
2910                Some(WindowButton::Close),
2911                Some(WindowButton::Minimize),
2912                None
2913            ]
2914        );
2915        assert_eq!(layout.format(), ":close,minimize");
2916    }
2917
2918    #[test]
2919    fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
2920        let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
2921        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
2922        assert_eq!(
2923            layout.right,
2924            [
2925                Some(WindowButton::Maximize),
2926                Some(WindowButton::Minimize),
2927                None
2928            ]
2929        );
2930
2931        let button_ids: Vec<_> = layout
2932            .left
2933            .iter()
2934            .chain(layout.right.iter())
2935            .flatten()
2936            .map(WindowButton::id)
2937            .collect();
2938        let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
2939        assert_eq!(unique_button_ids.len(), button_ids.len());
2940        assert_eq!(layout.format(), "close:maximize,minimize");
2941    }
2942
2943    #[test]
2944    fn test_window_button_layout_parse_gnome_style() {
2945        let layout = WindowButtonLayout::parse("close").unwrap();
2946        assert_eq!(layout.left, [None, None, None]);
2947        assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
2948    }
2949
2950    #[test]
2951    fn test_window_button_layout_parse_elementary_style() {
2952        let layout = WindowButtonLayout::parse("close:maximize").unwrap();
2953        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
2954        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2955    }
2956
2957    #[test]
2958    fn test_window_button_layout_round_trip() {
2959        let cases = [
2960            "close:minimize,maximize",
2961            "minimize,maximize,close:",
2962            ":close",
2963            "close:",
2964            "close:maximize",
2965            ":",
2966        ];
2967
2968        for case in cases {
2969            let layout = WindowButtonLayout::parse(case).unwrap();
2970            assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
2971        }
2972    }
2973
2974    #[test]
2975    fn test_window_button_layout_linux_default() {
2976        let layout = WindowButtonLayout::linux_default();
2977        assert_eq!(layout.left, [None, None, None]);
2978        assert_eq!(
2979            layout.right,
2980            [
2981                Some(WindowButton::Minimize),
2982                Some(WindowButton::Maximize),
2983                Some(WindowButton::Close)
2984            ]
2985        );
2986
2987        let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
2988        assert_eq!(round_tripped, layout);
2989    }
2990
2991    #[test]
2992    fn test_window_button_layout_parse_all_invalid() {
2993        assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
2994    }
2995}