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) -> 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 fn convert_to_shortened_path(&self, path: &Path) -> PathBuf;
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 fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
95 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
96 fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
97
98 fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>);
99 fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
100 fn on_will_open_menu(&self, callback: Box<dyn FnMut()>);
101 fn set_menus(&self, menus: Vec<Menu>, matcher: &KeymapMatcher);
102 fn prompt_for_paths(
103 &self,
104 options: PathPromptOptions,
105 ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
106 fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
107 fn reveal_path(&self, path: &Path);
108}
109
110pub trait Dispatcher: Send + Sync {
111 fn is_main_thread(&self) -> bool;
112 fn run_on_main_thread(&self, task: Runnable);
113}
114
115pub trait InputHandler {
116 fn selected_text_range(&self) -> Option<Range<usize>>;
117 fn marked_text_range(&self) -> Option<Range<usize>>;
118 fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
119 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
120 fn replace_and_mark_text_in_range(
121 &mut self,
122 range_utf16: Option<Range<usize>>,
123 new_text: &str,
124 new_selected_range: Option<Range<usize>>,
125 );
126 fn unmark_text(&mut self);
127 fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF>;
128}
129
130pub trait Screen: Debug {
131 fn as_any(&self) -> &dyn Any;
132 fn bounds(&self) -> RectF;
133 fn display_uuid(&self) -> Option<Uuid>;
134}
135
136pub trait Window {
137 fn bounds(&self) -> WindowBounds;
138 fn content_size(&self) -> Vector2F;
139 fn scale_factor(&self) -> f32;
140 fn titlebar_height(&self) -> f32;
141 fn appearance(&self) -> Appearance;
142 fn screen(&self) -> Rc<dyn Screen>;
143
144 fn as_any_mut(&mut self) -> &mut dyn Any;
145 fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
146 fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
147 fn activate(&self);
148 fn set_title(&mut self, title: &str);
149 fn set_edited(&mut self, edited: bool);
150 fn show_character_palette(&self);
151 fn minimize(&self);
152 fn zoom(&self);
153 fn present_scene(&mut self, scene: Scene);
154 fn toggle_full_screen(&self);
155
156 fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
157 fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>);
158 fn on_resize(&mut self, callback: Box<dyn FnMut()>);
159 fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>);
160 fn on_moved(&mut self, callback: Box<dyn FnMut()>);
161 fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
162 fn on_close(&mut self, callback: Box<dyn FnOnce()>);
163 fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
164 fn is_topmost_for_position(&self, position: Vector2F) -> bool;
165}
166
167#[derive(Debug)]
168pub struct WindowOptions<'a> {
169 pub bounds: WindowBounds,
170 pub titlebar: Option<TitlebarOptions<'a>>,
171 pub center: bool,
172 pub focus: bool,
173 pub kind: WindowKind,
174 pub is_movable: bool,
175 pub screen: Option<Rc<dyn Screen>>,
176}
177
178#[derive(Debug)]
179pub struct TitlebarOptions<'a> {
180 pub title: Option<&'a str>,
181 pub appears_transparent: bool,
182 pub traffic_light_position: Option<Vector2F>,
183}
184
185#[derive(Copy, Clone, Debug)]
186pub enum Appearance {
187 Light,
188 VibrantLight,
189 Dark,
190 VibrantDark,
191}
192
193impl Default for Appearance {
194 fn default() -> Self {
195 Self::Light
196 }
197}
198
199#[derive(Copy, Clone, Debug, PartialEq, Eq)]
200pub enum WindowKind {
201 Normal,
202 PopUp,
203}
204
205#[derive(Copy, Clone, Debug, PartialEq)]
206pub enum WindowBounds {
207 Fullscreen,
208 Maximized,
209 Fixed(RectF),
210}
211
212impl StaticColumnCount for WindowBounds {
213 fn column_count() -> usize {
214 5
215 }
216}
217
218impl Bind for WindowBounds {
219 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
220 let (region, next_index) = match self {
221 WindowBounds::Fullscreen => {
222 let next_index = statement.bind("Fullscreen", start_index)?;
223 (None, next_index)
224 }
225 WindowBounds::Maximized => {
226 let next_index = statement.bind("Maximized", start_index)?;
227 (None, next_index)
228 }
229 WindowBounds::Fixed(region) => {
230 let next_index = statement.bind("Fixed", start_index)?;
231 (Some(*region), next_index)
232 }
233 };
234
235 statement.bind(
236 region.map(|region| {
237 (
238 region.min_x(),
239 region.min_y(),
240 region.width(),
241 region.height(),
242 )
243 }),
244 next_index,
245 )
246 }
247}
248
249impl Column for WindowBounds {
250 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
251 let (window_state, next_index) = String::column(statement, start_index)?;
252 let bounds = match window_state.as_str() {
253 "Fullscreen" => WindowBounds::Fullscreen,
254 "Maximized" => WindowBounds::Maximized,
255 "Fixed" => {
256 let ((x, y, width, height), _) = Column::column(statement, next_index)?;
257 WindowBounds::Fixed(RectF::new(
258 Vector2F::new(x, y),
259 Vector2F::new(width, height),
260 ))
261 }
262 _ => bail!("Window State did not have a valid string"),
263 };
264
265 Ok((bounds, next_index + 4))
266 }
267}
268
269pub struct PathPromptOptions {
270 pub files: bool,
271 pub directories: bool,
272 pub multiple: bool,
273}
274
275pub enum PromptLevel {
276 Info,
277 Warning,
278 Critical,
279}
280
281#[derive(Copy, Clone, Debug, Deserialize)]
282pub enum CursorStyle {
283 Arrow,
284 ResizeLeftRight,
285 ResizeUpDown,
286 PointingHand,
287 IBeam,
288}
289
290impl Default for CursorStyle {
291 fn default() -> Self {
292 Self::Arrow
293 }
294}
295
296#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
297pub struct AppVersion {
298 major: usize,
299 minor: usize,
300 patch: usize,
301}
302
303impl FromStr for AppVersion {
304 type Err = anyhow::Error;
305
306 fn from_str(s: &str) -> Result<Self> {
307 let mut components = s.trim().split('.');
308 let major = components
309 .next()
310 .ok_or_else(|| anyhow!("missing major version number"))?
311 .parse()?;
312 let minor = components
313 .next()
314 .ok_or_else(|| anyhow!("missing minor version number"))?
315 .parse()?;
316 let patch = components
317 .next()
318 .ok_or_else(|| anyhow!("missing patch version number"))?
319 .parse()?;
320 Ok(Self {
321 major,
322 minor,
323 patch,
324 })
325 }
326}
327
328impl Display for AppVersion {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
331 }
332}
333
334#[derive(Copy, Clone, Debug)]
335pub enum RasterizationOptions {
336 Alpha,
337 Bgra,
338}
339
340pub trait FontSystem: Send + Sync {
341 fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
342 fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
343 fn select_font(
344 &self,
345 font_ids: &[FontId],
346 properties: &FontProperties,
347 ) -> anyhow::Result<FontId>;
348 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
349 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF>;
350 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F>;
351 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
352 fn rasterize_glyph(
353 &self,
354 font_id: FontId,
355 font_size: f32,
356 glyph_id: GlyphId,
357 subpixel_shift: Vector2F,
358 scale_factor: f32,
359 options: RasterizationOptions,
360 ) -> Option<(RectI, Vec<u8>)>;
361 fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout;
362 fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize>;
363}
364
365impl<'a> Default for WindowOptions<'a> {
366 fn default() -> Self {
367 Self {
368 bounds: WindowBounds::Maximized,
369 titlebar: Some(TitlebarOptions {
370 title: Default::default(),
371 appears_transparent: Default::default(),
372 traffic_light_position: Default::default(),
373 }),
374 center: false,
375 focus: true,
376 kind: WindowKind::Normal,
377 is_movable: true,
378 screen: None,
379 }
380 }
381}