1mod keystroke;
2#[cfg(target_os = "macos")]
3mod mac;
4#[cfg(any(test, feature = "test"))]
5mod test;
6
7use crate::{
8 AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, FontRun,
9 GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams,
10 RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size,
11};
12use anyhow::anyhow;
13use async_task::Runnable;
14use futures::channel::oneshot;
15use seahash::SeaHasher;
16use serde::{Deserialize, Serialize};
17use std::borrow::Cow;
18use std::hash::{Hash, Hasher};
19use std::{
20 any::Any,
21 fmt::{self, Debug, Display},
22 ops::Range,
23 path::{Path, PathBuf},
24 rc::Rc,
25 str::FromStr,
26 sync::Arc,
27};
28
29pub use keystroke::*;
30#[cfg(target_os = "macos")]
31pub use mac::*;
32#[cfg(any(test, feature = "test"))]
33pub use test::*;
34pub use time::UtcOffset;
35
36#[cfg(target_os = "macos")]
37pub(crate) fn current_platform() -> Arc<dyn Platform> {
38 Arc::new(MacPlatform::new())
39}
40
41pub(crate) trait Platform: 'static {
42 fn executor(&self) -> Executor;
43 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
44
45 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
46 fn quit(&self);
47 fn restart(&self);
48 fn activate(&self, ignoring_other_apps: bool);
49 fn hide(&self);
50 fn hide_other_apps(&self);
51 fn unhide_other_apps(&self);
52
53 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
54 fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
55 fn main_window(&self) -> Option<AnyWindowHandle>;
56 fn open_window(
57 &self,
58 handle: AnyWindowHandle,
59 options: WindowOptions,
60 ) -> Box<dyn PlatformWindow>;
61
62 fn set_display_link_output_callback(
63 &self,
64 display_id: DisplayId,
65 callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
66 );
67 fn start_display_link(&self, display_id: DisplayId);
68 fn stop_display_link(&self, display_id: DisplayId);
69 // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
70
71 fn open_url(&self, url: &str);
72 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
73 fn prompt_for_paths(
74 &self,
75 options: PathPromptOptions,
76 ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
77 fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
78 fn reveal_path(&self, path: &Path);
79
80 fn on_become_active(&self, callback: Box<dyn FnMut()>);
81 fn on_resign_active(&self, callback: Box<dyn FnMut()>);
82 fn on_quit(&self, callback: Box<dyn FnMut()>);
83 fn on_reopen(&self, callback: Box<dyn FnMut()>);
84 fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
85
86 fn os_name(&self) -> &'static str;
87 fn os_version(&self) -> Result<SemanticVersion>;
88 fn app_version(&self) -> Result<SemanticVersion>;
89 fn app_path(&self) -> Result<PathBuf>;
90 fn local_timezone(&self) -> UtcOffset;
91 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
92
93 fn set_cursor_style(&self, style: CursorStyle);
94 fn should_auto_hide_scrollbars(&self) -> bool;
95
96 fn write_to_clipboard(&self, item: ClipboardItem);
97 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
98
99 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
100 fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
101 fn delete_credentials(&self, url: &str) -> Result<()>;
102}
103
104pub trait PlatformDisplay: Debug {
105 fn id(&self) -> DisplayId;
106 fn as_any(&self) -> &dyn Any;
107 fn bounds(&self) -> Bounds<GlobalPixels>;
108}
109
110#[derive(PartialEq, Eq, Hash, Copy, Clone)]
111pub struct DisplayId(pub(crate) u32);
112
113impl Debug for DisplayId {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(f, "DisplayId({})", self.0)
116 }
117}
118
119unsafe impl Send for DisplayId {}
120
121pub(crate) trait PlatformWindow {
122 fn bounds(&self) -> WindowBounds;
123 fn content_size(&self) -> Size<Pixels>;
124 fn scale_factor(&self) -> f32;
125 fn titlebar_height(&self) -> Pixels;
126 fn appearance(&self) -> WindowAppearance;
127 fn display(&self) -> Rc<dyn PlatformDisplay>;
128 fn mouse_position(&self) -> Point<Pixels>;
129 fn as_any_mut(&mut self) -> &mut dyn Any;
130 fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
131 fn prompt(
132 &self,
133 level: WindowPromptLevel,
134 msg: &str,
135 answers: &[&str],
136 ) -> oneshot::Receiver<usize>;
137 fn activate(&self);
138 fn set_title(&mut self, title: &str);
139 fn set_edited(&mut self, edited: bool);
140 fn show_character_palette(&self);
141 fn minimize(&self);
142 fn zoom(&self);
143 fn toggle_full_screen(&self);
144 fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
145 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
146 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
147 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
148 fn on_moved(&self, callback: Box<dyn FnMut()>);
149 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
150 fn on_close(&self, callback: Box<dyn FnOnce()>);
151 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
152 fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
153 fn draw(&self, scene: Scene);
154
155 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
156}
157
158pub trait PlatformDispatcher: Send + Sync {
159 fn is_main_thread(&self) -> bool;
160 fn dispatch(&self, task: Runnable);
161 fn dispatch_on_main_thread(&self, task: Runnable);
162}
163
164pub trait PlatformTextSystem: Send + Sync {
165 fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
166 fn all_font_families(&self) -> Vec<String>;
167 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
168 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
169 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
170 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
171 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
172 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
173 fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
174 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
175 fn wrap_line(
176 &self,
177 text: &str,
178 font_id: FontId,
179 font_size: Pixels,
180 width: Pixels,
181 ) -> Vec<usize>;
182}
183
184#[derive(PartialEq, Eq, Hash, Clone)]
185pub enum AtlasKey {
186 Glyph(RenderGlyphParams),
187 Svg(RenderSvgParams),
188 Image(RenderImageParams),
189}
190
191impl AtlasKey {
192 pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
193 match self {
194 AtlasKey::Glyph(params) => {
195 if params.is_emoji {
196 AtlasTextureKind::Polychrome
197 } else {
198 AtlasTextureKind::Monochrome
199 }
200 }
201 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
202 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
203 }
204 }
205}
206
207impl From<RenderGlyphParams> for AtlasKey {
208 fn from(params: RenderGlyphParams) -> Self {
209 Self::Glyph(params)
210 }
211}
212
213impl From<RenderSvgParams> for AtlasKey {
214 fn from(params: RenderSvgParams) -> Self {
215 Self::Svg(params)
216 }
217}
218
219impl From<RenderImageParams> for AtlasKey {
220 fn from(params: RenderImageParams) -> Self {
221 Self::Image(params)
222 }
223}
224
225pub trait PlatformAtlas: Send + Sync {
226 fn get_or_insert_with<'a>(
227 &self,
228 key: &AtlasKey,
229 build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
230 ) -> Result<AtlasTile>;
231
232 fn clear(&self);
233}
234
235#[derive(Clone, Debug, PartialEq, Eq)]
236#[repr(C)]
237pub struct AtlasTile {
238 pub(crate) texture_id: AtlasTextureId,
239 pub(crate) tile_id: TileId,
240 pub(crate) bounds: Bounds<DevicePixels>,
241}
242
243#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
244#[repr(C)]
245pub(crate) struct AtlasTextureId {
246 // We use u32 instead of usize for Metal Shader Language compatibility
247 pub(crate) index: u32,
248 pub(crate) kind: AtlasTextureKind,
249}
250
251#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
252#[repr(C)]
253pub(crate) enum AtlasTextureKind {
254 Monochrome = 0,
255 Polychrome = 1,
256 Path = 2,
257}
258
259#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
260#[repr(C)]
261pub(crate) struct TileId(pub(crate) u32);
262
263impl From<etagere::AllocId> for TileId {
264 fn from(id: etagere::AllocId) -> Self {
265 Self(id.serialize())
266 }
267}
268
269impl From<TileId> for etagere::AllocId {
270 fn from(id: TileId) -> Self {
271 Self::deserialize(id.0)
272 }
273}
274
275pub trait PlatformInputHandler {
276 fn selected_text_range(&self) -> Option<Range<usize>>;
277 fn marked_text_range(&self) -> Option<Range<usize>>;
278 fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
279 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
280 fn replace_and_mark_text_in_range(
281 &mut self,
282 range_utf16: Option<Range<usize>>,
283 new_text: &str,
284 new_selected_range: Option<Range<usize>>,
285 );
286 fn unmark_text(&mut self);
287 fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
288}
289
290#[derive(Debug)]
291pub struct WindowOptions {
292 pub bounds: WindowBounds,
293 pub titlebar: Option<TitlebarOptions>,
294 pub center: bool,
295 pub focus: bool,
296 pub show: bool,
297 pub kind: WindowKind,
298 pub is_movable: bool,
299 pub display_id: Option<DisplayId>,
300}
301
302impl Default for WindowOptions {
303 fn default() -> Self {
304 Self {
305 bounds: WindowBounds::default(),
306 titlebar: Some(TitlebarOptions {
307 title: Default::default(),
308 appears_transparent: Default::default(),
309 traffic_light_position: Default::default(),
310 }),
311 center: false,
312 focus: true,
313 show: true,
314 kind: WindowKind::Normal,
315 is_movable: true,
316 display_id: None,
317 }
318 }
319}
320
321#[derive(Debug, Default)]
322pub struct TitlebarOptions {
323 pub title: Option<SharedString>,
324 pub appears_transparent: bool,
325 pub traffic_light_position: Option<Point<Pixels>>,
326}
327
328#[derive(Copy, Clone, Debug)]
329pub enum Appearance {
330 Light,
331 VibrantLight,
332 Dark,
333 VibrantDark,
334}
335
336impl Default for Appearance {
337 fn default() -> Self {
338 Self::Light
339 }
340}
341
342#[derive(Copy, Clone, Debug, PartialEq, Eq)]
343pub enum WindowKind {
344 Normal,
345 PopUp,
346}
347
348#[derive(Copy, Clone, Debug, PartialEq, Default)]
349pub enum WindowBounds {
350 Fullscreen,
351 #[default]
352 Maximized,
353 Fixed(Bounds<GlobalPixels>),
354}
355
356#[derive(Copy, Clone, Debug)]
357pub enum WindowAppearance {
358 Light,
359 VibrantLight,
360 Dark,
361 VibrantDark,
362}
363
364impl Default for WindowAppearance {
365 fn default() -> Self {
366 Self::Light
367 }
368}
369
370#[derive(Copy, Clone, Debug, PartialEq, Default)]
371pub enum WindowPromptLevel {
372 #[default]
373 Info,
374 Warning,
375 Critical,
376}
377
378#[derive(Copy, Clone, Debug)]
379pub struct PathPromptOptions {
380 pub files: bool,
381 pub directories: bool,
382 pub multiple: bool,
383}
384
385#[derive(Copy, Clone, Debug)]
386pub enum PromptLevel {
387 Info,
388 Warning,
389 Critical,
390}
391
392#[derive(Copy, Clone, Debug)]
393pub enum CursorStyle {
394 Arrow,
395 ResizeLeftRight,
396 ResizeUpDown,
397 PointingHand,
398 IBeam,
399}
400
401impl Default for CursorStyle {
402 fn default() -> Self {
403 Self::Arrow
404 }
405}
406
407#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
408pub struct SemanticVersion {
409 major: usize,
410 minor: usize,
411 patch: usize,
412}
413
414impl FromStr for SemanticVersion {
415 type Err = anyhow::Error;
416
417 fn from_str(s: &str) -> Result<Self> {
418 let mut components = s.trim().split('.');
419 let major = components
420 .next()
421 .ok_or_else(|| anyhow!("missing major version number"))?
422 .parse()?;
423 let minor = components
424 .next()
425 .ok_or_else(|| anyhow!("missing minor version number"))?
426 .parse()?;
427 let patch = components
428 .next()
429 .ok_or_else(|| anyhow!("missing patch version number"))?
430 .parse()?;
431 Ok(Self {
432 major,
433 minor,
434 patch,
435 })
436 }
437}
438
439impl Display for SemanticVersion {
440 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
442 }
443}
444
445#[derive(Clone, Debug, Eq, PartialEq)]
446pub struct ClipboardItem {
447 pub(crate) text: String,
448 pub(crate) metadata: Option<String>,
449}
450
451impl ClipboardItem {
452 pub fn new(text: String) -> Self {
453 Self {
454 text,
455 metadata: None,
456 }
457 }
458
459 pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
460 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
461 self
462 }
463
464 pub fn text(&self) -> &String {
465 &self.text
466 }
467
468 pub fn metadata<T>(&self) -> Option<T>
469 where
470 T: for<'a> Deserialize<'a>,
471 {
472 self.metadata
473 .as_ref()
474 .and_then(|m| serde_json::from_str(m).ok())
475 }
476
477 pub(crate) fn text_hash(text: &str) -> u64 {
478 let mut hasher = SeaHasher::new();
479 text.hash(&mut hasher);
480 hasher.finish()
481 }
482}