Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-platform/src/web.rs
1//! WebAssembly platform implementation
2 
3use crate::{Platform, PlatformError, Window, WindowBuilder, WindowId, WindowInner};
4use strato_core::event::Event;
5use wasm_bindgen::prelude::*;
6use wasm_bindgen::JsCast;
7use web_sys::{Document, HtmlCanvasElement, Window as WebWindow};
8 
9/// Web platform implementation
10pub struct WebPlatform {
11 canvas: Option<HtmlCanvasElement>,
12 window_id: WindowId,
13}
14 
15impl WebPlatform {
16 /// Create a new web platform
17 pub fn new() -> Self {
18 Self {
19 canvas: None,
20 window_id: 0,
21 }
22 }
23 
24 /// Get the web window
25 fn web_window() -> Result<WebWindow, PlatformError> {
26 web_sys::window().ok_or_else(|| PlatformError::Wasm("Failed to get window".to_string()))
27 }
28 
29 /// Get the document
30 fn document() -> Result<Document, PlatformError> {
31 Self::web_window()?
32 .document()
33 .ok_or_else(|| PlatformError::Wasm("Failed to get document".to_string()))
34 }
35 
36 /// Create a canvas element
37 fn create_canvas(builder: &WindowBuilder) -> Result<HtmlCanvasElement, PlatformError> {
38 let document = Self::document()?;
39 
40 let canvas = document
41 .create_element("canvas")
42 .map_err(|e| PlatformError::Wasm(format!("Failed to create canvas: {:?}", e)))?
43 .dyn_into::<HtmlCanvasElement>()
44 .map_err(|_| PlatformError::Wasm("Failed to cast to HtmlCanvasElement".to_string()))?;
45 
46 canvas.set_width(builder.size.width as u32);
47 canvas.set_height(builder.size.height as u32);
48 canvas.set_id("strato-sdk-canvas");
49 
50 // Set canvas style
51 let style = canvas.style();
52 style.set_property("display", "block").ok();
53 style.set_property("margin", "0 auto").ok();
54 
55 // Append to body
56 document
57 .body()
58 .ok_or_else(|| PlatformError::Wasm("No body element".to_string()))?
59 .append_child(&canvas)
60 .map_err(|e| PlatformError::Wasm(format!("Failed to append canvas: {:?}", e)))?;
61 
62 Ok(canvas)
63 }
64 
65 /// Setup event listeners
66 fn setup_event_listeners(canvas: &HtmlCanvasElement) -> Result<(), PlatformError> {
67 let canvas_clone = canvas.clone();
68 
69 // Mouse move
70 let mouse_move = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
71 // Handle mouse move
72 let _x = event.offset_x();
73 let _y = event.offset_y();
74 // TODO: Dispatch to event handler
75 }) as Box<dyn FnMut(_)>);
76 
77 canvas
78 .add_event_listener_with_callback("mousemove", mouse_move.as_ref().unchecked_ref())
79 .map_err(|e| {
80 PlatformError::Wasm(format!("Failed to add mousemove listener: {:?}", e))
81 })?;
82 mouse_move.forget();
83 
84 // Mouse down
85 let mouse_down = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
86 // Handle mouse down
87 let _button = event.button();
88 // TODO: Dispatch to event handler
89 }) as Box<dyn FnMut(_)>);
90 
91 canvas
92 .add_event_listener_with_callback("mousedown", mouse_down.as_ref().unchecked_ref())
93 .map_err(|e| {
94 PlatformError::Wasm(format!("Failed to add mousedown listener: {:?}", e))
95 })?;
96 mouse_down.forget();
97 
98 // Mouse up
99 let mouse_up = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
100 // Handle mouse up
101 let _button = event.button();
102 // TODO: Dispatch to event handler
103 }) as Box<dyn FnMut(_)>);
104 
105 canvas
106 .add_event_listener_with_callback("mouseup", mouse_up.as_ref().unchecked_ref())
107 .map_err(|e| PlatformError::Wasm(format!("Failed to add mouseup listener: {:?}", e)))?;
108 mouse_up.forget();
109 
110 // Keyboard events
111 let keydown = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
112 // Handle keydown
113 let _key = event.key();
114 // TODO: Dispatch to event handler
115 }) as Box<dyn FnMut(_)>);
116 
117 Self::document()?
118 .add_event_listener_with_callback("keydown", keydown.as_ref().unchecked_ref())
119 .map_err(|e| PlatformError::Wasm(format!("Failed to add keydown listener: {:?}", e)))?;
120 keydown.forget();
121 
122 let keyup = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
123 // Handle keyup
124 let _key = event.key();
125 // TODO: Dispatch to event handler
126 }) as Box<dyn FnMut(_)>);
127 
128 Self::document()?
129 .add_event_listener_with_callback("keyup", keyup.as_ref().unchecked_ref())
130 .map_err(|e| PlatformError::Wasm(format!("Failed to add keyup listener: {:?}", e)))?;
131 keyup.forget();
132 
133 // Resize observer
134 let resize = Closure::wrap(Box::new(move || {
135 // Handle resize
136 // TODO: Dispatch resize event
137 }) as Box<dyn FnMut()>);
138 
139 Self::web_window()?
140 .add_event_listener_with_callback("resize", resize.as_ref().unchecked_ref())
141 .map_err(|e| PlatformError::Wasm(format!("Failed to add resize listener: {:?}", e)))?;
142 resize.forget();
143 
144 Ok(())
145 }
146}
147 
148impl Platform for WebPlatform {
149 fn init() -> Result<Self, PlatformError> {
150 // Set panic hook for better error messages
151 console_error_panic_hook::set_once();
152 
153 Ok(Self::new())
154 }
155 
156 fn create_window(&mut self, builder: WindowBuilder) -> Result<Window, PlatformError> {
157 // In web, we typically have one canvas
158 if self.canvas.is_some() {
159 return Err(PlatformError::Wasm("Canvas already created".to_string()));
160 }
161 
162 let canvas = Self::create_canvas(&builder)?;
163 Self::setup_event_listeners(&canvas)?;
164 
165 // Set document title
166 Self::document()?.set_title(&builder.title);
167 
168 let window_id = self.window_id;
169 self.window_id += 1;
170 
171 self.canvas = Some(canvas.clone());
172 
173 Ok(Window {
174 id: window_id,
175 inner: WindowInner::Web(canvas),
176 })
177 }
178 
179 fn run_event_loop<F>(&mut self, callback: F) -> Result<(), PlatformError>
180 where
181 F: FnMut(Event) + 'static,
182 {
183 // In web, the event loop is handled by the browser
184 // We use requestAnimationFrame for the render loop
185 
186 let window = Self::web_window()?;
187 let callback = std::rc::Rc::new(std::cell::RefCell::new(callback));
188 
189 // Animation frame loop
190 let f = std::rc::Rc::new(std::cell::RefCell::new(None));
191 let g = f.clone();
192 
193 *g.borrow_mut() = Some(Closure::wrap(Box::new(move || {
194 // Request next frame
195 request_animation_frame(f.borrow().as_ref().unwrap());
196 
197 // Handle frame update
198 // TODO: Dispatch update event
199 }) as Box<dyn FnMut()>));
200 
201 request_animation_frame(g.borrow().as_ref().unwrap());
202 
203 Ok(())
204 }
205 
206 fn request_redraw(&self, _window_id: WindowId) {
207 // In web, we use requestAnimationFrame
208 // This is handled in the event loop
209 }
210 
211 fn exit(&mut self) {
212 // Can't really exit in web
213 // Could navigate away or close tab
214 }
215}
216 
217/// Request animation frame helper
218fn request_animation_frame(f: &Closure<dyn FnMut()>) {
219 web_sys::window()
220 .unwrap()
221 .request_animation_frame(f.as_ref().unchecked_ref())
222 .unwrap();
223}
224