platform.rs

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