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