1#![allow(dead_code)]
66
67pub use uzor;
68
69use std::collections::VecDeque;
70use std::sync::{Arc, Mutex};
71
72use uzor::platform::{
73 backends::PlatformBackend,
74 types::{PlatformError, WindowId, SystemIntegration},
75 PlatformEvent, SystemTheme, WindowConfig,
76};
77
78#[cfg(target_os = "android")]
80mod android;
81#[cfg(target_os = "android")]
82use android::AndroidBackend;
83
84#[cfg(target_os = "ios")]
85mod ios;
86#[cfg(target_os = "ios")]
87use ios::IosBackend;
88
89mod common;
90
91pub struct MobilePlatform {
100 state: Arc<Mutex<MobileState>>,
101}
102
103struct MobileState {
104 window: Option<MobileWindow>,
106
107 #[cfg(target_os = "android")]
109 backend: AndroidBackend,
110 #[cfg(target_os = "ios")]
111 backend: IosBackend,
112 #[cfg(not(any(target_os = "android", target_os = "ios")))]
113 backend: StubBackend,
114
115 event_queue: VecDeque<PlatformEvent>,
117
118 ime_position: (f64, f64),
120 ime_allowed: bool,
121}
122
123struct MobileWindow {
124 id: WindowId,
125 config: WindowConfig,
126 width: u32,
127 height: u32,
128 scale_factor: f64,
129}
130
131impl MobilePlatform {
132 pub fn new() -> Result<Self, PlatformError> {
138 #[cfg(target_os = "android")]
139 let backend = AndroidBackend::new()
140 .map_err(|e| PlatformError::CreationFailed(format!("Android backend init failed: {}", e)))?;
141
142 #[cfg(target_os = "ios")]
143 let backend = IosBackend::new()
144 .map_err(|e| PlatformError::CreationFailed(format!("iOS backend init failed: {}", e)))?;
145
146 #[cfg(not(any(target_os = "android", target_os = "ios")))]
147 let backend = StubBackend::new();
148
149 Ok(Self {
150 state: Arc::new(Mutex::new(MobileState {
151 window: None,
152 backend,
153 event_queue: VecDeque::new(),
154 ime_position: (0.0, 0.0),
155 ime_allowed: false,
156 })),
157 })
158 }
159
160 pub fn safe_area_insets(&self) -> (f64, f64, f64, f64) {
164 let state = self.state.lock().unwrap();
165 state.backend.safe_area_insets()
166 }
167
168 pub fn orientation(&self) -> ScreenOrientation {
170 let state = self.state.lock().unwrap();
171 state.backend.orientation()
172 }
173
174 pub fn haptic_feedback(&mut self, style: HapticStyle) {
180 let mut state = self.state.lock().unwrap();
181 state.backend.haptic_feedback(style);
182 }
183}
184
185impl Default for MobilePlatform {
186 fn default() -> Self {
187 Self::new().expect("Failed to create mobile platform")
188 }
189}
190
191impl PlatformBackend for MobilePlatform {
196 fn name(&self) -> &'static str {
197 todo!("not yet implemented for this platform")
198 }
199
200 fn create_window(&mut self, config: WindowConfig) -> Result<WindowId, PlatformError> {
201 let mut state = self.state.lock().unwrap();
202
203 if state.window.is_some() {
205 return Err(PlatformError::CreationFailed(
206 "Mobile platform supports only one window".to_string(),
207 ));
208 }
209
210 let window_id = WindowId::new();
211
212 let (width, height) = state.backend.screen_size();
214 let scale_factor = state.backend.scale_factor();
215
216 let window = MobileWindow {
217 id: window_id,
218 config,
219 width,
220 height,
221 scale_factor,
222 };
223
224 state.window = Some(window);
225 state.event_queue.push_back(PlatformEvent::WindowCreated);
226
227 Ok(window_id)
228 }
229
230 fn close_window(&mut self, window_id: WindowId) -> Result<(), PlatformError> {
231 let mut state = self.state.lock().unwrap();
232
233 if let Some(window) = &state.window {
234 if window.id == window_id {
235 state.window = None;
236 state.event_queue.push_back(PlatformEvent::WindowDestroyed);
237 return Ok(());
238 }
239 }
240
241 Err(PlatformError::WindowNotFound)
242 }
243
244 fn primary_window(&self) -> Option<WindowId> {
245 todo!("not yet implemented for this platform")
246 }
247
248 fn poll_events(&mut self) -> Vec<PlatformEvent> {
249 todo!("not yet implemented for this platform")
250 }
251
252 fn request_redraw(&self, id: WindowId) {
253 let _ = id;
254 }
256}
257
258impl SystemIntegration for MobilePlatform {
263 fn get_clipboard(&self) -> Option<String> {
264 todo!("not yet implemented for this platform")
265 }
266
267 fn set_clipboard(&self, _text: &str) {
268 todo!("not yet implemented for this platform")
269 }
270
271 fn get_system_theme(&self) -> Option<SystemTheme> {
272 let state = self.state.lock().unwrap();
273 state.backend.system_theme()
274 }
275}
276
277#[derive(Clone, Copy, Debug, PartialEq, Eq)]
283pub enum ScreenOrientation {
284 Portrait,
286 Landscape,
288 PortraitUpsideDown,
290 LandscapeRight,
292}
293
294#[derive(Clone, Copy, Debug, PartialEq, Eq)]
296pub enum HapticStyle {
297 Light,
299 Medium,
301 Heavy,
303 Selection,
305 Success,
307 Warning,
309 Error,
311}
312
313#[cfg(not(any(target_os = "android", target_os = "ios")))]
318struct StubBackend;
319
320#[cfg(not(any(target_os = "android", target_os = "ios")))]
321impl StubBackend {
322 fn new() -> Self {
323 StubBackend
324 }
325
326 fn screen_size(&self) -> (u32, u32) {
327 (800, 600)
328 }
329
330 fn scale_factor(&self) -> f64 {
331 1.0
332 }
333
334 fn safe_area_insets(&self) -> (f64, f64, f64, f64) {
335 (0.0, 0.0, 0.0, 0.0)
336 }
337
338 fn orientation(&self) -> ScreenOrientation {
339 ScreenOrientation::Portrait
340 }
341
342 fn haptic_feedback(&mut self, _style: HapticStyle) {}
343
344 fn poll_event(&mut self) -> Option<PlatformEvent> {
345 None
346 }
347
348 fn set_title(&mut self, _title: &str) {}
349
350 fn get_clipboard_text(&self) -> Option<String> {
351 None
352 }
353
354 fn set_clipboard_text(&mut self, _text: &str) -> Result<(), String> {
355 Err("Clipboard not available on stub backend".to_string())
356 }
357
358 fn open_url(&self, _url: &str) -> Result<(), String> {
359 Err("URL opening not available on stub backend".to_string())
360 }
361
362 fn system_theme(&self) -> Option<SystemTheme> {
363 Some(SystemTheme::Light)
364 }
365
366 fn set_ime_position(&mut self, _x: f64, _y: f64) {}
367
368 fn show_keyboard(&mut self) {}
369
370 fn hide_keyboard(&mut self) {}
371}
372
373#[cfg(test)]
378mod tests {
379 use super::*;
380
381 #[test]
382 fn test_haptic_style_variants() {
383 let styles = vec![
384 HapticStyle::Light,
385 HapticStyle::Medium,
386 HapticStyle::Heavy,
387 HapticStyle::Selection,
388 HapticStyle::Success,
389 HapticStyle::Warning,
390 HapticStyle::Error,
391 ];
392
393 assert_eq!(styles.len(), 7);
394 }
395
396 #[test]
397 fn test_screen_orientation_variants() {
398 let orientations = vec![
399 ScreenOrientation::Portrait,
400 ScreenOrientation::Landscape,
401 ScreenOrientation::PortraitUpsideDown,
402 ScreenOrientation::LandscapeRight,
403 ];
404
405 assert_eq!(orientations.len(), 4);
406 }
407
408 #[cfg(not(any(target_os = "android", target_os = "ios")))]
409 #[test]
410 fn test_stub_backend() {
411 let backend = StubBackend::new();
412
413 assert_eq!(backend.screen_size(), (800, 600));
414 assert_eq!(backend.scale_factor(), 1.0);
415 assert_eq!(backend.safe_area_insets(), (0.0, 0.0, 0.0, 0.0));
416 assert_eq!(backend.orientation(), ScreenOrientation::Portrait);
417 assert_eq!(backend.get_clipboard_text(), None);
418 assert_eq!(backend.system_theme(), Some(SystemTheme::Light));
419 }
420}