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