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