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, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
 10    FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
 11    Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
 12    Scene, SharedString, Size, TaskLabel, WindowContext,
 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: PlatformInputHandler);
153    fn take_input_handler(&mut self) -> Option<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(crate) struct PlatformInputHandler {
329    cx: AsyncWindowContext,
330    handler: Box<dyn InputHandler>,
331}
332
333impl PlatformInputHandler {
334    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
335        Self { cx, handler }
336    }
337
338    fn selected_text_range(&mut self) -> Option<Range<usize>> {
339        self.cx
340            .update(|cx| self.handler.selected_text_range(cx))
341            .ok()
342            .flatten()
343    }
344
345    fn marked_text_range(&mut self) -> Option<Range<usize>> {
346        self.cx
347            .update(|cx| self.handler.marked_text_range(cx))
348            .ok()
349            .flatten()
350    }
351
352    fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
353        self.cx
354            .update(|cx| self.handler.text_for_range(range_utf16, cx))
355            .ok()
356            .flatten()
357    }
358
359    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
360        self.cx
361            .update(|cx| {
362                self.handler
363                    .replace_text_in_range(replacement_range, text, cx)
364            })
365            .ok();
366    }
367
368    fn replace_and_mark_text_in_range(
369        &mut self,
370        range_utf16: Option<Range<usize>>,
371        new_text: &str,
372        new_selected_range: Option<Range<usize>>,
373    ) {
374        self.cx
375            .update(|cx| {
376                self.handler.replace_and_mark_text_in_range(
377                    range_utf16,
378                    new_text,
379                    new_selected_range,
380                    cx,
381                )
382            })
383            .ok();
384    }
385
386    fn unmark_text(&mut self) {
387        self.cx.update(|cx| self.handler.unmark_text(cx)).ok();
388    }
389
390    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
391        self.cx
392            .update(|cx| self.handler.bounds_for_range(range_utf16, cx))
393            .ok()
394            .flatten()
395    }
396}
397
398pub trait InputHandler: 'static {
399    fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
400    fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
401    fn text_for_range(
402        &mut self,
403        range_utf16: Range<usize>,
404        cx: &mut WindowContext,
405    ) -> Option<String>;
406    fn replace_text_in_range(
407        &mut self,
408        replacement_range: Option<Range<usize>>,
409        text: &str,
410        cx: &mut WindowContext,
411    );
412    fn replace_and_mark_text_in_range(
413        &mut self,
414        range_utf16: Option<Range<usize>>,
415        new_text: &str,
416        new_selected_range: Option<Range<usize>>,
417        cx: &mut WindowContext,
418    );
419    fn unmark_text(&mut self, cx: &mut WindowContext);
420    fn bounds_for_range(
421        &mut self,
422        range_utf16: Range<usize>,
423        cx: &mut WindowContext,
424    ) -> Option<Bounds<Pixels>>;
425}
426
427#[derive(Debug)]
428pub struct WindowOptions {
429    pub bounds: WindowBounds,
430    pub titlebar: Option<TitlebarOptions>,
431    pub center: bool,
432    pub focus: bool,
433    pub show: bool,
434    pub kind: WindowKind,
435    pub is_movable: bool,
436    pub display_id: Option<DisplayId>,
437}
438
439impl Default for WindowOptions {
440    fn default() -> Self {
441        Self {
442            bounds: WindowBounds::default(),
443            titlebar: Some(TitlebarOptions {
444                title: Default::default(),
445                appears_transparent: Default::default(),
446                traffic_light_position: Default::default(),
447            }),
448            center: false,
449            focus: true,
450            show: true,
451            kind: WindowKind::Normal,
452            is_movable: true,
453            display_id: None,
454        }
455    }
456}
457
458#[derive(Debug, Default)]
459pub struct TitlebarOptions {
460    pub title: Option<SharedString>,
461    pub appears_transparent: bool,
462    pub traffic_light_position: Option<Point<Pixels>>,
463}
464
465#[derive(Copy, Clone, Debug)]
466pub enum Appearance {
467    Light,
468    VibrantLight,
469    Dark,
470    VibrantDark,
471}
472
473impl Default for Appearance {
474    fn default() -> Self {
475        Self::Light
476    }
477}
478
479#[derive(Copy, Clone, Debug, PartialEq, Eq)]
480pub enum WindowKind {
481    Normal,
482    PopUp,
483}
484
485#[derive(Copy, Clone, Debug, PartialEq, Default)]
486pub enum WindowBounds {
487    Fullscreen,
488    #[default]
489    Maximized,
490    Fixed(Bounds<GlobalPixels>),
491}
492
493#[derive(Copy, Clone, Debug)]
494pub enum WindowAppearance {
495    Light,
496    VibrantLight,
497    Dark,
498    VibrantDark,
499}
500
501impl Default for WindowAppearance {
502    fn default() -> Self {
503        Self::Light
504    }
505}
506
507#[derive(Copy, Clone, Debug)]
508pub struct PathPromptOptions {
509    pub files: bool,
510    pub directories: bool,
511    pub multiple: bool,
512}
513
514#[derive(Copy, Clone, Debug)]
515pub enum PromptLevel {
516    Info,
517    Warning,
518    Critical,
519}
520
521/// The style of the cursor (pointer)
522#[derive(Copy, Clone, Debug)]
523pub enum CursorStyle {
524    Arrow,
525    IBeam,
526    Crosshair,
527    ClosedHand,
528    OpenHand,
529    PointingHand,
530    ResizeLeft,
531    ResizeRight,
532    ResizeLeftRight,
533    ResizeUp,
534    ResizeDown,
535    ResizeUpDown,
536    DisappearingItem,
537    IBeamCursorForVerticalLayout,
538    OperationNotAllowed,
539    DragLink,
540    DragCopy,
541    ContextualMenu,
542}
543
544impl Default for CursorStyle {
545    fn default() -> Self {
546        Self::Arrow
547    }
548}
549
550#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
551pub struct SemanticVersion {
552    major: usize,
553    minor: usize,
554    patch: usize,
555}
556
557impl FromStr for SemanticVersion {
558    type Err = anyhow::Error;
559
560    fn from_str(s: &str) -> Result<Self> {
561        let mut components = s.trim().split('.');
562        let major = components
563            .next()
564            .ok_or_else(|| anyhow!("missing major version number"))?
565            .parse()?;
566        let minor = components
567            .next()
568            .ok_or_else(|| anyhow!("missing minor version number"))?
569            .parse()?;
570        let patch = components
571            .next()
572            .ok_or_else(|| anyhow!("missing patch version number"))?
573            .parse()?;
574        Ok(Self {
575            major,
576            minor,
577            patch,
578        })
579    }
580}
581
582impl Display for SemanticVersion {
583    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
585    }
586}
587
588#[derive(Clone, Debug, Eq, PartialEq)]
589pub struct ClipboardItem {
590    pub(crate) text: String,
591    pub(crate) metadata: Option<String>,
592}
593
594impl ClipboardItem {
595    pub fn new(text: String) -> Self {
596        Self {
597            text,
598            metadata: None,
599        }
600    }
601
602    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
603        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
604        self
605    }
606
607    pub fn text(&self) -> &String {
608        &self.text
609    }
610
611    pub fn metadata<T>(&self) -> Option<T>
612    where
613        T: for<'a> Deserialize<'a>,
614    {
615        self.metadata
616            .as_ref()
617            .and_then(|m| serde_json::from_str(m).ok())
618    }
619
620    pub(crate) fn text_hash(text: &str) -> u64 {
621        let mut hasher = SeaHasher::new();
622        text.hash(&mut hasher);
623        hasher.finish()
624    }
625}