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 font_id(&self, descriptor: &Font) -> Result<FontId>;
205    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
206    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
207    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
208    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
209    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
210    fn rasterize_glyph(
211        &self,
212        params: &RenderGlyphParams,
213        raster_bounds: Bounds<DevicePixels>,
214    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
215    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
216    fn wrap_line(
217        &self,
218        text: &str,
219        font_id: FontId,
220        font_size: Pixels,
221        width: Pixels,
222    ) -> Vec<usize>;
223}
224
225/// Basic metadata about the current application and operating system.
226#[derive(Clone, Debug)]
227pub struct AppMetadata {
228    /// The name of the current operating system
229    pub os_name: &'static str,
230
231    /// The operating system's version
232    pub os_version: Option<SemanticVersion>,
233
234    /// The current version of the application
235    pub app_version: Option<SemanticVersion>,
236}
237
238#[derive(PartialEq, Eq, Hash, Clone)]
239pub(crate) enum AtlasKey {
240    Glyph(RenderGlyphParams),
241    Svg(RenderSvgParams),
242    Image(RenderImageParams),
243}
244
245impl AtlasKey {
246    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
247        match self {
248            AtlasKey::Glyph(params) => {
249                if params.is_emoji {
250                    AtlasTextureKind::Polychrome
251                } else {
252                    AtlasTextureKind::Monochrome
253                }
254            }
255            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
256            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
257        }
258    }
259}
260
261impl From<RenderGlyphParams> for AtlasKey {
262    fn from(params: RenderGlyphParams) -> Self {
263        Self::Glyph(params)
264    }
265}
266
267impl From<RenderSvgParams> for AtlasKey {
268    fn from(params: RenderSvgParams) -> Self {
269        Self::Svg(params)
270    }
271}
272
273impl From<RenderImageParams> for AtlasKey {
274    fn from(params: RenderImageParams) -> Self {
275        Self::Image(params)
276    }
277}
278
279pub(crate) trait PlatformAtlas: Send + Sync {
280    fn get_or_insert_with<'a>(
281        &self,
282        key: &AtlasKey,
283        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
284    ) -> Result<AtlasTile>;
285
286    fn clear(&self);
287}
288
289#[derive(Clone, Debug, PartialEq, Eq)]
290#[repr(C)]
291pub(crate) struct AtlasTile {
292    pub(crate) texture_id: AtlasTextureId,
293    pub(crate) tile_id: TileId,
294    pub(crate) bounds: Bounds<DevicePixels>,
295}
296
297#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
298#[repr(C)]
299pub(crate) struct AtlasTextureId {
300    // We use u32 instead of usize for Metal Shader Language compatibility
301    pub(crate) index: u32,
302    pub(crate) kind: AtlasTextureKind,
303}
304
305#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
306#[repr(C)]
307pub(crate) enum AtlasTextureKind {
308    Monochrome = 0,
309    Polychrome = 1,
310    Path = 2,
311}
312
313#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
314#[repr(C)]
315pub(crate) struct TileId(pub(crate) u32);
316
317impl From<etagere::AllocId> for TileId {
318    fn from(id: etagere::AllocId) -> Self {
319        Self(id.serialize())
320    }
321}
322
323impl From<TileId> for etagere::AllocId {
324    fn from(id: TileId) -> Self {
325        Self::deserialize(id.0)
326    }
327}
328
329pub trait PlatformInputHandler: 'static {
330    fn selected_text_range(&mut self) -> Option<Range<usize>>;
331    fn marked_text_range(&mut self) -> Option<Range<usize>>;
332    fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
333    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
334    fn replace_and_mark_text_in_range(
335        &mut self,
336        range_utf16: Option<Range<usize>>,
337        new_text: &str,
338        new_selected_range: Option<Range<usize>>,
339    );
340    fn unmark_text(&mut self);
341    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
342}
343
344#[derive(Debug)]
345pub struct WindowOptions {
346    pub bounds: WindowBounds,
347    pub titlebar: Option<TitlebarOptions>,
348    pub center: bool,
349    pub focus: bool,
350    pub show: bool,
351    pub kind: WindowKind,
352    pub is_movable: bool,
353    pub display_id: Option<DisplayId>,
354}
355
356impl Default for WindowOptions {
357    fn default() -> Self {
358        Self {
359            bounds: WindowBounds::default(),
360            titlebar: Some(TitlebarOptions {
361                title: Default::default(),
362                appears_transparent: Default::default(),
363                traffic_light_position: Default::default(),
364            }),
365            center: false,
366            focus: true,
367            show: true,
368            kind: WindowKind::Normal,
369            is_movable: true,
370            display_id: None,
371        }
372    }
373}
374
375#[derive(Debug, Default)]
376pub struct TitlebarOptions {
377    pub title: Option<SharedString>,
378    pub appears_transparent: bool,
379    pub traffic_light_position: Option<Point<Pixels>>,
380}
381
382#[derive(Copy, Clone, Debug)]
383pub enum Appearance {
384    Light,
385    VibrantLight,
386    Dark,
387    VibrantDark,
388}
389
390impl Default for Appearance {
391    fn default() -> Self {
392        Self::Light
393    }
394}
395
396#[derive(Copy, Clone, Debug, PartialEq, Eq)]
397pub enum WindowKind {
398    Normal,
399    PopUp,
400}
401
402#[derive(Copy, Clone, Debug, PartialEq, Default)]
403pub enum WindowBounds {
404    Fullscreen,
405    #[default]
406    Maximized,
407    Fixed(Bounds<GlobalPixels>),
408}
409
410#[derive(Copy, Clone, Debug)]
411pub enum WindowAppearance {
412    Light,
413    VibrantLight,
414    Dark,
415    VibrantDark,
416}
417
418impl Default for WindowAppearance {
419    fn default() -> Self {
420        Self::Light
421    }
422}
423
424#[derive(Copy, Clone, Debug)]
425pub struct PathPromptOptions {
426    pub files: bool,
427    pub directories: bool,
428    pub multiple: bool,
429}
430
431#[derive(Copy, Clone, Debug)]
432pub enum PromptLevel {
433    Info,
434    Warning,
435    Critical,
436}
437
438/// The style of the cursor (pointer)
439#[derive(Copy, Clone, Debug)]
440pub enum CursorStyle {
441    Arrow,
442    IBeam,
443    Crosshair,
444    ClosedHand,
445    OpenHand,
446    PointingHand,
447    ResizeLeft,
448    ResizeRight,
449    ResizeLeftRight,
450    ResizeUp,
451    ResizeDown,
452    ResizeUpDown,
453    DisappearingItem,
454    IBeamCursorForVerticalLayout,
455    OperationNotAllowed,
456    DragLink,
457    DragCopy,
458    ContextualMenu,
459}
460
461impl Default for CursorStyle {
462    fn default() -> Self {
463        Self::Arrow
464    }
465}
466
467#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
468pub struct SemanticVersion {
469    major: usize,
470    minor: usize,
471    patch: usize,
472}
473
474impl FromStr for SemanticVersion {
475    type Err = anyhow::Error;
476
477    fn from_str(s: &str) -> Result<Self> {
478        let mut components = s.trim().split('.');
479        let major = components
480            .next()
481            .ok_or_else(|| anyhow!("missing major version number"))?
482            .parse()?;
483        let minor = components
484            .next()
485            .ok_or_else(|| anyhow!("missing minor version number"))?
486            .parse()?;
487        let patch = components
488            .next()
489            .ok_or_else(|| anyhow!("missing patch version number"))?
490            .parse()?;
491        Ok(Self {
492            major,
493            minor,
494            patch,
495        })
496    }
497}
498
499impl Display for SemanticVersion {
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
502    }
503}
504
505#[derive(Clone, Debug, Eq, PartialEq)]
506pub struct ClipboardItem {
507    pub(crate) text: String,
508    pub(crate) metadata: Option<String>,
509}
510
511impl ClipboardItem {
512    pub fn new(text: String) -> Self {
513        Self {
514            text,
515            metadata: None,
516        }
517    }
518
519    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
520        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
521        self
522    }
523
524    pub fn text(&self) -> &String {
525        &self.text
526    }
527
528    pub fn metadata<T>(&self) -> Option<T>
529    where
530        T: for<'a> Deserialize<'a>,
531    {
532        self.metadata
533            .as_ref()
534            .and_then(|m| serde_json::from_str(m).ok())
535    }
536
537    pub(crate) fn text_hash(text: &str) -> u64 {
538        let mut hasher = SeaHasher::new();
539        text.hash(&mut hasher);
540        hasher.finish()
541    }
542}