platform.rs

  1// todo(linux): remove
  2#![cfg_attr(target_os = "linux", allow(dead_code))]
  3// todo(windows): remove
  4#![cfg_attr(windows, allow(dead_code))]
  5
  6mod app_menu;
  7mod keystroke;
  8
  9#[cfg(not(target_os = "macos"))]
 10mod cosmic_text;
 11
 12#[cfg(target_os = "linux")]
 13mod linux;
 14
 15#[cfg(target_os = "macos")]
 16mod mac;
 17
 18#[cfg(any(target_os = "linux", target_os = "windows", feature = "macos-blade"))]
 19mod blade;
 20
 21#[cfg(any(test, feature = "test-support"))]
 22mod test;
 23
 24#[cfg(target_os = "windows")]
 25mod windows;
 26
 27use crate::{
 28    Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels,
 29    DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, Keymap,
 30    LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams,
 31    RenderSvgParams, Scene, SharedString, Size, Task, TaskLabel, WindowContext,
 32};
 33use anyhow::Result;
 34use async_task::Runnable;
 35use futures::channel::oneshot;
 36use parking::Unparker;
 37use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
 38use seahash::SeaHasher;
 39use serde::{Deserialize, Serialize};
 40use std::borrow::Cow;
 41use std::hash::{Hash, Hasher};
 42use std::time::Duration;
 43use std::{
 44    any::Any,
 45    fmt::{self, Debug},
 46    ops::Range,
 47    path::{Path, PathBuf},
 48    rc::Rc,
 49    sync::Arc,
 50};
 51use uuid::Uuid;
 52
 53pub use app_menu::*;
 54pub use keystroke::*;
 55
 56#[cfg(not(target_os = "macos"))]
 57pub(crate) use cosmic_text::*;
 58#[cfg(target_os = "linux")]
 59pub(crate) use linux::*;
 60#[cfg(target_os = "macos")]
 61pub(crate) use mac::*;
 62pub use semantic_version::SemanticVersion;
 63#[cfg(any(test, feature = "test-support"))]
 64pub(crate) use test::*;
 65use time::UtcOffset;
 66#[cfg(target_os = "windows")]
 67pub(crate) use windows::*;
 68
 69#[cfg(target_os = "macos")]
 70pub(crate) fn current_platform() -> Rc<dyn Platform> {
 71    Rc::new(MacPlatform::new())
 72}
 73#[cfg(target_os = "linux")]
 74pub(crate) fn current_platform() -> Rc<dyn Platform> {
 75    let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
 76    let x11_display = std::env::var_os("DISPLAY");
 77
 78    let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
 79    let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
 80
 81    if use_wayland {
 82        Rc::new(WaylandClient::new())
 83    } else if use_x11 {
 84        Rc::new(X11Client::new())
 85    } else {
 86        Rc::new(HeadlessClient::new())
 87    }
 88}
 89// todo("windows")
 90#[cfg(target_os = "windows")]
 91pub(crate) fn current_platform() -> Rc<dyn Platform> {
 92    Rc::new(WindowsPlatform::new())
 93}
 94
 95pub(crate) trait Platform: 'static {
 96    fn background_executor(&self) -> BackgroundExecutor;
 97    fn foreground_executor(&self) -> ForegroundExecutor;
 98    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
 99
100    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
101    fn quit(&self);
102    fn restart(&self);
103    fn activate(&self, ignoring_other_apps: bool);
104    fn hide(&self);
105    fn hide_other_apps(&self);
106    fn unhide_other_apps(&self);
107
108    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
109    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
110    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
111    fn active_window(&self) -> Option<AnyWindowHandle>;
112    fn open_window(
113        &self,
114        handle: AnyWindowHandle,
115        options: WindowParams,
116    ) -> Box<dyn PlatformWindow>;
117
118    /// Returns the appearance of the application's windows.
119    fn window_appearance(&self) -> WindowAppearance;
120
121    fn open_url(&self, url: &str);
122    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
123    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
124
125    fn prompt_for_paths(
126        &self,
127        options: PathPromptOptions,
128    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
129    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
130    fn reveal_path(&self, path: &Path);
131
132    fn on_become_active(&self, callback: Box<dyn FnMut()>);
133    fn on_resign_active(&self, callback: Box<dyn FnMut()>);
134    fn on_quit(&self, callback: Box<dyn FnMut()>);
135    fn on_reopen(&self, callback: Box<dyn FnMut()>);
136    fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
137
138    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
139    fn add_recent_document(&self, _path: &Path) {}
140    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
141    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
142    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
143
144    fn os_name(&self) -> &'static str;
145    fn os_version(&self) -> Result<SemanticVersion>;
146    fn app_version(&self) -> Result<SemanticVersion>;
147    fn app_path(&self) -> Result<PathBuf>;
148    fn local_timezone(&self) -> UtcOffset;
149    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
150
151    fn set_cursor_style(&self, style: CursorStyle);
152    fn should_auto_hide_scrollbars(&self) -> bool;
153
154    fn write_to_clipboard(&self, item: ClipboardItem);
155    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
156
157    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
158    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
159    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
160}
161
162/// A handle to a platform's display, e.g. a monitor or laptop screen.
163pub trait PlatformDisplay: Send + Sync + Debug {
164    /// Get the ID for this display
165    fn id(&self) -> DisplayId;
166
167    /// Returns a stable identifier for this display that can be persisted and used
168    /// across system restarts.
169    fn uuid(&self) -> Result<Uuid>;
170
171    /// Get the bounds for this display
172    fn bounds(&self) -> Bounds<DevicePixels>;
173}
174
175/// An opaque identifier for a hardware display
176#[derive(PartialEq, Eq, Hash, Copy, Clone)]
177pub struct DisplayId(pub(crate) u32);
178
179impl Debug for DisplayId {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        write!(f, "DisplayId({})", self.0)
182    }
183}
184
185unsafe impl Send for DisplayId {}
186
187pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
188    fn bounds(&self) -> Bounds<DevicePixels>;
189    fn is_maximized(&self) -> bool;
190    fn is_minimized(&self) -> bool;
191    fn content_size(&self) -> Size<Pixels>;
192    fn scale_factor(&self) -> f32;
193    fn appearance(&self) -> WindowAppearance;
194    fn display(&self) -> Rc<dyn PlatformDisplay>;
195    fn mouse_position(&self) -> Point<Pixels>;
196    fn modifiers(&self) -> Modifiers;
197    fn as_any_mut(&mut self) -> &mut dyn Any;
198    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
199    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
200    fn prompt(
201        &self,
202        level: PromptLevel,
203        msg: &str,
204        detail: Option<&str>,
205        answers: &[&str],
206    ) -> Option<oneshot::Receiver<usize>>;
207    fn activate(&self);
208    fn is_active(&self) -> bool;
209    fn set_title(&mut self, title: &str);
210    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance);
211    fn set_edited(&mut self, edited: bool);
212    fn show_character_palette(&self);
213    fn minimize(&self);
214    fn zoom(&self);
215    fn toggle_fullscreen(&self);
216    fn is_fullscreen(&self) -> bool;
217    fn on_request_frame(&self, callback: Box<dyn FnMut()>);
218    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
219    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
220    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
221    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
222    fn on_moved(&self, callback: Box<dyn FnMut()>);
223    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
224    fn on_close(&self, callback: Box<dyn FnOnce()>);
225    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
226    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
227    fn draw(&self, scene: &Scene);
228    fn completed_frame(&self) {}
229    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
230
231    #[cfg(target_os = "windows")]
232    fn get_raw_handle(&self) -> windows::HWND;
233
234    #[cfg(any(test, feature = "test-support"))]
235    fn as_test(&mut self) -> Option<&mut TestWindow> {
236        None
237    }
238}
239
240/// This type is public so that our test macro can generate and use it, but it should not
241/// be considered part of our public API.
242#[doc(hidden)]
243pub trait PlatformDispatcher: Send + Sync {
244    fn is_main_thread(&self) -> bool;
245    fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
246    fn dispatch_on_main_thread(&self, runnable: Runnable);
247    fn dispatch_after(&self, duration: Duration, runnable: Runnable);
248    fn tick(&self, background_only: bool) -> bool;
249    fn park(&self);
250    fn unparker(&self) -> Unparker;
251
252    #[cfg(any(test, feature = "test-support"))]
253    fn as_test(&self) -> Option<&TestDispatcher> {
254        None
255    }
256}
257
258pub(crate) trait PlatformTextSystem: Send + Sync {
259    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
260    fn all_font_names(&self) -> Vec<String>;
261    fn all_font_families(&self) -> Vec<String>;
262    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
263    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
264    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
265    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
266    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
267    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
268    fn rasterize_glyph(
269        &self,
270        params: &RenderGlyphParams,
271        raster_bounds: Bounds<DevicePixels>,
272    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
273    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
274    fn wrap_line(
275        &self,
276        text: &str,
277        font_id: FontId,
278        font_size: Pixels,
279        width: Pixels,
280    ) -> Vec<usize>;
281}
282
283/// Basic metadata about the current application and operating system.
284#[derive(Clone, Debug)]
285pub struct AppMetadata {
286    /// The name of the current operating system
287    pub os_name: &'static str,
288
289    /// The operating system's version
290    pub os_version: Option<SemanticVersion>,
291
292    /// The current version of the application
293    pub app_version: Option<SemanticVersion>,
294}
295
296#[derive(PartialEq, Eq, Hash, Clone)]
297pub(crate) enum AtlasKey {
298    Glyph(RenderGlyphParams),
299    Svg(RenderSvgParams),
300    Image(RenderImageParams),
301}
302
303impl AtlasKey {
304    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
305        match self {
306            AtlasKey::Glyph(params) => {
307                if params.is_emoji {
308                    AtlasTextureKind::Polychrome
309                } else {
310                    AtlasTextureKind::Monochrome
311                }
312            }
313            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
314            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
315        }
316    }
317}
318
319impl From<RenderGlyphParams> for AtlasKey {
320    fn from(params: RenderGlyphParams) -> Self {
321        Self::Glyph(params)
322    }
323}
324
325impl From<RenderSvgParams> for AtlasKey {
326    fn from(params: RenderSvgParams) -> Self {
327        Self::Svg(params)
328    }
329}
330
331impl From<RenderImageParams> for AtlasKey {
332    fn from(params: RenderImageParams) -> Self {
333        Self::Image(params)
334    }
335}
336
337pub(crate) trait PlatformAtlas: Send + Sync {
338    fn get_or_insert_with<'a>(
339        &self,
340        key: &AtlasKey,
341        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
342    ) -> Result<AtlasTile>;
343}
344
345#[derive(Clone, Debug, PartialEq, Eq)]
346#[repr(C)]
347pub(crate) struct AtlasTile {
348    pub(crate) texture_id: AtlasTextureId,
349    pub(crate) tile_id: TileId,
350    pub(crate) padding: u32,
351    pub(crate) bounds: Bounds<DevicePixels>,
352}
353
354#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
355#[repr(C)]
356pub(crate) struct AtlasTextureId {
357    // We use u32 instead of usize for Metal Shader Language compatibility
358    pub(crate) index: u32,
359    pub(crate) kind: AtlasTextureKind,
360}
361
362#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
363#[repr(C)]
364pub(crate) enum AtlasTextureKind {
365    Monochrome = 0,
366    Polychrome = 1,
367    Path = 2,
368}
369
370#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
371#[repr(C)]
372pub(crate) struct TileId(pub(crate) u32);
373
374impl From<etagere::AllocId> for TileId {
375    fn from(id: etagere::AllocId) -> Self {
376        Self(id.serialize())
377    }
378}
379
380impl From<TileId> for etagere::AllocId {
381    fn from(id: TileId) -> Self {
382        Self::deserialize(id.0)
383    }
384}
385
386pub(crate) struct PlatformInputHandler {
387    cx: AsyncWindowContext,
388    handler: Box<dyn InputHandler>,
389}
390
391impl PlatformInputHandler {
392    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
393        Self { cx, handler }
394    }
395
396    fn selected_text_range(&mut self) -> Option<Range<usize>> {
397        self.cx
398            .update(|cx| self.handler.selected_text_range(cx))
399            .ok()
400            .flatten()
401    }
402
403    fn marked_text_range(&mut self) -> Option<Range<usize>> {
404        self.cx
405            .update(|cx| self.handler.marked_text_range(cx))
406            .ok()
407            .flatten()
408    }
409
410    fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
411        self.cx
412            .update(|cx| self.handler.text_for_range(range_utf16, cx))
413            .ok()
414            .flatten()
415    }
416
417    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
418        self.cx
419            .update(|cx| {
420                self.handler
421                    .replace_text_in_range(replacement_range, text, cx);
422            })
423            .ok();
424    }
425
426    fn replace_and_mark_text_in_range(
427        &mut self,
428        range_utf16: Option<Range<usize>>,
429        new_text: &str,
430        new_selected_range: Option<Range<usize>>,
431    ) {
432        self.cx
433            .update(|cx| {
434                self.handler.replace_and_mark_text_in_range(
435                    range_utf16,
436                    new_text,
437                    new_selected_range,
438                    cx,
439                )
440            })
441            .ok();
442    }
443
444    fn unmark_text(&mut self) {
445        self.cx.update(|cx| self.handler.unmark_text(cx)).ok();
446    }
447
448    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
449        self.cx
450            .update(|cx| self.handler.bounds_for_range(range_utf16, cx))
451            .ok()
452            .flatten()
453    }
454
455    pub(crate) fn dispatch_input(&mut self, input: &str, cx: &mut WindowContext) {
456        self.handler.replace_text_in_range(None, input, cx);
457    }
458}
459
460/// Zed's interface for handling text input from the platform's IME system
461/// This is currently a 1:1 exposure of the NSTextInputClient API:
462///
463/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
464pub trait InputHandler: 'static {
465    /// Get the range of the user's currently selected text, if any
466    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
467    ///
468    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
469    fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
470
471    /// Get the range of the currently marked text, if any
472    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
473    ///
474    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
475    fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
476
477    /// Get the text for the given document range in UTF-16 characters
478    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
479    ///
480    /// range_utf16 is in terms of UTF-16 characters
481    fn text_for_range(
482        &mut self,
483        range_utf16: Range<usize>,
484        cx: &mut WindowContext,
485    ) -> Option<String>;
486
487    /// Replace the text in the given document range with the given text
488    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
489    ///
490    /// replacement_range is in terms of UTF-16 characters
491    fn replace_text_in_range(
492        &mut self,
493        replacement_range: Option<Range<usize>>,
494        text: &str,
495        cx: &mut WindowContext,
496    );
497
498    /// Replace the text in the given document range with the given text,
499    /// and mark the given text as part of of an IME 'composing' state
500    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
501    ///
502    /// range_utf16 is in terms of UTF-16 characters
503    /// new_selected_range is in terms of UTF-16 characters
504    fn replace_and_mark_text_in_range(
505        &mut self,
506        range_utf16: Option<Range<usize>>,
507        new_text: &str,
508        new_selected_range: Option<Range<usize>>,
509        cx: &mut WindowContext,
510    );
511
512    /// Remove the IME 'composing' state from the document
513    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
514    fn unmark_text(&mut self, cx: &mut WindowContext);
515
516    /// Get the bounds of the given document range in screen coordinates
517    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
518    ///
519    /// This is used for positioning the IME candidate window
520    fn bounds_for_range(
521        &mut self,
522        range_utf16: Range<usize>,
523        cx: &mut WindowContext,
524    ) -> Option<Bounds<Pixels>>;
525}
526
527/// The variables that can be configured when creating a new window
528#[derive(Debug)]
529pub struct WindowOptions {
530    /// The bounds of the window in screen coordinates.
531    /// None -> inherit, Some(bounds) -> set bounds
532    pub bounds: Option<Bounds<DevicePixels>>,
533
534    /// The titlebar configuration of the window
535    pub titlebar: Option<TitlebarOptions>,
536
537    /// Whether the window should be focused when created
538    pub focus: bool,
539
540    /// Whether the window should be shown when created
541    pub show: bool,
542
543    /// Whether the window should be fullscreen when created
544    pub fullscreen: bool,
545
546    /// The kind of window to create
547    pub kind: WindowKind,
548
549    /// Whether the window should be movable by the user
550    pub is_movable: bool,
551
552    /// The display to create the window on, if this is None,
553    /// the window will be created on the main display
554    pub display_id: Option<DisplayId>,
555
556    /// The appearance of the window background.
557    pub window_background: WindowBackgroundAppearance,
558}
559
560/// The variables that can be configured when creating a new window
561#[derive(Debug)]
562pub(crate) struct WindowParams {
563    ///
564    pub bounds: Bounds<DevicePixels>,
565
566    /// The titlebar configuration of the window
567    pub titlebar: Option<TitlebarOptions>,
568
569    /// The kind of window to create
570    pub kind: WindowKind,
571
572    /// Whether the window should be movable by the user
573    pub is_movable: bool,
574
575    pub focus: bool,
576
577    pub show: bool,
578
579    pub display_id: Option<DisplayId>,
580
581    pub window_background: WindowBackgroundAppearance,
582}
583
584impl Default for WindowOptions {
585    fn default() -> Self {
586        Self {
587            bounds: None,
588            titlebar: Some(TitlebarOptions {
589                title: Default::default(),
590                appears_transparent: Default::default(),
591                traffic_light_position: Default::default(),
592            }),
593            focus: true,
594            show: true,
595            kind: WindowKind::Normal,
596            is_movable: true,
597            display_id: None,
598            fullscreen: false,
599            window_background: WindowBackgroundAppearance::default(),
600        }
601    }
602}
603
604/// The options that can be configured for a window's titlebar
605#[derive(Debug, Default)]
606pub struct TitlebarOptions {
607    /// The initial title of the window
608    pub title: Option<SharedString>,
609
610    /// Whether the titlebar should appear transparent
611    pub appears_transparent: bool,
612
613    /// The position of the macOS traffic light buttons
614    pub traffic_light_position: Option<Point<Pixels>>,
615}
616
617/// The kind of window to create
618#[derive(Copy, Clone, Debug, PartialEq, Eq)]
619pub enum WindowKind {
620    /// A normal application window
621    Normal,
622
623    /// A window that appears above all other windows, usually used for alerts or popups
624    /// use sparingly!
625    PopUp,
626}
627
628/// The appearance of the window, as defined by the operating system.
629///
630/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
631/// values.
632#[derive(Copy, Clone, Debug)]
633pub enum WindowAppearance {
634    /// A light appearance.
635    ///
636    /// On macOS, this corresponds to the `aqua` appearance.
637    Light,
638
639    /// A light appearance with vibrant colors.
640    ///
641    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
642    VibrantLight,
643
644    /// A dark appearance.
645    ///
646    /// On macOS, this corresponds to the `darkAqua` appearance.
647    Dark,
648
649    /// A dark appearance with vibrant colors.
650    ///
651    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
652    VibrantDark,
653}
654
655impl Default for WindowAppearance {
656    fn default() -> Self {
657        Self::Light
658    }
659}
660
661/// The appearance of the background of the window itself, when there is
662/// no content or the content is transparent.
663#[derive(Copy, Clone, Debug, Default, PartialEq)]
664pub enum WindowBackgroundAppearance {
665    /// Opaque.
666    ///
667    /// This lets the window manager know that content behind this
668    /// window does not need to be drawn.
669    ///
670    /// Actual color depends on the system and themes should define a fully
671    /// opaque background color instead.
672    #[default]
673    Opaque,
674    /// Plain alpha transparency.
675    Transparent,
676    /// Transparency, but the contents behind the window are blurred.
677    ///
678    /// Not always supported.
679    Blurred,
680}
681
682/// The options that can be configured for a file dialog prompt
683#[derive(Copy, Clone, Debug)]
684pub struct PathPromptOptions {
685    /// Should the prompt allow files to be selected?
686    pub files: bool,
687    /// Should the prompt allow directories to be selected?
688    pub directories: bool,
689    /// Should the prompt allow multiple files to be selected?
690    pub multiple: bool,
691}
692
693/// What kind of prompt styling to show
694#[derive(Copy, Clone, Debug)]
695pub enum PromptLevel {
696    /// A prompt that is shown when the user should be notified of something
697    Info,
698
699    /// A prompt that is shown when the user needs to be warned of a potential problem
700    Warning,
701
702    /// A prompt that is shown when a critical problem has occurred
703    Critical,
704}
705
706/// The style of the cursor (pointer)
707#[derive(Copy, Clone, Debug)]
708pub enum CursorStyle {
709    /// The default cursor
710    Arrow,
711
712    /// A text input cursor
713    /// corresponds to the CSS cursor value `text`
714    IBeam,
715
716    /// A crosshair cursor
717    /// corresponds to the CSS cursor value `crosshair`
718    Crosshair,
719
720    /// A closed hand cursor
721    /// corresponds to the CSS cursor value `grabbing`
722    ClosedHand,
723
724    /// An open hand cursor
725    /// corresponds to the CSS cursor value `grab`
726    OpenHand,
727
728    /// A pointing hand cursor
729    /// corresponds to the CSS cursor value `pointer`
730    PointingHand,
731
732    /// A resize left cursor
733    /// corresponds to the CSS cursor value `w-resize`
734    ResizeLeft,
735
736    /// A resize right cursor
737    /// corresponds to the CSS cursor value `e-resize`
738    ResizeRight,
739
740    /// A resize cursor to the left and right
741    /// corresponds to the CSS cursor value `col-resize`
742    ResizeLeftRight,
743
744    /// A resize up cursor
745    /// corresponds to the CSS cursor value `n-resize`
746    ResizeUp,
747
748    /// A resize down cursor
749    /// corresponds to the CSS cursor value `s-resize`
750    ResizeDown,
751
752    /// A resize cursor directing up and down
753    /// corresponds to the CSS cursor value `row-resize`
754    ResizeUpDown,
755
756    /// A cursor indicating that something will disappear if moved here
757    /// Does not correspond to a CSS cursor value
758    DisappearingItem,
759
760    /// A text input cursor for vertical layout
761    /// corresponds to the CSS cursor value `vertical-text`
762    IBeamCursorForVerticalLayout,
763
764    /// A cursor indicating that the operation is not allowed
765    /// corresponds to the CSS cursor value `not-allowed`
766    OperationNotAllowed,
767
768    /// A cursor indicating that the operation will result in a link
769    /// corresponds to the CSS cursor value `alias`
770    DragLink,
771
772    /// A cursor indicating that the operation will result in a copy
773    /// corresponds to the CSS cursor value `copy`
774    DragCopy,
775
776    /// A cursor indicating that the operation will result in a context menu
777    /// corresponds to the CSS cursor value `context-menu`
778    ContextualMenu,
779}
780
781impl Default for CursorStyle {
782    fn default() -> Self {
783        Self::Arrow
784    }
785}
786
787/// A clipboard item that should be copied to the clipboard
788#[derive(Clone, Debug, Eq, PartialEq)]
789pub struct ClipboardItem {
790    pub(crate) text: String,
791    pub(crate) metadata: Option<String>,
792}
793
794impl ClipboardItem {
795    /// Create a new clipboard item with the given text
796    pub fn new(text: String) -> Self {
797        Self {
798            text,
799            metadata: None,
800        }
801    }
802
803    /// Create a new clipboard item with the given text and metadata
804    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
805        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
806        self
807    }
808
809    /// Get the text of the clipboard item
810    pub fn text(&self) -> &String {
811        &self.text
812    }
813
814    /// Get the metadata of the clipboard item
815    pub fn metadata<T>(&self) -> Option<T>
816    where
817        T: for<'a> Deserialize<'a>,
818    {
819        self.metadata
820            .as_ref()
821            .and_then(|m| serde_json::from_str(m).ok())
822    }
823
824    pub(crate) fn text_hash(text: &str) -> u64 {
825        let mut hasher = SeaHasher::new();
826        text.hash(&mut hasher);
827        hasher.finish()
828    }
829}