1use crate::tree::NodeId;
8use serde::{Serialize, Deserialize};
9use std::collections::HashMap;
10
11pub trait PlatformBridge: Send + Sync {
17 fn platform_id(&self) -> PlatformId;
18
19 fn create_view(&self, view_type: ViewType, node_id: NodeId) -> NativeHandle;
21 fn update_view(&self, handle: NativeHandle, props: &PropsDiff) -> Result<(), PlatformError>;
22 fn remove_view(&self, handle: NativeHandle);
23 fn insert_child(&self, parent: NativeHandle, child: NativeHandle, index: usize);
24 fn remove_child(&self, parent: NativeHandle, child: NativeHandle);
25
26 fn measure_text(&self, text: &str, style: &TextStyle, max_width: f32) -> TextMetrics;
28
29 fn screen_size(&self) -> ScreenSize;
31 fn scale_factor(&self) -> f32;
32
33 fn supports(&self, capability: PlatformCapability) -> bool;
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub enum PlatformId { Ios, Android, Macos, Windows, Web }
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub struct NativeHandle(pub u64);
47
48#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
50pub enum ViewType {
51 Container,
52 Text,
53 TextInput,
54 Image,
55 ScrollView,
56 Button,
57 Switch,
58 Slider,
59 ActivityIndicator,
60 DatePicker,
61 Modal,
62 BottomSheet,
63 MenuBar,
64 TitleBar,
65 Custom(String),
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub enum PlatformCapability {
70 Haptics,
71 Biometrics,
72 MenuBar,
73 SystemTray,
74 MultiWindow,
75 DragAndDrop,
76 ContextMenu,
77 NativeShare,
78 PushNotifications,
79 BackgroundFetch,
80 NativeDatePicker,
81 NativeFilePicker,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
90#[serde(untagged)]
91pub enum PropValue {
92 String(String),
93 F32(f32),
94 F64(f64),
95 I32(i32),
96 Bool(bool),
97 Color(Color),
98 Rect { x: f32, y: f32, width: f32, height: f32 },
99 Null,
100}
101
102#[derive(Debug, Clone, Default, Serialize, Deserialize)]
104pub struct PropsDiff {
105 pub changes: HashMap<String, PropValue>,
106}
107
108impl PropsDiff {
109 pub fn new() -> Self { Self::default() }
110
111 pub fn set(&mut self, key: impl Into<String>, value: PropValue) {
112 self.changes.insert(key.into(), value);
113 }
114
115 pub fn is_empty(&self) -> bool { self.changes.is_empty() }
116}
117
118#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
119pub struct Color {
120 pub r: u8,
121 pub g: u8,
122 pub b: u8,
123 pub a: f32,
124}
125
126impl Color {
127 pub fn rgb(r: u8, g: u8, b: u8) -> Self {
128 Self { r, g, b, a: 1.0 }
129 }
130
131 pub fn rgba(r: u8, g: u8, b: u8, a: f32) -> Self {
132 Self { r, g, b, a }
133 }
134}
135
136#[derive(Debug, Clone, Default, Serialize, Deserialize)]
141pub struct TextStyle {
142 pub font_family: Option<String>,
143 pub font_size: Option<f32>,
144 pub font_weight: Option<FontWeight>,
145 pub color: Option<Color>,
146 pub line_height: Option<f32>,
147 pub letter_spacing: Option<f32>,
148 pub text_align: Option<TextAlign>,
149}
150
151#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
152pub enum FontWeight { Thin, Light, Regular, Medium, SemiBold, Bold, Heavy }
153
154#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
155pub enum TextAlign { Left, Center, Right, Justify }
156
157#[derive(Debug, Clone, Copy, Default)]
158pub struct TextMetrics {
159 pub width: f32,
160 pub height: f32,
161 pub baseline: f32,
162 pub line_count: u32,
163}
164
165#[derive(Debug, Clone, Copy)]
166pub struct ScreenSize {
167 pub width: f32,
168 pub height: f32,
169}
170
171#[derive(Debug, thiserror::Error)]
176pub enum PlatformError {
177 #[error("View not found: {0:?}")]
178 ViewNotFound(NativeHandle),
179
180 #[error("Unsupported view type: {0:?}")]
181 UnsupportedViewType(ViewType),
182
183 #[error("Native error: {0}")]
184 Native(String),
185}
186
187#[cfg(test)]
194pub mod mock {
195 use super::*;
196 use std::sync::Mutex;
197
198 #[derive(Debug, Clone)]
199 pub enum MockOp {
200 CreateView(ViewType, NodeId),
201 UpdateView(NativeHandle, PropsDiff),
202 RemoveView(NativeHandle),
203 InsertChild(NativeHandle, NativeHandle, usize),
204 RemoveChild(NativeHandle, NativeHandle),
205 }
206
207 pub struct MockPlatform {
208 next_handle: Mutex<u64>,
209 pub ops: Mutex<Vec<MockOp>>,
210 }
211
212 impl MockPlatform {
213 pub fn new() -> Self {
214 Self {
215 next_handle: Mutex::new(1),
216 ops: Mutex::new(Vec::new()),
217 }
218 }
219 }
220
221 impl PlatformBridge for MockPlatform {
222 fn platform_id(&self) -> PlatformId { PlatformId::Web }
223
224 fn create_view(&self, view_type: ViewType, node_id: NodeId) -> NativeHandle {
225 let mut h = self.next_handle.lock().unwrap();
226 let handle = NativeHandle(*h);
227 *h += 1;
228 self.ops.lock().unwrap().push(MockOp::CreateView(view_type, node_id));
229 handle
230 }
231
232 fn update_view(&self, handle: NativeHandle, props: &PropsDiff) -> Result<(), PlatformError> {
233 self.ops.lock().unwrap().push(MockOp::UpdateView(handle, props.clone()));
234 Ok(())
235 }
236
237 fn remove_view(&self, handle: NativeHandle) {
238 self.ops.lock().unwrap().push(MockOp::RemoveView(handle));
239 }
240
241 fn insert_child(&self, parent: NativeHandle, child: NativeHandle, index: usize) {
242 self.ops.lock().unwrap().push(MockOp::InsertChild(parent, child, index));
243 }
244
245 fn remove_child(&self, parent: NativeHandle, child: NativeHandle) {
246 self.ops.lock().unwrap().push(MockOp::RemoveChild(parent, child));
247 }
248
249 fn measure_text(&self, _text: &str, style: &TextStyle, _max_width: f32) -> TextMetrics {
250 let font_size = style.font_size.unwrap_or(14.0);
251 TextMetrics {
252 width: font_size * 0.6 * _text.len() as f32,
253 height: font_size * 1.2,
254 baseline: font_size,
255 line_count: 1,
256 }
257 }
258
259 fn screen_size(&self) -> ScreenSize {
260 ScreenSize { width: 390.0, height: 844.0 }
261 }
262
263 fn scale_factor(&self) -> f32 { 3.0 }
264
265 fn supports(&self, _cap: PlatformCapability) -> bool { false }
266 }
267}