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