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 point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
10 FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
11 LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
12 Scene, SharedString, Size, TaskLabel,
13};
14use anyhow::{anyhow, bail};
15use async_task::Runnable;
16use futures::channel::oneshot;
17use parking::Unparker;
18use seahash::SeaHasher;
19use serde::{Deserialize, Serialize};
20use sqlez::bindable::{Bind, Column, StaticColumnCount};
21use sqlez::statement::Statement;
22use std::borrow::Cow;
23use std::hash::{Hash, Hasher};
24use std::time::Duration;
25use std::{
26 any::Any,
27 fmt::{self, Debug, Display},
28 ops::Range,
29 path::{Path, PathBuf},
30 rc::Rc,
31 str::FromStr,
32 sync::Arc,
33};
34use uuid::Uuid;
35
36pub use app_menu::*;
37pub use keystroke::*;
38#[cfg(target_os = "macos")]
39pub use mac::*;
40#[cfg(any(test, feature = "test-support"))]
41pub use test::*;
42pub use time::UtcOffset;
43
44#[cfg(target_os = "macos")]
45pub(crate) fn current_platform() -> Rc<dyn Platform> {
46 Rc::new(MacPlatform::new())
47}
48
49pub type DrawWindow = Box<dyn FnMut() -> Result<Scene>>;
50
51pub(crate) trait Platform: 'static {
52 fn background_executor(&self) -> BackgroundExecutor;
53 fn foreground_executor(&self) -> ForegroundExecutor;
54 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
55
56 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
57 fn quit(&self);
58 fn restart(&self);
59 fn activate(&self, ignoring_other_apps: bool);
60 fn hide(&self);
61 fn hide_other_apps(&self);
62 fn unhide_other_apps(&self);
63
64 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
65 fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
66 fn active_window(&self) -> Option<AnyWindowHandle>;
67 fn open_window(
68 &self,
69 handle: AnyWindowHandle,
70 options: WindowOptions,
71 draw: DrawWindow,
72 ) -> Box<dyn PlatformWindow>;
73
74 fn set_display_link_output_callback(
75 &self,
76 display_id: DisplayId,
77 callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
78 );
79 fn start_display_link(&self, display_id: DisplayId);
80 fn stop_display_link(&self, display_id: DisplayId);
81 // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
82
83 fn open_url(&self, url: &str);
84 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
85 fn prompt_for_paths(
86 &self,
87 options: PathPromptOptions,
88 ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
89 fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
90 fn reveal_path(&self, path: &Path);
91
92 fn on_become_active(&self, callback: Box<dyn FnMut()>);
93 fn on_resign_active(&self, callback: Box<dyn FnMut()>);
94 fn on_quit(&self, callback: Box<dyn FnMut()>);
95 fn on_reopen(&self, callback: Box<dyn FnMut()>);
96 fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
97
98 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
99 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
100 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
101 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
102
103 fn os_name(&self) -> &'static str;
104 fn os_version(&self) -> Result<SemanticVersion>;
105 fn app_version(&self) -> Result<SemanticVersion>;
106 fn app_path(&self) -> Result<PathBuf>;
107 fn local_timezone(&self) -> UtcOffset;
108 fn double_click_interval(&self) -> Duration;
109 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
110
111 fn set_cursor_style(&self, style: CursorStyle);
112 fn should_auto_hide_scrollbars(&self) -> bool;
113
114 fn write_to_clipboard(&self, item: ClipboardItem);
115 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
116
117 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
118 fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
119 fn delete_credentials(&self, url: &str) -> Result<()>;
120}
121
122pub trait PlatformDisplay: Send + Sync + Debug {
123 fn id(&self) -> DisplayId;
124 /// Returns a stable identifier for this display that can be persisted and used
125 /// across system restarts.
126 fn uuid(&self) -> Result<Uuid>;
127 fn as_any(&self) -> &dyn Any;
128 fn bounds(&self) -> Bounds<GlobalPixels>;
129}
130
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 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 as_any_mut(&mut self) -> &mut dyn Any;
151 fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
152 fn clear_input_handler(&mut self);
153 fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
154 fn activate(&self);
155 fn set_title(&mut self, title: &str);
156 fn set_edited(&mut self, edited: bool);
157 fn show_character_palette(&self);
158 fn minimize(&self);
159 fn zoom(&self);
160 fn toggle_full_screen(&self);
161 fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
162 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
163 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
164 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
165 fn on_moved(&self, callback: Box<dyn FnMut()>);
166 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
167 fn on_close(&self, callback: Box<dyn FnOnce()>);
168 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
169 fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
170 fn invalidate(&self);
171
172 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
173
174 #[cfg(any(test, feature = "test-support"))]
175 fn as_test(&mut self) -> Option<&mut TestWindow> {
176 None
177 }
178}
179
180pub trait PlatformDispatcher: Send + Sync {
181 fn is_main_thread(&self) -> bool;
182 fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
183 fn dispatch_on_main_thread(&self, runnable: Runnable);
184 fn dispatch_after(&self, duration: Duration, runnable: Runnable);
185 fn tick(&self, background_only: bool) -> bool;
186 fn park(&self);
187 fn unparker(&self) -> Unparker;
188
189 #[cfg(any(test, feature = "test-support"))]
190 fn as_test(&self) -> Option<&TestDispatcher> {
191 None
192 }
193}
194
195pub trait PlatformTextSystem: Send + Sync {
196 fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
197 fn all_font_families(&self) -> Vec<String>;
198 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
199 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
200 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
201 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
202 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
203 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
204 fn rasterize_glyph(
205 &self,
206 params: &RenderGlyphParams,
207 raster_bounds: Bounds<DevicePixels>,
208 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
209 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
210 fn wrap_line(
211 &self,
212 text: &str,
213 font_id: FontId,
214 font_size: Pixels,
215 width: Pixels,
216 ) -> Vec<usize>;
217}
218
219#[derive(Clone, Debug)]
220pub struct AppMetadata {
221 pub os_name: &'static str,
222 pub os_version: Option<SemanticVersion>,
223 pub app_version: Option<SemanticVersion>,
224}
225
226#[derive(PartialEq, Eq, Hash, Clone)]
227pub enum AtlasKey {
228 Glyph(RenderGlyphParams),
229 Svg(RenderSvgParams),
230 Image(RenderImageParams),
231}
232
233impl AtlasKey {
234 pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
235 match self {
236 AtlasKey::Glyph(params) => {
237 if params.is_emoji {
238 AtlasTextureKind::Polychrome
239 } else {
240 AtlasTextureKind::Monochrome
241 }
242 }
243 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
244 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
245 }
246 }
247}
248
249impl From<RenderGlyphParams> for AtlasKey {
250 fn from(params: RenderGlyphParams) -> Self {
251 Self::Glyph(params)
252 }
253}
254
255impl From<RenderSvgParams> for AtlasKey {
256 fn from(params: RenderSvgParams) -> Self {
257 Self::Svg(params)
258 }
259}
260
261impl From<RenderImageParams> for AtlasKey {
262 fn from(params: RenderImageParams) -> Self {
263 Self::Image(params)
264 }
265}
266
267pub trait PlatformAtlas: Send + Sync {
268 fn get_or_insert_with<'a>(
269 &self,
270 key: &AtlasKey,
271 build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
272 ) -> Result<AtlasTile>;
273
274 fn clear(&self);
275}
276
277#[derive(Clone, Debug, PartialEq, Eq)]
278#[repr(C)]
279pub struct AtlasTile {
280 pub(crate) texture_id: AtlasTextureId,
281 pub(crate) tile_id: TileId,
282 pub(crate) bounds: Bounds<DevicePixels>,
283}
284
285#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
286#[repr(C)]
287pub(crate) struct AtlasTextureId {
288 // We use u32 instead of usize for Metal Shader Language compatibility
289 pub(crate) index: u32,
290 pub(crate) kind: AtlasTextureKind,
291}
292
293#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
294#[repr(C)]
295pub(crate) enum AtlasTextureKind {
296 Monochrome = 0,
297 Polychrome = 1,
298 Path = 2,
299}
300
301#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
302#[repr(C)]
303pub(crate) struct TileId(pub(crate) u32);
304
305impl From<etagere::AllocId> for TileId {
306 fn from(id: etagere::AllocId) -> Self {
307 Self(id.serialize())
308 }
309}
310
311impl From<TileId> for etagere::AllocId {
312 fn from(id: TileId) -> Self {
313 Self::deserialize(id.0)
314 }
315}
316
317pub trait PlatformInputHandler: 'static {
318 fn selected_text_range(&mut self) -> Option<Range<usize>>;
319 fn marked_text_range(&mut self) -> Option<Range<usize>>;
320 fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
321 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
322 fn replace_and_mark_text_in_range(
323 &mut self,
324 range_utf16: Option<Range<usize>>,
325 new_text: &str,
326 new_selected_range: Option<Range<usize>>,
327 );
328 fn unmark_text(&mut self);
329 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
330}
331
332#[derive(Debug)]
333pub struct WindowOptions {
334 pub bounds: WindowBounds,
335 pub titlebar: Option<TitlebarOptions>,
336 pub center: bool,
337 pub focus: bool,
338 pub show: bool,
339 pub kind: WindowKind,
340 pub is_movable: bool,
341 pub display_id: Option<DisplayId>,
342}
343
344impl Default for WindowOptions {
345 fn default() -> Self {
346 Self {
347 bounds: WindowBounds::default(),
348 titlebar: Some(TitlebarOptions {
349 title: Default::default(),
350 appears_transparent: Default::default(),
351 traffic_light_position: Default::default(),
352 }),
353 center: false,
354 focus: true,
355 show: true,
356 kind: WindowKind::Normal,
357 is_movable: true,
358 display_id: None,
359 }
360 }
361}
362
363#[derive(Debug, Default)]
364pub struct TitlebarOptions {
365 pub title: Option<SharedString>,
366 pub appears_transparent: bool,
367 pub traffic_light_position: Option<Point<Pixels>>,
368}
369
370#[derive(Copy, Clone, Debug)]
371pub enum Appearance {
372 Light,
373 VibrantLight,
374 Dark,
375 VibrantDark,
376}
377
378impl Default for Appearance {
379 fn default() -> Self {
380 Self::Light
381 }
382}
383
384#[derive(Copy, Clone, Debug, PartialEq, Eq)]
385pub enum WindowKind {
386 Normal,
387 PopUp,
388}
389
390#[derive(Copy, Clone, Debug, PartialEq, Default)]
391pub enum WindowBounds {
392 Fullscreen,
393 #[default]
394 Maximized,
395 Fixed(Bounds<GlobalPixels>),
396}
397
398impl StaticColumnCount for WindowBounds {
399 fn column_count() -> usize {
400 5
401 }
402}
403
404impl Bind for WindowBounds {
405 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
406 let (region, next_index) = match self {
407 WindowBounds::Fullscreen => {
408 let next_index = statement.bind(&"Fullscreen", start_index)?;
409 (None, next_index)
410 }
411 WindowBounds::Maximized => {
412 let next_index = statement.bind(&"Maximized", start_index)?;
413 (None, next_index)
414 }
415 WindowBounds::Fixed(region) => {
416 let next_index = statement.bind(&"Fixed", start_index)?;
417 (Some(*region), next_index)
418 }
419 };
420
421 statement.bind(
422 ®ion.map(|region| {
423 (
424 region.origin.x,
425 region.origin.y,
426 region.size.width,
427 region.size.height,
428 )
429 }),
430 next_index,
431 )
432 }
433}
434
435impl Column for WindowBounds {
436 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
437 let (window_state, next_index) = String::column(statement, start_index)?;
438 let bounds = match window_state.as_str() {
439 "Fullscreen" => WindowBounds::Fullscreen,
440 "Maximized" => WindowBounds::Maximized,
441 "Fixed" => {
442 let ((x, y, width, height), _) = Column::column(statement, next_index)?;
443 let x: f64 = x;
444 let y: f64 = y;
445 let width: f64 = width;
446 let height: f64 = height;
447 WindowBounds::Fixed(Bounds {
448 origin: point(x.into(), y.into()),
449 size: size(width.into(), height.into()),
450 })
451 }
452 _ => bail!("Window State did not have a valid string"),
453 };
454
455 Ok((bounds, next_index + 4))
456 }
457}
458
459#[derive(Copy, Clone, Debug)]
460pub enum WindowAppearance {
461 Light,
462 VibrantLight,
463 Dark,
464 VibrantDark,
465}
466
467impl Default for WindowAppearance {
468 fn default() -> Self {
469 Self::Light
470 }
471}
472
473#[derive(Copy, Clone, Debug)]
474pub struct PathPromptOptions {
475 pub files: bool,
476 pub directories: bool,
477 pub multiple: bool,
478}
479
480#[derive(Copy, Clone, Debug)]
481pub enum PromptLevel {
482 Info,
483 Warning,
484 Critical,
485}
486
487/// The style of the cursor (pointer)
488#[derive(Copy, Clone, Debug)]
489pub enum CursorStyle {
490 Arrow,
491 IBeam,
492 Crosshair,
493 ClosedHand,
494 OpenHand,
495 PointingHand,
496 ResizeLeft,
497 ResizeRight,
498 ResizeLeftRight,
499 ResizeUp,
500 ResizeDown,
501 ResizeUpDown,
502 DisappearingItem,
503 IBeamCursorForVerticalLayout,
504 OperationNotAllowed,
505 DragLink,
506 DragCopy,
507 ContextualMenu,
508}
509
510impl Default for CursorStyle {
511 fn default() -> Self {
512 Self::Arrow
513 }
514}
515
516#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
517pub struct SemanticVersion {
518 major: usize,
519 minor: usize,
520 patch: usize,
521}
522
523impl FromStr for SemanticVersion {
524 type Err = anyhow::Error;
525
526 fn from_str(s: &str) -> Result<Self> {
527 let mut components = s.trim().split('.');
528 let major = components
529 .next()
530 .ok_or_else(|| anyhow!("missing major version number"))?
531 .parse()?;
532 let minor = components
533 .next()
534 .ok_or_else(|| anyhow!("missing minor version number"))?
535 .parse()?;
536 let patch = components
537 .next()
538 .ok_or_else(|| anyhow!("missing patch version number"))?
539 .parse()?;
540 Ok(Self {
541 major,
542 minor,
543 patch,
544 })
545 }
546}
547
548impl Display for SemanticVersion {
549 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
551 }
552}
553
554#[derive(Clone, Debug, Eq, PartialEq)]
555pub struct ClipboardItem {
556 pub(crate) text: String,
557 pub(crate) metadata: Option<String>,
558}
559
560impl ClipboardItem {
561 pub fn new(text: String) -> Self {
562 Self {
563 text,
564 metadata: None,
565 }
566 }
567
568 pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
569 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
570 self
571 }
572
573 pub fn text(&self) -> &String {
574 &self.text
575 }
576
577 pub fn metadata<T>(&self) -> Option<T>
578 where
579 T: for<'a> Deserialize<'a>,
580 {
581 self.metadata
582 .as_ref()
583 .and_then(|m| serde_json::from_str(m).ok())
584 }
585
586 pub(crate) fn text_hash(text: &str) -> u64 {
587 let mut hasher = SeaHasher::new();
588 text.hash(&mut hasher);
589 hasher.finish()
590 }
591}