Skip to main content

appscale_core/
platform_windows.rs

1//! Windows Desktop Platform Bridge
2//!
3//! Implements PlatformBridge for Windows using WinUI 3 / Win32 concepts.
4//! At this stage, this is a scaffold that records operations and will
5//! later connect to the actual C++/WinRT bridge via FFI.
6//!
7//! Architecture:
8//!   Rust PlatformBridge → FFI (C ABI) → C++/WinRT WinUI wrapper
9//!
10//! The Windows bridge handles XAML-like view hierarchy, DirectWrite
11//! text measurement, and Windows-specific capabilities.
12
13use crate::platform::*;
14use crate::tree::NodeId;
15use std::sync::Mutex;
16use std::collections::HashMap;
17
18/// Windows platform bridge.
19///
20/// In production, each method will call through FFI to the C++/WinRT layer.
21/// For now, we maintain an in-memory view registry for testing and development.
22pub struct WindowsPlatform {
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 WindowsPlatform {
36    pub fn new() -> Self {
37        Self {
38            next_handle: Mutex::new(1),
39            views: Mutex::new(HashMap::new()),
40            // Default: 1080p logical resolution at 150% scaling
41            screen: ScreenSize { width: 1280.0, height: 720.0 },
42            scale: 1.5,
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 WindowsPlatform {
58    fn default() -> Self { Self::new() }
59}
60
61impl PlatformBridge for WindowsPlatform {
62    fn platform_id(&self) -> PlatformId {
63        PlatformId::Windows
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        // In production: forward props to WinUI XAML control via FFI
86        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        // Approximate measurement until connected to DirectWrite via FFI.
110        // Uses Segoe UI metrics as baseline (Windows system font).
111        let font_size = style.font_size.unwrap_or(14.0); // Windows default
112        let char_width = font_size * 0.52; // Segoe UI average
113        let line_height = style.line_height.unwrap_or(font_size * 1.33);
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.78,
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::SystemTray
141            | PlatformCapability::MultiWindow
142            | PlatformCapability::DragAndDrop
143            | PlatformCapability::ContextMenu
144            | PlatformCapability::NativeFilePicker
145            | PlatformCapability::NativeDatePicker
146            | PlatformCapability::PushNotifications
147        )
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_windows_create_and_remove() {
157        let platform = WindowsPlatform::new();
158        assert_eq!(platform.platform_id(), PlatformId::Windows);
159
160        let h1 = platform.create_view(ViewType::Container, NodeId(1));
161        let h2 = platform.create_view(ViewType::Button, 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_windows_capabilities() {
172        let platform = WindowsPlatform::new();
173        assert!(platform.supports(PlatformCapability::SystemTray));
174        assert!(platform.supports(PlatformCapability::MultiWindow));
175        assert!(platform.supports(PlatformCapability::DragAndDrop));
176        assert!(platform.supports(PlatformCapability::PushNotifications));
177        assert!(!platform.supports(PlatformCapability::MenuBar));  // Windows doesn't have macOS-style menu bar
178        assert!(!platform.supports(PlatformCapability::Haptics));
179    }
180
181    #[test]
182    fn test_windows_screen_info() {
183        let platform = WindowsPlatform::new();
184        let screen = platform.screen_size();
185        assert_eq!(screen.width, 1280.0);
186        assert_eq!(screen.height, 720.0);
187        assert_eq!(platform.scale_factor(), 1.5);
188    }
189
190    #[test]
191    fn test_windows_custom_screen() {
192        let platform = WindowsPlatform::new().with_screen(2560.0, 1440.0, 2.0);
193        let screen = platform.screen_size();
194        assert_eq!(screen.width, 2560.0);
195        assert_eq!(platform.scale_factor(), 2.0);
196    }
197
198    #[test]
199    fn test_windows_text_measurement() {
200        let platform = WindowsPlatform::new();
201        let style = TextStyle { font_size: Some(16.0), ..TextStyle::default() };
202        let metrics = platform.measure_text("Hello Windows", &style, 200.0);
203        assert!(metrics.width > 0.0);
204        assert!(metrics.height > 0.0);
205        assert_eq!(metrics.line_count, 1);
206    }
207
208    #[test]
209    fn test_windows_update_missing_view() {
210        let platform = WindowsPlatform::new();
211        let result = platform.update_view(NativeHandle(999), &PropsDiff::new());
212        assert!(result.is_err());
213    }
214}