platform.rs

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