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