appscale_core/
platform_ios.rs1use crate::platform::*;
14use crate::tree::NodeId;
15use std::sync::Mutex;
16use std::collections::HashMap;
17
18pub struct IosPlatform {
23 next_handle: Mutex<u64>,
24 views: Mutex<HashMap<u64, ViewRecord>>,
25 screen: ScreenSize,
26 scale: f32,
27}
28
29struct ViewRecord {
30 view_type: ViewType,
31 node_id: NodeId,
32 children: Vec<NativeHandle>,
33}
34
35impl IosPlatform {
36 pub fn new() -> Self {
37 Self {
38 next_handle: Mutex::new(1),
39 views: Mutex::new(HashMap::new()),
40 screen: ScreenSize { width: 390.0, height: 844.0 },
42 scale: 3.0,
43 }
44 }
45
46 pub fn with_screen(mut self, width: f32, height: f32, scale: f32) -> Self {
47 self.screen = ScreenSize { width, height };
48 self.scale = scale;
49 self
50 }
51
52 pub fn view_count(&self) -> usize {
53 self.views.lock().unwrap().len()
54 }
55}
56
57impl Default for IosPlatform {
58 fn default() -> Self { Self::new() }
59}
60
61impl PlatformBridge for IosPlatform {
62 fn platform_id(&self) -> PlatformId {
63 PlatformId::Ios
64 }
65
66 fn create_view(&self, view_type: ViewType, node_id: NodeId) -> NativeHandle {
67 let mut h = self.next_handle.lock().unwrap();
68 let handle = NativeHandle(*h);
69 *h += 1;
70
71 self.views.lock().unwrap().insert(handle.0, ViewRecord {
72 view_type,
73 node_id,
74 children: Vec::new(),
75 });
76
77 handle
78 }
79
80 fn update_view(&self, handle: NativeHandle, _props: &PropsDiff) -> Result<(), PlatformError> {
81 let views = self.views.lock().unwrap();
82 if !views.contains_key(&handle.0) {
83 return Err(PlatformError::ViewNotFound(handle));
84 }
85 Ok(())
87 }
88
89 fn remove_view(&self, handle: NativeHandle) {
90 self.views.lock().unwrap().remove(&handle.0);
91 }
92
93 fn insert_child(&self, parent: NativeHandle, child: NativeHandle, index: usize) {
94 let mut views = self.views.lock().unwrap();
95 if let Some(parent_record) = views.get_mut(&parent.0) {
96 let idx = index.min(parent_record.children.len());
97 parent_record.children.insert(idx, child);
98 }
99 }
100
101 fn remove_child(&self, parent: NativeHandle, child: NativeHandle) {
102 let mut views = self.views.lock().unwrap();
103 if let Some(parent_record) = views.get_mut(&parent.0) {
104 parent_record.children.retain(|c| *c != child);
105 }
106 }
107
108 fn measure_text(&self, text: &str, style: &TextStyle, max_width: f32) -> TextMetrics {
109 let font_size = style.font_size.unwrap_or(17.0); let char_width = font_size * 0.55; let line_height = style.line_height.unwrap_or(font_size * 1.2);
114 let total_width = char_width * text.len() as f32;
115
116 let lines = if max_width > 0.0 && total_width > max_width {
117 (total_width / max_width).ceil() as u32
118 } else {
119 1
120 };
121
122 TextMetrics {
123 width: if lines > 1 { max_width } else { total_width },
124 height: line_height * lines as f32,
125 baseline: font_size * 0.75,
126 line_count: lines,
127 }
128 }
129
130 fn screen_size(&self) -> ScreenSize {
131 self.screen
132 }
133
134 fn scale_factor(&self) -> f32 {
135 self.scale
136 }
137
138 fn supports(&self, capability: PlatformCapability) -> bool {
139 matches!(capability,
140 PlatformCapability::Haptics
141 | PlatformCapability::Biometrics
142 | PlatformCapability::PushNotifications
143 | PlatformCapability::NativeDatePicker
144 | PlatformCapability::NativeShare
145 | PlatformCapability::BackgroundFetch
146 | PlatformCapability::ContextMenu
147 )
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_ios_create_and_remove() {
157 let platform = IosPlatform::new();
158 assert_eq!(platform.platform_id(), PlatformId::Ios);
159
160 let h1 = platform.create_view(ViewType::Container, NodeId(1));
161 let h2 = platform.create_view(ViewType::Text, NodeId(2));
162 assert_eq!(platform.view_count(), 2);
163
164 platform.insert_child(h1, h2, 0);
165 platform.remove_child(h1, h2);
166 platform.remove_view(h2);
167 assert_eq!(platform.view_count(), 1);
168 }
169
170 #[test]
171 fn test_ios_capabilities() {
172 let platform = IosPlatform::new();
173 assert!(platform.supports(PlatformCapability::Haptics));
174 assert!(platform.supports(PlatformCapability::Biometrics));
175 assert!(platform.supports(PlatformCapability::PushNotifications));
176 assert!(platform.supports(PlatformCapability::NativeShare));
177 assert!(!platform.supports(PlatformCapability::MenuBar));
178 assert!(!platform.supports(PlatformCapability::SystemTray));
179 assert!(!platform.supports(PlatformCapability::MultiWindow));
180 }
181
182 #[test]
183 fn test_ios_screen_info() {
184 let platform = IosPlatform::new();
185 let screen = platform.screen_size();
186 assert_eq!(screen.width, 390.0);
187 assert_eq!(screen.height, 844.0);
188 assert_eq!(platform.scale_factor(), 3.0);
189 }
190
191 #[test]
192 fn test_ios_custom_screen() {
193 let platform = IosPlatform::new().with_screen(1024.0, 1366.0, 2.0);
195 let screen = platform.screen_size();
196 assert_eq!(screen.width, 1024.0);
197 assert_eq!(screen.height, 1366.0);
198 assert_eq!(platform.scale_factor(), 2.0);
199 }
200
201 #[test]
202 fn test_ios_text_measurement() {
203 let platform = IosPlatform::new();
204 let style = TextStyle { font_size: Some(16.0), ..TextStyle::default() };
205 let metrics = platform.measure_text("Hello iOS", &style, 200.0);
206 assert!(metrics.width > 0.0);
207 assert!(metrics.height > 0.0);
208 assert_eq!(metrics.line_count, 1);
209 }
210
211 #[test]
212 fn test_ios_text_wrapping() {
213 let platform = IosPlatform::new();
214 let style = TextStyle { font_size: Some(16.0), ..TextStyle::default() };
215 let metrics = platform.measure_text("This is a longer text that should wrap", &style, 50.0);
217 assert!(metrics.line_count > 1);
218 assert_eq!(metrics.width, 50.0); }
220
221 #[test]
222 fn test_ios_update_missing_view() {
223 let platform = IosPlatform::new();
224 let result = platform.update_view(NativeHandle(999), &PropsDiff::new());
225 assert!(result.is_err());
226 }
227}