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