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