platform.rs

  1mod events;
  2mod keystroke;
  3#[cfg(target_os = "macos")]
  4mod mac;
  5#[cfg(any(test, feature = "test"))]
  6mod test;
  7
  8use crate::{
  9    AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId,
 10    LineLayout, Pixels, Point, Result, RunStyle, SharedString, Size,
 11};
 12use anyhow::anyhow;
 13use async_task::Runnable;
 14use futures::channel::oneshot;
 15use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
 16use seahash::SeaHasher;
 17use serde::{Deserialize, Serialize};
 18use std::ffi::c_void;
 19use std::hash::{Hash, Hasher};
 20use std::{
 21    any::Any,
 22    fmt::{self, Debug, Display},
 23    ops::Range,
 24    path::{Path, PathBuf},
 25    rc::Rc,
 26    str::FromStr,
 27    sync::Arc,
 28};
 29pub use time::UtcOffset;
 30use uuid::Uuid;
 31
 32pub use events::*;
 33pub use keystroke::*;
 34#[cfg(target_os = "macos")]
 35pub use mac::*;
 36#[cfg(any(test, feature = "test"))]
 37pub use test::*;
 38
 39#[cfg(target_os = "macos")]
 40pub(crate) fn current_platform() -> Arc<dyn Platform> {
 41    Arc::new(MacPlatform::new())
 42}
 43
 44pub trait Platform: 'static {
 45    fn dispatcher(&self) -> Arc<dyn PlatformDispatcher>;
 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 screens(&self) -> Vec<Rc<dyn PlatformScreen>>;
 57    fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>>;
 58    fn main_window(&self) -> Option<AnyWindowHandle>;
 59    fn open_window(
 60        &self,
 61        handle: AnyWindowHandle,
 62        options: WindowOptions,
 63    ) -> Box<dyn PlatformWindow>;
 64    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
 65
 66    fn open_url(&self, url: &str);
 67    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
 68    fn prompt_for_paths(
 69        &self,
 70        options: PathPromptOptions,
 71    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
 72    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
 73    fn reveal_path(&self, path: &Path);
 74
 75    fn on_become_active(&self, callback: Box<dyn FnMut()>);
 76    fn on_resign_active(&self, callback: Box<dyn FnMut()>);
 77    fn on_quit(&self, callback: Box<dyn FnMut()>);
 78    fn on_reopen(&self, callback: Box<dyn FnMut()>);
 79    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
 80
 81    fn os_name(&self) -> &'static str;
 82    fn os_version(&self) -> Result<SemanticVersion>;
 83    fn app_version(&self) -> Result<SemanticVersion>;
 84    fn app_path(&self) -> Result<PathBuf>;
 85    fn local_timezone(&self) -> UtcOffset;
 86    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
 87
 88    fn set_cursor_style(&self, style: CursorStyle);
 89    fn should_auto_hide_scrollbars(&self) -> bool;
 90
 91    fn write_to_clipboard(&self, item: ClipboardItem);
 92    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
 93
 94    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
 95    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
 96    fn delete_credentials(&self, url: &str) -> Result<()>;
 97}
 98
 99pub trait PlatformScreen: Debug {
100    fn id(&self) -> Option<ScreenId>;
101    fn handle(&self) -> PlatformScreenHandle;
102    fn as_any(&self) -> &dyn Any;
103    fn bounds(&self) -> Bounds<Pixels>;
104    fn content_bounds(&self) -> Bounds<Pixels>;
105}
106
107pub struct PlatformScreenHandle(pub *mut c_void);
108
109impl Debug for PlatformScreenHandle {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        write!(f, "PlatformScreenHandle({:p})", self.0)
112    }
113}
114
115unsafe impl Send for PlatformScreenHandle {}
116
117pub trait PlatformWindow: HasRawWindowHandle + HasRawDisplayHandle {
118    fn bounds(&self) -> WindowBounds;
119    fn content_size(&self) -> Size<Pixels>;
120    fn scale_factor(&self) -> f32;
121    fn titlebar_height(&self) -> Pixels;
122    fn appearance(&self) -> WindowAppearance;
123    fn screen(&self) -> Rc<dyn PlatformScreen>;
124    fn mouse_position(&self) -> Point<Pixels>;
125    fn as_any_mut(&mut self) -> &mut dyn Any;
126    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
127    fn prompt(
128        &self,
129        level: WindowPromptLevel,
130        msg: &str,
131        answers: &[&str],
132    ) -> oneshot::Receiver<usize>;
133    fn activate(&self);
134    fn set_title(&mut self, title: &str);
135    fn set_edited(&mut self, edited: bool);
136    fn show_character_palette(&self);
137    fn minimize(&self);
138    fn zoom(&self);
139    fn toggle_full_screen(&self);
140    fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
141    fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>);
142    fn on_resize(&mut self, callback: Box<dyn FnMut()>);
143    fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>);
144    fn on_moved(&mut self, callback: Box<dyn FnMut()>);
145    fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
146    fn on_close(&mut self, callback: Box<dyn FnOnce()>);
147    fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
148    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
149}
150
151pub trait PlatformDispatcher: Send + Sync {
152    fn is_main_thread(&self) -> bool;
153    fn run_on_main_thread(&self, task: Runnable);
154}
155
156pub trait PlatformTextSystem: Send + Sync {
157    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
158    fn all_families(&self) -> Vec<String>;
159    fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
160    fn select_font(
161        &self,
162        font_ids: &[FontId],
163        weight: FontWeight,
164        style: FontStyle,
165    ) -> anyhow::Result<FontId>;
166    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
167    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId)
168        -> anyhow::Result<Bounds<f32>>;
169    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>>;
170    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
171    fn rasterize_glyph(
172        &self,
173        font_id: FontId,
174        font_size: f32,
175        glyph_id: GlyphId,
176        subpixel_shift: Point<Pixels>,
177        scale_factor: f32,
178        options: RasterizationOptions,
179    ) -> Option<(Bounds<u32>, Vec<u8>)>;
180    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, RunStyle)]) -> LineLayout;
181    fn wrap_line(
182        &self,
183        text: &str,
184        font_id: FontId,
185        font_size: Pixels,
186        width: Pixels,
187    ) -> Vec<usize>;
188}
189
190pub trait InputHandler {
191    fn selected_text_range(&self) -> Option<Range<usize>>;
192    fn marked_text_range(&self) -> Option<Range<usize>>;
193    fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
194    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
195    fn replace_and_mark_text_in_range(
196        &mut self,
197        range_utf16: Option<Range<usize>>,
198        new_text: &str,
199        new_selected_range: Option<Range<usize>>,
200    );
201    fn unmark_text(&mut self);
202    fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
203}
204
205#[derive(Copy, Clone, Debug, PartialEq)]
206pub struct ScreenId(pub(crate) Uuid);
207
208#[derive(Copy, Clone, Debug)]
209pub enum RasterizationOptions {
210    Alpha,
211    Bgra,
212}
213
214#[derive(Debug)]
215pub struct WindowOptions {
216    pub bounds: WindowBounds,
217    pub titlebar: Option<TitlebarOptions>,
218    pub center: bool,
219    pub focus: bool,
220    pub show: bool,
221    pub kind: WindowKind,
222    pub is_movable: bool,
223    pub screen: Option<PlatformScreenHandle>,
224}
225
226impl Default for WindowOptions {
227    fn default() -> Self {
228        Self {
229            bounds: WindowBounds::default(),
230            titlebar: Some(TitlebarOptions {
231                title: Default::default(),
232                appears_transparent: Default::default(),
233                traffic_light_position: Default::default(),
234            }),
235            center: false,
236            focus: true,
237            show: true,
238            kind: WindowKind::Normal,
239            is_movable: true,
240            screen: None,
241        }
242    }
243}
244
245#[derive(Debug, Default)]
246pub struct TitlebarOptions {
247    pub title: Option<SharedString>,
248    pub appears_transparent: bool,
249    pub traffic_light_position: Option<Point<Pixels>>,
250}
251
252#[derive(Copy, Clone, Debug)]
253pub enum Appearance {
254    Light,
255    VibrantLight,
256    Dark,
257    VibrantDark,
258}
259
260impl Default for Appearance {
261    fn default() -> Self {
262        Self::Light
263    }
264}
265
266#[derive(Copy, Clone, Debug, PartialEq, Eq)]
267pub enum WindowKind {
268    Normal,
269    PopUp,
270}
271
272#[derive(Copy, Clone, Debug, PartialEq, Default)]
273pub enum WindowBounds {
274    Fullscreen,
275    #[default]
276    Maximized,
277    Fixed(Bounds<Pixels>),
278}
279
280#[derive(Copy, Clone, Debug)]
281pub enum WindowAppearance {
282    Light,
283    VibrantLight,
284    Dark,
285    VibrantDark,
286}
287
288impl Default for WindowAppearance {
289    fn default() -> Self {
290        Self::Light
291    }
292}
293
294#[derive(Copy, Clone, Debug, PartialEq, Default)]
295pub enum WindowPromptLevel {
296    #[default]
297    Info,
298    Warning,
299    Critical,
300}
301
302#[derive(Copy, Clone, Debug)]
303pub struct PathPromptOptions {
304    pub files: bool,
305    pub directories: bool,
306    pub multiple: bool,
307}
308
309#[derive(Copy, Clone, Debug)]
310pub enum PromptLevel {
311    Info,
312    Warning,
313    Critical,
314}
315
316#[derive(Copy, Clone, Debug)]
317pub enum CursorStyle {
318    Arrow,
319    ResizeLeftRight,
320    ResizeUpDown,
321    PointingHand,
322    IBeam,
323}
324
325impl Default for CursorStyle {
326    fn default() -> Self {
327        Self::Arrow
328    }
329}
330
331#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
332pub struct SemanticVersion {
333    major: usize,
334    minor: usize,
335    patch: usize,
336}
337
338impl FromStr for SemanticVersion {
339    type Err = anyhow::Error;
340
341    fn from_str(s: &str) -> Result<Self> {
342        let mut components = s.trim().split('.');
343        let major = components
344            .next()
345            .ok_or_else(|| anyhow!("missing major version number"))?
346            .parse()?;
347        let minor = components
348            .next()
349            .ok_or_else(|| anyhow!("missing minor version number"))?
350            .parse()?;
351        let patch = components
352            .next()
353            .ok_or_else(|| anyhow!("missing patch version number"))?
354            .parse()?;
355        Ok(Self {
356            major,
357            minor,
358            patch,
359        })
360    }
361}
362
363impl Display for SemanticVersion {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
366    }
367}
368
369#[derive(Clone, Debug, Eq, PartialEq)]
370pub struct ClipboardItem {
371    pub(crate) text: String,
372    pub(crate) metadata: Option<String>,
373}
374
375impl ClipboardItem {
376    pub fn new(text: String) -> Self {
377        Self {
378            text,
379            metadata: None,
380        }
381    }
382
383    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
384        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
385        self
386    }
387
388    pub fn text(&self) -> &String {
389        &self.text
390    }
391
392    pub fn metadata<T>(&self) -> Option<T>
393    where
394        T: for<'a> Deserialize<'a>,
395    {
396        self.metadata
397            .as_ref()
398            .and_then(|m| serde_json::from_str(m).ok())
399    }
400
401    pub(crate) fn text_hash(text: &str) -> u64 {
402        let mut hasher = SeaHasher::new();
403        text.hash(&mut hasher);
404        hasher.finish()
405    }
406}