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