Skip to main content

gpui/
platform.rs

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