Skip to main content

gpui_core/
platform.rs

1mod app_menu;
2mod keyboard;
3mod keystroke;
4/// Wayland layer-shell window configuration.
5#[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
6pub mod layer_shell;
7/// Cross-platform single instance enforcement using Unix domain sockets and Windows named mutexes.
8pub mod single_instance;
9/// Pure Rust utility for computing window bounds from a semantic [`WindowPosition`].
10pub mod window_positioner;
11
12#[cfg(all(
13    feature = "screen-capture",
14    any(target_os = "linux", target_os = "freebsd")
15))]
16type PlatformScreenCaptureFrame = scap::frame::Frame;
17#[cfg(all(
18    any(target_os = "linux", target_os = "freebsd"),
19    not(feature = "screen-capture")
20))]
21type PlatformScreenCaptureFrame = ();
22
23#[cfg(target_os = "macos")]
24type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
25
26#[cfg(all(target_os = "windows", feature = "screen-capture"))]
27type PlatformScreenCaptureFrame = scap::frame::Frame;
28#[cfg(all(target_os = "windows", not(feature = "screen-capture")))]
29type PlatformScreenCaptureFrame = ();
30
31#[cfg(any(test, feature = "test-support"))]
32#[doc(hidden)]
33#[allow(missing_docs)]
34pub mod render_image;
35
36#[cfg(any(test, feature = "test-support"))]
37mod test;
38#[cfg(any(test, feature = "test-support"))]
39mod threaded_dispatcher;
40
41#[cfg(all(
42    feature = "screen-capture",
43    any(
44        target_os = "windows",
45        all(
46            any(target_os = "linux", target_os = "freebsd"),
47            any(feature = "wayland", feature = "x11"),
48        )
49    )
50))]
51/// Shared screen-capture implementation used by desktop backends.
52#[doc(hidden)]
53pub mod scap_screen_capture;
54
55use crate::{
56    Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
57    DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
58    ForegroundExecutor, GlyphId, GpuResourceBudget, GpuSpecs, Hsla, ImageSource, Keymap,
59    LineLayout, MissingGlyphSink, Pixels, PlatformInput, Point, QuitMode, RenderGlyphParams,
60    RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString,
61    Size, SvgRenderer, SystemWindowTab, Task, TaskLabel, Window, WindowControlArea, hash, point,
62    px, size,
63};
64use anyhow::{Context as _, Result};
65use async_task::Runnable;
66use futures::channel::oneshot;
67#[cfg(feature = "image-format-gif")]
68use image::AnimationDecoder as _;
69#[cfg(feature = "image-format-gif")]
70use image::codecs::gif::GifDecoder;
71use image::{DynamicImage, Frame};
72use parking::Unparker;
73use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
74use schemars::JsonSchema;
75use seahash::SeaHasher;
76use serde::{Deserialize, Serialize};
77use smallvec::SmallVec;
78use std::borrow::Cow;
79use std::hash::{Hash, Hasher};
80use std::io::Cursor;
81use std::ops;
82use std::time::{Duration, Instant};
83use std::{
84    ffi::OsString,
85    fmt::{self, Debug},
86    ops::Range,
87    path::{Path, PathBuf},
88    rc::Rc,
89    sync::Arc,
90};
91use strum::EnumIter;
92use uuid::Uuid;
93
94pub use app_menu::*;
95pub use keyboard::*;
96pub use keystroke::*;
97
98pub use semantic_version::SemanticVersion;
99#[cfg(any(test, feature = "test-support"))]
100pub(crate) use test::*;
101
102/// Keeps an operating system activity, such as an idle sleep inhibitor, alive until dropped.
103pub struct ActivityGuard {
104    _release: util::Deferred<Box<dyn FnOnce() + Send>>,
105}
106
107impl std::fmt::Debug for ActivityGuard {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        f.debug_struct("ActivityGuard").finish_non_exhaustive()
110    }
111}
112
113impl ActivityGuard {
114    /// Runs `release` when the guard is dropped.
115    pub fn new(release: impl FnOnce() + Send + 'static) -> Self {
116        Self {
117            _release: util::defer(Box::new(release)),
118        }
119    }
120
121    /// A guard for platforms without a corresponding activity.
122    pub fn noop() -> Self {
123        Self::new(|| {})
124    }
125}
126
127#[cfg(any(test, feature = "test-support"))]
128pub use test::{
129    TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream, VisualRenderArtifact,
130};
131#[cfg(any(test, feature = "test-support"))]
132pub use threaded_dispatcher::ThreadedDispatcher;
133
134#[cfg(all(
135    any(test, feature = "test-support"),
136    any(
137        target_os = "macos",
138        target_os = "linux",
139        target_os = "freebsd",
140        target_os = "windows"
141    )
142))]
143#[doc(hidden)]
144pub use test::VisualTestPlatform;
145
146/// Returns which Linux compositor the environment indicates without connecting to it.
147#[cfg(any(target_os = "linux", target_os = "freebsd"))]
148#[inline]
149pub fn guess_compositor() -> &'static str {
150    if std::env::var_os("ZED_HEADLESS").is_some() {
151        return "Headless";
152    }
153
154    #[cfg(feature = "wayland")]
155    let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
156    #[cfg(not(feature = "wayland"))]
157    let wayland_display: Option<std::ffi::OsString> = None;
158
159    #[cfg(feature = "x11")]
160    let x11_display = std::env::var_os("DISPLAY");
161    #[cfg(not(feature = "x11"))]
162    let x11_display: Option<std::ffi::OsString> = None;
163
164    let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
165    let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
166
167    if use_wayland {
168        "Wayland"
169    } else if use_x11 {
170        "X11"
171    } else {
172        "Headless"
173    }
174}
175
176/// Snapshot of the renderer's pooled GPU resource usage.
177///
178/// Returned by [`App::renderer_cache_stats`]. Use together with
179/// [`App::trim_gpu_caches`] to decide whether reclaiming idle GPU memory is
180/// worthwhile. This is a best-effort diagnostic snapshot; platforms that do
181/// not maintain trimmable GPU pools report zero.
182#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
183#[non_exhaustive]
184pub struct RendererCacheStats {
185    /// Number of idle GPU instance buffers held by the renderer pool.
186    pub idle_gpu_buffers: usize,
187    /// Configured size in bytes of each pooled GPU instance buffer.
188    pub gpu_buffer_size_bytes: usize,
189}
190
191impl RendererCacheStats {
192    /// Creates a renderer cache snapshot from backend pool counters.
193    #[doc(hidden)]
194    pub fn new(idle_gpu_buffers: usize, gpu_buffer_size_bytes: usize) -> Self {
195        Self {
196            idle_gpu_buffers,
197            gpu_buffer_size_bytes,
198        }
199    }
200}
201
202/// Low-level operating-system integration contract implemented by GPUI backends.
203#[allow(missing_docs)]
204pub trait Platform: 'static {
205    fn background_executor(&self) -> BackgroundExecutor;
206    fn foreground_executor(&self) -> ForegroundExecutor;
207    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
208
209    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
210    fn quit(&self);
211    fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
212    fn activate(&self, ignoring_other_apps: bool);
213    fn hide(&self);
214    fn hide_other_apps(&self);
215    fn unhide_other_apps(&self);
216
217    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
218    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
219    fn active_window(&self) -> Option<AnyWindowHandle>;
220    fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
221        None
222    }
223
224    #[cfg(feature = "screen-capture")]
225    fn is_screen_capture_supported(&self) -> bool;
226    #[cfg(not(feature = "screen-capture"))]
227    fn is_screen_capture_supported(&self) -> bool {
228        false
229    }
230    #[cfg(feature = "screen-capture")]
231    fn screen_capture_sources(&self)
232    -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
233    #[cfg(not(feature = "screen-capture"))]
234    fn screen_capture_sources(
235        &self,
236    ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
237        let (sources_tx, sources_rx) = oneshot::channel();
238        sources_tx
239            .send(Err(anyhow::anyhow!(
240                "gpui was compiled without the screen-capture feature"
241            )))
242            .ok();
243        sources_rx
244    }
245
246    fn open_window(
247        &self,
248        handle: AnyWindowHandle,
249        options: WindowParams,
250    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
251
252    /// Drop any idle GPU buffers held by the renderer's pools, retaining
253    /// configuration. Default implementation is a no-op for platforms that do
254    /// not maintain such pools.
255    fn trim_renderer_caches(&self) {}
256
257    /// Apply renderer GPU resource budgets. Default implementation is a no-op
258    /// for platforms that do not expose configurable renderer pools.
259    fn configure_gpu_resources(&self, _gpu: &GpuResourceBudget) {}
260
261    /// Snapshot of the renderer's pooled GPU resource usage. Default
262    /// implementation reports an empty snapshot for platforms that do not
263    /// maintain such pools.
264    fn renderer_cache_stats(&self) -> RendererCacheStats {
265        RendererCacheStats::default()
266    }
267
268    /// Returns the appearance of the application's windows.
269    fn window_appearance(&self) -> WindowAppearance;
270
271    /// Overrides the appearance (light/dark) applied to the app's windows, independent
272    /// of the OS-wide setting. Pass `None` to clear the override and follow the system
273    /// again. The override is reflected by [`Platform::window_appearance`].
274    ///
275    /// On macOS this sets `NSApplication.appearance` so the native window chrome (the
276    /// window border and titlebar) of every window matches a dark app theme even when
277    /// the system is in light mode (or vice versa). Other backends store the override
278    /// for [`Platform::window_appearance`] without changing native chrome.
279    fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
280
281    fn open_url(&self, url: &str);
282    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
283    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
284
285    fn prompt_for_paths(
286        &self,
287        options: PathPromptOptions,
288    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
289    fn prompt_for_new_path(
290        &self,
291        directory: &Path,
292        suggested_name: Option<&str>,
293    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
294    fn can_select_mixed_files_and_dirs(&self) -> bool;
295    fn reveal_path(&self, path: &Path);
296    fn open_with_system(&self, path: &Path);
297
298    fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
299    fn on_reopen(&self, callback: Box<dyn FnMut()>);
300
301    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
302    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
303        None
304    }
305
306    fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
307    fn perform_dock_menu_action(&self, _action: usize) {}
308    fn add_recent_document(&self, _path: &Path) {}
309    fn update_jump_list(
310        &self,
311        _menus: Vec<MenuItem>,
312        _entries: Vec<SmallVec<[PathBuf; 2]>>,
313    ) -> Vec<SmallVec<[PathBuf; 2]>> {
314        Vec::new()
315    }
316    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
317    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
318    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
319
320    fn thermal_state(&self) -> ThermalState {
321        ThermalState::Nominal
322    }
323    fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}
324
325    /// Prevents idle sleep while the returned guard is held.
326    ///
327    /// Dropping the guard restores the previous sleep policy.
328    fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
329
330    fn compositor_name(&self) -> &'static str {
331        ""
332    }
333    fn app_path(&self) -> Result<PathBuf>;
334    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
335
336    fn set_cursor_style(&self, style: CursorStyle);
337    fn should_auto_hide_scrollbars(&self) -> bool;
338
339    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
340    fn write_to_primary(&self, item: ClipboardItem);
341    fn write_to_clipboard(&self, item: ClipboardItem);
342    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
343    fn read_from_primary(&self) -> Option<ClipboardItem>;
344    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
345
346    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
347    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
348    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
349
350    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
351    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
352    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
353
354    fn set_tray_icon(&self, _icon: Option<&[u8]>) {}
355    fn set_tray_icon_rendering_mode(&self, _rendering_mode: TrayIconRenderingMode) {}
356    fn set_tray_menu(&self, _menu: Vec<TrayMenuItem>) {}
357    fn set_tray_tooltip(&self, _tooltip: &str) {}
358    fn set_tray_panel_mode(&self, _enabled: bool) {}
359    fn get_tray_icon_anchor(&self) -> Option<TrayAnchor> {
360        None
361    }
362    fn get_tray_icon_bounds(&self) -> Option<Bounds<Pixels>> {
363        None
364    }
365    fn on_tray_icon_event(&self, mut callback: Box<dyn FnMut(TrayIconEvent)>) {
366        self.on_tray_icon_click_event(Box::new(move |event| callback(event.kind)));
367    }
368    fn on_tray_icon_click_event(&self, _callback: Box<dyn FnMut(TrayIconClickEvent)>) {}
369    fn on_tray_menu_action(&self, _callback: Box<dyn FnMut(SharedString)>) {}
370
371    fn register_global_hotkey(&self, _id: u32, _keystroke: &Keystroke) -> Result<()> {
372        Err(anyhow::anyhow!(
373            "Global hotkeys not supported on this platform"
374        ))
375    }
376    fn unregister_global_hotkey(&self, _id: u32) {}
377    fn on_global_hotkey(&self, _callback: Box<dyn FnMut(u32)>) {}
378
379    fn focused_window_info(&self) -> Option<FocusedWindowInfo> {
380        None
381    }
382
383    fn accessibility_status(&self) -> PermissionStatus {
384        PermissionStatus::Unavailable
385    }
386    fn request_accessibility_permission(&self) -> PermissionRequestStatus {
387        PermissionRequestStatus::Unavailable
388    }
389
390    fn microphone_status(&self) -> PermissionStatus {
391        PermissionStatus::Unavailable
392    }
393    fn request_microphone_permission(
394        &self,
395        callback: Box<dyn FnOnce(bool)>,
396    ) -> PermissionRequestStatus {
397        callback(false);
398        PermissionRequestStatus::Unavailable
399    }
400
401    fn set_auto_launch(&self, _app_id: &str, _enabled: bool) -> Result<()> {
402        Err(anyhow::anyhow!(
403            "Auto-launch not supported on this platform"
404        ))
405    }
406    fn is_auto_launch_enabled(&self, _app_id: &str) -> bool {
407        false
408    }
409
410    fn show_notification(&self, _title: &str, _body: &str) -> Result<()> {
411        Err(anyhow::anyhow!(
412            "Notifications not supported on this platform"
413        ))
414    }
415
416    /// Receive the application's configured [`QuitMode`].
417    ///
418    /// The core application state owns the actual lifecycle decision; platforms
419    /// only need this hook when quit mode carries a platform-visible side
420    /// effect (for example macOS `ActivationPolicy`). The default
421    /// implementation is a no-op.
422    fn set_quit_mode(&self, _mode: QuitMode) {}
423
424    fn on_system_power_event(&self, _callback: Box<dyn FnMut(SystemPowerEvent)>) {}
425    fn start_power_save_blocker(&self, _kind: PowerSaveBlockerKind) -> Option<u32> {
426        None
427    }
428    fn stop_power_save_blocker(&self, _id: u32) {}
429    fn system_idle_time(&self) -> Option<Duration> {
430        None
431    }
432    fn network_status(&self) -> NetworkStatus {
433        NetworkStatus::Online
434    }
435    fn on_network_status_change(&self, _callback: Box<dyn FnMut(NetworkStatus)>) {}
436    fn on_media_key_event(&self, _callback: Box<dyn FnMut(MediaKeyEvent)>) {}
437    fn request_user_attention(&self, _attention_type: AttentionType) {}
438    fn cancel_user_attention(&self) {}
439    fn set_dock_badge(&self, _label: Option<&str>) {}
440    fn show_context_menu(
441        &self,
442        _position: Point<Pixels>,
443        _items: Vec<TrayMenuItem>,
444        _callback: Box<dyn FnMut(SharedString)>,
445    ) {
446    }
447    fn show_dialog(&self, _options: DialogOptions) -> oneshot::Receiver<usize> {
448        let (tx, rx) = oneshot::channel();
449        tx.send(0).ok();
450        rx
451    }
452    fn os_info(&self) -> OsInfo {
453        OsInfo {
454            name: std::env::consts::OS.into(),
455            arch: std::env::consts::ARCH.into(),
456            version: String::new().into(),
457            locale: String::new().into(),
458            hostname: String::new().into(),
459        }
460    }
461    fn biometric_status(&self) -> BiometricStatus {
462        BiometricStatus::Unavailable
463    }
464    fn authenticate_biometric(&self, _reason: &str, callback: Box<dyn FnOnce(bool) + Send>) {
465        callback(false);
466    }
467}
468
469/// A handle to a platform's display, e.g. a monitor or laptop screen.
470pub trait PlatformDisplay: Send + Sync + Debug {
471    /// Get the ID for this display
472    fn id(&self) -> DisplayId;
473
474    /// Returns a stable identifier for this display that can be persisted and used
475    /// across system restarts.
476    fn uuid(&self) -> Result<Uuid>;
477
478    /// Get the bounds for this display
479    fn bounds(&self) -> Bounds<Pixels>;
480
481    /// Get the visible bounds for this display, excluding taskbar/dock areas.
482    /// Defaults to the full display bounds if the platform does not expose a
483    /// separate work area.
484    fn visible_bounds(&self) -> Bounds<Pixels> {
485        self.bounds()
486    }
487
488    /// Get the default bounds for this display to place a window
489    fn default_bounds(&self) -> Bounds<Pixels> {
490        let bounds = self.visible_bounds();
491        let center = bounds.center();
492        let window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
493        let offset = window_size / 2.0;
494        let origin = point(center.x - offset.width, center.y - offset.height);
495        Bounds::new(origin, window_size)
496    }
497}
498
499/// Thermal state of the system.
500#[derive(Debug, Clone, Copy, PartialEq, Eq)]
501pub enum ThermalState {
502    /// System has no thermal constraints.
503    Nominal,
504    /// System is slightly constrained, reduce discretionary work.
505    Fair,
506    /// System is moderately constrained, reduce CPU/GPU intensive work.
507    Serious,
508    /// System is critically constrained, minimize all resource usage.
509    Critical,
510}
511
512/// Metadata for a given [ScreenCaptureSource]
513#[derive(Clone)]
514pub struct SourceMetadata {
515    /// Opaque identifier of this screen.
516    pub id: u64,
517    /// Human-readable label for this source.
518    pub label: Option<SharedString>,
519    /// Whether this source is the main display.
520    pub is_main: Option<bool>,
521    /// Video resolution of this source.
522    pub resolution: Size<DevicePixels>,
523}
524
525/// A source of on-screen video content that can be captured.
526pub trait ScreenCaptureSource {
527    /// Returns metadata for this source.
528    fn metadata(&self) -> Result<SourceMetadata>;
529
530    /// Start capture video from this source, invoking the given callback
531    /// with each frame.
532    fn stream(
533        &self,
534        foreground_executor: &ForegroundExecutor,
535        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
536    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
537
538    /// Starts capture and reports its terminal state.
539    ///
540    /// Startup failures are returned through the receiver. Implementations
541    /// invoke `termination_callback` exactly once after a started stream ends,
542    /// is cancelled, or fails.
543    fn stream_with_termination(
544        &self,
545        _foreground_executor: &ForegroundExecutor,
546        _frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
547        _termination_callback: ScreenCaptureTerminationCallback,
548    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
549        let (mut sender, receiver) = oneshot::channel();
550        sender
551            .send(Err(anyhow::anyhow!(
552                "runtime termination notification is not supported by this screen capture source"
553            )))
554            .ok();
555        receiver
556    }
557}
558
559/// Receives a single terminal state from a started screen-capture stream.
560pub type ScreenCaptureTerminationCallback = Box<dyn FnOnce(ScreenCaptureStreamTermination) + Send>;
561
562/// The terminal state of a started screen-capture stream.
563#[derive(Debug, Clone, PartialEq, Eq)]
564pub enum ScreenCaptureStreamTermination {
565    /// The stream completed without an error.
566    Ended,
567    /// The stream was cancelled before completion.
568    Cancelled,
569    /// The stream ended because capture failed at runtime.
570    Failed(SharedString),
571}
572
573/// A video stream captured from a screen.
574pub trait ScreenCaptureStream {
575    /// Returns metadata for this source.
576    fn metadata(&self) -> Result<SourceMetadata>;
577}
578
579/// A frame of video captured from a screen.
580pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
581
582/// An opaque identifier for a hardware display
583#[derive(PartialEq, Eq, Hash, Copy, Clone)]
584pub struct DisplayId(pub(crate) u64);
585
586impl DisplayId {
587    /// Creates a display identifier from a raw platform display value.
588    #[doc(hidden)]
589    pub fn new(id: u64) -> Self {
590        Self(id)
591    }
592
593    /// Returns this identifier as a zero-based platform display index.
594    #[doc(hidden)]
595    pub fn index(self) -> usize {
596        self.0 as usize
597    }
598}
599
600impl From<u32> for DisplayId {
601    fn from(id: u32) -> Self {
602        Self(u64::from(id))
603    }
604}
605
606impl From<DisplayId> for u32 {
607    fn from(id: DisplayId) -> Self {
608        id.0 as u32
609    }
610}
611
612impl From<DisplayId> for u64 {
613    fn from(id: DisplayId) -> Self {
614        id.0
615    }
616}
617
618impl Debug for DisplayId {
619    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620        write!(f, "DisplayId({})", self.0)
621    }
622}
623
624unsafe impl Send for DisplayId {}
625
626/// Which part of the window to resize
627#[derive(Debug, Clone, Copy, PartialEq, Eq)]
628pub enum ResizeEdge {
629    /// The top edge
630    Top,
631    /// The top right corner
632    TopRight,
633    /// The right edge
634    Right,
635    /// The bottom right corner
636    BottomRight,
637    /// The bottom edge
638    Bottom,
639    /// The bottom left corner
640    BottomLeft,
641    /// The left edge
642    Left,
643    /// The top left corner
644    TopLeft,
645}
646
647/// A type to describe the appearance of a window
648#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
649pub enum WindowDecorations {
650    #[default]
651    /// Server side decorations
652    Server,
653    /// Client side decorations
654    Client,
655}
656
657/// A type to describe how this window is currently configured
658#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
659pub enum Decorations {
660    /// The window is configured to use server side decorations
661    #[default]
662    Server,
663    /// The window is configured to use client side decorations
664    Client {
665        /// The edge tiling state
666        tiling: Tiling,
667    },
668}
669
670/// What window controls this platform supports
671#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
672pub struct WindowControls {
673    /// Whether this platform supports fullscreen
674    pub fullscreen: bool,
675    /// Whether this platform supports maximize
676    pub maximize: bool,
677    /// Whether this platform supports minimize
678    pub minimize: bool,
679    /// Whether this platform supports a window menu
680    pub window_menu: bool,
681}
682
683impl Default for WindowControls {
684    fn default() -> Self {
685        // Assume that we can do anything, unless told otherwise
686        Self {
687            fullscreen: true,
688            maximize: true,
689            minimize: true,
690            window_menu: true,
691        }
692    }
693}
694
695/// A type to describe which sides of the window are currently tiled in some way
696#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
697pub struct Tiling {
698    /// Whether the top edge is tiled
699    pub top: bool,
700    /// Whether the left edge is tiled
701    pub left: bool,
702    /// Whether the right edge is tiled
703    pub right: bool,
704    /// Whether the bottom edge is tiled
705    pub bottom: bool,
706}
707
708impl Tiling {
709    /// Initializes a [`Tiling`] type with all sides tiled
710    pub fn tiled() -> Self {
711        Self {
712            top: true,
713            left: true,
714            right: true,
715            bottom: true,
716        }
717    }
718
719    /// Whether any edge is tiled
720    pub fn is_tiled(&self) -> bool {
721        self.top || self.left || self.right || self.bottom
722    }
723}
724
725#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
726#[allow(missing_docs)]
727pub struct RequestFrameOptions {
728    pub require_presentation: bool,
729    /// Force refresh of all rendering states when true
730    pub force_render: bool,
731}
732
733/// Callbacks for the accessibility adapter.
734pub struct A11yCallbacks {
735    /// Called when the adapter is activated.
736    pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
737    /// Called when an action is requested by the screen reader.
738    pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
739    /// Called when the adapter is deactivated.
740    pub deactivation: Box<dyn Fn() + Send + 'static>,
741}
742
743/// Whether the platform is presenting a window's frames.
744///
745/// This is about presentation, not the window's shown/hidden state: a shown
746/// window that is fully covered by other windows, minimized, on another
747/// Space or virtual desktop, or on a display that is asleep is `Hidden`. A
748/// window only partly covered by other windows is `Visible`.
749///
750/// Each platform reports from a single source, and what that source can see
751/// differs:
752///
753/// * macOS: `NSWindow.occlusionState`. Covers all of the cases above.
754/// * Windows: `WS_VISIBLE` and the minimized state. Windows keeps compositing
755///   covered windows for thumbnails and Alt-Tab and offers no occlusion
756///   notification, so a fully covered window stays `Visible`. Display sleep is
757///   not reported either.
758/// * Wayland: the `xdg_toplevel` `suspended` state (xdg-shell v6). Compositors
759///   that don't support it never report `Hidden`.
760/// * X11: mapped state plus `VisibilityNotify`. Compositing window managers
761///   generally never report a window as fully obscured, so covering is only
762///   detected without compositing.
763#[derive(Debug, Clone, Copy, PartialEq, Eq)]
764pub enum WindowVisibility {
765    /// At least part of the window is being presented; frames drawn for it
766    /// will be shown.
767    Visible,
768    /// No part of the window is being presented. The platform will not
769    /// request frames for it until it becomes visible again.
770    Hidden,
771}
772
773impl WindowVisibility {
774    /// Whether frames drawn for the window will be shown.
775    pub fn is_visible(self) -> bool {
776        self == Self::Visible
777    }
778}
779
780#[allow(missing_docs)]
781pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
782    fn bounds(&self) -> Bounds<Pixels>;
783    fn is_maximized(&self) -> bool;
784    fn window_bounds(&self) -> WindowBounds;
785    fn content_size(&self) -> Size<Pixels>;
786    fn resize(&mut self, size: Size<Pixels>);
787    fn scale_factor(&self) -> f32;
788    fn appearance(&self) -> WindowAppearance;
789    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
790    fn mouse_position(&self) -> Point<Pixels>;
791    fn modifiers(&self) -> Modifiers;
792    fn capslock(&self) -> Capslock;
793    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
794    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
795    fn prompt(
796        &self,
797        level: PromptLevel,
798        msg: &str,
799        detail: Option<&str>,
800        answers: &[PromptButton],
801    ) -> Option<oneshot::Receiver<usize>>;
802    fn activate(&self);
803    /// Requests that the operating system draw attention to this window.
804    fn request_attention(&self) {}
805    fn is_active(&self) -> bool;
806    /// The current [`WindowVisibility`]. Read once when the window is created;
807    /// afterwards changes arrive through [`Self::on_visibility_change`].
808    fn visibility(&self) -> WindowVisibility;
809    fn is_hovered(&self) -> bool;
810    fn set_title(&mut self, title: &str);
811    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
812    fn minimize(&self);
813    fn zoom(&self);
814    fn toggle_fullscreen(&self);
815    fn is_fullscreen(&self) -> bool;
816    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
817    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
818    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
819    /// Registers the callback invoked when [`Self::visibility`] changes. Only
820    /// transitions are reported; the callback runs on the main thread outside
821    /// of any window update.
822    fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
823    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
824    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
825    fn on_moved(&self, callback: Box<dyn FnMut()>);
826    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
827    fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
828    fn on_close(&self, callback: Box<dyn FnOnce()>);
829    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
830    fn draw(&self, scene: &Scene);
831    #[cfg(any(test, feature = "test-support"))]
832    fn render_to_image(&self, _scene: &Scene) -> anyhow::Result<image::RgbaImage> {
833        anyhow::bail!("render_to_image not implemented for this platform")
834    }
835    fn schedule_frame(&self) {}
836    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
837
838    // macOS specific methods
839    fn get_title(&self) -> String {
840        String::new()
841    }
842    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
843        None
844    }
845    fn tab_bar_visible(&self) -> bool {
846        false
847    }
848    fn set_edited(&mut self, _edited: bool) {}
849    fn toggle_simple_fullscreen(&self) {}
850    fn is_simple_fullscreen(&self) -> bool {
851        false
852    }
853    fn show_character_palette(&self) {}
854    fn titlebar_double_click(&self) {}
855    fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
856    fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
857    fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
858    fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
859    fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
860    fn merge_all_windows(&self) {}
861    fn move_tab_to_new_window(&self) {}
862    fn toggle_window_tab_overview(&self) {}
863    fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
864
865    fn native_window_state(&self) -> Option<Vec<u8>> {
866        None
867    }
868    fn restore_native_window_state(&self, _state: &[u8]) {}
869
870    #[cfg(target_os = "windows")]
871    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
872
873    // Linux specific methods
874    fn inner_window_bounds(&self) -> WindowBounds {
875        self.window_bounds()
876    }
877    fn request_decorations(&self, _decorations: WindowDecorations) {}
878    fn show_window_menu(&self, _position: Point<Pixels>) {}
879    fn start_window_move(&self) {}
880    fn start_window_resize(&self, _edge: ResizeEdge) {}
881    fn set_exclusive_zone(&self, _zone: Pixels) {}
882    #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
883    fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
884    fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
885    fn window_decorations(&self) -> Decorations {
886        Decorations::Server
887    }
888    fn set_app_id(&mut self, _app_id: &str) {}
889    fn map_window(&mut self) -> anyhow::Result<()> {
890        Ok(())
891    }
892    fn window_controls(&self) -> WindowControls {
893        WindowControls::default()
894    }
895    fn set_client_inset(&self, _inset: Pixels) {}
896    fn gpu_specs(&self) -> Option<GpuSpecs>;
897
898    fn update_ime_position(&self, _bounds: Bounds<Pixels>);
899
900    fn play_system_bell(&self) {}
901
902    /// Initialize the accessibility adapter with callbacks.
903    fn a11y_init(&self, _callbacks: A11yCallbacks) {}
904
905    /// Provide a tree update to the accessibility adapter.
906    fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
907
908    /// Inform the adapter of updated window bounds.
909    fn a11y_update_window_bounds(&self) {}
910
911    fn show(&self) {}
912    fn hide(&self) {}
913    fn is_visible(&self) -> bool {
914        true
915    }
916    fn set_mouse_passthrough(&self, _passthrough: bool) {}
917    fn set_progress_bar(&self, _state: ProgressBarState) {}
918    #[allow(dead_code)]
919    fn set_window_icon(&self, _icon: Option<image::RgbaImage>) {}
920
921    fn set_cursor_style(&self, _style: CursorStyle) {}
922
923    #[cfg(any(test, feature = "test-support"))]
924    fn as_test(&mut self) -> Option<&mut TestWindow> {
925        None
926    }
927}
928
929/// This type is public so that our test macro can generate and use it, but it should not
930/// be considered part of our public API.
931#[doc(hidden)]
932pub trait PlatformDispatcher: Send + Sync {
933    fn is_main_thread(&self) -> bool;
934    fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
935    fn dispatch_on_main_thread(&self, runnable: Runnable);
936    fn dispatch_after(&self, duration: Duration, runnable: Runnable);
937    fn park(&self, timeout: Option<Duration>) -> bool;
938    fn unparker(&self) -> Unparker;
939    fn now(&self) -> Instant {
940        Instant::now()
941    }
942
943    /// Prevents App Nap-style throttling while the returned guard is held.
944    ///
945    /// This does not prevent the system from entering idle sleep.
946    fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
947        ActivityGuard::noop()
948    }
949
950    #[cfg(any(test, feature = "test-support"))]
951    fn as_test(&self) -> Option<&TestDispatcher> {
952        None
953    }
954
955    #[cfg(any(test, feature = "test-support"))]
956    fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
957        None
958    }
959}
960
961#[allow(missing_docs)]
962pub trait PlatformTextSystem: Send + Sync {
963    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
964    fn all_font_names(&self) -> Vec<String>;
965    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
966    /// Drop platform-side glyph rasterization data associated with the core raster-bounds cache.
967    fn clear_glyph_raster_cache(&self) {}
968    /// Prewarm any system font caches needed to shape text.
969    fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
970    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
971    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
972    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
973    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
974    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
975    fn rasterize_glyph(
976        &self,
977        params: &RenderGlyphParams,
978        raster_bounds: Bounds<DevicePixels>,
979    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
980    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
981
982    /// Installs the sink for grapheme clusters that exhaust font fallback during
983    /// [`Self::layout_line`]. The core text system installs its reporter when it is
984    /// created. Platforms that do not detect missing glyphs keep the default no-op.
985    fn set_missing_glyph_sink(&self, _sink: Arc<dyn MissingGlyphSink>) {}
986
987    fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
988        0
989    }
990}
991
992#[allow(missing_docs)]
993pub struct NoopTextSystem;
994
995#[allow(missing_docs)]
996impl NoopTextSystem {
997    #[allow(dead_code)]
998    pub fn new() -> Self {
999        Self
1000    }
1001}
1002
1003impl PlatformTextSystem for NoopTextSystem {
1004    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1005        Ok(())
1006    }
1007
1008    fn all_font_names(&self) -> Vec<String> {
1009        Vec::new()
1010    }
1011
1012    fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1013        Ok(FontId(1))
1014    }
1015
1016    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1017        FontMetrics {
1018            units_per_em: 1000,
1019            ascent: 1025.0,
1020            descent: -275.0,
1021            line_gap: 0.0,
1022            underline_position: -95.0,
1023            underline_thickness: 60.0,
1024            cap_height: 698.0,
1025            x_height: 516.0,
1026            bounding_box: Bounds {
1027                origin: Point {
1028                    x: -260.0,
1029                    y: -245.0,
1030                },
1031                size: Size {
1032                    width: 1501.0,
1033                    height: 1364.0,
1034                },
1035            },
1036        }
1037    }
1038
1039    fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1040        Ok(Bounds {
1041            origin: Point { x: 54.0, y: 0.0 },
1042            size: size(392.0, 528.0),
1043        })
1044    }
1045
1046    fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1047        Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1048    }
1049
1050    fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1051        Some(GlyphId(ch.len_utf16() as u32))
1052    }
1053
1054    fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1055        Ok(Default::default())
1056    }
1057
1058    fn rasterize_glyph(
1059        &self,
1060        _params: &RenderGlyphParams,
1061        raster_bounds: Bounds<DevicePixels>,
1062    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1063        Ok((raster_bounds.size, Vec::new()))
1064    }
1065
1066    fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1067        let mut position = px(0.);
1068        let metrics = self.font_metrics(FontId(0));
1069        let em_width = font_size
1070            * self
1071                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1072                .unwrap()
1073                .width
1074            / metrics.units_per_em as f32;
1075        let mut glyphs = Vec::new();
1076        for (ix, c) in text.char_indices() {
1077            if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1078                glyphs.push(ShapedGlyph {
1079                    id: glyph,
1080                    position: point(position, px(0.)),
1081                    index: ix,
1082                    is_emoji: glyph.0 == 2,
1083                });
1084                if glyph.0 == 2 {
1085                    position += em_width * 2.0;
1086                } else {
1087                    position += em_width;
1088                }
1089            } else {
1090                position += em_width
1091            }
1092        }
1093        let mut runs = Vec::default();
1094        if !glyphs.is_empty() {
1095            runs.push(ShapedRun {
1096                font_id: FontId(0),
1097                glyphs,
1098            });
1099        } else {
1100            position = px(0.);
1101        }
1102
1103        LineLayout {
1104            font_size,
1105            width: position,
1106            ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1107            descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1108            runs,
1109            len: text.len(),
1110        }
1111    }
1112}
1113
1114#[derive(PartialEq, Eq, Hash, Clone)]
1115#[allow(missing_docs)]
1116pub enum AtlasKey {
1117    Glyph(RenderGlyphParams),
1118    Svg(RenderSvgParams),
1119    Image(RenderImageParams),
1120}
1121
1122#[allow(missing_docs)]
1123impl AtlasKey {
1124    #[cfg_attr(
1125        all(
1126            any(target_os = "linux", target_os = "freebsd"),
1127            not(any(feature = "x11", feature = "wayland"))
1128        ),
1129        allow(dead_code)
1130    )]
1131    pub fn texture_kind(&self) -> AtlasTextureKind {
1132        match self {
1133            AtlasKey::Glyph(params) => {
1134                if params.is_emoji {
1135                    AtlasTextureKind::Polychrome
1136                } else {
1137                    AtlasTextureKind::Monochrome
1138                }
1139            }
1140            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1141            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1142        }
1143    }
1144}
1145
1146impl From<RenderGlyphParams> for AtlasKey {
1147    fn from(params: RenderGlyphParams) -> Self {
1148        Self::Glyph(params)
1149    }
1150}
1151
1152impl From<RenderSvgParams> for AtlasKey {
1153    fn from(params: RenderSvgParams) -> Self {
1154        Self::Svg(params)
1155    }
1156}
1157
1158impl From<RenderImageParams> for AtlasKey {
1159    fn from(params: RenderImageParams) -> Self {
1160        Self::Image(params)
1161    }
1162}
1163
1164#[allow(missing_docs)]
1165pub trait PlatformAtlas: Send + Sync {
1166    fn get_or_insert_with<'a>(
1167        &self,
1168        key: &AtlasKey,
1169        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1170    ) -> Result<Option<AtlasTile>>;
1171    fn remove(&self, key: &AtlasKey);
1172}
1173
1174#[doc(hidden)]
1175pub struct AtlasTextureList<T> {
1176    pub textures: Vec<Option<T>>,
1177    pub free_list: Vec<usize>,
1178}
1179
1180impl<T> Default for AtlasTextureList<T> {
1181    fn default() -> Self {
1182        Self {
1183            textures: Vec::default(),
1184            free_list: Vec::default(),
1185        }
1186    }
1187}
1188
1189impl<T> ops::Index<usize> for AtlasTextureList<T> {
1190    type Output = Option<T>;
1191
1192    fn index(&self, index: usize) -> &Self::Output {
1193        &self.textures[index]
1194    }
1195}
1196
1197#[allow(missing_docs)]
1198impl<T> AtlasTextureList<T> {
1199    #[allow(unused)]
1200    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1201        self.free_list.clear();
1202        self.textures.drain(..)
1203    }
1204
1205    #[allow(dead_code)]
1206    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1207        self.textures.iter_mut().flatten()
1208    }
1209}
1210
1211#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1212#[repr(C)]
1213#[allow(missing_docs)]
1214pub struct AtlasTile {
1215    pub texture_id: AtlasTextureId,
1216    pub tile_id: TileId,
1217    pub padding: u32,
1218    pub bounds: Bounds<DevicePixels>,
1219}
1220
1221#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1222#[repr(C)]
1223#[allow(missing_docs)]
1224pub struct AtlasTextureId {
1225    // We use u32 instead of usize for Metal Shader Language compatibility
1226    pub index: u32,
1227    pub kind: AtlasTextureKind,
1228}
1229
1230#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1231#[repr(C)]
1232#[cfg_attr(
1233    all(
1234        any(target_os = "linux", target_os = "freebsd"),
1235        not(any(feature = "x11", feature = "wayland"))
1236    ),
1237    allow(dead_code)
1238)]
1239#[allow(missing_docs)]
1240pub enum AtlasTextureKind {
1241    Monochrome = 0,
1242    Polychrome = 1,
1243}
1244
1245#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1246#[repr(C)]
1247#[allow(missing_docs)]
1248pub struct TileId(pub u32);
1249
1250impl From<etagere::AllocId> for TileId {
1251    fn from(id: etagere::AllocId) -> Self {
1252        Self(id.serialize())
1253    }
1254}
1255
1256impl From<TileId> for etagere::AllocId {
1257    fn from(id: TileId) -> Self {
1258        Self::deserialize(id.0)
1259    }
1260}
1261
1262#[allow(missing_docs)]
1263pub struct PlatformInputHandler {
1264    cx: AsyncWindowContext,
1265    handler: Box<dyn InputHandler>,
1266}
1267
1268#[cfg_attr(
1269    all(
1270        any(target_os = "linux", target_os = "freebsd"),
1271        not(any(feature = "x11", feature = "wayland"))
1272    ),
1273    allow(dead_code)
1274)]
1275#[allow(missing_docs)]
1276impl PlatformInputHandler {
1277    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1278        Self { cx, handler }
1279    }
1280
1281    fn mark_keyboard_input(window: &Window) {
1282        window.input_rate_tracker.borrow_mut().record_input();
1283        window.last_input_was_keyboard.set(true);
1284    }
1285
1286    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1287        self.cx
1288            .update(|window, cx| {
1289                self.handler
1290                    .selected_text_range(ignore_disabled_input, window, cx)
1291            })
1292            .ok()
1293            .flatten()
1294    }
1295
1296    #[cfg_attr(target_os = "windows", allow(dead_code))]
1297    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1298        self.cx
1299            .update(|window, cx| self.handler.marked_text_range(window, cx))
1300            .ok()
1301            .flatten()
1302    }
1303
1304    #[cfg_attr(
1305        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1306        allow(dead_code)
1307    )]
1308    pub fn text_for_range(
1309        &mut self,
1310        range_utf16: Range<usize>,
1311        adjusted: &mut Option<Range<usize>>,
1312    ) -> Option<String> {
1313        self.cx
1314            .update(|window, cx| {
1315                self.handler
1316                    .text_for_range(range_utf16, adjusted, window, cx)
1317            })
1318            .ok()
1319            .flatten()
1320    }
1321
1322    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1323        self.cx
1324            .update(|window, cx| {
1325                Self::mark_keyboard_input(window);
1326                self.handler
1327                    .replace_text_in_range(replacement_range, text, window, cx);
1328            })
1329            .ok();
1330    }
1331
1332    pub fn replace_and_mark_text_in_range(
1333        &mut self,
1334        range_utf16: Option<Range<usize>>,
1335        new_text: &str,
1336        new_selected_range: Option<Range<usize>>,
1337    ) {
1338        self.cx
1339            .update(|window, cx| {
1340                Self::mark_keyboard_input(window);
1341                self.handler.replace_and_mark_text_in_range(
1342                    range_utf16,
1343                    new_text,
1344                    new_selected_range,
1345                    window,
1346                    cx,
1347                )
1348            })
1349            .ok();
1350    }
1351
1352    #[cfg_attr(target_os = "windows", allow(dead_code))]
1353    pub fn unmark_text(&mut self) {
1354        self.cx
1355            .update(|window, cx| {
1356                Self::mark_keyboard_input(window);
1357                self.handler.unmark_text(window, cx)
1358            })
1359            .ok();
1360    }
1361
1362    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1363        self.cx
1364            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1365            .ok()
1366            .flatten()
1367    }
1368
1369    #[allow(dead_code)]
1370    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1371        self.handler.apple_press_and_hold_enabled()
1372    }
1373
1374    pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1375        // Note: record_input() is already called by dispatch_event for the key event
1376        // that triggered this text insertion, so we only update last_input_was_keyboard here.
1377        window.last_input_was_keyboard.set(true);
1378        self.handler.replace_text_in_range(None, input, window, cx);
1379    }
1380
1381    pub fn compute_ime_candidate_bounds(
1382        marked_range: Option<Range<usize>>,
1383        selection: &UTF16Selection,
1384        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1385    ) -> Option<Bounds<Pixels>> {
1386        if let Some(marked_range) = marked_range {
1387            let mut line_start = marked_range.start;
1388            let caret = selection.range.end;
1389            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1390                for i in (marked_range.start..caret).rev() {
1391                    if let Some(bounds) = bounds_for_range(i..i)
1392                        && (bounds.origin.y - caret_bounds.origin.y).abs() > px(0.1)
1393                    {
1394                        line_start = i + 1;
1395                        break;
1396                    }
1397                }
1398            }
1399            bounds_for_range(line_start..line_start)
1400        } else {
1401            let offset = if selection.reversed {
1402                selection.range.start
1403            } else {
1404                selection.range.end
1405            };
1406            bounds_for_range(offset..offset)
1407        }
1408    }
1409
1410    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1411        let marked_range = self.handler.marked_text_range(window, cx);
1412        let selection = self.handler.selected_text_range(true, window, cx)?;
1413        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1414            self.handler.bounds_for_range(range, window, cx)
1415        })
1416    }
1417
1418    #[allow(dead_code)]
1419    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1420        let marked_range = self.marked_text_range();
1421        let selection = self.selected_text_range(true)?;
1422        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1423            self.bounds_for_range(range)
1424        })
1425    }
1426
1427    #[allow(unused)]
1428    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1429        self.cx
1430            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1431            .ok()
1432            .flatten()
1433    }
1434
1435    #[allow(dead_code)]
1436    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1437        self.handler.accepts_text_input(window, cx)
1438    }
1439
1440    #[allow(dead_code)]
1441    pub fn query_accepts_text_input(&mut self) -> bool {
1442        self.cx
1443            .update(|window, cx| self.handler.accepts_text_input(window, cx))
1444            .unwrap_or(true)
1445    }
1446}
1447
1448/// A struct representing a selection in a text buffer, in UTF16 characters.
1449/// This is different from a range because the head may be before the tail.
1450#[derive(Debug)]
1451pub struct UTF16Selection {
1452    /// The range of text in the document this selection corresponds to
1453    /// in UTF16 characters.
1454    pub range: Range<usize>,
1455    /// Whether the head of this selection is at the start (true), or end (false)
1456    /// of the range
1457    pub reversed: bool,
1458}
1459
1460/// Zed's interface for handling text input from the platform's IME system
1461/// This is currently a 1:1 exposure of the NSTextInputClient API:
1462///
1463/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
1464pub trait InputHandler: 'static {
1465    /// Get the range of the user's currently selected text, if any
1466    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
1467    ///
1468    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1469    fn selected_text_range(
1470        &mut self,
1471        ignore_disabled_input: bool,
1472        window: &mut Window,
1473        cx: &mut App,
1474    ) -> Option<UTF16Selection>;
1475
1476    /// Get the range of the currently marked text, if any
1477    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
1478    ///
1479    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1480    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1481
1482    /// Get the text for the given document range in UTF-16 characters
1483    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
1484    ///
1485    /// range_utf16 is in terms of UTF-16 characters
1486    fn text_for_range(
1487        &mut self,
1488        range_utf16: Range<usize>,
1489        adjusted_range: &mut Option<Range<usize>>,
1490        window: &mut Window,
1491        cx: &mut App,
1492    ) -> Option<String>;
1493
1494    /// Replace the text in the given document range with the given text
1495    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
1496    ///
1497    /// replacement_range is in terms of UTF-16 characters
1498    fn replace_text_in_range(
1499        &mut self,
1500        replacement_range: Option<Range<usize>>,
1501        text: &str,
1502        window: &mut Window,
1503        cx: &mut App,
1504    );
1505
1506    /// Replace the text in the given document range with the given text,
1507    /// and mark the given text as part of an IME 'composing' state
1508    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
1509    ///
1510    /// range_utf16 is in terms of UTF-16 characters
1511    /// new_selected_range is in terms of UTF-16 characters
1512    fn replace_and_mark_text_in_range(
1513        &mut self,
1514        range_utf16: Option<Range<usize>>,
1515        new_text: &str,
1516        new_selected_range: Option<Range<usize>>,
1517        window: &mut Window,
1518        cx: &mut App,
1519    );
1520
1521    /// Remove the IME 'composing' state from the document
1522    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
1523    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1524
1525    /// Get the bounds of the given document range in screen coordinates
1526    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
1527    ///
1528    /// This is used for positioning the IME candidate window
1529    fn bounds_for_range(
1530        &mut self,
1531        range_utf16: Range<usize>,
1532        window: &mut Window,
1533        cx: &mut App,
1534    ) -> Option<Bounds<Pixels>>;
1535
1536    /// Get the character offset for the given point in terms of UTF16 characters
1537    ///
1538    /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
1539    fn character_index_for_point(
1540        &mut self,
1541        point: Point<Pixels>,
1542        window: &mut Window,
1543        cx: &mut App,
1544    ) -> Option<usize>;
1545
1546    /// Allows a given input context to opt into getting raw key repeats instead of
1547    /// sending these to the platform.
1548    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
1549    /// (which is how iTerm does it) but it doesn't seem to work for me.
1550    #[allow(dead_code)]
1551    fn apple_press_and_hold_enabled(&mut self) -> bool {
1552        true
1553    }
1554
1555    /// Returns whether this handler is accepting text input to be inserted.
1556    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1557        true
1558    }
1559}
1560
1561/// The variables that can be configured when creating a new window
1562#[derive(Debug)]
1563pub struct WindowOptions {
1564    /// Specifies the state and bounds of the window in screen coordinates.
1565    /// - `None`: Inherit the bounds.
1566    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
1567    pub window_bounds: Option<WindowBounds>,
1568
1569    /// The titlebar configuration of the window
1570    pub titlebar: Option<TitlebarOptions>,
1571
1572    /// Whether the window should be focused when created
1573    pub focus: bool,
1574
1575    /// Whether the window should be shown when created
1576    pub show: bool,
1577
1578    /// The kind of window to create
1579    pub kind: WindowKind,
1580
1581    /// Whether the window should be movable by the user.
1582    pub is_movable: bool,
1583
1584    /// Whether the application owns dragging of its custom titlebar on macOS.
1585    ///
1586    /// This prevents AppKit from claiming the titlebar region or delaying clicks,
1587    /// while leaving `is_movable` enabled for application-driven window moves.
1588    pub app_owns_titlebar_drag: bool,
1589
1590    /// The minimum interval between animation frames while the window is inactive.
1591    ///
1592    /// Set to `None` to disable inactive-window animation frame throttling.
1593    /// Overlay, HUD, and daemon windows that keep animating while unfocused
1594    /// can set a shorter interval (or `None`) here.
1595    pub inactive_frame_interval: Option<Duration>,
1596
1597    /// Whether the window should be resizable by the user
1598    pub is_resizable: bool,
1599
1600    /// Whether the window should be minimized by the user
1601    pub is_minimizable: bool,
1602
1603    /// The display to create the window on, if this is None,
1604    /// the window will be created on the main display
1605    pub display_id: Option<DisplayId>,
1606
1607    /// The appearance of the window background.
1608    pub window_background: WindowBackgroundAppearance,
1609
1610    /// Application identifier of the window. Can by used by desktop environments to group applications together.
1611    pub app_id: Option<String>,
1612
1613    /// Window minimum size
1614    pub window_min_size: Option<Size<Pixels>>,
1615
1616    /// Whether to use client or server side decorations. Wayland only
1617    /// Note that this may be ignored.
1618    pub window_decorations: Option<WindowDecorations>,
1619
1620    /// 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.
1621    pub tabbing_identifier: Option<String>,
1622
1623    /// Whether the window should allow mouse events to pass through to windows behind it
1624    pub mouse_passthrough: bool,
1625
1626    /// Optional window icon (currently consumed only on X11; ignored elsewhere)
1627    pub icon: Option<image::RgbaImage>,
1628}
1629
1630/// The variables that can be configured when creating a new window
1631#[derive(Debug)]
1632#[cfg_attr(
1633    all(
1634        any(target_os = "linux", target_os = "freebsd"),
1635        not(any(feature = "x11", feature = "wayland"))
1636    ),
1637    allow(dead_code)
1638)]
1639#[allow(missing_docs)]
1640pub struct WindowParams {
1641    pub bounds: Bounds<Pixels>,
1642
1643    /// The titlebar configuration of the window
1644    #[cfg_attr(feature = "wayland", allow(dead_code))]
1645    pub titlebar: Option<TitlebarOptions>,
1646
1647    /// The kind of window to create
1648    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1649    pub kind: WindowKind,
1650
1651    /// Whether the window should be movable by the user
1652    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1653    pub is_movable: bool,
1654
1655    /// Whether the application owns custom-titlebar dragging on macOS.
1656    #[cfg_attr(
1657        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1658        allow(dead_code)
1659    )]
1660    pub app_owns_titlebar_drag: bool,
1661
1662    /// Whether the window should be resizable by the user
1663    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1664    pub is_resizable: bool,
1665
1666    /// Whether the window should be minimized by the user
1667    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1668    pub is_minimizable: bool,
1669
1670    #[cfg_attr(
1671        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1672        allow(dead_code)
1673    )]
1674    pub focus: bool,
1675
1676    #[cfg_attr(
1677        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1678        allow(dead_code)
1679    )]
1680    pub app_id: Option<String>,
1681
1682    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1683    pub show: bool,
1684
1685    #[cfg_attr(feature = "wayland", allow(dead_code))]
1686    pub display_id: Option<DisplayId>,
1687
1688    pub window_min_size: Option<Size<Pixels>>,
1689    #[cfg(target_os = "macos")]
1690    pub tabbing_identifier: Option<String>,
1691
1692    #[allow(dead_code)]
1693    pub mouse_passthrough: bool,
1694
1695    /// Optional window icon (primarily for X11)
1696    #[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
1697    pub icon: Option<image::RgbaImage>,
1698}
1699
1700/// Represents the status of how a window should be opened.
1701#[derive(Debug, Copy, Clone, PartialEq)]
1702pub enum WindowBounds {
1703    /// Indicates that the window should open in a windowed state with the given bounds.
1704    Windowed(Bounds<Pixels>),
1705    /// Indicates that the window should open in a maximized state.
1706    /// The bounds provided here represent the restore size of the window.
1707    Maximized(Bounds<Pixels>),
1708    /// Indicates that the window should open in fullscreen mode.
1709    /// The bounds provided here represent the restore size of the window.
1710    Fullscreen(Bounds<Pixels>),
1711}
1712
1713impl Default for WindowBounds {
1714    fn default() -> Self {
1715        WindowBounds::Windowed(Bounds::default())
1716    }
1717}
1718
1719impl WindowBounds {
1720    /// Retrieve the inner bounds
1721    pub fn get_bounds(&self) -> Bounds<Pixels> {
1722        match self {
1723            WindowBounds::Windowed(bounds) => *bounds,
1724            WindowBounds::Maximized(bounds) => *bounds,
1725            WindowBounds::Fullscreen(bounds) => *bounds,
1726        }
1727    }
1728
1729    /// Creates windowed bounds centered in the primary display's work area.
1730    ///
1731    /// Delegates to [`Bounds::centered`], so the size is clamped with [`Size::min`]
1732    /// and the origin uses `visible_bounds` rather than the full display.
1733    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1734        WindowBounds::Windowed(Bounds::centered(None, size, cx))
1735    }
1736}
1737
1738impl Default for WindowOptions {
1739    fn default() -> Self {
1740        Self {
1741            window_bounds: None,
1742            titlebar: Some(TitlebarOptions {
1743                title: Default::default(),
1744                appears_transparent: Default::default(),
1745                traffic_light_position: Default::default(),
1746            }),
1747            focus: true,
1748            show: true,
1749            kind: WindowKind::Normal,
1750            is_movable: true,
1751            app_owns_titlebar_drag: false,
1752            inactive_frame_interval: Some(Duration::from_micros(33_333)),
1753            is_resizable: true,
1754            is_minimizable: true,
1755            display_id: None,
1756            window_background: WindowBackgroundAppearance::default(),
1757            app_id: None,
1758            window_min_size: None,
1759            window_decorations: None,
1760            tabbing_identifier: None,
1761            mouse_passthrough: false,
1762            icon: None,
1763        }
1764    }
1765}
1766
1767#[cfg(test)]
1768mod platform_input_handler_tests {
1769    use super::*;
1770    use crate::{
1771        AppContext, Context, ElementInputHandler, Empty, EntityInputHandler, TestAppContext,
1772    };
1773
1774    struct RejectingEntityInputHandler;
1775
1776    impl EntityInputHandler for RejectingEntityInputHandler {
1777        fn text_for_range(
1778            &mut self,
1779            _range: Range<usize>,
1780            _adjusted_range: &mut Option<Range<usize>>,
1781            _window: &mut Window,
1782            _cx: &mut Context<Self>,
1783        ) -> Option<String> {
1784            None
1785        }
1786
1787        fn selected_text_range(
1788            &mut self,
1789            _ignore_disabled_input: bool,
1790            _window: &mut Window,
1791            _cx: &mut Context<Self>,
1792        ) -> Option<UTF16Selection> {
1793            None
1794        }
1795
1796        fn marked_text_range(
1797            &self,
1798            _window: &mut Window,
1799            _cx: &mut Context<Self>,
1800        ) -> Option<Range<usize>> {
1801            None
1802        }
1803
1804        fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
1805
1806        fn replace_text_in_range(
1807            &mut self,
1808            _range: Option<Range<usize>>,
1809            _text: &str,
1810            _window: &mut Window,
1811            _cx: &mut Context<Self>,
1812        ) {
1813        }
1814
1815        fn replace_and_mark_text_in_range(
1816            &mut self,
1817            _range: Option<Range<usize>>,
1818            _new_text: &str,
1819            _new_selected_range: Option<Range<usize>>,
1820            _window: &mut Window,
1821            _cx: &mut Context<Self>,
1822        ) {
1823        }
1824
1825        fn bounds_for_range(
1826            &mut self,
1827            _range_utf16: Range<usize>,
1828            _element_bounds: Bounds<Pixels>,
1829            _window: &mut Window,
1830            _cx: &mut Context<Self>,
1831        ) -> Option<Bounds<Pixels>> {
1832            None
1833        }
1834
1835        fn character_index_for_point(
1836            &mut self,
1837            _point: Point<Pixels>,
1838            _window: &mut Window,
1839            _cx: &mut Context<Self>,
1840        ) -> Option<usize> {
1841            None
1842        }
1843
1844        fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
1845            false
1846        }
1847    }
1848
1849    #[gpui_macros::test]
1850    fn element_input_handler_forwards_text_input_rejection(cx: &mut TestAppContext) {
1851        let entity = cx.new(|_| RejectingEntityInputHandler);
1852        let window = cx.add_window(|_, _| Empty);
1853
1854        cx.update_window(window.into(), |_, window, cx| {
1855            let mut handler = ElementInputHandler::new(Bounds::default(), entity);
1856            assert!(!handler.accepts_text_input(window, cx));
1857        })
1858        .unwrap();
1859    }
1860
1861    #[gpui_macros::test]
1862    fn platform_input_handler_accepts_text_when_window_update_fails(cx: &mut TestAppContext) {
1863        let entity = cx.new(|_| RejectingEntityInputHandler);
1864        let window = cx.add_window(|_, _| Empty);
1865        let window = AnyWindowHandle::from(window);
1866        let async_cx = cx
1867            .update_window(window, |_, window, cx| window.to_async(cx))
1868            .unwrap();
1869
1870        cx.update_window(window, |_, window, _| window.remove_window())
1871            .unwrap();
1872        assert!(cx.update_window(window, |_, _, _| ()).is_err());
1873
1874        let mut handler = PlatformInputHandler::new(
1875            async_cx,
1876            Box::new(ElementInputHandler::new(Bounds::default(), entity)),
1877        );
1878        assert!(handler.query_accepts_text_input());
1879    }
1880
1881    fn bounds_at(offset: usize, y: f32) -> Bounds<Pixels> {
1882        Bounds::new(
1883            point(px(offset as f32 * 10.0), px(y)),
1884            size(px(1.0), px(20.0)),
1885        )
1886    }
1887
1888    #[test]
1889    fn ime_candidate_bounds_uses_selection_endpoint_without_marked_range() {
1890        let selection = UTF16Selection {
1891            range: 3..5,
1892            reversed: false,
1893        };
1894        let mut requests = Vec::new();
1895        let bounds =
1896            PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1897                requests.push(range.clone());
1898                Some(bounds_at(range.start, 0.0))
1899            })
1900            .unwrap();
1901
1902        assert_eq!(bounds.origin.x, px(50.0));
1903        assert_eq!(requests, vec![5..5]);
1904
1905        let selection = UTF16Selection {
1906            range: 3..5,
1907            reversed: true,
1908        };
1909        let bounds =
1910            PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1911                Some(bounds_at(range.start, 0.0))
1912            })
1913            .unwrap();
1914
1915        assert_eq!(bounds.origin.x, px(30.0));
1916    }
1917
1918    #[test]
1919    fn ime_candidate_bounds_anchors_marked_text_to_visual_line_start() {
1920        let selection = UTF16Selection {
1921            range: 15..15,
1922            reversed: false,
1923        };
1924        let mut requests = Vec::new();
1925        let bounds =
1926            PlatformInputHandler::compute_ime_candidate_bounds(Some(10..15), &selection, |range| {
1927                requests.push(range.clone());
1928                let y = if range.start >= 13 { 20.0 } else { 0.0 };
1929                Some(bounds_at(range.start, y))
1930            })
1931            .unwrap();
1932
1933        assert_eq!(bounds.origin.x, px(130.0));
1934        assert_eq!(requests.last(), Some(&(13..13)));
1935    }
1936}
1937
1938/// The options that can be configured for a window's titlebar
1939#[derive(Debug, Default)]
1940pub struct TitlebarOptions {
1941    /// The initial title of the window
1942    pub title: Option<SharedString>,
1943
1944    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
1945    /// Refer to [`WindowOptions::window_decorations`] on Linux
1946    pub appears_transparent: bool,
1947
1948    /// The position of the macOS traffic light buttons
1949    pub traffic_light_position: Option<Point<Pixels>>,
1950}
1951
1952/// The kind of window to create
1953#[derive(Clone, Debug, PartialEq)]
1954pub enum WindowKind {
1955    /// A normal application window
1956    Normal,
1957
1958    /// A window that appears above all other windows, usually used for alerts or popups
1959    /// use sparingly!
1960    PopUp,
1961
1962    /// A floating window that appears on top of its parent window
1963    Floating,
1964
1965    /// An overlay window that appears above all other windows, including fullscreen apps
1966    Overlay,
1967
1968    /// A Wayland layer-shell window, used for overlays or backgrounds such as docks,
1969    /// notifications, and wallpapers.
1970    #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
1971    LayerShell(layer_shell::LayerShellOptions),
1972}
1973
1974/// The appearance of the window, as defined by the operating system.
1975///
1976/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
1977/// values.
1978#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1979pub enum WindowAppearance {
1980    /// A light appearance.
1981    ///
1982    /// On macOS, this corresponds to the `aqua` appearance.
1983    #[default]
1984    Light,
1985
1986    /// A light appearance with vibrant colors.
1987    ///
1988    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
1989    VibrantLight,
1990
1991    /// A dark appearance.
1992    ///
1993    /// On macOS, this corresponds to the `darkAqua` appearance.
1994    Dark,
1995
1996    /// A dark appearance with vibrant colors.
1997    ///
1998    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
1999    VibrantDark,
2000}
2001
2002/// The appearance of the background of the window itself, when there is
2003/// no content or the content is transparent.
2004#[derive(Copy, Clone, Debug, Default, PartialEq)]
2005pub enum WindowBackgroundAppearance {
2006    /// Opaque.
2007    ///
2008    /// This lets the window manager know that content behind this
2009    /// window does not need to be drawn.
2010    ///
2011    /// Actual color depends on the system and themes should define a fully
2012    /// opaque background color instead.
2013    #[default]
2014    Opaque,
2015    /// Plain alpha transparency.
2016    Transparent,
2017    /// Transparency, but the contents behind the window are blurred.
2018    ///
2019    /// Not always supported.
2020    Blurred,
2021}
2022
2023/// Controls how a system tray icon should be rendered.
2024#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2025#[non_exhaustive]
2026pub enum TrayIconRenderingMode {
2027    /// Render using the platform's default status icon appearance.
2028    ///
2029    /// On macOS, this uses template rendering so the icon adapts to the menu bar appearance.
2030    #[default]
2031    Adaptive,
2032    /// Render the icon with its original colors when supported by the platform.
2033    ///
2034    /// On macOS, this disables template rendering.
2035    Original,
2036}
2037
2038/// Events that can occur on a system tray icon.
2039#[derive(Debug, Clone, PartialEq, Eq)]
2040pub enum TrayIconEvent {
2041    /// The user left-clicked the tray icon.
2042    LeftClick,
2043    /// The user right-clicked the tray icon.
2044    RightClick,
2045    /// The user double-clicked the tray icon.
2046    DoubleClick,
2047}
2048
2049/// A tray icon click event with optional screen position information.
2050#[derive(Debug, Clone, PartialEq)]
2051pub struct TrayIconClickEvent {
2052    /// The kind of click that occurred.
2053    pub kind: TrayIconEvent,
2054    /// The click position in GPUI logical screen coordinates, if the platform provides it.
2055    ///
2056    /// Linux StatusNotifierItem provides a raw screen-coordinate hint; GPUI
2057    /// converts that hint through the active display scale before exposing it.
2058    /// On Wayland fractional scaling, the hint has no surface association, so
2059    /// conversion uses the integer output scale and may remain approximate.
2060    pub position: Option<Point<Pixels>>,
2061}
2062
2063impl TrayIconClickEvent {
2064    /// Create a tray click event without position information.
2065    pub fn new(kind: TrayIconEvent) -> Self {
2066        Self {
2067            kind,
2068            position: None,
2069        }
2070    }
2071
2072    /// Create a tray click event with a logical screen-coordinate hint.
2073    pub fn with_position(kind: TrayIconEvent, position: Point<Pixels>) -> Self {
2074        Self {
2075            kind,
2076            position: Some(position),
2077        }
2078    }
2079}
2080
2081#[allow(missing_docs)]
2082#[derive(Debug, Clone, PartialEq)]
2083pub struct TrayAnchor {
2084    pub display_id: DisplayId,
2085    pub bounds: Bounds<Pixels>,
2086}
2087
2088/// A menu item for a system tray context menu.
2089#[derive(Debug, Clone)]
2090pub enum TrayMenuItem {
2091    /// A clickable action item.
2092    Action {
2093        /// The display label.
2094        label: SharedString,
2095        /// A unique identifier for this action.
2096        id: SharedString,
2097    },
2098    /// A visual separator between menu items.
2099    Separator,
2100    /// A submenu containing nested items.
2101    Submenu {
2102        /// The display label.
2103        label: SharedString,
2104        /// The nested menu items.
2105        items: Vec<TrayMenuItem>,
2106    },
2107    /// A toggleable menu item with a checkmark.
2108    Toggle {
2109        /// The display label.
2110        label: SharedString,
2111        /// Whether the item is currently checked.
2112        checked: bool,
2113        /// A unique identifier for this toggle.
2114        id: SharedString,
2115    },
2116}
2117
2118/// Information about the currently focused window from any application.
2119#[derive(Debug, Clone)]
2120pub struct FocusedWindowInfo {
2121    /// The name of the application that owns the focused window.
2122    pub app_name: String,
2123    /// The title of the focused window.
2124    pub window_title: String,
2125    /// The bundle identifier of the application (macOS only).
2126    pub bundle_id: Option<String>,
2127    /// The process ID of the application.
2128    pub pid: Option<u32>,
2129}
2130
2131/// The status of a system permission.
2132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2133pub enum PermissionStatus {
2134    /// Permission has been granted.
2135    Granted,
2136    /// Permission has been denied.
2137    Denied,
2138    /// Permission has not yet been requested.
2139    NotDetermined,
2140    /// The platform does not expose this permission capability.
2141    Unavailable,
2142}
2143
2144/// Whether the platform accepted a permission request.
2145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2146pub enum PermissionRequestStatus {
2147    /// The platform accepted the request and will resolve it through its normal flow.
2148    Requested,
2149    /// The platform does not expose this permission request.
2150    Unavailable,
2151}
2152
2153/// System power state change events.
2154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2155pub enum SystemPowerEvent {
2156    /// The system is about to suspend/sleep.
2157    Suspend,
2158    /// The system has resumed from suspend/sleep.
2159    Resume,
2160    /// The screen has been locked.
2161    LockScreen,
2162    /// The screen has been unlocked.
2163    UnlockScreen,
2164    /// The system is shutting down.
2165    Shutdown,
2166}
2167
2168/// The kind of power save blocker to create.
2169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2170pub enum PowerSaveBlockerKind {
2171    /// Prevent the application from being suspended.
2172    PreventAppSuspension,
2173    /// Prevent the display from sleeping.
2174    PreventDisplaySleep,
2175}
2176
2177/// The current network connectivity status.
2178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2179pub enum NetworkStatus {
2180    /// The system has network connectivity.
2181    Online,
2182    /// The system has no network connectivity.
2183    Offline,
2184}
2185
2186/// Media key events from hardware media keys or OS media controls.
2187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2188pub enum MediaKeyEvent {
2189    /// Play media.
2190    Play,
2191    /// Pause media.
2192    Pause,
2193    /// Toggle play/pause.
2194    PlayPause,
2195    /// Stop media playback.
2196    Stop,
2197    /// Skip to the next track.
2198    NextTrack,
2199    /// Skip to the previous track.
2200    PreviousTrack,
2201}
2202
2203/// The type of user attention to request from the OS.
2204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2205pub enum AttentionType {
2206    /// An informational attention request (e.g. bounce dock icon once).
2207    Informational,
2208    /// A critical attention request (e.g. bounce dock icon continuously).
2209    Critical,
2210}
2211
2212/// The state of a taskbar/dock progress bar for a window.
2213#[derive(Debug, Clone, Copy, PartialEq)]
2214pub enum ProgressBarState {
2215    /// No progress bar is shown.
2216    None,
2217    /// An indeterminate progress bar is shown.
2218    Indeterminate,
2219    /// A normal progress bar with the given fraction (0.0 to 1.0).
2220    Normal(f64),
2221    /// An error progress bar with the given fraction (0.0 to 1.0).
2222    Error(f64),
2223    /// A paused progress bar with the given fraction (0.0 to 1.0).
2224    Paused(f64),
2225}
2226
2227/// The kind of a native dialog.
2228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2229pub enum DialogKind {
2230    /// An informational dialog.
2231    Info,
2232    /// A warning dialog.
2233    Warning,
2234    /// An error dialog.
2235    Error,
2236}
2237
2238/// Options for displaying a native dialog.
2239#[derive(Debug, Clone)]
2240pub struct DialogOptions {
2241    /// The kind of dialog to display.
2242    pub kind: DialogKind,
2243    /// The title of the dialog.
2244    pub title: SharedString,
2245    /// The primary message of the dialog.
2246    pub message: SharedString,
2247    /// Optional detail text shown below the message.
2248    pub detail: Option<SharedString>,
2249    /// The button labels for the dialog.
2250    pub buttons: Vec<SharedString>,
2251}
2252
2253/// Information about the operating system.
2254#[derive(Debug, Clone)]
2255pub struct OsInfo {
2256    /// The name of the operating system (e.g. "macos", "linux", "windows").
2257    pub name: SharedString,
2258    /// The version of the operating system.
2259    pub version: SharedString,
2260    /// The CPU architecture (e.g. "x86_64", "aarch64").
2261    pub arch: SharedString,
2262    /// The system locale (e.g. "en-US").
2263    pub locale: SharedString,
2264    /// The hostname of the system.
2265    pub hostname: SharedString,
2266}
2267
2268/// The kind of biometric authentication available.
2269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2270pub enum BiometricKind {
2271    /// macOS Touch ID.
2272    TouchId,
2273    /// Windows Hello.
2274    WindowsHello,
2275    /// Generic fingerprint reader.
2276    Fingerprint,
2277}
2278
2279/// The availability status of biometric authentication.
2280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2281pub enum BiometricStatus {
2282    /// Biometric authentication is available with the given kind.
2283    Available(BiometricKind),
2284    /// Biometric authentication is not available.
2285    Unavailable,
2286}
2287
2288/// A snapshot of a window's state for save/restore.
2289#[derive(Debug, Clone, PartialEq)]
2290pub struct WindowState {
2291    /// The window bounds.
2292    pub bounds: WindowBounds,
2293    /// The display the window is on.
2294    pub display_id: Option<DisplayId>,
2295    /// Whether the window is fullscreen.
2296    pub fullscreen: bool,
2297}
2298
2299/// A semantic window position for positioning windows relative to the screen.
2300#[derive(Debug, Clone, PartialEq)]
2301pub enum WindowPosition {
2302    /// Center the window on the primary display.
2303    Center,
2304    /// Center the window on the given display.
2305    CenterOnDisplay(DisplayId),
2306    /// Center the window above the tray icon area.
2307    ///
2308    /// Deprecated: this variant only carries the tray icon bounds and does
2309    /// not know which display the tray is on, which causes window placement
2310    /// to be off on multi-monitor setups. Prefer [`WindowPosition::TrayAnchored`],
2311    /// which carries a full [`TrayAnchor`] (including `display_id`).
2312    #[deprecated(
2313        since = "0.5.3",
2314        note = "Use `WindowPosition::TrayAnchored(TrayAnchor)` instead; the new variant carries `display_id` and fixes multi-monitor placement. This variant will be removed in a future release."
2315    )]
2316    TrayCenter(Bounds<Pixels>),
2317    /// Center the window above the tray icon area, using a full [`TrayAnchor`].
2318    ///
2319    /// The `TrayAnchor` carries both the tray icon `bounds` (display-local,
2320    /// top-left origin) and the `display_id` it resides on. Callers should
2321    /// also propagate `display_id` to `WindowOptions.display_id` so the
2322    /// window is opened on the correct screen in multi-monitor setups.
2323    TrayAnchored(TrayAnchor),
2324    /// Position the window in the top-right corner.
2325    TopRight {
2326        /// The margin from the screen edge.
2327        margin: Pixels,
2328    },
2329    /// Position the window in the bottom-right corner.
2330    BottomRight {
2331        /// The margin from the screen edge.
2332        margin: Pixels,
2333    },
2334    /// Position the window in the top-left corner.
2335    TopLeft {
2336        /// The margin from the screen edge.
2337        margin: Pixels,
2338    },
2339    /// Position the window in the bottom-left corner.
2340    BottomLeft {
2341        /// The margin from the screen edge.
2342        margin: Pixels,
2343    },
2344}
2345
2346/// Information collected for a crash report.
2347#[derive(Debug, Clone)]
2348pub struct CrashReport {
2349    /// The error message.
2350    pub message: String,
2351    /// The backtrace at the time of the crash.
2352    pub backtrace: String,
2353    /// Information about the operating system.
2354    pub os_info: OsInfo,
2355    /// The application version, if available.
2356    pub app_version: Option<String>,
2357}
2358
2359/// The options that can be configured for a file dialog prompt
2360#[derive(Clone, Debug)]
2361pub struct PathPromptOptions {
2362    /// Should the prompt allow files to be selected?
2363    pub files: bool,
2364    /// Should the prompt allow directories to be selected?
2365    pub directories: bool,
2366    /// Should the prompt allow multiple files to be selected?
2367    pub multiple: bool,
2368    /// The prompt to show to a user when selecting a path
2369    pub prompt: Option<SharedString>,
2370}
2371
2372/// What kind of prompt styling to show
2373#[derive(Copy, Clone, Debug, PartialEq)]
2374pub enum PromptLevel {
2375    /// A prompt that is shown when the user should be notified of something
2376    Info,
2377
2378    /// A prompt that is shown when the user needs to be warned of a potential problem
2379    Warning,
2380
2381    /// A prompt that is shown when a critical problem has occurred
2382    Critical,
2383}
2384
2385/// Prompt Button
2386#[derive(Clone, Debug, PartialEq)]
2387pub enum PromptButton {
2388    /// Ok button
2389    Ok(SharedString),
2390    /// Cancel button
2391    Cancel(SharedString),
2392    /// Other button
2393    Other(SharedString),
2394}
2395
2396impl PromptButton {
2397    /// Create a button with label
2398    pub fn new(label: impl Into<SharedString>) -> Self {
2399        PromptButton::Other(label.into())
2400    }
2401
2402    /// Create an Ok button
2403    pub fn ok(label: impl Into<SharedString>) -> Self {
2404        PromptButton::Ok(label.into())
2405    }
2406
2407    /// Create a Cancel button
2408    pub fn cancel(label: impl Into<SharedString>) -> Self {
2409        PromptButton::Cancel(label.into())
2410    }
2411
2412    #[allow(dead_code)]
2413    #[doc(hidden)]
2414    pub fn is_cancel(&self) -> bool {
2415        matches!(self, PromptButton::Cancel(_))
2416    }
2417
2418    /// Returns the label of the button
2419    pub fn label(&self) -> &SharedString {
2420        match self {
2421            PromptButton::Ok(label) => label,
2422            PromptButton::Cancel(label) => label,
2423            PromptButton::Other(label) => label,
2424        }
2425    }
2426}
2427
2428impl From<&str> for PromptButton {
2429    fn from(value: &str) -> Self {
2430        match value.to_lowercase().as_str() {
2431            "ok" => PromptButton::Ok("Ok".into()),
2432            "cancel" => PromptButton::Cancel("Cancel".into()),
2433            _ => PromptButton::Other(SharedString::from(value.to_owned())),
2434        }
2435    }
2436}
2437
2438/// The style of the cursor (pointer)
2439#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2440pub enum CursorStyle {
2441    /// The default cursor
2442    #[default]
2443    Arrow,
2444
2445    /// A text input cursor
2446    /// corresponds to the CSS cursor value `text`
2447    IBeam,
2448
2449    /// A crosshair cursor
2450    /// corresponds to the CSS cursor value `crosshair`
2451    Crosshair,
2452
2453    /// A closed hand cursor
2454    /// corresponds to the CSS cursor value `grabbing`
2455    ClosedHand,
2456
2457    /// An open hand cursor
2458    /// corresponds to the CSS cursor value `grab`
2459    OpenHand,
2460
2461    /// A pointing hand cursor
2462    /// corresponds to the CSS cursor value `pointer`
2463    PointingHand,
2464
2465    /// A resize left cursor
2466    /// corresponds to the CSS cursor value `w-resize`
2467    ResizeLeft,
2468
2469    /// A resize right cursor
2470    /// corresponds to the CSS cursor value `e-resize`
2471    ResizeRight,
2472
2473    /// A resize cursor to the left and right
2474    /// corresponds to the CSS cursor value `ew-resize`
2475    ResizeLeftRight,
2476
2477    /// A resize up cursor
2478    /// corresponds to the CSS cursor value `n-resize`
2479    ResizeUp,
2480
2481    /// A resize down cursor
2482    /// corresponds to the CSS cursor value `s-resize`
2483    ResizeDown,
2484
2485    /// A resize cursor directing up and down
2486    /// corresponds to the CSS cursor value `ns-resize`
2487    ResizeUpDown,
2488
2489    /// A resize cursor directing up-left and down-right
2490    /// corresponds to the CSS cursor value `nesw-resize`
2491    ResizeUpLeftDownRight,
2492
2493    /// A resize cursor directing up-right and down-left
2494    /// corresponds to the CSS cursor value `nwse-resize`
2495    ResizeUpRightDownLeft,
2496
2497    /// A cursor indicating that the item/column can be resized horizontally.
2498    /// corresponds to the CSS cursor value `col-resize`
2499    ResizeColumn,
2500
2501    /// A cursor indicating that the item/row can be resized vertically.
2502    /// corresponds to the CSS cursor value `row-resize`
2503    ResizeRow,
2504
2505    /// A text input cursor for vertical layout
2506    /// corresponds to the CSS cursor value `vertical-text`
2507    IBeamCursorForVerticalLayout,
2508
2509    /// A cursor indicating that the operation is not allowed
2510    /// corresponds to the CSS cursor value `not-allowed`
2511    OperationNotAllowed,
2512
2513    /// A cursor indicating that the operation will result in a link
2514    /// corresponds to the CSS cursor value `alias`
2515    DragLink,
2516
2517    /// A cursor indicating that the operation will result in a copy
2518    /// corresponds to the CSS cursor value `copy`
2519    DragCopy,
2520
2521    /// A cursor indicating that the operation will result in a context menu
2522    /// corresponds to the CSS cursor value `context-menu`
2523    ContextualMenu,
2524
2525    /// Hide the cursor
2526    None,
2527}
2528
2529/// A clipboard item that should be copied to the clipboard
2530#[derive(Clone, Debug, Eq, PartialEq)]
2531pub struct ClipboardItem {
2532    entries: Vec<ClipboardEntry>,
2533}
2534
2535/// Either a ClipboardString or a ClipboardImage
2536#[derive(Clone, Debug, Eq, PartialEq)]
2537pub enum ClipboardEntry {
2538    /// A string entry
2539    String(ClipboardString),
2540    /// An image entry
2541    Image(Image),
2542}
2543
2544impl ClipboardItem {
2545    /// Create a new ClipboardItem::String with no associated metadata
2546    pub fn new_string(text: String) -> Self {
2547        Self {
2548            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2549        }
2550    }
2551
2552    /// Create a new ClipboardItem::String with the given text and associated metadata
2553    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2554        Self {
2555            entries: vec![ClipboardEntry::String(ClipboardString {
2556                text,
2557                metadata: Some(metadata),
2558            })],
2559        }
2560    }
2561
2562    /// Create a new ClipboardItem::String with the given text and associated metadata
2563    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2564        Self {
2565            entries: vec![ClipboardEntry::String(
2566                ClipboardString::new(text).with_json_metadata(metadata),
2567            )],
2568        }
2569    }
2570
2571    /// Create a new ClipboardItem::Image with the given image with no associated metadata
2572    pub fn new_image(image: &Image) -> Self {
2573        Self {
2574            entries: vec![ClipboardEntry::Image(image.clone())],
2575        }
2576    }
2577
2578    /// Concatenates together all the ClipboardString entries in the item.
2579    /// Returns None if there were no ClipboardString entries.
2580    pub fn text(&self) -> Option<String> {
2581        let mut answer = String::new();
2582        let mut any_entries = false;
2583
2584        for entry in self.entries.iter() {
2585            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2586                answer.push_str(text);
2587                any_entries = true;
2588            }
2589        }
2590
2591        if any_entries { Some(answer) } else { None }
2592    }
2593
2594    /// If this item is one ClipboardEntry::String, returns its metadata.
2595    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2596    pub fn metadata(&self) -> Option<&String> {
2597        match self.entries().first() {
2598            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2599                clipboard_string.metadata.as_ref()
2600            }
2601            _ => None,
2602        }
2603    }
2604
2605    /// Get the item's entries
2606    pub fn entries(&self) -> &[ClipboardEntry] {
2607        &self.entries
2608    }
2609
2610    /// Get owned versions of the item's entries
2611    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2612        self.entries.into_iter()
2613    }
2614}
2615
2616impl From<ClipboardString> for ClipboardEntry {
2617    fn from(value: ClipboardString) -> Self {
2618        Self::String(value)
2619    }
2620}
2621
2622impl From<String> for ClipboardEntry {
2623    fn from(value: String) -> Self {
2624        Self::from(ClipboardString::from(value))
2625    }
2626}
2627
2628impl From<Image> for ClipboardEntry {
2629    fn from(value: Image) -> Self {
2630        Self::Image(value)
2631    }
2632}
2633
2634impl From<ClipboardEntry> for ClipboardItem {
2635    fn from(value: ClipboardEntry) -> Self {
2636        Self {
2637            entries: vec![value],
2638        }
2639    }
2640}
2641
2642impl From<String> for ClipboardItem {
2643    fn from(value: String) -> Self {
2644        Self::from(ClipboardEntry::from(value))
2645    }
2646}
2647
2648impl From<Image> for ClipboardItem {
2649    fn from(value: Image) -> Self {
2650        Self::from(ClipboardEntry::from(value))
2651    }
2652}
2653
2654/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
2655#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2656pub enum ImageFormat {
2657    // Sorted from most to least likely to be pasted into an editor,
2658    // which matters when we iterate through them trying to see if
2659    // clipboard content matches them.
2660    /// .png
2661    Png,
2662    /// .jpeg or .jpg
2663    Jpeg,
2664    /// .webp
2665    Webp,
2666    /// .gif
2667    Gif,
2668    /// .svg
2669    Svg,
2670    /// .bmp
2671    Bmp,
2672    /// .tif or .tiff
2673    Tiff,
2674}
2675
2676impl ImageFormat {
2677    /// Returns the mime type for the ImageFormat
2678    pub const fn mime_type(self) -> &'static str {
2679        match self {
2680            ImageFormat::Png => "image/png",
2681            ImageFormat::Jpeg => "image/jpeg",
2682            ImageFormat::Webp => "image/webp",
2683            ImageFormat::Gif => "image/gif",
2684            ImageFormat::Svg => "image/svg+xml",
2685            ImageFormat::Bmp => "image/bmp",
2686            ImageFormat::Tiff => "image/tiff",
2687        }
2688    }
2689
2690    /// Returns the ImageFormat for the given mime type
2691    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2692        match mime_type {
2693            "image/png" => Some(Self::Png),
2694            "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
2695            "image/webp" => Some(Self::Webp),
2696            "image/gif" => Some(Self::Gif),
2697            "image/svg+xml" => Some(Self::Svg),
2698            "image/bmp" => Some(Self::Bmp),
2699            "image/tiff" | "image/tif" => Some(Self::Tiff),
2700            _ => None,
2701        }
2702    }
2703}
2704
2705/// An image, with a format and certain bytes
2706#[derive(Clone, Debug, PartialEq, Eq)]
2707pub struct Image {
2708    /// The image format the bytes represent (e.g. PNG)
2709    pub format: ImageFormat,
2710    /// The raw image bytes
2711    pub bytes: Vec<u8>,
2712    /// The unique ID for the image
2713    id: u64,
2714}
2715
2716impl Image {
2717    /// Creates an image from encoded bytes and derives its stable clipboard identifier.
2718    pub fn new(format: ImageFormat, bytes: Vec<u8>) -> Self {
2719        let id = crate::hash(&bytes);
2720        Self { format, bytes, id }
2721    }
2722}
2723
2724impl Hash for Image {
2725    fn hash<H: Hasher>(&self, state: &mut H) {
2726        state.write_u64(self.id);
2727    }
2728}
2729
2730pub(crate) fn decode_static_image(
2731    bytes: &[u8],
2732    format: image::ImageFormat,
2733) -> Result<SmallVec<[Frame; 1]>> {
2734    let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2735        .into_decoder()
2736        .context("creating image decoder")?;
2737    decode_static_image_from_decoder(decoder)
2738}
2739
2740pub(crate) fn decode_static_image_from_decoder(
2741    mut decoder: impl image::ImageDecoder,
2742) -> Result<SmallVec<[Frame; 1]>> {
2743    let orientation = decoder.orientation().context("reading image orientation")?;
2744    let mut image = DynamicImage::from_decoder(decoder).context("decoding static image")?;
2745    image.apply_orientation(orientation);
2746
2747    let mut data = image.into_rgba8();
2748    for pixel in data.chunks_exact_mut(4) {
2749        pixel.swap(0, 2);
2750    }
2751
2752    Ok(SmallVec::from_elem(Frame::new(data), 1))
2753}
2754
2755impl Image {
2756    /// An empty image containing no data
2757    pub fn empty() -> Self {
2758        Self::from_bytes(ImageFormat::Png, Vec::new())
2759    }
2760
2761    /// Create an image from a format and bytes
2762    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2763        Self {
2764            id: hash(&bytes),
2765            format,
2766            bytes,
2767        }
2768    }
2769
2770    /// Get this image's ID
2771    pub fn id(&self) -> u64 {
2772        self.id
2773    }
2774
2775    /// Use the GPUI `use_asset` API to make this image renderable
2776    pub fn use_render_image(
2777        self: Arc<Self>,
2778        window: &mut Window,
2779        cx: &mut App,
2780    ) -> Option<Arc<RenderImage>> {
2781        ImageSource::Image(self)
2782            .use_data(None, window, cx)
2783            .and_then(|result| result.ok())
2784    }
2785
2786    /// Use the GPUI `get_asset` API to make this image renderable
2787    pub fn get_render_image(
2788        self: Arc<Self>,
2789        window: &mut Window,
2790        cx: &mut App,
2791    ) -> Option<Arc<RenderImage>> {
2792        ImageSource::Image(self)
2793            .get_data(None, window, cx)
2794            .and_then(|result| result.ok())
2795    }
2796
2797    /// Use the GPUI `remove_asset` API to drop this image, if possible.
2798    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2799        ImageSource::Image(self).remove_asset(cx);
2800    }
2801
2802    /// Convert the clipboard image to an `ImageData` object.
2803    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2804        let frames = match self.format {
2805            #[cfg(feature = "image-format-gif")]
2806            ImageFormat::Gif => {
2807                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2808                let mut frames = SmallVec::new();
2809
2810                for frame in decoder.into_frames() {
2811                    let mut frame = frame?;
2812                    // Convert from RGBA to BGRA.
2813                    for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2814                        pixel.swap(0, 2);
2815                    }
2816                    frames.push(frame);
2817                }
2818
2819                frames
2820            }
2821            #[cfg(not(feature = "image-format-gif"))]
2822            ImageFormat::Gif => decode_static_image(&self.bytes, image::ImageFormat::Gif)?,
2823            ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2824            ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2825            ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2826            ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2827            ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2828            ImageFormat::Svg => {
2829                return svg_renderer
2830                    .render_single_frame(&self.bytes, 1.0)
2831                    .map_err(Into::into);
2832            }
2833        };
2834
2835        Ok(Arc::new(RenderImage::new(frames)))
2836    }
2837
2838    /// Get the format of the clipboard image
2839    pub fn format(&self) -> ImageFormat {
2840        self.format
2841    }
2842
2843    /// Get the raw bytes of the clipboard image
2844    pub fn bytes(&self) -> &[u8] {
2845        self.bytes.as_slice()
2846    }
2847}
2848
2849#[cfg(test)]
2850mod image_tests {
2851    use super::*;
2852
2853    #[test]
2854    fn image_to_image_data_applies_exif_orientation() {
2855        let image = Image::from_bytes(
2856            ImageFormat::Jpeg,
2857            include_bytes!("../test_data/exif-orientation-rotate-180.jpg").to_vec(),
2858        );
2859
2860        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2861
2862        assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2863        let bytes = render_image.as_bytes(0).unwrap();
2864        assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2865        assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2866    }
2867}
2868
2869/// A clipboard item that should be copied to the clipboard
2870#[derive(Clone, Debug, Eq, PartialEq)]
2871pub struct ClipboardString {
2872    pub(crate) text: String,
2873    pub(crate) metadata: Option<String>,
2874}
2875
2876impl ClipboardString {
2877    /// Create a new clipboard string with the given text
2878    pub fn new(text: String) -> Self {
2879        Self {
2880            text,
2881            metadata: None,
2882        }
2883    }
2884
2885    /// Replace the associated metadata with an opaque string.
2886    #[doc(hidden)]
2887    pub fn with_metadata(mut self, metadata: String) -> Self {
2888        self.metadata = Some(metadata);
2889        self
2890    }
2891
2892    /// Return a new clipboard item with the metadata replaced by the given metadata,
2893    /// after serializing it as JSON.
2894    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2895        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2896        self
2897    }
2898
2899    /// Get the text of the clipboard string
2900    pub fn text(&self) -> &String {
2901        &self.text
2902    }
2903
2904    /// Get the owned text of the clipboard string
2905    pub fn into_text(self) -> String {
2906        self.text
2907    }
2908
2909    /// Get the metadata of the clipboard string, formatted as JSON
2910    pub fn metadata_json<T>(&self) -> Option<T>
2911    where
2912        T: for<'a> Deserialize<'a>,
2913    {
2914        self.metadata
2915            .as_ref()
2916            .and_then(|m| serde_json::from_str(m).ok())
2917    }
2918
2919    /// Returns the raw metadata string associated with this clipboard entry.
2920    #[doc(hidden)]
2921    pub fn metadata(&self) -> Option<&str> {
2922        self.metadata.as_deref()
2923    }
2924
2925    #[doc(hidden)]
2926    pub fn text_hash(text: &str) -> u64 {
2927        let mut hasher = SeaHasher::new();
2928        text.hash(&mut hasher);
2929        hasher.finish()
2930    }
2931}
2932
2933impl From<String> for ClipboardString {
2934    fn from(value: String) -> Self {
2935        Self {
2936            text: value,
2937            metadata: None,
2938        }
2939    }
2940}
2941
2942#[cfg(test)]
2943mod clipboard_string_tests {
2944    use super::ClipboardString;
2945
2946    #[test]
2947    fn raw_metadata_preserves_the_clipboard_text() {
2948        let clipboard_string = ClipboardString::new("copied text".to_owned())
2949            .with_metadata("application/x-adabraka".to_owned());
2950
2951        assert_eq!(clipboard_string.text(), "copied text");
2952        assert_eq!(clipboard_string.metadata(), Some("application/x-adabraka"));
2953    }
2954}