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}