1mod event;
2#[cfg(target_os = "macos")]
3pub mod mac;
4pub mod test;
5pub mod current {
6 #[cfg(target_os = "macos")]
7 pub use super::mac::*;
8}
9
10use crate::{
11 executor,
12 fonts::{
13 Features as FontFeatures, FontId, GlyphId, Metrics as FontMetrics,
14 Properties as FontProperties,
15 },
16 geometry::{
17 rect::{RectF, RectI},
18 vector::Vector2F,
19 },
20 keymap_matcher::KeymapMatcher,
21 text_layout::{LineLayout, RunStyle},
22 Action, ClipboardItem, Menu, Scene,
23};
24use anyhow::{anyhow, bail, Result};
25use async_task::Runnable;
26pub use event::*;
27use postage::oneshot;
28use schemars::JsonSchema;
29use serde::Deserialize;
30use sqlez::{
31 bindable::{Bind, Column, StaticColumnCount},
32 statement::Statement,
33};
34use std::{
35 any::Any,
36 fmt::{self, Debug, Display},
37 ops::Range,
38 path::{Path, PathBuf},
39 rc::Rc,
40 str::FromStr,
41 sync::Arc,
42};
43use time::UtcOffset;
44use uuid::Uuid;
45
46pub trait Platform: Send + Sync {
47 fn dispatcher(&self) -> Arc<dyn Dispatcher>;
48 fn fonts(&self) -> Arc<dyn FontSystem>;
49
50 fn activate(&self, ignoring_other_apps: bool);
51 fn hide(&self);
52 fn hide_other_apps(&self);
53 fn unhide_other_apps(&self);
54 fn quit(&self);
55
56 fn screen_by_id(&self, id: Uuid) -> Option<Rc<dyn Screen>>;
57 fn screens(&self) -> Vec<Rc<dyn Screen>>;
58
59 fn open_window(
60 &self,
61 id: usize,
62 options: WindowOptions,
63 executor: Rc<executor::Foreground>,
64 ) -> Box<dyn Window>;
65 fn main_window_id(&self) -> Option<usize>;
66
67 fn add_status_item(&self, id: usize) -> Box<dyn Window>;
68
69 fn write_to_clipboard(&self, item: ClipboardItem);
70 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
71 fn open_url(&self, url: &str);
72
73 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
74 fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
75 fn delete_credentials(&self, url: &str) -> Result<()>;
76
77 fn set_cursor_style(&self, style: CursorStyle);
78 fn should_auto_hide_scrollbars(&self) -> bool;
79
80 fn local_timezone(&self) -> UtcOffset;
81
82 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
83 fn app_path(&self) -> Result<PathBuf>;
84 fn app_version(&self) -> Result<AppVersion>;
85 fn os_name(&self) -> &'static str;
86 fn os_version(&self) -> Result<AppVersion>;
87 fn restart(&self);
88}
89
90pub(crate) trait ForegroundPlatform {
91 fn on_become_active(&self, callback: Box<dyn FnMut()>);
92 fn on_resign_active(&self, callback: Box<dyn FnMut()>);
93 fn on_quit(&self, callback: Box<dyn FnMut()>);
94
95 /// Handle the application being re-activated with no windows open.
96 fn on_reopen(&self, callback: Box<dyn FnMut()>);
97
98 fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
99 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
100 fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
101
102 fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>);
103 fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
104 fn on_will_open_menu(&self, callback: Box<dyn FnMut()>);
105 fn set_menus(&self, menus: Vec<Menu>, matcher: &KeymapMatcher);
106 fn prompt_for_paths(
107 &self,
108 options: PathPromptOptions,
109 ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
110 fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
111 fn reveal_path(&self, path: &Path);
112}
113
114pub trait Dispatcher: Send + Sync {
115 fn is_main_thread(&self) -> bool;
116 fn run_on_main_thread(&self, task: Runnable);
117}
118
119pub trait InputHandler {
120 fn selected_text_range(&self) -> Option<Range<usize>>;
121 fn marked_text_range(&self) -> Option<Range<usize>>;
122 fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
123 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
124 fn replace_and_mark_text_in_range(
125 &mut self,
126 range_utf16: Option<Range<usize>>,
127 new_text: &str,
128 new_selected_range: Option<Range<usize>>,
129 );
130 fn unmark_text(&mut self);
131 fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF>;
132}
133
134pub trait Screen: Debug {
135 fn as_any(&self) -> &dyn Any;
136 fn bounds(&self) -> RectF;
137 fn display_uuid(&self) -> Option<Uuid>;
138}
139
140pub trait Window {
141 fn bounds(&self) -> WindowBounds;
142 fn content_size(&self) -> Vector2F;
143 fn scale_factor(&self) -> f32;
144 fn titlebar_height(&self) -> f32;
145 fn appearance(&self) -> Appearance;
146 fn screen(&self) -> Rc<dyn Screen>;
147
148 fn as_any_mut(&mut self) -> &mut dyn Any;
149 fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
150 fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
151 fn activate(&self);
152 fn set_title(&mut self, title: &str);
153 fn set_edited(&mut self, edited: bool);
154 fn show_character_palette(&self);
155 fn minimize(&self);
156 fn zoom(&self);
157 fn present_scene(&mut self, scene: Scene);
158 fn toggle_full_screen(&self);
159
160 fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
161 fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>);
162 fn on_resize(&mut self, callback: Box<dyn FnMut()>);
163 fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>);
164 fn on_moved(&mut self, callback: Box<dyn FnMut()>);
165 fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
166 fn on_close(&mut self, callback: Box<dyn FnOnce()>);
167 fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
168 fn is_topmost_for_position(&self, position: Vector2F) -> bool;
169}
170
171#[derive(Debug)]
172pub struct WindowOptions<'a> {
173 pub bounds: WindowBounds,
174 pub titlebar: Option<TitlebarOptions<'a>>,
175 pub center: bool,
176 pub focus: bool,
177 pub show: bool,
178 pub kind: WindowKind,
179 pub is_movable: bool,
180 pub screen: Option<Rc<dyn Screen>>,
181}
182
183#[derive(Debug)]
184pub struct TitlebarOptions<'a> {
185 pub title: Option<&'a str>,
186 pub appears_transparent: bool,
187 pub traffic_light_position: Option<Vector2F>,
188}
189
190#[derive(Copy, Clone, Debug)]
191pub enum Appearance {
192 Light,
193 VibrantLight,
194 Dark,
195 VibrantDark,
196}
197
198impl Default for Appearance {
199 fn default() -> Self {
200 Self::Light
201 }
202}
203
204#[derive(Copy, Clone, Debug, PartialEq, Eq)]
205pub enum WindowKind {
206 Normal,
207 PopUp,
208}
209
210#[derive(Copy, Clone, Debug, PartialEq)]
211pub enum WindowBounds {
212 Fullscreen,
213 Maximized,
214 Fixed(RectF),
215}
216
217impl StaticColumnCount for WindowBounds {
218 fn column_count() -> usize {
219 5
220 }
221}
222
223impl Bind for WindowBounds {
224 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
225 let (region, next_index) = match self {
226 WindowBounds::Fullscreen => {
227 let next_index = statement.bind(&"Fullscreen", start_index)?;
228 (None, next_index)
229 }
230 WindowBounds::Maximized => {
231 let next_index = statement.bind(&"Maximized", start_index)?;
232 (None, next_index)
233 }
234 WindowBounds::Fixed(region) => {
235 let next_index = statement.bind(&"Fixed", start_index)?;
236 (Some(*region), next_index)
237 }
238 };
239
240 statement.bind(
241 ®ion.map(|region| {
242 (
243 region.min_x(),
244 region.min_y(),
245 region.width(),
246 region.height(),
247 )
248 }),
249 next_index,
250 )
251 }
252}
253
254impl Column for WindowBounds {
255 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
256 let (window_state, next_index) = String::column(statement, start_index)?;
257 let bounds = match window_state.as_str() {
258 "Fullscreen" => WindowBounds::Fullscreen,
259 "Maximized" => WindowBounds::Maximized,
260 "Fixed" => {
261 let ((x, y, width, height), _) = Column::column(statement, next_index)?;
262 WindowBounds::Fixed(RectF::new(
263 Vector2F::new(x, y),
264 Vector2F::new(width, height),
265 ))
266 }
267 _ => bail!("Window State did not have a valid string"),
268 };
269
270 Ok((bounds, next_index + 4))
271 }
272}
273
274pub struct PathPromptOptions {
275 pub files: bool,
276 pub directories: bool,
277 pub multiple: bool,
278}
279
280pub enum PromptLevel {
281 Info,
282 Warning,
283 Critical,
284}
285
286#[derive(Copy, Clone, Debug, Deserialize, JsonSchema)]
287pub enum CursorStyle {
288 Arrow,
289 ResizeLeftRight,
290 ResizeUpDown,
291 PointingHand,
292 IBeam,
293}
294
295impl Default for CursorStyle {
296 fn default() -> Self {
297 Self::Arrow
298 }
299}
300
301#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
302pub struct AppVersion {
303 major: usize,
304 minor: usize,
305 patch: usize,
306}
307
308impl FromStr for AppVersion {
309 type Err = anyhow::Error;
310
311 fn from_str(s: &str) -> Result<Self> {
312 let mut components = s.trim().split('.');
313 let major = components
314 .next()
315 .ok_or_else(|| anyhow!("missing major version number"))?
316 .parse()?;
317 let minor = components
318 .next()
319 .ok_or_else(|| anyhow!("missing minor version number"))?
320 .parse()?;
321 let patch = components
322 .next()
323 .ok_or_else(|| anyhow!("missing patch version number"))?
324 .parse()?;
325 Ok(Self {
326 major,
327 minor,
328 patch,
329 })
330 }
331}
332
333impl Display for AppVersion {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
336 }
337}
338
339#[derive(Copy, Clone, Debug)]
340pub enum RasterizationOptions {
341 Alpha,
342 Bgra,
343}
344
345pub trait FontSystem: Send + Sync {
346 fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
347 fn all_families(&self) -> Vec<String>;
348 fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
349 fn select_font(
350 &self,
351 font_ids: &[FontId],
352 properties: &FontProperties,
353 ) -> anyhow::Result<FontId>;
354 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
355 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF>;
356 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F>;
357 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
358 fn rasterize_glyph(
359 &self,
360 font_id: FontId,
361 font_size: f32,
362 glyph_id: GlyphId,
363 subpixel_shift: Vector2F,
364 scale_factor: f32,
365 options: RasterizationOptions,
366 ) -> Option<(RectI, Vec<u8>)>;
367 fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout;
368 fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize>;
369}
370
371impl<'a> Default for WindowOptions<'a> {
372 fn default() -> Self {
373 Self {
374 bounds: WindowBounds::Maximized,
375 titlebar: Some(TitlebarOptions {
376 title: Default::default(),
377 appears_transparent: Default::default(),
378 traffic_light_position: Default::default(),
379 }),
380 center: false,
381 focus: true,
382 show: true,
383 kind: WindowKind::Normal,
384 is_movable: true,
385 screen: None,
386 }
387 }
388}