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