gpui/
platform.rs

1mod app_menu;
2mod keyboard;
3mod keystroke;
4
5#[cfg(any(target_os = "linux", target_os = "freebsd"))]
6mod linux;
7
8#[cfg(target_os = "macos")]
9mod mac;
10
11#[cfg(any(
12    all(
13        any(target_os = "linux", target_os = "freebsd"),
14        any(feature = "x11", feature = "wayland")
15    ),
16    all(target_os = "macos", feature = "macos-blade")
17))]
18mod blade;
19
20#[cfg(any(test, feature = "test-support"))]
21mod test;
22
23#[cfg(target_os = "windows")]
24mod windows;
25
26#[cfg(all(
27    feature = "screen-capture",
28    any(
29        target_os = "windows",
30        all(
31            any(target_os = "linux", target_os = "freebsd"),
32            any(feature = "wayland", feature = "x11"),
33        )
34    )
35))]
36pub(crate) mod scap_screen_capture;
37
38use crate::{
39    Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
40    DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
41    ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
42    Point, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph,
43    ShapedRun, SharedString, Size, SvgRenderer, SvgSize, SystemWindowTab, Task, TaskLabel, Window,
44    WindowControlArea, hash, point, px, size,
45};
46use anyhow::Result;
47use async_task::Runnable;
48use futures::channel::oneshot;
49use image::codecs::gif::GifDecoder;
50use image::{AnimationDecoder as _, Frame};
51use parking::Unparker;
52use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
53use schemars::JsonSchema;
54use seahash::SeaHasher;
55use serde::{Deserialize, Serialize};
56use smallvec::SmallVec;
57use std::borrow::Cow;
58use std::hash::{Hash, Hasher};
59use std::io::Cursor;
60use std::ops;
61use std::time::{Duration, Instant};
62use std::{
63    fmt::{self, Debug},
64    ops::Range,
65    path::{Path, PathBuf},
66    rc::Rc,
67    sync::Arc,
68};
69use strum::EnumIter;
70use uuid::Uuid;
71
72pub use app_menu::*;
73pub use keyboard::*;
74pub use keystroke::*;
75
76#[cfg(any(target_os = "linux", target_os = "freebsd"))]
77pub(crate) use linux::*;
78#[cfg(target_os = "macos")]
79pub(crate) use mac::*;
80pub use semantic_version::SemanticVersion;
81#[cfg(any(test, feature = "test-support"))]
82pub(crate) use test::*;
83#[cfg(target_os = "windows")]
84pub(crate) use windows::*;
85
86#[cfg(any(test, feature = "test-support"))]
87pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
88
89/// Returns a background executor for the current platform.
90pub fn background_executor() -> BackgroundExecutor {
91    current_platform(true).background_executor()
92}
93
94#[cfg(target_os = "macos")]
95pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
96    Rc::new(MacPlatform::new(headless))
97}
98
99#[cfg(any(target_os = "linux", target_os = "freebsd"))]
100pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
101    #[cfg(feature = "x11")]
102    use anyhow::Context as _;
103
104    if headless {
105        return Rc::new(HeadlessClient::new());
106    }
107
108    match guess_compositor() {
109        #[cfg(feature = "wayland")]
110        "Wayland" => Rc::new(WaylandClient::new()),
111
112        #[cfg(feature = "x11")]
113        "X11" => Rc::new(
114            X11Client::new()
115                .context("Failed to initialize X11 client.")
116                .unwrap(),
117        ),
118
119        "Headless" => Rc::new(HeadlessClient::new()),
120        _ => unreachable!(),
121    }
122}
123
124/// Return which compositor we're guessing we'll use.
125/// Does not attempt to connect to the given compositor
126#[cfg(any(target_os = "linux", target_os = "freebsd"))]
127#[inline]
128pub fn guess_compositor() -> &'static str {
129    if std::env::var_os("ZED_HEADLESS").is_some() {
130        return "Headless";
131    }
132
133    #[cfg(feature = "wayland")]
134    let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
135    #[cfg(not(feature = "wayland"))]
136    let wayland_display: Option<std::ffi::OsString> = None;
137
138    #[cfg(feature = "x11")]
139    let x11_display = std::env::var_os("DISPLAY");
140    #[cfg(not(feature = "x11"))]
141    let x11_display: Option<std::ffi::OsString> = None;
142
143    let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
144    let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
145
146    if use_wayland {
147        "Wayland"
148    } else if use_x11 {
149        "X11"
150    } else {
151        "Headless"
152    }
153}
154
155#[cfg(target_os = "windows")]
156pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
157    Rc::new(
158        WindowsPlatform::new()
159            .inspect_err(|err| show_error("Failed to launch", err.to_string()))
160            .unwrap(),
161    )
162}
163
164pub(crate) trait Platform: 'static {
165    fn background_executor(&self) -> BackgroundExecutor;
166    fn foreground_executor(&self) -> ForegroundExecutor;
167    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
168
169    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
170    fn quit(&self);
171    fn restart(&self, binary_path: Option<PathBuf>);
172    fn activate(&self, ignoring_other_apps: bool);
173    fn hide(&self);
174    fn hide_other_apps(&self);
175    fn unhide_other_apps(&self);
176
177    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
178    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
179    fn active_window(&self) -> Option<AnyWindowHandle>;
180    fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
181        None
182    }
183
184    #[cfg(feature = "screen-capture")]
185    fn is_screen_capture_supported(&self) -> bool;
186    #[cfg(not(feature = "screen-capture"))]
187    fn is_screen_capture_supported(&self) -> bool {
188        false
189    }
190    #[cfg(feature = "screen-capture")]
191    fn screen_capture_sources(&self)
192    -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
193    #[cfg(not(feature = "screen-capture"))]
194    fn screen_capture_sources(
195        &self,
196    ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
197        let (sources_tx, sources_rx) = oneshot::channel();
198        sources_tx
199            .send(Err(anyhow::anyhow!(
200                "gpui was compiled without the screen-capture feature"
201            )))
202            .ok();
203        sources_rx
204    }
205
206    fn open_window(
207        &self,
208        handle: AnyWindowHandle,
209        options: WindowParams,
210    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
211
212    /// Returns the appearance of the application's windows.
213    fn window_appearance(&self) -> WindowAppearance;
214
215    fn open_url(&self, url: &str);
216    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
217    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
218
219    fn prompt_for_paths(
220        &self,
221        options: PathPromptOptions,
222    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
223    fn prompt_for_new_path(
224        &self,
225        directory: &Path,
226        suggested_name: Option<&str>,
227    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
228    fn can_select_mixed_files_and_dirs(&self) -> bool;
229    fn reveal_path(&self, path: &Path);
230    fn open_with_system(&self, path: &Path);
231
232    fn on_quit(&self, callback: Box<dyn FnMut()>);
233    fn on_reopen(&self, callback: Box<dyn FnMut()>);
234
235    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
236    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
237        None
238    }
239
240    fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
241    fn perform_dock_menu_action(&self, _action: usize) {}
242    fn add_recent_document(&self, _path: &Path) {}
243    fn update_jump_list(
244        &self,
245        _menus: Vec<MenuItem>,
246        _entries: Vec<SmallVec<[PathBuf; 2]>>,
247    ) -> Vec<SmallVec<[PathBuf; 2]>> {
248        Vec::new()
249    }
250    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
251    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
252    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
253
254    fn compositor_name(&self) -> &'static str {
255        ""
256    }
257    fn app_path(&self) -> Result<PathBuf>;
258    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
259
260    fn set_cursor_style(&self, style: CursorStyle);
261    fn should_auto_hide_scrollbars(&self) -> bool;
262
263    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
264    fn write_to_primary(&self, item: ClipboardItem);
265    fn write_to_clipboard(&self, item: ClipboardItem);
266    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
267    fn read_from_primary(&self) -> Option<ClipboardItem>;
268    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
269
270    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
271    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
272    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
273
274    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
275    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
276    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
277}
278
279/// A handle to a platform's display, e.g. a monitor or laptop screen.
280pub trait PlatformDisplay: Send + Sync + Debug {
281    /// Get the ID for this display
282    fn id(&self) -> DisplayId;
283
284    /// Returns a stable identifier for this display that can be persisted and used
285    /// across system restarts.
286    fn uuid(&self) -> Result<Uuid>;
287
288    /// Get the bounds for this display
289    fn bounds(&self) -> Bounds<Pixels>;
290
291    /// Get the default bounds for this display to place a window
292    fn default_bounds(&self) -> Bounds<Pixels> {
293        let center = self.bounds().center();
294        let offset = DEFAULT_WINDOW_SIZE / 2.0;
295        let origin = point(center.x - offset.width, center.y - offset.height);
296        Bounds::new(origin, DEFAULT_WINDOW_SIZE)
297    }
298}
299
300/// Metadata for a given [ScreenCaptureSource]
301#[derive(Clone)]
302pub struct SourceMetadata {
303    /// Opaque identifier of this screen.
304    pub id: u64,
305    /// Human-readable label for this source.
306    pub label: Option<SharedString>,
307    /// Whether this source is the main display.
308    pub is_main: Option<bool>,
309    /// Video resolution of this source.
310    pub resolution: Size<DevicePixels>,
311}
312
313/// A source of on-screen video content that can be captured.
314pub trait ScreenCaptureSource {
315    /// Returns metadata for this source.
316    fn metadata(&self) -> Result<SourceMetadata>;
317
318    /// Start capture video from this source, invoking the given callback
319    /// with each frame.
320    fn stream(
321        &self,
322        foreground_executor: &ForegroundExecutor,
323        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
324    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
325}
326
327/// A video stream captured from a screen.
328pub trait ScreenCaptureStream {
329    /// Returns metadata for this source.
330    fn metadata(&self) -> Result<SourceMetadata>;
331}
332
333/// A frame of video captured from a screen.
334pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
335
336/// An opaque identifier for a hardware display
337#[derive(PartialEq, Eq, Hash, Copy, Clone)]
338pub struct DisplayId(pub(crate) u32);
339
340impl From<DisplayId> for u32 {
341    fn from(id: DisplayId) -> Self {
342        id.0
343    }
344}
345
346impl Debug for DisplayId {
347    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348        write!(f, "DisplayId({})", self.0)
349    }
350}
351
352unsafe impl Send for DisplayId {}
353
354/// Which part of the window to resize
355#[derive(Debug, Clone, Copy, PartialEq, Eq)]
356pub enum ResizeEdge {
357    /// The top edge
358    Top,
359    /// The top right corner
360    TopRight,
361    /// The right edge
362    Right,
363    /// The bottom right corner
364    BottomRight,
365    /// The bottom edge
366    Bottom,
367    /// The bottom left corner
368    BottomLeft,
369    /// The left edge
370    Left,
371    /// The top left corner
372    TopLeft,
373}
374
375/// A type to describe the appearance of a window
376#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
377pub enum WindowDecorations {
378    #[default]
379    /// Server side decorations
380    Server,
381    /// Client side decorations
382    Client,
383}
384
385/// A type to describe how this window is currently configured
386#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
387pub enum Decorations {
388    /// The window is configured to use server side decorations
389    #[default]
390    Server,
391    /// The window is configured to use client side decorations
392    Client {
393        /// The edge tiling state
394        tiling: Tiling,
395    },
396}
397
398/// What window controls this platform supports
399#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
400pub struct WindowControls {
401    /// Whether this platform supports fullscreen
402    pub fullscreen: bool,
403    /// Whether this platform supports maximize
404    pub maximize: bool,
405    /// Whether this platform supports minimize
406    pub minimize: bool,
407    /// Whether this platform supports a window menu
408    pub window_menu: bool,
409}
410
411impl Default for WindowControls {
412    fn default() -> Self {
413        // Assume that we can do anything, unless told otherwise
414        Self {
415            fullscreen: true,
416            maximize: true,
417            minimize: true,
418            window_menu: true,
419        }
420    }
421}
422
423/// A type to describe which sides of the window are currently tiled in some way
424#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
425pub struct Tiling {
426    /// Whether the top edge is tiled
427    pub top: bool,
428    /// Whether the left edge is tiled
429    pub left: bool,
430    /// Whether the right edge is tiled
431    pub right: bool,
432    /// Whether the bottom edge is tiled
433    pub bottom: bool,
434}
435
436impl Tiling {
437    /// Initializes a [`Tiling`] type with all sides tiled
438    pub fn tiled() -> Self {
439        Self {
440            top: true,
441            left: true,
442            right: true,
443            bottom: true,
444        }
445    }
446
447    /// Whether any edge is tiled
448    pub fn is_tiled(&self) -> bool {
449        self.top || self.left || self.right || self.bottom
450    }
451}
452
453#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
454pub(crate) struct RequestFrameOptions {
455    pub(crate) require_presentation: bool,
456    /// Force refresh of all rendering states when true
457    pub(crate) force_render: bool,
458}
459
460pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
461    fn bounds(&self) -> Bounds<Pixels>;
462    fn is_maximized(&self) -> bool;
463    fn window_bounds(&self) -> WindowBounds;
464    fn content_size(&self) -> Size<Pixels>;
465    fn resize(&mut self, size: Size<Pixels>);
466    fn scale_factor(&self) -> f32;
467    fn appearance(&self) -> WindowAppearance;
468    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
469    fn mouse_position(&self) -> Point<Pixels>;
470    fn modifiers(&self) -> Modifiers;
471    fn capslock(&self) -> Capslock;
472    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
473    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
474    fn prompt(
475        &self,
476        level: PromptLevel,
477        msg: &str,
478        detail: Option<&str>,
479        answers: &[PromptButton],
480    ) -> Option<oneshot::Receiver<usize>>;
481    fn activate(&self);
482    fn is_active(&self) -> bool;
483    fn is_hovered(&self) -> bool;
484    fn set_title(&mut self, title: &str);
485    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
486    fn minimize(&self);
487    fn zoom(&self);
488    fn toggle_fullscreen(&self);
489    fn is_fullscreen(&self) -> bool;
490    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
491    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
492    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
493    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
494    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
495    fn on_moved(&self, callback: Box<dyn FnMut()>);
496    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
497    fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
498    fn on_close(&self, callback: Box<dyn FnOnce()>);
499    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
500    fn draw(&self, scene: &Scene);
501    fn completed_frame(&self) {}
502    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
503
504    // macOS specific methods
505    fn get_title(&self) -> String {
506        String::new()
507    }
508    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
509        None
510    }
511    fn tab_bar_visible(&self) -> bool {
512        false
513    }
514    fn set_edited(&mut self, _edited: bool) {}
515    fn show_character_palette(&self) {}
516    fn titlebar_double_click(&self) {}
517    fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
518    fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
519    fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
520    fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
521    fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
522    fn merge_all_windows(&self) {}
523    fn move_tab_to_new_window(&self) {}
524    fn toggle_window_tab_overview(&self) {}
525    fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
526
527    #[cfg(target_os = "windows")]
528    fn get_raw_handle(&self) -> windows::HWND;
529
530    // Linux specific methods
531    fn inner_window_bounds(&self) -> WindowBounds {
532        self.window_bounds()
533    }
534    fn request_decorations(&self, _decorations: WindowDecorations) {}
535    fn show_window_menu(&self, _position: Point<Pixels>) {}
536    fn start_window_move(&self) {}
537    fn start_window_resize(&self, _edge: ResizeEdge) {}
538    fn window_decorations(&self) -> Decorations {
539        Decorations::Server
540    }
541    fn set_app_id(&mut self, _app_id: &str) {}
542    fn map_window(&mut self) -> anyhow::Result<()> {
543        Ok(())
544    }
545    fn window_controls(&self) -> WindowControls {
546        WindowControls::default()
547    }
548    fn set_client_inset(&self, _inset: Pixels) {}
549    fn gpu_specs(&self) -> Option<GpuSpecs>;
550
551    fn update_ime_position(&self, _bounds: Bounds<Pixels>);
552
553    #[cfg(any(test, feature = "test-support"))]
554    fn as_test(&mut self) -> Option<&mut TestWindow> {
555        None
556    }
557}
558
559/// This type is public so that our test macro can generate and use it, but it should not
560/// be considered part of our public API.
561#[doc(hidden)]
562pub trait PlatformDispatcher: Send + Sync {
563    fn is_main_thread(&self) -> bool;
564    fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
565    fn dispatch_on_main_thread(&self, runnable: Runnable);
566    fn dispatch_after(&self, duration: Duration, runnable: Runnable);
567    fn park(&self, timeout: Option<Duration>) -> bool;
568    fn unparker(&self) -> Unparker;
569    fn now(&self) -> Instant {
570        Instant::now()
571    }
572
573    #[cfg(any(test, feature = "test-support"))]
574    fn as_test(&self) -> Option<&TestDispatcher> {
575        None
576    }
577}
578
579pub(crate) trait PlatformTextSystem: Send + Sync {
580    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
581    fn all_font_names(&self) -> Vec<String>;
582    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
583    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
584    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
585    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
586    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
587    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
588    fn rasterize_glyph(
589        &self,
590        params: &RenderGlyphParams,
591        raster_bounds: Bounds<DevicePixels>,
592    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
593    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
594}
595
596pub(crate) struct NoopTextSystem;
597
598impl NoopTextSystem {
599    #[allow(dead_code)]
600    pub fn new() -> Self {
601        Self
602    }
603}
604
605impl PlatformTextSystem for NoopTextSystem {
606    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
607        Ok(())
608    }
609
610    fn all_font_names(&self) -> Vec<String> {
611        Vec::new()
612    }
613
614    fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
615        Ok(FontId(1))
616    }
617
618    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
619        FontMetrics {
620            units_per_em: 1000,
621            ascent: 1025.0,
622            descent: -275.0,
623            line_gap: 0.0,
624            underline_position: -95.0,
625            underline_thickness: 60.0,
626            cap_height: 698.0,
627            x_height: 516.0,
628            bounding_box: Bounds {
629                origin: Point {
630                    x: -260.0,
631                    y: -245.0,
632                },
633                size: Size {
634                    width: 1501.0,
635                    height: 1364.0,
636                },
637            },
638        }
639    }
640
641    fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
642        Ok(Bounds {
643            origin: Point { x: 54.0, y: 0.0 },
644            size: size(392.0, 528.0),
645        })
646    }
647
648    fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
649        Ok(size(600.0 * glyph_id.0 as f32, 0.0))
650    }
651
652    fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
653        Some(GlyphId(ch.len_utf16() as u32))
654    }
655
656    fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
657        Ok(Default::default())
658    }
659
660    fn rasterize_glyph(
661        &self,
662        _params: &RenderGlyphParams,
663        raster_bounds: Bounds<DevicePixels>,
664    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
665        Ok((raster_bounds.size, Vec::new()))
666    }
667
668    fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
669        let mut position = px(0.);
670        let metrics = self.font_metrics(FontId(0));
671        let em_width = font_size
672            * self
673                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
674                .unwrap()
675                .width
676            / metrics.units_per_em as f32;
677        let mut glyphs = Vec::new();
678        for (ix, c) in text.char_indices() {
679            if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
680                glyphs.push(ShapedGlyph {
681                    id: glyph,
682                    position: point(position, px(0.)),
683                    index: ix,
684                    is_emoji: glyph.0 == 2,
685                });
686                if glyph.0 == 2 {
687                    position += em_width * 2.0;
688                } else {
689                    position += em_width;
690                }
691            } else {
692                position += em_width
693            }
694        }
695        let mut runs = Vec::default();
696        if !glyphs.is_empty() {
697            runs.push(ShapedRun {
698                font_id: FontId(0),
699                glyphs,
700            });
701        } else {
702            position = px(0.);
703        }
704
705        LineLayout {
706            font_size,
707            width: position,
708            ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
709            descent: font_size * (metrics.descent / metrics.units_per_em as f32),
710            runs,
711            len: text.len(),
712        }
713    }
714}
715
716#[derive(PartialEq, Eq, Hash, Clone)]
717pub(crate) enum AtlasKey {
718    Glyph(RenderGlyphParams),
719    Svg(RenderSvgParams),
720    Image(RenderImageParams),
721}
722
723impl AtlasKey {
724    #[cfg_attr(
725        all(
726            any(target_os = "linux", target_os = "freebsd"),
727            not(any(feature = "x11", feature = "wayland"))
728        ),
729        allow(dead_code)
730    )]
731    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
732        match self {
733            AtlasKey::Glyph(params) => {
734                if params.is_emoji {
735                    AtlasTextureKind::Polychrome
736                } else {
737                    AtlasTextureKind::Monochrome
738                }
739            }
740            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
741            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
742        }
743    }
744}
745
746impl From<RenderGlyphParams> for AtlasKey {
747    fn from(params: RenderGlyphParams) -> Self {
748        Self::Glyph(params)
749    }
750}
751
752impl From<RenderSvgParams> for AtlasKey {
753    fn from(params: RenderSvgParams) -> Self {
754        Self::Svg(params)
755    }
756}
757
758impl From<RenderImageParams> for AtlasKey {
759    fn from(params: RenderImageParams) -> Self {
760        Self::Image(params)
761    }
762}
763
764pub(crate) trait PlatformAtlas: Send + Sync {
765    fn get_or_insert_with<'a>(
766        &self,
767        key: &AtlasKey,
768        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
769    ) -> Result<Option<AtlasTile>>;
770    fn remove(&self, key: &AtlasKey);
771}
772
773struct AtlasTextureList<T> {
774    textures: Vec<Option<T>>,
775    free_list: Vec<usize>,
776}
777
778impl<T> Default for AtlasTextureList<T> {
779    fn default() -> Self {
780        Self {
781            textures: Vec::default(),
782            free_list: Vec::default(),
783        }
784    }
785}
786
787impl<T> ops::Index<usize> for AtlasTextureList<T> {
788    type Output = Option<T>;
789
790    fn index(&self, index: usize) -> &Self::Output {
791        &self.textures[index]
792    }
793}
794
795impl<T> AtlasTextureList<T> {
796    #[allow(unused)]
797    fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
798        self.free_list.clear();
799        self.textures.drain(..)
800    }
801
802    #[allow(dead_code)]
803    fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
804        self.textures.iter_mut().flatten()
805    }
806}
807
808#[derive(Clone, Debug, PartialEq, Eq)]
809#[repr(C)]
810pub(crate) struct AtlasTile {
811    pub(crate) texture_id: AtlasTextureId,
812    pub(crate) tile_id: TileId,
813    pub(crate) padding: u32,
814    pub(crate) bounds: Bounds<DevicePixels>,
815}
816
817#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
818#[repr(C)]
819pub(crate) struct AtlasTextureId {
820    // We use u32 instead of usize for Metal Shader Language compatibility
821    pub(crate) index: u32,
822    pub(crate) kind: AtlasTextureKind,
823}
824
825#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
826#[repr(C)]
827#[cfg_attr(
828    all(
829        any(target_os = "linux", target_os = "freebsd"),
830        not(any(feature = "x11", feature = "wayland"))
831    ),
832    allow(dead_code)
833)]
834pub(crate) enum AtlasTextureKind {
835    Monochrome = 0,
836    Polychrome = 1,
837}
838
839#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
840#[repr(C)]
841pub(crate) struct TileId(pub(crate) u32);
842
843impl From<etagere::AllocId> for TileId {
844    fn from(id: etagere::AllocId) -> Self {
845        Self(id.serialize())
846    }
847}
848
849impl From<TileId> for etagere::AllocId {
850    fn from(id: TileId) -> Self {
851        Self::deserialize(id.0)
852    }
853}
854
855pub(crate) struct PlatformInputHandler {
856    cx: AsyncWindowContext,
857    handler: Box<dyn InputHandler>,
858}
859
860#[cfg_attr(
861    all(
862        any(target_os = "linux", target_os = "freebsd"),
863        not(any(feature = "x11", feature = "wayland"))
864    ),
865    allow(dead_code)
866)]
867impl PlatformInputHandler {
868    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
869        Self { cx, handler }
870    }
871
872    fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
873        self.cx
874            .update(|window, cx| {
875                self.handler
876                    .selected_text_range(ignore_disabled_input, window, cx)
877            })
878            .ok()
879            .flatten()
880    }
881
882    #[cfg_attr(target_os = "windows", allow(dead_code))]
883    fn marked_text_range(&mut self) -> Option<Range<usize>> {
884        self.cx
885            .update(|window, cx| self.handler.marked_text_range(window, cx))
886            .ok()
887            .flatten()
888    }
889
890    #[cfg_attr(
891        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
892        allow(dead_code)
893    )]
894    fn text_for_range(
895        &mut self,
896        range_utf16: Range<usize>,
897        adjusted: &mut Option<Range<usize>>,
898    ) -> Option<String> {
899        self.cx
900            .update(|window, cx| {
901                self.handler
902                    .text_for_range(range_utf16, adjusted, window, cx)
903            })
904            .ok()
905            .flatten()
906    }
907
908    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
909        self.cx
910            .update(|window, cx| {
911                self.handler
912                    .replace_text_in_range(replacement_range, text, window, cx);
913            })
914            .ok();
915    }
916
917    pub fn replace_and_mark_text_in_range(
918        &mut self,
919        range_utf16: Option<Range<usize>>,
920        new_text: &str,
921        new_selected_range: Option<Range<usize>>,
922    ) {
923        self.cx
924            .update(|window, cx| {
925                self.handler.replace_and_mark_text_in_range(
926                    range_utf16,
927                    new_text,
928                    new_selected_range,
929                    window,
930                    cx,
931                )
932            })
933            .ok();
934    }
935
936    #[cfg_attr(target_os = "windows", allow(dead_code))]
937    fn unmark_text(&mut self) {
938        self.cx
939            .update(|window, cx| self.handler.unmark_text(window, cx))
940            .ok();
941    }
942
943    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
944        self.cx
945            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
946            .ok()
947            .flatten()
948    }
949
950    #[allow(dead_code)]
951    fn apple_press_and_hold_enabled(&mut self) -> bool {
952        self.handler.apple_press_and_hold_enabled()
953    }
954
955    pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
956        self.handler.replace_text_in_range(None, input, window, cx);
957    }
958
959    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
960        let selection = self.handler.selected_text_range(true, window, cx)?;
961        self.handler.bounds_for_range(
962            if selection.reversed {
963                selection.range.start..selection.range.start
964            } else {
965                selection.range.end..selection.range.end
966            },
967            window,
968            cx,
969        )
970    }
971
972    #[allow(unused)]
973    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
974        self.cx
975            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
976            .ok()
977            .flatten()
978    }
979}
980
981/// A struct representing a selection in a text buffer, in UTF16 characters.
982/// This is different from a range because the head may be before the tail.
983#[derive(Debug)]
984pub struct UTF16Selection {
985    /// The range of text in the document this selection corresponds to
986    /// in UTF16 characters.
987    pub range: Range<usize>,
988    /// Whether the head of this selection is at the start (true), or end (false)
989    /// of the range
990    pub reversed: bool,
991}
992
993/// Zed's interface for handling text input from the platform's IME system
994/// This is currently a 1:1 exposure of the NSTextInputClient API:
995///
996/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
997pub trait InputHandler: 'static {
998    /// Get the range of the user's currently selected text, if any
999    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
1000    ///
1001    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1002    fn selected_text_range(
1003        &mut self,
1004        ignore_disabled_input: bool,
1005        window: &mut Window,
1006        cx: &mut App,
1007    ) -> Option<UTF16Selection>;
1008
1009    /// Get the range of the currently marked text, if any
1010    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
1011    ///
1012    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1013    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1014
1015    /// Get the text for the given document range in UTF-16 characters
1016    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
1017    ///
1018    /// range_utf16 is in terms of UTF-16 characters
1019    fn text_for_range(
1020        &mut self,
1021        range_utf16: Range<usize>,
1022        adjusted_range: &mut Option<Range<usize>>,
1023        window: &mut Window,
1024        cx: &mut App,
1025    ) -> Option<String>;
1026
1027    /// Replace the text in the given document range with the given text
1028    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
1029    ///
1030    /// replacement_range is in terms of UTF-16 characters
1031    fn replace_text_in_range(
1032        &mut self,
1033        replacement_range: Option<Range<usize>>,
1034        text: &str,
1035        window: &mut Window,
1036        cx: &mut App,
1037    );
1038
1039    /// Replace the text in the given document range with the given text,
1040    /// and mark the given text as part of an IME 'composing' state
1041    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
1042    ///
1043    /// range_utf16 is in terms of UTF-16 characters
1044    /// new_selected_range is in terms of UTF-16 characters
1045    fn replace_and_mark_text_in_range(
1046        &mut self,
1047        range_utf16: Option<Range<usize>>,
1048        new_text: &str,
1049        new_selected_range: Option<Range<usize>>,
1050        window: &mut Window,
1051        cx: &mut App,
1052    );
1053
1054    /// Remove the IME 'composing' state from the document
1055    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
1056    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1057
1058    /// Get the bounds of the given document range in screen coordinates
1059    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
1060    ///
1061    /// This is used for positioning the IME candidate window
1062    fn bounds_for_range(
1063        &mut self,
1064        range_utf16: Range<usize>,
1065        window: &mut Window,
1066        cx: &mut App,
1067    ) -> Option<Bounds<Pixels>>;
1068
1069    /// Get the character offset for the given point in terms of UTF16 characters
1070    ///
1071    /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
1072    fn character_index_for_point(
1073        &mut self,
1074        point: Point<Pixels>,
1075        window: &mut Window,
1076        cx: &mut App,
1077    ) -> Option<usize>;
1078
1079    /// Allows a given input context to opt into getting raw key repeats instead of
1080    /// sending these to the platform.
1081    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
1082    /// (which is how iTerm does it) but it doesn't seem to work for me.
1083    #[allow(dead_code)]
1084    fn apple_press_and_hold_enabled(&mut self) -> bool {
1085        true
1086    }
1087}
1088
1089/// The variables that can be configured when creating a new window
1090#[derive(Debug)]
1091pub struct WindowOptions {
1092    /// Specifies the state and bounds of the window in screen coordinates.
1093    /// - `None`: Inherit the bounds.
1094    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
1095    pub window_bounds: Option<WindowBounds>,
1096
1097    /// The titlebar configuration of the window
1098    pub titlebar: Option<TitlebarOptions>,
1099
1100    /// Whether the window should be focused when created
1101    pub focus: bool,
1102
1103    /// Whether the window should be shown when created
1104    pub show: bool,
1105
1106    /// The kind of window to create
1107    pub kind: WindowKind,
1108
1109    /// Whether the window should be movable by the user
1110    pub is_movable: bool,
1111
1112    /// Whether the window should be resizable by the user
1113    pub is_resizable: bool,
1114
1115    /// Whether the window should be minimized by the user
1116    pub is_minimizable: bool,
1117
1118    /// The display to create the window on, if this is None,
1119    /// the window will be created on the main display
1120    pub display_id: Option<DisplayId>,
1121
1122    /// The appearance of the window background.
1123    pub window_background: WindowBackgroundAppearance,
1124
1125    /// Application identifier of the window. Can by used by desktop environments to group applications together.
1126    pub app_id: Option<String>,
1127
1128    /// Window minimum size
1129    pub window_min_size: Option<Size<Pixels>>,
1130
1131    /// Whether to use client or server side decorations. Wayland only
1132    /// Note that this may be ignored.
1133    pub window_decorations: Option<WindowDecorations>,
1134
1135    /// 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.
1136    pub tabbing_identifier: Option<String>,
1137}
1138
1139/// The variables that can be configured when creating a new window
1140#[derive(Debug)]
1141#[cfg_attr(
1142    all(
1143        any(target_os = "linux", target_os = "freebsd"),
1144        not(any(feature = "x11", feature = "wayland"))
1145    ),
1146    allow(dead_code)
1147)]
1148pub(crate) struct WindowParams {
1149    pub bounds: Bounds<Pixels>,
1150
1151    /// The titlebar configuration of the window
1152    #[cfg_attr(feature = "wayland", allow(dead_code))]
1153    pub titlebar: Option<TitlebarOptions>,
1154
1155    /// The kind of window to create
1156    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1157    pub kind: WindowKind,
1158
1159    /// Whether the window should be movable by the user
1160    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1161    pub is_movable: bool,
1162
1163    /// Whether the window should be resizable by the user
1164    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1165    pub is_resizable: bool,
1166
1167    /// Whether the window should be minimized by the user
1168    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1169    pub is_minimizable: bool,
1170
1171    #[cfg_attr(
1172        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1173        allow(dead_code)
1174    )]
1175    pub focus: bool,
1176
1177    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1178    pub show: bool,
1179
1180    #[cfg_attr(feature = "wayland", allow(dead_code))]
1181    pub display_id: Option<DisplayId>,
1182
1183    pub window_min_size: Option<Size<Pixels>>,
1184    #[cfg(target_os = "macos")]
1185    pub tabbing_identifier: Option<String>,
1186}
1187
1188/// Represents the status of how a window should be opened.
1189#[derive(Debug, Copy, Clone, PartialEq)]
1190pub enum WindowBounds {
1191    /// Indicates that the window should open in a windowed state with the given bounds.
1192    Windowed(Bounds<Pixels>),
1193    /// Indicates that the window should open in a maximized state.
1194    /// The bounds provided here represent the restore size of the window.
1195    Maximized(Bounds<Pixels>),
1196    /// Indicates that the window should open in fullscreen mode.
1197    /// The bounds provided here represent the restore size of the window.
1198    Fullscreen(Bounds<Pixels>),
1199}
1200
1201impl Default for WindowBounds {
1202    fn default() -> Self {
1203        WindowBounds::Windowed(Bounds::default())
1204    }
1205}
1206
1207impl WindowBounds {
1208    /// Retrieve the inner bounds
1209    pub fn get_bounds(&self) -> Bounds<Pixels> {
1210        match self {
1211            WindowBounds::Windowed(bounds) => *bounds,
1212            WindowBounds::Maximized(bounds) => *bounds,
1213            WindowBounds::Fullscreen(bounds) => *bounds,
1214        }
1215    }
1216
1217    /// Creates a new window bounds that centers the window on the screen.
1218    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1219        WindowBounds::Windowed(Bounds::centered(None, size, cx))
1220    }
1221}
1222
1223impl Default for WindowOptions {
1224    fn default() -> Self {
1225        Self {
1226            window_bounds: None,
1227            titlebar: Some(TitlebarOptions {
1228                title: Default::default(),
1229                appears_transparent: Default::default(),
1230                traffic_light_position: Default::default(),
1231            }),
1232            focus: true,
1233            show: true,
1234            kind: WindowKind::Normal,
1235            is_movable: true,
1236            is_resizable: true,
1237            is_minimizable: true,
1238            display_id: None,
1239            window_background: WindowBackgroundAppearance::default(),
1240            app_id: None,
1241            window_min_size: None,
1242            window_decorations: None,
1243            tabbing_identifier: None,
1244        }
1245    }
1246}
1247
1248/// The options that can be configured for a window's titlebar
1249#[derive(Debug, Default)]
1250pub struct TitlebarOptions {
1251    /// The initial title of the window
1252    pub title: Option<SharedString>,
1253
1254    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
1255    /// Refer to [`WindowOptions::window_decorations`] on Linux
1256    pub appears_transparent: bool,
1257
1258    /// The position of the macOS traffic light buttons
1259    pub traffic_light_position: Option<Point<Pixels>>,
1260}
1261
1262/// The kind of window to create
1263#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1264pub enum WindowKind {
1265    /// A normal application window
1266    Normal,
1267
1268    /// A window that appears above all other windows, usually used for alerts or popups
1269    /// use sparingly!
1270    PopUp,
1271
1272    /// A floating window that appears on top of its parent window
1273    Floating,
1274}
1275
1276/// The appearance of the window, as defined by the operating system.
1277///
1278/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
1279/// values.
1280#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1281pub enum WindowAppearance {
1282    /// A light appearance.
1283    ///
1284    /// On macOS, this corresponds to the `aqua` appearance.
1285    Light,
1286
1287    /// A light appearance with vibrant colors.
1288    ///
1289    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
1290    VibrantLight,
1291
1292    /// A dark appearance.
1293    ///
1294    /// On macOS, this corresponds to the `darkAqua` appearance.
1295    Dark,
1296
1297    /// A dark appearance with vibrant colors.
1298    ///
1299    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
1300    VibrantDark,
1301}
1302
1303impl Default for WindowAppearance {
1304    fn default() -> Self {
1305        Self::Light
1306    }
1307}
1308
1309/// The appearance of the background of the window itself, when there is
1310/// no content or the content is transparent.
1311#[derive(Copy, Clone, Debug, Default, PartialEq)]
1312pub enum WindowBackgroundAppearance {
1313    /// Opaque.
1314    ///
1315    /// This lets the window manager know that content behind this
1316    /// window does not need to be drawn.
1317    ///
1318    /// Actual color depends on the system and themes should define a fully
1319    /// opaque background color instead.
1320    #[default]
1321    Opaque,
1322    /// Plain alpha transparency.
1323    Transparent,
1324    /// Transparency, but the contents behind the window are blurred.
1325    ///
1326    /// Not always supported.
1327    Blurred,
1328}
1329
1330/// The options that can be configured for a file dialog prompt
1331#[derive(Clone, Debug)]
1332pub struct PathPromptOptions {
1333    /// Should the prompt allow files to be selected?
1334    pub files: bool,
1335    /// Should the prompt allow directories to be selected?
1336    pub directories: bool,
1337    /// Should the prompt allow multiple files to be selected?
1338    pub multiple: bool,
1339    /// The prompt to show to a user when selecting a path
1340    pub prompt: Option<SharedString>,
1341}
1342
1343/// What kind of prompt styling to show
1344#[derive(Copy, Clone, Debug, PartialEq)]
1345pub enum PromptLevel {
1346    /// A prompt that is shown when the user should be notified of something
1347    Info,
1348
1349    /// A prompt that is shown when the user needs to be warned of a potential problem
1350    Warning,
1351
1352    /// A prompt that is shown when a critical problem has occurred
1353    Critical,
1354}
1355
1356/// Prompt Button
1357#[derive(Clone, Debug, PartialEq)]
1358pub enum PromptButton {
1359    /// Ok button
1360    Ok(SharedString),
1361    /// Cancel button
1362    Cancel(SharedString),
1363    /// Other button
1364    Other(SharedString),
1365}
1366
1367impl PromptButton {
1368    /// Create a button with label
1369    pub fn new(label: impl Into<SharedString>) -> Self {
1370        PromptButton::Other(label.into())
1371    }
1372
1373    /// Create an Ok button
1374    pub fn ok(label: impl Into<SharedString>) -> Self {
1375        PromptButton::Ok(label.into())
1376    }
1377
1378    /// Create a Cancel button
1379    pub fn cancel(label: impl Into<SharedString>) -> Self {
1380        PromptButton::Cancel(label.into())
1381    }
1382
1383    #[allow(dead_code)]
1384    pub(crate) fn is_cancel(&self) -> bool {
1385        matches!(self, PromptButton::Cancel(_))
1386    }
1387
1388    /// Returns the label of the button
1389    pub fn label(&self) -> &SharedString {
1390        match self {
1391            PromptButton::Ok(label) => label,
1392            PromptButton::Cancel(label) => label,
1393            PromptButton::Other(label) => label,
1394        }
1395    }
1396}
1397
1398impl From<&str> for PromptButton {
1399    fn from(value: &str) -> Self {
1400        match value.to_lowercase().as_str() {
1401            "ok" => PromptButton::Ok("Ok".into()),
1402            "cancel" => PromptButton::Cancel("Cancel".into()),
1403            _ => PromptButton::Other(SharedString::from(value.to_owned())),
1404        }
1405    }
1406}
1407
1408/// The style of the cursor (pointer)
1409#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1410pub enum CursorStyle {
1411    /// The default cursor
1412    Arrow,
1413
1414    /// A text input cursor
1415    /// corresponds to the CSS cursor value `text`
1416    IBeam,
1417
1418    /// A crosshair cursor
1419    /// corresponds to the CSS cursor value `crosshair`
1420    Crosshair,
1421
1422    /// A closed hand cursor
1423    /// corresponds to the CSS cursor value `grabbing`
1424    ClosedHand,
1425
1426    /// An open hand cursor
1427    /// corresponds to the CSS cursor value `grab`
1428    OpenHand,
1429
1430    /// A pointing hand cursor
1431    /// corresponds to the CSS cursor value `pointer`
1432    PointingHand,
1433
1434    /// A resize left cursor
1435    /// corresponds to the CSS cursor value `w-resize`
1436    ResizeLeft,
1437
1438    /// A resize right cursor
1439    /// corresponds to the CSS cursor value `e-resize`
1440    ResizeRight,
1441
1442    /// A resize cursor to the left and right
1443    /// corresponds to the CSS cursor value `ew-resize`
1444    ResizeLeftRight,
1445
1446    /// A resize up cursor
1447    /// corresponds to the CSS cursor value `n-resize`
1448    ResizeUp,
1449
1450    /// A resize down cursor
1451    /// corresponds to the CSS cursor value `s-resize`
1452    ResizeDown,
1453
1454    /// A resize cursor directing up and down
1455    /// corresponds to the CSS cursor value `ns-resize`
1456    ResizeUpDown,
1457
1458    /// A resize cursor directing up-left and down-right
1459    /// corresponds to the CSS cursor value `nesw-resize`
1460    ResizeUpLeftDownRight,
1461
1462    /// A resize cursor directing up-right and down-left
1463    /// corresponds to the CSS cursor value `nwse-resize`
1464    ResizeUpRightDownLeft,
1465
1466    /// A cursor indicating that the item/column can be resized horizontally.
1467    /// corresponds to the CSS cursor value `col-resize`
1468    ResizeColumn,
1469
1470    /// A cursor indicating that the item/row can be resized vertically.
1471    /// corresponds to the CSS cursor value `row-resize`
1472    ResizeRow,
1473
1474    /// A text input cursor for vertical layout
1475    /// corresponds to the CSS cursor value `vertical-text`
1476    IBeamCursorForVerticalLayout,
1477
1478    /// A cursor indicating that the operation is not allowed
1479    /// corresponds to the CSS cursor value `not-allowed`
1480    OperationNotAllowed,
1481
1482    /// A cursor indicating that the operation will result in a link
1483    /// corresponds to the CSS cursor value `alias`
1484    DragLink,
1485
1486    /// A cursor indicating that the operation will result in a copy
1487    /// corresponds to the CSS cursor value `copy`
1488    DragCopy,
1489
1490    /// A cursor indicating that the operation will result in a context menu
1491    /// corresponds to the CSS cursor value `context-menu`
1492    ContextualMenu,
1493
1494    /// Hide the cursor
1495    None,
1496}
1497
1498impl Default for CursorStyle {
1499    fn default() -> Self {
1500        Self::Arrow
1501    }
1502}
1503
1504/// A clipboard item that should be copied to the clipboard
1505#[derive(Clone, Debug, Eq, PartialEq)]
1506pub struct ClipboardItem {
1507    entries: Vec<ClipboardEntry>,
1508}
1509
1510/// Either a ClipboardString or a ClipboardImage
1511#[derive(Clone, Debug, Eq, PartialEq)]
1512pub enum ClipboardEntry {
1513    /// A string entry
1514    String(ClipboardString),
1515    /// An image entry
1516    Image(Image),
1517}
1518
1519impl ClipboardItem {
1520    /// Create a new ClipboardItem::String with no associated metadata
1521    pub fn new_string(text: String) -> Self {
1522        Self {
1523            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
1524        }
1525    }
1526
1527    /// Create a new ClipboardItem::String with the given text and associated metadata
1528    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
1529        Self {
1530            entries: vec![ClipboardEntry::String(ClipboardString {
1531                text,
1532                metadata: Some(metadata),
1533            })],
1534        }
1535    }
1536
1537    /// Create a new ClipboardItem::String with the given text and associated metadata
1538    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
1539        Self {
1540            entries: vec![ClipboardEntry::String(
1541                ClipboardString::new(text).with_json_metadata(metadata),
1542            )],
1543        }
1544    }
1545
1546    /// Create a new ClipboardItem::Image with the given image with no associated metadata
1547    pub fn new_image(image: &Image) -> Self {
1548        Self {
1549            entries: vec![ClipboardEntry::Image(image.clone())],
1550        }
1551    }
1552
1553    /// Concatenates together all the ClipboardString entries in the item.
1554    /// Returns None if there were no ClipboardString entries.
1555    pub fn text(&self) -> Option<String> {
1556        let mut answer = String::new();
1557        let mut any_entries = false;
1558
1559        for entry in self.entries.iter() {
1560            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
1561                answer.push_str(text);
1562                any_entries = true;
1563            }
1564        }
1565
1566        if any_entries { Some(answer) } else { None }
1567    }
1568
1569    /// If this item is one ClipboardEntry::String, returns its metadata.
1570    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
1571    pub fn metadata(&self) -> Option<&String> {
1572        match self.entries().first() {
1573            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
1574                clipboard_string.metadata.as_ref()
1575            }
1576            _ => None,
1577        }
1578    }
1579
1580    /// Get the item's entries
1581    pub fn entries(&self) -> &[ClipboardEntry] {
1582        &self.entries
1583    }
1584
1585    /// Get owned versions of the item's entries
1586    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
1587        self.entries.into_iter()
1588    }
1589}
1590
1591impl From<ClipboardString> for ClipboardEntry {
1592    fn from(value: ClipboardString) -> Self {
1593        Self::String(value)
1594    }
1595}
1596
1597impl From<String> for ClipboardEntry {
1598    fn from(value: String) -> Self {
1599        Self::from(ClipboardString::from(value))
1600    }
1601}
1602
1603impl From<Image> for ClipboardEntry {
1604    fn from(value: Image) -> Self {
1605        Self::Image(value)
1606    }
1607}
1608
1609impl From<ClipboardEntry> for ClipboardItem {
1610    fn from(value: ClipboardEntry) -> Self {
1611        Self {
1612            entries: vec![value],
1613        }
1614    }
1615}
1616
1617impl From<String> for ClipboardItem {
1618    fn from(value: String) -> Self {
1619        Self::from(ClipboardEntry::from(value))
1620    }
1621}
1622
1623impl From<Image> for ClipboardItem {
1624    fn from(value: Image) -> Self {
1625        Self::from(ClipboardEntry::from(value))
1626    }
1627}
1628
1629/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
1630#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
1631pub enum ImageFormat {
1632    // Sorted from most to least likely to be pasted into an editor,
1633    // which matters when we iterate through them trying to see if
1634    // clipboard content matches them.
1635    /// .png
1636    Png,
1637    /// .jpeg or .jpg
1638    Jpeg,
1639    /// .webp
1640    Webp,
1641    /// .gif
1642    Gif,
1643    /// .svg
1644    Svg,
1645    /// .bmp
1646    Bmp,
1647    /// .tif or .tiff
1648    Tiff,
1649}
1650
1651impl ImageFormat {
1652    /// Returns the mime type for the ImageFormat
1653    pub const fn mime_type(self) -> &'static str {
1654        match self {
1655            ImageFormat::Png => "image/png",
1656            ImageFormat::Jpeg => "image/jpeg",
1657            ImageFormat::Webp => "image/webp",
1658            ImageFormat::Gif => "image/gif",
1659            ImageFormat::Svg => "image/svg+xml",
1660            ImageFormat::Bmp => "image/bmp",
1661            ImageFormat::Tiff => "image/tiff",
1662        }
1663    }
1664
1665    /// Returns the ImageFormat for the given mime type
1666    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
1667        match mime_type {
1668            "image/png" => Some(Self::Png),
1669            "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
1670            "image/webp" => Some(Self::Webp),
1671            "image/gif" => Some(Self::Gif),
1672            "image/svg+xml" => Some(Self::Svg),
1673            "image/bmp" => Some(Self::Bmp),
1674            "image/tiff" | "image/tif" => Some(Self::Tiff),
1675            _ => None,
1676        }
1677    }
1678}
1679
1680/// An image, with a format and certain bytes
1681#[derive(Clone, Debug, PartialEq, Eq)]
1682pub struct Image {
1683    /// The image format the bytes represent (e.g. PNG)
1684    pub format: ImageFormat,
1685    /// The raw image bytes
1686    pub bytes: Vec<u8>,
1687    /// The unique ID for the image
1688    id: u64,
1689}
1690
1691impl Hash for Image {
1692    fn hash<H: Hasher>(&self, state: &mut H) {
1693        state.write_u64(self.id);
1694    }
1695}
1696
1697impl Image {
1698    /// An empty image containing no data
1699    pub fn empty() -> Self {
1700        Self::from_bytes(ImageFormat::Png, Vec::new())
1701    }
1702
1703    /// Create an image from a format and bytes
1704    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
1705        Self {
1706            id: hash(&bytes),
1707            format,
1708            bytes,
1709        }
1710    }
1711
1712    /// Get this image's ID
1713    pub fn id(&self) -> u64 {
1714        self.id
1715    }
1716
1717    /// Use the GPUI `use_asset` API to make this image renderable
1718    pub fn use_render_image(
1719        self: Arc<Self>,
1720        window: &mut Window,
1721        cx: &mut App,
1722    ) -> Option<Arc<RenderImage>> {
1723        ImageSource::Image(self)
1724            .use_data(None, window, cx)
1725            .and_then(|result| result.ok())
1726    }
1727
1728    /// Use the GPUI `get_asset` API to make this image renderable
1729    pub fn get_render_image(
1730        self: Arc<Self>,
1731        window: &mut Window,
1732        cx: &mut App,
1733    ) -> Option<Arc<RenderImage>> {
1734        ImageSource::Image(self)
1735            .get_data(None, window, cx)
1736            .and_then(|result| result.ok())
1737    }
1738
1739    /// Use the GPUI `remove_asset` API to drop this image, if possible.
1740    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
1741        ImageSource::Image(self).remove_asset(cx);
1742    }
1743
1744    /// Convert the clipboard image to an `ImageData` object.
1745    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
1746        fn frames_for_image(
1747            bytes: &[u8],
1748            format: image::ImageFormat,
1749        ) -> Result<SmallVec<[Frame; 1]>> {
1750            let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
1751
1752            // Convert from RGBA to BGRA.
1753            for pixel in data.chunks_exact_mut(4) {
1754                pixel.swap(0, 2);
1755            }
1756
1757            Ok(SmallVec::from_elem(Frame::new(data), 1))
1758        }
1759
1760        let frames = match self.format {
1761            ImageFormat::Gif => {
1762                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
1763                let mut frames = SmallVec::new();
1764
1765                for frame in decoder.into_frames() {
1766                    let mut frame = frame?;
1767                    // Convert from RGBA to BGRA.
1768                    for pixel in frame.buffer_mut().chunks_exact_mut(4) {
1769                        pixel.swap(0, 2);
1770                    }
1771                    frames.push(frame);
1772                }
1773
1774                frames
1775            }
1776            ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
1777            ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
1778            ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
1779            ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
1780            ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
1781            ImageFormat::Svg => {
1782                let pixmap = svg_renderer.render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?;
1783
1784                let buffer =
1785                    image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
1786                        .unwrap();
1787
1788                SmallVec::from_elem(Frame::new(buffer), 1)
1789            }
1790        };
1791
1792        Ok(Arc::new(RenderImage::new(frames)))
1793    }
1794
1795    /// Get the format of the clipboard image
1796    pub fn format(&self) -> ImageFormat {
1797        self.format
1798    }
1799
1800    /// Get the raw bytes of the clipboard image
1801    pub fn bytes(&self) -> &[u8] {
1802        self.bytes.as_slice()
1803    }
1804}
1805
1806/// A clipboard item that should be copied to the clipboard
1807#[derive(Clone, Debug, Eq, PartialEq)]
1808pub struct ClipboardString {
1809    pub(crate) text: String,
1810    pub(crate) metadata: Option<String>,
1811}
1812
1813impl ClipboardString {
1814    /// Create a new clipboard string with the given text
1815    pub fn new(text: String) -> Self {
1816        Self {
1817            text,
1818            metadata: None,
1819        }
1820    }
1821
1822    /// Return a new clipboard item with the metadata replaced by the given metadata,
1823    /// after serializing it as JSON.
1824    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
1825        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
1826        self
1827    }
1828
1829    /// Get the text of the clipboard string
1830    pub fn text(&self) -> &String {
1831        &self.text
1832    }
1833
1834    /// Get the owned text of the clipboard string
1835    pub fn into_text(self) -> String {
1836        self.text
1837    }
1838
1839    /// Get the metadata of the clipboard string, formatted as JSON
1840    pub fn metadata_json<T>(&self) -> Option<T>
1841    where
1842        T: for<'a> Deserialize<'a>,
1843    {
1844        self.metadata
1845            .as_ref()
1846            .and_then(|m| serde_json::from_str(m).ok())
1847    }
1848
1849    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1850    pub(crate) fn text_hash(text: &str) -> u64 {
1851        let mut hasher = SeaHasher::new();
1852        text.hash(&mut hasher);
1853        hasher.finish()
1854    }
1855}
1856
1857impl From<String> for ClipboardString {
1858    fn from(value: String) -> Self {
1859        Self {
1860            text: value,
1861            metadata: None,
1862        }
1863    }
1864}