Skip to main content

gpui/
platform.rs

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