1use std::{
2 borrow::Cow,
3 cell::RefCell,
4 rc::Rc,
5};
6
7use bytes::Bytes;
8pub use mundy::{
9 AccentColor,
10 Srgba,
11};
12use rustc_hash::FxHashMap;
13use torin::prelude::Size2D;
14
15use crate::{
16 accessibility::id::AccessibilityId,
17 current_context::CurrentContext,
18 prelude::{
19 GlobalContexts,
20 State,
21 try_consume_root_context,
22 },
23 user_event::{
24 GlobalUserEvent,
25 UserEvent,
26 },
27};
28
29#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Hash)]
31pub enum NavigationMode {
32 #[default]
34 NotKeyboard,
35 Keyboard,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
41pub enum PreferredTheme {
42 #[default]
44 Light,
45 Dark,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum TargetPlatform {
52 Windows,
54 MacOs,
56 Linux,
58 Android,
60 Ios,
62 Unknown,
64}
65
66impl TargetPlatform {
67 pub fn get() -> Self {
69 CurrentContext::try_with(|_| try_consume_root_context())
70 .flatten()
71 .unwrap_or_else(Self::detect)
72 }
73
74 pub fn detect() -> Self {
76 if cfg!(target_os = "windows") {
77 Self::Windows
78 } else if cfg!(target_os = "macos") {
79 Self::MacOs
80 } else if cfg!(target_os = "linux") {
81 Self::Linux
82 } else if cfg!(target_os = "android") {
83 Self::Android
84 } else if cfg!(target_os = "ios") {
85 Self::Ios
86 } else {
87 Self::Unknown
88 }
89 }
90
91 pub fn is_desktop(&self) -> bool {
93 matches!(self, Self::Windows | Self::MacOs | Self::Linux)
94 }
95
96 pub fn is_mobile(&self) -> bool {
98 matches!(self, Self::Android | Self::Ios)
99 }
100}
101
102#[derive(Clone)]
104pub struct Platform {
105 sender: Rc<dyn Fn(GlobalUserEvent)>,
106 windows: Rc<RefCell<FxHashMap<u64, PlatformWindow>>>,
107}
108
109impl Platform {
110 pub fn new(sender: impl Fn(GlobalUserEvent) + 'static) -> Self {
111 Self {
112 sender: Rc::new(sender),
113 windows: Rc::default(),
114 }
115 }
116
117 #[track_caller]
118 pub fn get() -> Self {
119 GlobalContexts::get().get_context()
120 }
121
122 #[track_caller]
124 pub fn window(&self, window_id: impl Into<u64>) -> PlatformWindow {
125 let window_id = window_id.into();
126 self.try_window(window_id)
127 .unwrap_or_else(|| panic!("Window {window_id} does not exist or has closed."))
128 }
129
130 pub fn try_window(&self, window_id: impl Into<u64>) -> Option<PlatformWindow> {
132 self.windows.borrow().get(&window_id.into()).cloned()
133 }
134
135 #[track_caller]
137 pub fn current_window(&self) -> PlatformWindow {
138 let window_id: CurrentWindowId = CurrentContext::try_with(|_| try_consume_root_context())
139 .flatten()
140 .expect(concat!(
141 "Platform::current_window() requires a Freya window context. ",
142 "In `spawn_global`, use `Platform::get().window(window_id)` or capture ",
143 "the current window before spawning."
144 ));
145 self.window(window_id.0)
146 }
147
148 pub fn register_window(&self, window: PlatformWindow) {
149 self.windows.borrow_mut().insert(window.id, window);
150 }
151
152 pub fn unregister_window(&self, window_id: impl Into<u64>) {
153 let _removed_window = self.windows.borrow_mut().remove(&window_id.into());
154 }
155
156 pub fn send(&self, event: GlobalUserEvent) {
157 (self.sender)(event)
158 }
159
160 pub fn open_url(&self, url: impl Into<String>) {
162 self.send(GlobalUserEvent::OpenUrl(url.into()));
163 }
164
165 pub fn exit(&self) {
167 self.send(GlobalUserEvent::Exit);
168 }
169
170 pub fn load_font(&self, font_name: impl Into<Cow<'static, str>>, font_data: impl Into<Bytes>) {
183 self.send(GlobalUserEvent::LoadFont {
184 font_name: font_name.into(),
185 font_data: font_data.into(),
186 });
187 }
188}
189
190#[derive(Clone, Copy)]
192pub struct CurrentWindowId(pub u64);
193
194#[derive(Clone)]
213pub struct PlatformWindow {
214 pub id: u64,
215 pub focused_accessibility_id: State<AccessibilityId>,
217 pub focused_accessibility_node: State<accesskit::Node>,
219 pub root_size: State<Size2D>,
221 pub scale_factor: State<f64>,
223 pub custom_scale_factor: State<f64>,
225 pub navigation_mode: State<NavigationMode>,
227 pub preferred_theme: State<PreferredTheme>,
229 pub is_app_focused: State<bool>,
231 pub accent_color: State<AccentColor>,
233 pub sender: Rc<dyn Fn(UserEvent)>,
235}
236
237impl PlatformWindow {
238 pub fn send(&self, event: UserEvent) {
239 (self.sender)(event)
240 }
241
242 pub fn request_redraw(&self) {
244 self.send(UserEvent::RequestRedraw);
245 }
246
247 pub fn set_custom_scale_factor(&self, custom_scale_factor: f64) {
249 self.send(UserEvent::SetCustomScaleFactor(custom_scale_factor));
250 }
251}