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(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(InputEvent) -> 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
117pub trait PlatformDisplay: Send + Sync + Debug {
118 fn id(&self) -> DisplayId;
119 /// Returns a stable identifier for this display that can be persisted and used
120 /// across system restarts.
121 fn uuid(&self) -> Result<Uuid>;
122 fn as_any(&self) -> &dyn Any;
123 fn bounds(&self) -> Bounds<GlobalPixels>;
124}
125
126#[derive(PartialEq, Eq, Hash, Copy, Clone)]
127pub struct DisplayId(pub(crate) u32);
128
129impl Debug for DisplayId {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 write!(f, "DisplayId({})", self.0)
132 }
133}
134
135unsafe impl Send for DisplayId {}
136
137pub trait PlatformWindow {
138 fn bounds(&self) -> WindowBounds;
139 fn content_size(&self) -> Size<Pixels>;
140 fn scale_factor(&self) -> f32;
141 fn titlebar_height(&self) -> Pixels;
142 fn appearance(&self) -> WindowAppearance;
143 fn display(&self) -> Rc<dyn PlatformDisplay>;
144 fn mouse_position(&self) -> Point<Pixels>;
145 fn modifiers(&self) -> Modifiers;
146 fn as_any_mut(&mut self) -> &mut dyn Any;
147 fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
148 fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>>;
149 fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
150 fn activate(&self);
151 fn set_title(&mut self, title: &str);
152 fn set_edited(&mut self, edited: bool);
153 fn show_character_palette(&self);
154 fn minimize(&self);
155 fn zoom(&self);
156 fn toggle_full_screen(&self);
157 fn on_request_frame(&self, callback: Box<dyn FnMut()>);
158 fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
159 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
160 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
161 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
162 fn on_moved(&self, callback: Box<dyn FnMut()>);
163 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
164 fn on_close(&self, callback: Box<dyn FnOnce()>);
165 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
166 fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
167 fn invalidate(&self);
168 fn draw(&self, scene: &Scene);
169
170 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
171
172 #[cfg(any(test, feature = "test-support"))]
173 fn as_test(&mut self) -> Option<&mut TestWindow> {
174 None
175 }
176}
177
178pub trait PlatformDispatcher: Send + Sync {
179 fn is_main_thread(&self) -> bool;
180 fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
181 fn dispatch_on_main_thread(&self, runnable: Runnable);
182 fn dispatch_after(&self, duration: Duration, runnable: Runnable);
183 fn tick(&self, background_only: bool) -> bool;
184 fn park(&self);
185 fn unparker(&self) -> Unparker;
186
187 #[cfg(any(test, feature = "test-support"))]
188 fn as_test(&self) -> Option<&TestDispatcher> {
189 None
190 }
191}
192
193pub trait PlatformTextSystem: Send + Sync {
194 fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
195 fn all_font_names(&self) -> Vec<String>;
196 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
197 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
198 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
199 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
200 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
201 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
202 fn rasterize_glyph(
203 &self,
204 params: &RenderGlyphParams,
205 raster_bounds: Bounds<DevicePixels>,
206 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
207 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
208 fn wrap_line(
209 &self,
210 text: &str,
211 font_id: FontId,
212 font_size: Pixels,
213 width: Pixels,
214 ) -> Vec<usize>;
215}
216
217#[derive(Clone, Debug)]
218pub struct AppMetadata {
219 pub os_name: &'static str,
220 pub os_version: Option<SemanticVersion>,
221 pub app_version: Option<SemanticVersion>,
222}
223
224#[derive(PartialEq, Eq, Hash, Clone)]
225pub enum AtlasKey {
226 Glyph(RenderGlyphParams),
227 Svg(RenderSvgParams),
228 Image(RenderImageParams),
229}
230
231impl AtlasKey {
232 pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
233 match self {
234 AtlasKey::Glyph(params) => {
235 if params.is_emoji {
236 AtlasTextureKind::Polychrome
237 } else {
238 AtlasTextureKind::Monochrome
239 }
240 }
241 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
242 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
243 }
244 }
245}
246
247impl From<RenderGlyphParams> for AtlasKey {
248 fn from(params: RenderGlyphParams) -> Self {
249 Self::Glyph(params)
250 }
251}
252
253impl From<RenderSvgParams> for AtlasKey {
254 fn from(params: RenderSvgParams) -> Self {
255 Self::Svg(params)
256 }
257}
258
259impl From<RenderImageParams> for AtlasKey {
260 fn from(params: RenderImageParams) -> Self {
261 Self::Image(params)
262 }
263}
264
265pub trait PlatformAtlas: Send + Sync {
266 fn get_or_insert_with<'a>(
267 &self,
268 key: &AtlasKey,
269 build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
270 ) -> Result<AtlasTile>;
271
272 fn clear(&self);
273}
274
275#[derive(Clone, Debug, PartialEq, Eq)]
276#[repr(C)]
277pub struct AtlasTile {
278 pub(crate) texture_id: AtlasTextureId,
279 pub(crate) tile_id: TileId,
280 pub(crate) bounds: Bounds<DevicePixels>,
281}
282
283#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
284#[repr(C)]
285pub(crate) struct AtlasTextureId {
286 // We use u32 instead of usize for Metal Shader Language compatibility
287 pub(crate) index: u32,
288 pub(crate) kind: AtlasTextureKind,
289}
290
291#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
292#[repr(C)]
293pub(crate) enum AtlasTextureKind {
294 Monochrome = 0,
295 Polychrome = 1,
296 Path = 2,
297}
298
299#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
300#[repr(C)]
301pub(crate) struct TileId(pub(crate) u32);
302
303impl From<etagere::AllocId> for TileId {
304 fn from(id: etagere::AllocId) -> Self {
305 Self(id.serialize())
306 }
307}
308
309impl From<TileId> for etagere::AllocId {
310 fn from(id: TileId) -> Self {
311 Self::deserialize(id.0)
312 }
313}
314
315pub trait PlatformInputHandler: 'static {
316 fn selected_text_range(&mut self) -> Option<Range<usize>>;
317 fn marked_text_range(&mut self) -> Option<Range<usize>>;
318 fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
319 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
320 fn replace_and_mark_text_in_range(
321 &mut self,
322 range_utf16: Option<Range<usize>>,
323 new_text: &str,
324 new_selected_range: Option<Range<usize>>,
325 );
326 fn unmark_text(&mut self);
327 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
328}
329
330#[derive(Debug)]
331pub struct WindowOptions {
332 pub bounds: WindowBounds,
333 pub titlebar: Option<TitlebarOptions>,
334 pub center: bool,
335 pub focus: bool,
336 pub show: bool,
337 pub kind: WindowKind,
338 pub is_movable: bool,
339 pub display_id: Option<DisplayId>,
340}
341
342impl Default for WindowOptions {
343 fn default() -> Self {
344 Self {
345 bounds: WindowBounds::default(),
346 titlebar: Some(TitlebarOptions {
347 title: Default::default(),
348 appears_transparent: Default::default(),
349 traffic_light_position: Default::default(),
350 }),
351 center: false,
352 focus: true,
353 show: true,
354 kind: WindowKind::Normal,
355 is_movable: true,
356 display_id: None,
357 }
358 }
359}
360
361#[derive(Debug, Default)]
362pub struct TitlebarOptions {
363 pub title: Option<SharedString>,
364 pub appears_transparent: bool,
365 pub traffic_light_position: Option<Point<Pixels>>,
366}
367
368#[derive(Copy, Clone, Debug)]
369pub enum Appearance {
370 Light,
371 VibrantLight,
372 Dark,
373 VibrantDark,
374}
375
376impl Default for Appearance {
377 fn default() -> Self {
378 Self::Light
379 }
380}
381
382#[derive(Copy, Clone, Debug, PartialEq, Eq)]
383pub enum WindowKind {
384 Normal,
385 PopUp,
386}
387
388#[derive(Copy, Clone, Debug, PartialEq, Default)]
389pub enum WindowBounds {
390 Fullscreen,
391 #[default]
392 Maximized,
393 Fixed(Bounds<GlobalPixels>),
394}
395
396#[derive(Copy, Clone, Debug)]
397pub enum WindowAppearance {
398 Light,
399 VibrantLight,
400 Dark,
401 VibrantDark,
402}
403
404impl Default for WindowAppearance {
405 fn default() -> Self {
406 Self::Light
407 }
408}
409
410#[derive(Copy, Clone, Debug)]
411pub struct PathPromptOptions {
412 pub files: bool,
413 pub directories: bool,
414 pub multiple: bool,
415}
416
417#[derive(Copy, Clone, Debug)]
418pub enum PromptLevel {
419 Info,
420 Warning,
421 Critical,
422}
423
424/// The style of the cursor (pointer)
425#[derive(Copy, Clone, Debug)]
426pub enum CursorStyle {
427 Arrow,
428 IBeam,
429 Crosshair,
430 ClosedHand,
431 OpenHand,
432 PointingHand,
433 ResizeLeft,
434 ResizeRight,
435 ResizeLeftRight,
436 ResizeUp,
437 ResizeDown,
438 ResizeUpDown,
439 DisappearingItem,
440 IBeamCursorForVerticalLayout,
441 OperationNotAllowed,
442 DragLink,
443 DragCopy,
444 ContextualMenu,
445}
446
447impl Default for CursorStyle {
448 fn default() -> Self {
449 Self::Arrow
450 }
451}
452
453#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
454pub struct SemanticVersion {
455 major: usize,
456 minor: usize,
457 patch: usize,
458}
459
460impl FromStr for SemanticVersion {
461 type Err = anyhow::Error;
462
463 fn from_str(s: &str) -> Result<Self> {
464 let mut components = s.trim().split('.');
465 let major = components
466 .next()
467 .ok_or_else(|| anyhow!("missing major version number"))?
468 .parse()?;
469 let minor = components
470 .next()
471 .ok_or_else(|| anyhow!("missing minor version number"))?
472 .parse()?;
473 let patch = components
474 .next()
475 .ok_or_else(|| anyhow!("missing patch version number"))?
476 .parse()?;
477 Ok(Self {
478 major,
479 minor,
480 patch,
481 })
482 }
483}
484
485impl Display for SemanticVersion {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
488 }
489}
490
491#[derive(Clone, Debug, Eq, PartialEq)]
492pub struct ClipboardItem {
493 pub(crate) text: String,
494 pub(crate) metadata: Option<String>,
495}
496
497impl ClipboardItem {
498 pub fn new(text: String) -> Self {
499 Self {
500 text,
501 metadata: None,
502 }
503 }
504
505 pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
506 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
507 self
508 }
509
510 pub fn text(&self) -> &String {
511 &self.text
512 }
513
514 pub fn metadata<T>(&self) -> Option<T>
515 where
516 T: for<'a> Deserialize<'a>,
517 {
518 self.metadata
519 .as_ref()
520 .and_then(|m| serde_json::from_str(m).ok())
521 }
522
523 pub(crate) fn text_hash(text: &str) -> u64 {
524 let mut hasher = SeaHasher::new();
525 text.hash(&mut hasher);
526 hasher.finish()
527 }
528}