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, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics,
 10    FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, Pixels, PlatformInput,
 11    Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString,
 12    Size, TaskLabel,
 13};
 14use anyhow::anyhow;
 15use async_task::Runnable;
 16use futures::channel::oneshot;
 17use parking::Unparker;
 18use seahash::SeaHasher;
 19use serde::{Deserialize, Serialize};
 20use std::borrow::Cow;
 21use std::hash::{Hash, Hasher};
 22use std::time::Duration;
 23use std::{
 24    any::Any,
 25    fmt::{self, Debug, Display},
 26    ops::Range,
 27    path::{Path, PathBuf},
 28    rc::Rc,
 29    str::FromStr,
 30    sync::Arc,
 31};
 32use uuid::Uuid;
 33
 34pub use app_menu::*;
 35pub use keystroke::*;
 36#[cfg(target_os = "macos")]
 37pub use mac::*;
 38#[cfg(any(test, feature = "test-support"))]
 39pub use test::*;
 40use time::UtcOffset;
 41
 42#[cfg(target_os = "macos")]
 43pub(crate) fn current_platform() -> Rc<dyn Platform> {
 44    Rc::new(MacPlatform::new())
 45}
 46
 47pub(crate) trait Platform: 'static {
 48    fn background_executor(&self) -> BackgroundExecutor;
 49    fn foreground_executor(&self) -> ForegroundExecutor;
 50    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
 51
 52    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
 53    fn quit(&self);
 54    fn restart(&self);
 55    fn activate(&self, ignoring_other_apps: bool);
 56    fn hide(&self);
 57    fn hide_other_apps(&self);
 58    fn unhide_other_apps(&self);
 59
 60    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
 61    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
 62    fn active_window(&self) -> Option<AnyWindowHandle>;
 63    fn open_window(
 64        &self,
 65        handle: AnyWindowHandle,
 66        options: WindowOptions,
 67    ) -> Box<dyn PlatformWindow>;
 68
 69    fn set_display_link_output_callback(
 70        &self,
 71        display_id: DisplayId,
 72        callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
 73    );
 74    fn start_display_link(&self, display_id: DisplayId);
 75    fn stop_display_link(&self, display_id: DisplayId);
 76    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
 77
 78    fn open_url(&self, url: &str);
 79    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
 80    fn prompt_for_paths(
 81        &self,
 82        options: PathPromptOptions,
 83    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
 84    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
 85    fn reveal_path(&self, path: &Path);
 86
 87    fn on_become_active(&self, callback: Box<dyn FnMut()>);
 88    fn on_resign_active(&self, callback: Box<dyn FnMut()>);
 89    fn on_quit(&self, callback: Box<dyn FnMut()>);
 90    fn on_reopen(&self, callback: Box<dyn FnMut()>);
 91    fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
 92
 93    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
 94    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
 95    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
 96    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
 97
 98    fn os_name(&self) -> &'static str;
 99    fn os_version(&self) -> Result<SemanticVersion>;
100    fn app_version(&self) -> Result<SemanticVersion>;
101    fn app_path(&self) -> Result<PathBuf>;
102    fn local_timezone(&self) -> UtcOffset;
103    fn double_click_interval(&self) -> Duration;
104    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
105
106    fn set_cursor_style(&self, style: CursorStyle);
107    fn should_auto_hide_scrollbars(&self) -> bool;
108
109    fn write_to_clipboard(&self, item: ClipboardItem);
110    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
111
112    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
113    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
114    fn delete_credentials(&self, url: &str) -> Result<()>;
115}
116
117/// A handle to a platform's display, e.g. a monitor or laptop screen.
118pub trait PlatformDisplay: Send + Sync + Debug {
119    /// Get the ID for this display
120    fn id(&self) -> DisplayId;
121
122    /// Returns a stable identifier for this display that can be persisted and used
123    /// across system restarts.
124    fn uuid(&self) -> Result<Uuid>;
125
126    /// Get the bounds for this display
127    fn bounds(&self) -> Bounds<GlobalPixels>;
128}
129
130/// An opaque identifier for a hardware display
131#[derive(PartialEq, Eq, Hash, Copy, Clone)]
132pub struct DisplayId(pub(crate) u32);
133
134impl Debug for DisplayId {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(f, "DisplayId({})", self.0)
137    }
138}
139
140unsafe impl Send for DisplayId {}
141
142pub(crate) trait PlatformWindow {
143    fn bounds(&self) -> WindowBounds;
144    fn content_size(&self) -> Size<Pixels>;
145    fn scale_factor(&self) -> f32;
146    fn titlebar_height(&self) -> Pixels;
147    fn appearance(&self) -> WindowAppearance;
148    fn display(&self) -> Rc<dyn PlatformDisplay>;
149    fn mouse_position(&self) -> Point<Pixels>;
150    fn modifiers(&self) -> Modifiers;
151    fn as_any_mut(&mut self) -> &mut dyn Any;
152    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
153    fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>>;
154    fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
155    fn activate(&self);
156    fn set_title(&mut self, title: &str);
157    fn set_edited(&mut self, edited: bool);
158    fn show_character_palette(&self);
159    fn minimize(&self);
160    fn zoom(&self);
161    fn toggle_full_screen(&self);
162    fn on_request_frame(&self, callback: Box<dyn FnMut()>);
163    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
164    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
165    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
166    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
167    fn on_moved(&self, callback: Box<dyn FnMut()>);
168    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
169    fn on_close(&self, callback: Box<dyn FnOnce()>);
170    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
171    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
172    fn invalidate(&self);
173    fn draw(&self, scene: &Scene);
174
175    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
176
177    #[cfg(any(test, feature = "test-support"))]
178    fn as_test(&mut self) -> Option<&mut TestWindow> {
179        None
180    }
181}
182
183/// This type is public so that our test macro can generate and use it, but it should not
184/// be considered part of our public API.
185#[doc(hidden)]
186pub trait PlatformDispatcher: Send + Sync {
187    fn is_main_thread(&self) -> bool;
188    fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
189    fn dispatch_on_main_thread(&self, runnable: Runnable);
190    fn dispatch_after(&self, duration: Duration, runnable: Runnable);
191    fn tick(&self, background_only: bool) -> bool;
192    fn park(&self);
193    fn unparker(&self) -> Unparker;
194
195    #[cfg(any(test, feature = "test-support"))]
196    fn as_test(&self) -> Option<&TestDispatcher> {
197        None
198    }
199}
200
201pub(crate) trait PlatformTextSystem: Send + Sync {
202    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
203    fn all_font_names(&self) -> Vec<String>;
204    fn all_font_families(&self) -> Vec<String>;
205    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
206    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
207    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
208    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
209    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
210    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
211    fn rasterize_glyph(
212        &self,
213        params: &RenderGlyphParams,
214        raster_bounds: Bounds<DevicePixels>,
215    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
216    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
217    fn wrap_line(
218        &self,
219        text: &str,
220        font_id: FontId,
221        font_size: Pixels,
222        width: Pixels,
223    ) -> Vec<usize>;
224}
225
226/// Basic metadata about the current application and operating system.
227#[derive(Clone, Debug)]
228pub struct AppMetadata {
229    /// The name of the current operating system
230    pub os_name: &'static str,
231
232    /// The operating system's version
233    pub os_version: Option<SemanticVersion>,
234
235    /// The current version of the application
236    pub app_version: Option<SemanticVersion>,
237}
238
239#[derive(PartialEq, Eq, Hash, Clone)]
240pub(crate) enum AtlasKey {
241    Glyph(RenderGlyphParams),
242    Svg(RenderSvgParams),
243    Image(RenderImageParams),
244}
245
246impl AtlasKey {
247    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
248        match self {
249            AtlasKey::Glyph(params) => {
250                if params.is_emoji {
251                    AtlasTextureKind::Polychrome
252                } else {
253                    AtlasTextureKind::Monochrome
254                }
255            }
256            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
257            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
258        }
259    }
260}
261
262impl From<RenderGlyphParams> for AtlasKey {
263    fn from(params: RenderGlyphParams) -> Self {
264        Self::Glyph(params)
265    }
266}
267
268impl From<RenderSvgParams> for AtlasKey {
269    fn from(params: RenderSvgParams) -> Self {
270        Self::Svg(params)
271    }
272}
273
274impl From<RenderImageParams> for AtlasKey {
275    fn from(params: RenderImageParams) -> Self {
276        Self::Image(params)
277    }
278}
279
280pub(crate) trait PlatformAtlas: Send + Sync {
281    fn get_or_insert_with<'a>(
282        &self,
283        key: &AtlasKey,
284        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
285    ) -> Result<AtlasTile>;
286}
287
288#[derive(Clone, Debug, PartialEq, Eq)]
289#[repr(C)]
290pub(crate) struct AtlasTile {
291    pub(crate) texture_id: AtlasTextureId,
292    pub(crate) tile_id: TileId,
293    pub(crate) bounds: Bounds<DevicePixels>,
294}
295
296#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
297#[repr(C)]
298pub(crate) struct AtlasTextureId {
299    // We use u32 instead of usize for Metal Shader Language compatibility
300    pub(crate) index: u32,
301    pub(crate) kind: AtlasTextureKind,
302}
303
304#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
305#[repr(C)]
306pub(crate) enum AtlasTextureKind {
307    Monochrome = 0,
308    Polychrome = 1,
309    Path = 2,
310}
311
312#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
313#[repr(C)]
314pub(crate) struct TileId(pub(crate) u32);
315
316impl From<etagere::AllocId> for TileId {
317    fn from(id: etagere::AllocId) -> Self {
318        Self(id.serialize())
319    }
320}
321
322impl From<TileId> for etagere::AllocId {
323    fn from(id: TileId) -> Self {
324        Self::deserialize(id.0)
325    }
326}
327
328pub trait PlatformInputHandler: 'static {
329    fn selected_text_range(&mut self) -> Option<Range<usize>>;
330    fn marked_text_range(&mut self) -> Option<Range<usize>>;
331    fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
332    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
333    fn replace_and_mark_text_in_range(
334        &mut self,
335        range_utf16: Option<Range<usize>>,
336        new_text: &str,
337        new_selected_range: Option<Range<usize>>,
338    );
339    fn unmark_text(&mut self);
340    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
341}
342
343#[derive(Debug)]
344pub struct WindowOptions {
345    pub bounds: WindowBounds,
346    pub titlebar: Option<TitlebarOptions>,
347    pub center: bool,
348    pub focus: bool,
349    pub show: bool,
350    pub kind: WindowKind,
351    pub is_movable: bool,
352    pub display_id: Option<DisplayId>,
353}
354
355impl Default for WindowOptions {
356    fn default() -> Self {
357        Self {
358            bounds: WindowBounds::default(),
359            titlebar: Some(TitlebarOptions {
360                title: Default::default(),
361                appears_transparent: Default::default(),
362                traffic_light_position: Default::default(),
363            }),
364            center: false,
365            focus: true,
366            show: true,
367            kind: WindowKind::Normal,
368            is_movable: true,
369            display_id: None,
370        }
371    }
372}
373
374#[derive(Debug, Default)]
375pub struct TitlebarOptions {
376    pub title: Option<SharedString>,
377    pub appears_transparent: bool,
378    pub traffic_light_position: Option<Point<Pixels>>,
379}
380
381#[derive(Copy, Clone, Debug)]
382pub enum Appearance {
383    Light,
384    VibrantLight,
385    Dark,
386    VibrantDark,
387}
388
389impl Default for Appearance {
390    fn default() -> Self {
391        Self::Light
392    }
393}
394
395#[derive(Copy, Clone, Debug, PartialEq, Eq)]
396pub enum WindowKind {
397    Normal,
398    PopUp,
399}
400
401#[derive(Copy, Clone, Debug, PartialEq, Default)]
402pub enum WindowBounds {
403    Fullscreen,
404    #[default]
405    Maximized,
406    Fixed(Bounds<GlobalPixels>),
407}
408
409#[derive(Copy, Clone, Debug)]
410pub enum WindowAppearance {
411    Light,
412    VibrantLight,
413    Dark,
414    VibrantDark,
415}
416
417impl Default for WindowAppearance {
418    fn default() -> Self {
419        Self::Light
420    }
421}
422
423#[derive(Copy, Clone, Debug)]
424pub struct PathPromptOptions {
425    pub files: bool,
426    pub directories: bool,
427    pub multiple: bool,
428}
429
430#[derive(Copy, Clone, Debug)]
431pub enum PromptLevel {
432    Info,
433    Warning,
434    Critical,
435}
436
437/// The style of the cursor (pointer)
438#[derive(Copy, Clone, Debug)]
439pub enum CursorStyle {
440    Arrow,
441    IBeam,
442    Crosshair,
443    ClosedHand,
444    OpenHand,
445    PointingHand,
446    ResizeLeft,
447    ResizeRight,
448    ResizeLeftRight,
449    ResizeUp,
450    ResizeDown,
451    ResizeUpDown,
452    DisappearingItem,
453    IBeamCursorForVerticalLayout,
454    OperationNotAllowed,
455    DragLink,
456    DragCopy,
457    ContextualMenu,
458}
459
460impl Default for CursorStyle {
461    fn default() -> Self {
462        Self::Arrow
463    }
464}
465
466#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
467pub struct SemanticVersion {
468    major: usize,
469    minor: usize,
470    patch: usize,
471}
472
473impl FromStr for SemanticVersion {
474    type Err = anyhow::Error;
475
476    fn from_str(s: &str) -> Result<Self> {
477        let mut components = s.trim().split('.');
478        let major = components
479            .next()
480            .ok_or_else(|| anyhow!("missing major version number"))?
481            .parse()?;
482        let minor = components
483            .next()
484            .ok_or_else(|| anyhow!("missing minor version number"))?
485            .parse()?;
486        let patch = components
487            .next()
488            .ok_or_else(|| anyhow!("missing patch version number"))?
489            .parse()?;
490        Ok(Self {
491            major,
492            minor,
493            patch,
494        })
495    }
496}
497
498impl Display for SemanticVersion {
499    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
501    }
502}
503
504#[derive(Clone, Debug, Eq, PartialEq)]
505pub struct ClipboardItem {
506    pub(crate) text: String,
507    pub(crate) metadata: Option<String>,
508}
509
510impl ClipboardItem {
511    pub fn new(text: String) -> Self {
512        Self {
513            text,
514            metadata: None,
515        }
516    }
517
518    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
519        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
520        self
521    }
522
523    pub fn text(&self) -> &String {
524        &self.text
525    }
526
527    pub fn metadata<T>(&self) -> Option<T>
528    where
529        T: for<'a> Deserialize<'a>,
530    {
531        self.metadata
532            .as_ref()
533            .and_then(|m| serde_json::from_str(m).ok())
534    }
535
536    pub(crate) fn text_hash(text: &str) -> u64 {
537        let mut hasher = SeaHasher::new();
538        text.hash(&mut hasher);
539        hasher.finish()
540    }
541}