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