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, InputEvent, Keymap, LineLayout, Pixels,
 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(InputEvent) -> 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
117pub trait PlatformDisplay: Send + Sync + Debug {
118    fn id(&self) -> DisplayId;
119    /// Returns a stable identifier for this display that can be persisted and used
120    /// across system restarts.
121    fn uuid(&self) -> Result<Uuid>;
122    fn as_any(&self) -> &dyn Any;
123    fn bounds(&self) -> Bounds<GlobalPixels>;
124}
125
126#[derive(PartialEq, Eq, Hash, Copy, Clone)]
127pub struct DisplayId(pub(crate) u32);
128
129impl Debug for DisplayId {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        write!(f, "DisplayId({})", self.0)
132    }
133}
134
135unsafe impl Send for DisplayId {}
136
137pub trait PlatformWindow {
138    fn bounds(&self) -> WindowBounds;
139    fn content_size(&self) -> Size<Pixels>;
140    fn scale_factor(&self) -> f32;
141    fn titlebar_height(&self) -> Pixels;
142    fn appearance(&self) -> WindowAppearance;
143    fn display(&self) -> Rc<dyn PlatformDisplay>;
144    fn mouse_position(&self) -> Point<Pixels>;
145    fn modifiers(&self) -> Modifiers;
146    fn as_any_mut(&mut self) -> &mut dyn Any;
147    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
148    fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>>;
149    fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
150    fn activate(&self);
151    fn set_title(&mut self, title: &str);
152    fn set_edited(&mut self, edited: bool);
153    fn show_character_palette(&self);
154    fn minimize(&self);
155    fn zoom(&self);
156    fn toggle_full_screen(&self);
157    fn on_request_frame(&self, callback: Box<dyn FnMut()>);
158    fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
159    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
160    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
161    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
162    fn on_moved(&self, callback: Box<dyn FnMut()>);
163    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
164    fn on_close(&self, callback: Box<dyn FnOnce()>);
165    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
166    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
167    fn invalidate(&self);
168    fn draw(&self, scene: &Scene);
169
170    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
171
172    #[cfg(any(test, feature = "test-support"))]
173    fn as_test(&mut self) -> Option<&mut TestWindow> {
174        None
175    }
176}
177
178pub trait PlatformDispatcher: Send + Sync {
179    fn is_main_thread(&self) -> bool;
180    fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
181    fn dispatch_on_main_thread(&self, runnable: Runnable);
182    fn dispatch_after(&self, duration: Duration, runnable: Runnable);
183    fn tick(&self, background_only: bool) -> bool;
184    fn park(&self);
185    fn unparker(&self) -> Unparker;
186
187    #[cfg(any(test, feature = "test-support"))]
188    fn as_test(&self) -> Option<&TestDispatcher> {
189        None
190    }
191}
192
193pub trait PlatformTextSystem: Send + Sync {
194    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
195    fn all_font_names(&self) -> Vec<String>;
196    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
197    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
198    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
199    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
200    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
201    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
202    fn rasterize_glyph(
203        &self,
204        params: &RenderGlyphParams,
205        raster_bounds: Bounds<DevicePixels>,
206    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
207    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
208    fn wrap_line(
209        &self,
210        text: &str,
211        font_id: FontId,
212        font_size: Pixels,
213        width: Pixels,
214    ) -> Vec<usize>;
215}
216
217#[derive(Clone, Debug)]
218pub struct AppMetadata {
219    pub os_name: &'static str,
220    pub os_version: Option<SemanticVersion>,
221    pub app_version: Option<SemanticVersion>,
222}
223
224#[derive(PartialEq, Eq, Hash, Clone)]
225pub enum AtlasKey {
226    Glyph(RenderGlyphParams),
227    Svg(RenderSvgParams),
228    Image(RenderImageParams),
229}
230
231impl AtlasKey {
232    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
233        match self {
234            AtlasKey::Glyph(params) => {
235                if params.is_emoji {
236                    AtlasTextureKind::Polychrome
237                } else {
238                    AtlasTextureKind::Monochrome
239                }
240            }
241            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
242            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
243        }
244    }
245}
246
247impl From<RenderGlyphParams> for AtlasKey {
248    fn from(params: RenderGlyphParams) -> Self {
249        Self::Glyph(params)
250    }
251}
252
253impl From<RenderSvgParams> for AtlasKey {
254    fn from(params: RenderSvgParams) -> Self {
255        Self::Svg(params)
256    }
257}
258
259impl From<RenderImageParams> for AtlasKey {
260    fn from(params: RenderImageParams) -> Self {
261        Self::Image(params)
262    }
263}
264
265pub trait PlatformAtlas: Send + Sync {
266    fn get_or_insert_with<'a>(
267        &self,
268        key: &AtlasKey,
269        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
270    ) -> Result<AtlasTile>;
271
272    fn clear(&self);
273}
274
275#[derive(Clone, Debug, PartialEq, Eq)]
276#[repr(C)]
277pub struct AtlasTile {
278    pub(crate) texture_id: AtlasTextureId,
279    pub(crate) tile_id: TileId,
280    pub(crate) bounds: Bounds<DevicePixels>,
281}
282
283#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
284#[repr(C)]
285pub(crate) struct AtlasTextureId {
286    // We use u32 instead of usize for Metal Shader Language compatibility
287    pub(crate) index: u32,
288    pub(crate) kind: AtlasTextureKind,
289}
290
291#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
292#[repr(C)]
293pub(crate) enum AtlasTextureKind {
294    Monochrome = 0,
295    Polychrome = 1,
296    Path = 2,
297}
298
299#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
300#[repr(C)]
301pub(crate) struct TileId(pub(crate) u32);
302
303impl From<etagere::AllocId> for TileId {
304    fn from(id: etagere::AllocId) -> Self {
305        Self(id.serialize())
306    }
307}
308
309impl From<TileId> for etagere::AllocId {
310    fn from(id: TileId) -> Self {
311        Self::deserialize(id.0)
312    }
313}
314
315pub trait PlatformInputHandler: 'static {
316    fn selected_text_range(&mut self) -> Option<Range<usize>>;
317    fn marked_text_range(&mut self) -> Option<Range<usize>>;
318    fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
319    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
320    fn replace_and_mark_text_in_range(
321        &mut self,
322        range_utf16: Option<Range<usize>>,
323        new_text: &str,
324        new_selected_range: Option<Range<usize>>,
325    );
326    fn unmark_text(&mut self);
327    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
328}
329
330#[derive(Debug)]
331pub struct WindowOptions {
332    pub bounds: WindowBounds,
333    pub titlebar: Option<TitlebarOptions>,
334    pub center: bool,
335    pub focus: bool,
336    pub show: bool,
337    pub kind: WindowKind,
338    pub is_movable: bool,
339    pub display_id: Option<DisplayId>,
340}
341
342impl Default for WindowOptions {
343    fn default() -> Self {
344        Self {
345            bounds: WindowBounds::default(),
346            titlebar: Some(TitlebarOptions {
347                title: Default::default(),
348                appears_transparent: Default::default(),
349                traffic_light_position: Default::default(),
350            }),
351            center: false,
352            focus: true,
353            show: true,
354            kind: WindowKind::Normal,
355            is_movable: true,
356            display_id: None,
357        }
358    }
359}
360
361#[derive(Debug, Default)]
362pub struct TitlebarOptions {
363    pub title: Option<SharedString>,
364    pub appears_transparent: bool,
365    pub traffic_light_position: Option<Point<Pixels>>,
366}
367
368#[derive(Copy, Clone, Debug)]
369pub enum Appearance {
370    Light,
371    VibrantLight,
372    Dark,
373    VibrantDark,
374}
375
376impl Default for Appearance {
377    fn default() -> Self {
378        Self::Light
379    }
380}
381
382#[derive(Copy, Clone, Debug, PartialEq, Eq)]
383pub enum WindowKind {
384    Normal,
385    PopUp,
386}
387
388#[derive(Copy, Clone, Debug, PartialEq, Default)]
389pub enum WindowBounds {
390    Fullscreen,
391    #[default]
392    Maximized,
393    Fixed(Bounds<GlobalPixels>),
394}
395
396#[derive(Copy, Clone, Debug)]
397pub enum WindowAppearance {
398    Light,
399    VibrantLight,
400    Dark,
401    VibrantDark,
402}
403
404impl Default for WindowAppearance {
405    fn default() -> Self {
406        Self::Light
407    }
408}
409
410#[derive(Copy, Clone, Debug)]
411pub struct PathPromptOptions {
412    pub files: bool,
413    pub directories: bool,
414    pub multiple: bool,
415}
416
417#[derive(Copy, Clone, Debug)]
418pub enum PromptLevel {
419    Info,
420    Warning,
421    Critical,
422}
423
424/// The style of the cursor (pointer)
425#[derive(Copy, Clone, Debug)]
426pub enum CursorStyle {
427    Arrow,
428    IBeam,
429    Crosshair,
430    ClosedHand,
431    OpenHand,
432    PointingHand,
433    ResizeLeft,
434    ResizeRight,
435    ResizeLeftRight,
436    ResizeUp,
437    ResizeDown,
438    ResizeUpDown,
439    DisappearingItem,
440    IBeamCursorForVerticalLayout,
441    OperationNotAllowed,
442    DragLink,
443    DragCopy,
444    ContextualMenu,
445}
446
447impl Default for CursorStyle {
448    fn default() -> Self {
449        Self::Arrow
450    }
451}
452
453#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
454pub struct SemanticVersion {
455    major: usize,
456    minor: usize,
457    patch: usize,
458}
459
460impl FromStr for SemanticVersion {
461    type Err = anyhow::Error;
462
463    fn from_str(s: &str) -> Result<Self> {
464        let mut components = s.trim().split('.');
465        let major = components
466            .next()
467            .ok_or_else(|| anyhow!("missing major version number"))?
468            .parse()?;
469        let minor = components
470            .next()
471            .ok_or_else(|| anyhow!("missing minor version number"))?
472            .parse()?;
473        let patch = components
474            .next()
475            .ok_or_else(|| anyhow!("missing patch version number"))?
476            .parse()?;
477        Ok(Self {
478            major,
479            minor,
480            patch,
481        })
482    }
483}
484
485impl Display for SemanticVersion {
486    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
488    }
489}
490
491#[derive(Clone, Debug, Eq, PartialEq)]
492pub struct ClipboardItem {
493    pub(crate) text: String,
494    pub(crate) metadata: Option<String>,
495}
496
497impl ClipboardItem {
498    pub fn new(text: String) -> Self {
499        Self {
500            text,
501            metadata: None,
502        }
503    }
504
505    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
506        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
507        self
508    }
509
510    pub fn text(&self) -> &String {
511        &self.text
512    }
513
514    pub fn metadata<T>(&self) -> Option<T>
515    where
516        T: for<'a> Deserialize<'a>,
517    {
518        self.metadata
519            .as_ref()
520            .and_then(|m| serde_json::from_str(m).ok())
521    }
522
523    pub(crate) fn text_hash(text: &str) -> u64 {
524        let mut hasher = SeaHasher::new();
525        text.hash(&mut hasher);
526        hasher.finish()
527    }
528}