Skip to main content

gpui/
platform.rs

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