#![allow(dead_code)]
pub use uzor;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use uzor::platform::{
backends::PlatformBackend,
types::{PlatformError, WindowId, SystemIntegration},
PlatformEvent, SystemTheme, WindowConfig,
};
#[cfg(target_os = "android")]
mod android;
#[cfg(target_os = "android")]
use android::AndroidBackend;
#[cfg(target_os = "ios")]
mod ios;
#[cfg(target_os = "ios")]
use ios::IosBackend;
mod common;
pub struct MobilePlatform {
state: Arc<Mutex<MobileState>>,
}
struct MobileState {
window: Option<MobileWindow>,
#[cfg(target_os = "android")]
backend: AndroidBackend,
#[cfg(target_os = "ios")]
backend: IosBackend,
#[cfg(not(any(target_os = "android", target_os = "ios")))]
backend: StubBackend,
event_queue: VecDeque<PlatformEvent>,
ime_position: (f64, f64),
ime_allowed: bool,
}
struct MobileWindow {
id: WindowId,
config: WindowConfig,
width: u32,
height: u32,
scale_factor: f64,
}
impl MobilePlatform {
pub fn new() -> Result<Self, PlatformError> {
#[cfg(target_os = "android")]
let backend = AndroidBackend::new()
.map_err(|e| PlatformError::CreationFailed(format!("Android backend init failed: {}", e)))?;
#[cfg(target_os = "ios")]
let backend = IosBackend::new()
.map_err(|e| PlatformError::CreationFailed(format!("iOS backend init failed: {}", e)))?;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let backend = StubBackend::new();
Ok(Self {
state: Arc::new(Mutex::new(MobileState {
window: None,
backend,
event_queue: VecDeque::new(),
ime_position: (0.0, 0.0),
ime_allowed: false,
})),
})
}
pub fn safe_area_insets(&self) -> (f64, f64, f64, f64) {
let state = self.state.lock().unwrap();
state.backend.safe_area_insets()
}
pub fn orientation(&self) -> ScreenOrientation {
let state = self.state.lock().unwrap();
state.backend.orientation()
}
pub fn haptic_feedback(&mut self, style: HapticStyle) {
let mut state = self.state.lock().unwrap();
state.backend.haptic_feedback(style);
}
}
impl Default for MobilePlatform {
fn default() -> Self {
Self::new().expect("Failed to create mobile platform")
}
}
impl PlatformBackend for MobilePlatform {
fn name(&self) -> &'static str {
todo!("not yet implemented for this platform")
}
fn create_window(&mut self, config: WindowConfig) -> Result<WindowId, PlatformError> {
let mut state = self.state.lock().unwrap();
if state.window.is_some() {
return Err(PlatformError::CreationFailed(
"Mobile platform supports only one window".to_string(),
));
}
let window_id = WindowId::new();
let (width, height) = state.backend.screen_size();
let scale_factor = state.backend.scale_factor();
let window = MobileWindow {
id: window_id,
config,
width,
height,
scale_factor,
};
state.window = Some(window);
state.event_queue.push_back(PlatformEvent::WindowCreated);
Ok(window_id)
}
fn close_window(&mut self, window_id: WindowId) -> Result<(), PlatformError> {
let mut state = self.state.lock().unwrap();
if let Some(window) = &state.window {
if window.id == window_id {
state.window = None;
state.event_queue.push_back(PlatformEvent::WindowDestroyed);
return Ok(());
}
}
Err(PlatformError::WindowNotFound)
}
fn primary_window(&self) -> Option<WindowId> {
todo!("not yet implemented for this platform")
}
fn poll_events(&mut self) -> Vec<PlatformEvent> {
todo!("not yet implemented for this platform")
}
fn request_redraw(&self, id: WindowId) {
let _ = id;
}
}
impl SystemIntegration for MobilePlatform {
fn get_clipboard(&self) -> Option<String> {
todo!("not yet implemented for this platform")
}
fn set_clipboard(&self, _text: &str) {
todo!("not yet implemented for this platform")
}
fn get_system_theme(&self) -> Option<SystemTheme> {
let state = self.state.lock().unwrap();
state.backend.system_theme()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScreenOrientation {
Portrait,
Landscape,
PortraitUpsideDown,
LandscapeRight,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HapticStyle {
Light,
Medium,
Heavy,
Selection,
Success,
Warning,
Error,
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
struct StubBackend;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
impl StubBackend {
fn new() -> Self {
StubBackend
}
fn screen_size(&self) -> (u32, u32) {
(800, 600)
}
fn scale_factor(&self) -> f64 {
1.0
}
fn safe_area_insets(&self) -> (f64, f64, f64, f64) {
(0.0, 0.0, 0.0, 0.0)
}
fn orientation(&self) -> ScreenOrientation {
ScreenOrientation::Portrait
}
fn haptic_feedback(&mut self, _style: HapticStyle) {}
fn poll_event(&mut self) -> Option<PlatformEvent> {
None
}
fn set_title(&mut self, _title: &str) {}
fn get_clipboard_text(&self) -> Option<String> {
None
}
fn set_clipboard_text(&mut self, _text: &str) -> Result<(), String> {
Err("Clipboard not available on stub backend".to_string())
}
fn open_url(&self, _url: &str) -> Result<(), String> {
Err("URL opening not available on stub backend".to_string())
}
fn system_theme(&self) -> Option<SystemTheme> {
Some(SystemTheme::Light)
}
fn set_ime_position(&mut self, _x: f64, _y: f64) {}
fn show_keyboard(&mut self) {}
fn hide_keyboard(&mut self) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_haptic_style_variants() {
let styles = vec![
HapticStyle::Light,
HapticStyle::Medium,
HapticStyle::Heavy,
HapticStyle::Selection,
HapticStyle::Success,
HapticStyle::Warning,
HapticStyle::Error,
];
assert_eq!(styles.len(), 7);
}
#[test]
fn test_screen_orientation_variants() {
let orientations = vec![
ScreenOrientation::Portrait,
ScreenOrientation::Landscape,
ScreenOrientation::PortraitUpsideDown,
ScreenOrientation::LandscapeRight,
];
assert_eq!(orientations.len(), 4);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[test]
fn test_stub_backend() {
let backend = StubBackend::new();
assert_eq!(backend.screen_size(), (800, 600));
assert_eq!(backend.scale_factor(), 1.0);
assert_eq!(backend.safe_area_insets(), (0.0, 0.0, 0.0, 0.0));
assert_eq!(backend.orientation(), ScreenOrientation::Portrait);
assert_eq!(backend.get_clipboard_text(), None);
assert_eq!(backend.system_theme(), Some(SystemTheme::Light));
}
}