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 font_id(&self, descriptor: &Font) -> Result<FontId>;
205 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
206 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
207 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
208 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
209 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
210 fn rasterize_glyph(
211 &self,
212 params: &RenderGlyphParams,
213 raster_bounds: Bounds<DevicePixels>,
214 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
215 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
216 fn wrap_line(
217 &self,
218 text: &str,
219 font_id: FontId,
220 font_size: Pixels,
221 width: Pixels,
222 ) -> Vec<usize>;
223}
224
225/// Basic metadata about the current application and operating system.
226#[derive(Clone, Debug)]
227pub struct AppMetadata {
228 /// The name of the current operating system
229 pub os_name: &'static str,
230
231 /// The operating system's version
232 pub os_version: Option<SemanticVersion>,
233
234 /// The current version of the application
235 pub app_version: Option<SemanticVersion>,
236}
237
238#[derive(PartialEq, Eq, Hash, Clone)]
239pub(crate) enum AtlasKey {
240 Glyph(RenderGlyphParams),
241 Svg(RenderSvgParams),
242 Image(RenderImageParams),
243}
244
245impl AtlasKey {
246 pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
247 match self {
248 AtlasKey::Glyph(params) => {
249 if params.is_emoji {
250 AtlasTextureKind::Polychrome
251 } else {
252 AtlasTextureKind::Monochrome
253 }
254 }
255 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
256 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
257 }
258 }
259}
260
261impl From<RenderGlyphParams> for AtlasKey {
262 fn from(params: RenderGlyphParams) -> Self {
263 Self::Glyph(params)
264 }
265}
266
267impl From<RenderSvgParams> for AtlasKey {
268 fn from(params: RenderSvgParams) -> Self {
269 Self::Svg(params)
270 }
271}
272
273impl From<RenderImageParams> for AtlasKey {
274 fn from(params: RenderImageParams) -> Self {
275 Self::Image(params)
276 }
277}
278
279pub(crate) trait PlatformAtlas: Send + Sync {
280 fn get_or_insert_with<'a>(
281 &self,
282 key: &AtlasKey,
283 build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
284 ) -> Result<AtlasTile>;
285
286 fn clear(&self);
287}
288
289#[derive(Clone, Debug, PartialEq, Eq)]
290#[repr(C)]
291pub(crate) struct AtlasTile {
292 pub(crate) texture_id: AtlasTextureId,
293 pub(crate) tile_id: TileId,
294 pub(crate) bounds: Bounds<DevicePixels>,
295}
296
297#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
298#[repr(C)]
299pub(crate) struct AtlasTextureId {
300 // We use u32 instead of usize for Metal Shader Language compatibility
301 pub(crate) index: u32,
302 pub(crate) kind: AtlasTextureKind,
303}
304
305#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
306#[repr(C)]
307pub(crate) enum AtlasTextureKind {
308 Monochrome = 0,
309 Polychrome = 1,
310 Path = 2,
311}
312
313#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
314#[repr(C)]
315pub(crate) struct TileId(pub(crate) u32);
316
317impl From<etagere::AllocId> for TileId {
318 fn from(id: etagere::AllocId) -> Self {
319 Self(id.serialize())
320 }
321}
322
323impl From<TileId> for etagere::AllocId {
324 fn from(id: TileId) -> Self {
325 Self::deserialize(id.0)
326 }
327}
328
329pub trait PlatformInputHandler: 'static {
330 fn selected_text_range(&mut self) -> Option<Range<usize>>;
331 fn marked_text_range(&mut self) -> Option<Range<usize>>;
332 fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
333 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
334 fn replace_and_mark_text_in_range(
335 &mut self,
336 range_utf16: Option<Range<usize>>,
337 new_text: &str,
338 new_selected_range: Option<Range<usize>>,
339 );
340 fn unmark_text(&mut self);
341 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
342}
343
344#[derive(Debug)]
345pub struct WindowOptions {
346 pub bounds: WindowBounds,
347 pub titlebar: Option<TitlebarOptions>,
348 pub center: bool,
349 pub focus: bool,
350 pub show: bool,
351 pub kind: WindowKind,
352 pub is_movable: bool,
353 pub display_id: Option<DisplayId>,
354}
355
356impl Default for WindowOptions {
357 fn default() -> Self {
358 Self {
359 bounds: WindowBounds::default(),
360 titlebar: Some(TitlebarOptions {
361 title: Default::default(),
362 appears_transparent: Default::default(),
363 traffic_light_position: Default::default(),
364 }),
365 center: false,
366 focus: true,
367 show: true,
368 kind: WindowKind::Normal,
369 is_movable: true,
370 display_id: None,
371 }
372 }
373}
374
375#[derive(Debug, Default)]
376pub struct TitlebarOptions {
377 pub title: Option<SharedString>,
378 pub appears_transparent: bool,
379 pub traffic_light_position: Option<Point<Pixels>>,
380}
381
382#[derive(Copy, Clone, Debug)]
383pub enum Appearance {
384 Light,
385 VibrantLight,
386 Dark,
387 VibrantDark,
388}
389
390impl Default for Appearance {
391 fn default() -> Self {
392 Self::Light
393 }
394}
395
396#[derive(Copy, Clone, Debug, PartialEq, Eq)]
397pub enum WindowKind {
398 Normal,
399 PopUp,
400}
401
402#[derive(Copy, Clone, Debug, PartialEq, Default)]
403pub enum WindowBounds {
404 Fullscreen,
405 #[default]
406 Maximized,
407 Fixed(Bounds<GlobalPixels>),
408}
409
410#[derive(Copy, Clone, Debug)]
411pub enum WindowAppearance {
412 Light,
413 VibrantLight,
414 Dark,
415 VibrantDark,
416}
417
418impl Default for WindowAppearance {
419 fn default() -> Self {
420 Self::Light
421 }
422}
423
424#[derive(Copy, Clone, Debug)]
425pub struct PathPromptOptions {
426 pub files: bool,
427 pub directories: bool,
428 pub multiple: bool,
429}
430
431#[derive(Copy, Clone, Debug)]
432pub enum PromptLevel {
433 Info,
434 Warning,
435 Critical,
436}
437
438/// The style of the cursor (pointer)
439#[derive(Copy, Clone, Debug)]
440pub enum CursorStyle {
441 Arrow,
442 IBeam,
443 Crosshair,
444 ClosedHand,
445 OpenHand,
446 PointingHand,
447 ResizeLeft,
448 ResizeRight,
449 ResizeLeftRight,
450 ResizeUp,
451 ResizeDown,
452 ResizeUpDown,
453 DisappearingItem,
454 IBeamCursorForVerticalLayout,
455 OperationNotAllowed,
456 DragLink,
457 DragCopy,
458 ContextualMenu,
459}
460
461impl Default for CursorStyle {
462 fn default() -> Self {
463 Self::Arrow
464 }
465}
466
467#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
468pub struct SemanticVersion {
469 major: usize,
470 minor: usize,
471 patch: usize,
472}
473
474impl FromStr for SemanticVersion {
475 type Err = anyhow::Error;
476
477 fn from_str(s: &str) -> Result<Self> {
478 let mut components = s.trim().split('.');
479 let major = components
480 .next()
481 .ok_or_else(|| anyhow!("missing major version number"))?
482 .parse()?;
483 let minor = components
484 .next()
485 .ok_or_else(|| anyhow!("missing minor version number"))?
486 .parse()?;
487 let patch = components
488 .next()
489 .ok_or_else(|| anyhow!("missing patch version number"))?
490 .parse()?;
491 Ok(Self {
492 major,
493 minor,
494 patch,
495 })
496 }
497}
498
499impl Display for SemanticVersion {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
502 }
503}
504
505#[derive(Clone, Debug, Eq, PartialEq)]
506pub struct ClipboardItem {
507 pub(crate) text: String,
508 pub(crate) metadata: Option<String>,
509}
510
511impl ClipboardItem {
512 pub fn new(text: String) -> Self {
513 Self {
514 text,
515 metadata: None,
516 }
517 }
518
519 pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
520 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
521 self
522 }
523
524 pub fn text(&self) -> &String {
525 &self.text
526 }
527
528 pub fn metadata<T>(&self) -> Option<T>
529 where
530 T: for<'a> Deserialize<'a>,
531 {
532 self.metadata
533 .as_ref()
534 .and_then(|m| serde_json::from_str(m).ok())
535 }
536
537 pub(crate) fn text_hash(text: &str) -> u64 {
538 let mut hasher = SeaHasher::new();
539 text.hash(&mut hasher);
540 hasher.finish()
541 }
542}