platform.rs

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