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, Keymap, LineLayout, Pixels, PlatformInput,
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(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: Box<dyn PlatformInputHandler>);
153 fn take_input_handler(&mut self) -> Option<Box<dyn 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 trait PlatformInputHandler: 'static {
329 fn selected_text_range(&mut self) -> Option<Range<usize>>;
330 fn marked_text_range(&mut self) -> Option<Range<usize>>;
331 fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
332 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
333 fn replace_and_mark_text_in_range(
334 &mut self,
335 range_utf16: Option<Range<usize>>,
336 new_text: &str,
337 new_selected_range: Option<Range<usize>>,
338 );
339 fn unmark_text(&mut self);
340 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
341}
342
343#[derive(Debug)]
344pub struct WindowOptions {
345 pub bounds: WindowBounds,
346 pub titlebar: Option<TitlebarOptions>,
347 pub center: bool,
348 pub focus: bool,
349 pub show: bool,
350 pub kind: WindowKind,
351 pub is_movable: bool,
352 pub display_id: Option<DisplayId>,
353}
354
355impl Default for WindowOptions {
356 fn default() -> Self {
357 Self {
358 bounds: WindowBounds::default(),
359 titlebar: Some(TitlebarOptions {
360 title: Default::default(),
361 appears_transparent: Default::default(),
362 traffic_light_position: Default::default(),
363 }),
364 center: false,
365 focus: true,
366 show: true,
367 kind: WindowKind::Normal,
368 is_movable: true,
369 display_id: None,
370 }
371 }
372}
373
374#[derive(Debug, Default)]
375pub struct TitlebarOptions {
376 pub title: Option<SharedString>,
377 pub appears_transparent: bool,
378 pub traffic_light_position: Option<Point<Pixels>>,
379}
380
381#[derive(Copy, Clone, Debug)]
382pub enum Appearance {
383 Light,
384 VibrantLight,
385 Dark,
386 VibrantDark,
387}
388
389impl Default for Appearance {
390 fn default() -> Self {
391 Self::Light
392 }
393}
394
395#[derive(Copy, Clone, Debug, PartialEq, Eq)]
396pub enum WindowKind {
397 Normal,
398 PopUp,
399}
400
401#[derive(Copy, Clone, Debug, PartialEq, Default)]
402pub enum WindowBounds {
403 Fullscreen,
404 #[default]
405 Maximized,
406 Fixed(Bounds<GlobalPixels>),
407}
408
409#[derive(Copy, Clone, Debug)]
410pub enum WindowAppearance {
411 Light,
412 VibrantLight,
413 Dark,
414 VibrantDark,
415}
416
417impl Default for WindowAppearance {
418 fn default() -> Self {
419 Self::Light
420 }
421}
422
423#[derive(Copy, Clone, Debug)]
424pub struct PathPromptOptions {
425 pub files: bool,
426 pub directories: bool,
427 pub multiple: bool,
428}
429
430#[derive(Copy, Clone, Debug)]
431pub enum PromptLevel {
432 Info,
433 Warning,
434 Critical,
435}
436
437/// The style of the cursor (pointer)
438#[derive(Copy, Clone, Debug)]
439pub enum CursorStyle {
440 Arrow,
441 IBeam,
442 Crosshair,
443 ClosedHand,
444 OpenHand,
445 PointingHand,
446 ResizeLeft,
447 ResizeRight,
448 ResizeLeftRight,
449 ResizeUp,
450 ResizeDown,
451 ResizeUpDown,
452 DisappearingItem,
453 IBeamCursorForVerticalLayout,
454 OperationNotAllowed,
455 DragLink,
456 DragCopy,
457 ContextualMenu,
458}
459
460impl Default for CursorStyle {
461 fn default() -> Self {
462 Self::Arrow
463 }
464}
465
466#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
467pub struct SemanticVersion {
468 major: usize,
469 minor: usize,
470 patch: usize,
471}
472
473impl FromStr for SemanticVersion {
474 type Err = anyhow::Error;
475
476 fn from_str(s: &str) -> Result<Self> {
477 let mut components = s.trim().split('.');
478 let major = components
479 .next()
480 .ok_or_else(|| anyhow!("missing major version number"))?
481 .parse()?;
482 let minor = components
483 .next()
484 .ok_or_else(|| anyhow!("missing minor version number"))?
485 .parse()?;
486 let patch = components
487 .next()
488 .ok_or_else(|| anyhow!("missing patch version number"))?
489 .parse()?;
490 Ok(Self {
491 major,
492 minor,
493 patch,
494 })
495 }
496}
497
498impl Display for SemanticVersion {
499 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
501 }
502}
503
504#[derive(Clone, Debug, Eq, PartialEq)]
505pub struct ClipboardItem {
506 pub(crate) text: String,
507 pub(crate) metadata: Option<String>,
508}
509
510impl ClipboardItem {
511 pub fn new(text: String) -> Self {
512 Self {
513 text,
514 metadata: None,
515 }
516 }
517
518 pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
519 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
520 self
521 }
522
523 pub fn text(&self) -> &String {
524 &self.text
525 }
526
527 pub fn metadata<T>(&self) -> Option<T>
528 where
529 T: for<'a> Deserialize<'a>,
530 {
531 self.metadata
532 .as_ref()
533 .and_then(|m| serde_json::from_str(m).ok())
534 }
535
536 pub(crate) fn text_hash(text: &str) -> u64 {
537 let mut hasher = SeaHasher::new();
538 text.hash(&mut hasher);
539 hasher.finish()
540 }
541}