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, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
10 FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
11 Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
12 Scene, SharedString, Size, TaskLabel, WindowContext,
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: PlatformInputHandler);
153 fn take_input_handler(&mut self) -> Option<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(crate) struct PlatformInputHandler {
329 cx: AsyncWindowContext,
330 handler: Box<dyn InputHandler>,
331}
332
333impl PlatformInputHandler {
334 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
335 Self { cx, handler }
336 }
337
338 fn selected_text_range(&mut self) -> Option<Range<usize>> {
339 self.cx
340 .update(|cx| self.handler.selected_text_range(cx))
341 .ok()
342 .flatten()
343 }
344
345 fn marked_text_range(&mut self) -> Option<Range<usize>> {
346 self.cx
347 .update(|cx| self.handler.marked_text_range(cx))
348 .ok()
349 .flatten()
350 }
351
352 fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
353 self.cx
354 .update(|cx| self.handler.text_for_range(range_utf16, cx))
355 .ok()
356 .flatten()
357 }
358
359 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
360 self.cx
361 .update(|cx| {
362 self.handler
363 .replace_text_in_range(replacement_range, text, cx)
364 })
365 .ok();
366 }
367
368 fn replace_and_mark_text_in_range(
369 &mut self,
370 range_utf16: Option<Range<usize>>,
371 new_text: &str,
372 new_selected_range: Option<Range<usize>>,
373 ) {
374 self.cx
375 .update(|cx| {
376 self.handler.replace_and_mark_text_in_range(
377 range_utf16,
378 new_text,
379 new_selected_range,
380 cx,
381 )
382 })
383 .ok();
384 }
385
386 fn unmark_text(&mut self) {
387 self.cx.update(|cx| self.handler.unmark_text(cx)).ok();
388 }
389
390 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
391 self.cx
392 .update(|cx| self.handler.bounds_for_range(range_utf16, cx))
393 .ok()
394 .flatten()
395 }
396}
397
398pub trait InputHandler: 'static {
399 fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
400 fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
401 fn text_for_range(
402 &mut self,
403 range_utf16: Range<usize>,
404 cx: &mut WindowContext,
405 ) -> Option<String>;
406 fn replace_text_in_range(
407 &mut self,
408 replacement_range: Option<Range<usize>>,
409 text: &str,
410 cx: &mut WindowContext,
411 );
412 fn replace_and_mark_text_in_range(
413 &mut self,
414 range_utf16: Option<Range<usize>>,
415 new_text: &str,
416 new_selected_range: Option<Range<usize>>,
417 cx: &mut WindowContext,
418 );
419 fn unmark_text(&mut self, cx: &mut WindowContext);
420 fn bounds_for_range(
421 &mut self,
422 range_utf16: Range<usize>,
423 cx: &mut WindowContext,
424 ) -> Option<Bounds<Pixels>>;
425}
426
427#[derive(Debug)]
428pub struct WindowOptions {
429 pub bounds: WindowBounds,
430 pub titlebar: Option<TitlebarOptions>,
431 pub center: bool,
432 pub focus: bool,
433 pub show: bool,
434 pub kind: WindowKind,
435 pub is_movable: bool,
436 pub display_id: Option<DisplayId>,
437}
438
439impl Default for WindowOptions {
440 fn default() -> Self {
441 Self {
442 bounds: WindowBounds::default(),
443 titlebar: Some(TitlebarOptions {
444 title: Default::default(),
445 appears_transparent: Default::default(),
446 traffic_light_position: Default::default(),
447 }),
448 center: false,
449 focus: true,
450 show: true,
451 kind: WindowKind::Normal,
452 is_movable: true,
453 display_id: None,
454 }
455 }
456}
457
458#[derive(Debug, Default)]
459pub struct TitlebarOptions {
460 pub title: Option<SharedString>,
461 pub appears_transparent: bool,
462 pub traffic_light_position: Option<Point<Pixels>>,
463}
464
465#[derive(Copy, Clone, Debug)]
466pub enum Appearance {
467 Light,
468 VibrantLight,
469 Dark,
470 VibrantDark,
471}
472
473impl Default for Appearance {
474 fn default() -> Self {
475 Self::Light
476 }
477}
478
479#[derive(Copy, Clone, Debug, PartialEq, Eq)]
480pub enum WindowKind {
481 Normal,
482 PopUp,
483}
484
485#[derive(Copy, Clone, Debug, PartialEq, Default)]
486pub enum WindowBounds {
487 Fullscreen,
488 #[default]
489 Maximized,
490 Fixed(Bounds<GlobalPixels>),
491}
492
493#[derive(Copy, Clone, Debug)]
494pub enum WindowAppearance {
495 Light,
496 VibrantLight,
497 Dark,
498 VibrantDark,
499}
500
501impl Default for WindowAppearance {
502 fn default() -> Self {
503 Self::Light
504 }
505}
506
507#[derive(Copy, Clone, Debug)]
508pub struct PathPromptOptions {
509 pub files: bool,
510 pub directories: bool,
511 pub multiple: bool,
512}
513
514#[derive(Copy, Clone, Debug)]
515pub enum PromptLevel {
516 Info,
517 Warning,
518 Critical,
519}
520
521/// The style of the cursor (pointer)
522#[derive(Copy, Clone, Debug)]
523pub enum CursorStyle {
524 Arrow,
525 IBeam,
526 Crosshair,
527 ClosedHand,
528 OpenHand,
529 PointingHand,
530 ResizeLeft,
531 ResizeRight,
532 ResizeLeftRight,
533 ResizeUp,
534 ResizeDown,
535 ResizeUpDown,
536 DisappearingItem,
537 IBeamCursorForVerticalLayout,
538 OperationNotAllowed,
539 DragLink,
540 DragCopy,
541 ContextualMenu,
542}
543
544impl Default for CursorStyle {
545 fn default() -> Self {
546 Self::Arrow
547 }
548}
549
550#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
551pub struct SemanticVersion {
552 major: usize,
553 minor: usize,
554 patch: usize,
555}
556
557impl FromStr for SemanticVersion {
558 type Err = anyhow::Error;
559
560 fn from_str(s: &str) -> Result<Self> {
561 let mut components = s.trim().split('.');
562 let major = components
563 .next()
564 .ok_or_else(|| anyhow!("missing major version number"))?
565 .parse()?;
566 let minor = components
567 .next()
568 .ok_or_else(|| anyhow!("missing minor version number"))?
569 .parse()?;
570 let patch = components
571 .next()
572 .ok_or_else(|| anyhow!("missing patch version number"))?
573 .parse()?;
574 Ok(Self {
575 major,
576 minor,
577 patch,
578 })
579 }
580}
581
582impl Display for SemanticVersion {
583 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
585 }
586}
587
588#[derive(Clone, Debug, Eq, PartialEq)]
589pub struct ClipboardItem {
590 pub(crate) text: String,
591 pub(crate) metadata: Option<String>,
592}
593
594impl ClipboardItem {
595 pub fn new(text: String) -> Self {
596 Self {
597 text,
598 metadata: None,
599 }
600 }
601
602 pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
603 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
604 self
605 }
606
607 pub fn text(&self) -> &String {
608 &self.text
609 }
610
611 pub fn metadata<T>(&self) -> Option<T>
612 where
613 T: for<'a> Deserialize<'a>,
614 {
615 self.metadata
616 .as_ref()
617 .and_then(|m| serde_json::from_str(m).ok())
618 }
619
620 pub(crate) fn text_hash(text: &str) -> u64 {
621 let mut hasher = SeaHasher::new();
622 text.hash(&mut hasher);
623 hasher.finish()
624 }
625}