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
examples/wasm_demo/src/lib.rs
1//! OxideUI WebAssembly Demo
2//!
3//! This example demonstrates how to build and deploy OxideUI applications
4//! to the web using WebAssembly.
5 
6use wasm_bindgen::prelude::*;
7use web_sys::{window, HtmlCanvasElement, Performance};
8use oxide_widgets::prelude::*;
9use oxide_core::{state::Signal, types::Color};
10use std::sync::Arc;
11use std::cell::RefCell;
12use std::rc::Rc;
13 
14/// Initialize the WASM module
15#[wasm_bindgen(start)]
16pub fn start() -> Result<(), JsValue> {
17 // Set up panic hook for better error messages in the browser console
18 console_error_panic_hook::set_once();
19
20 // Initialize logging to browser console
21 console_log::init_with_level(log::Level::Info)
22 .map_err(|e| JsValue::from_str(&format!("Failed to init logger: {:?}", e)))?;
23
24 log::info!("OxideUI WASM Demo initialized successfully");
25
26 Ok(())
27}
28 
29/// Main WASM application structure
30#[wasm_bindgen]
31pub struct WasmApp {
32 canvas_id: String,
33 counter: Arc<Signal<i32>>,
34 animation_frame_id: Option<i32>,
35 performance: Performance,
36 last_frame_time: f64,
37}
38 
39#[wasm_bindgen]
40impl WasmApp {
41 /// Create a new WASM application instance
42 #[wasm_bindgen(constructor)]
43 pub fn new(canvas_id: &str) -> Result<WasmApp, JsValue> {
44 log::info!("Creating WasmApp with canvas_id: {}", canvas_id);
45
46 let window = window().ok_or("No window found")?;
47 let document = window.document().ok_or("No document found")?;
48 let canvas = document
49 .get_element_by_id(canvas_id)
50 .ok_or_else(|| JsValue::from_str(&format!("Canvas '{}' not found", canvas_id)))?
51 .dyn_into::<HtmlCanvasElement>()?;
52
53 // Verify canvas dimensions
54 let width = canvas.width();
55 let height = canvas.height();
56 log::info!("Canvas dimensions: {}x{}", width, height);
57
58 let performance = window
59 .performance()
60 .ok_or("Performance API not available")?;
61
62 Ok(WasmApp {
63 canvas_id: canvas_id.to_string(),
64 counter: Arc::new(Signal::new(0)),
65 animation_frame_id: None,
66 performance,
67 last_frame_time: performance.now(),
68 })
69 }
70
71 /// Get the current counter value
72 #[wasm_bindgen(getter)]
73 pub fn counter(&self) -> i32 {
74 *self.counter.get()
75 }
76
77 /// Increment the counter
78 #[wasm_bindgen]
79 pub fn increment(&mut self) {
80 let current = *self.counter.get();
81 self.counter.set(current + 1);
82 log::info!("Counter incremented to: {}", current + 1);
83 }
84
85 /// Decrement the counter
86 #[wasm_bindgen]
87 pub fn decrement(&mut self) {
88 let current = *self.counter.get();
89 self.counter.set(current - 1);
90 log::info!("Counter decremented to: {}", current - 1);
91 }
92
93 /// Reset the counter
94 #[wasm_bindgen]
95 pub fn reset(&mut self) {
96 self.counter.set(0);
97 log::info!("Counter reset to 0");
98 }
99
100 /// Render a single frame
101 #[wasm_bindgen]
102 pub fn render(&mut self) -> Result<(), JsValue> {
103 let current_time = self.performance.now();
104 let delta_time = current_time - self.last_frame_time;
105 self.last_frame_time = current_time;
106
107 // Log FPS every second
108 if (current_time as i32) % 1000 < 16 {
109 let fps = 1000.0 / delta_time;
110 log::debug!("FPS: {:.2}", fps);
111 }
112
113 // Rendering logic would go here
114 // For now, we'll just update the DOM
115 self.update_dom()?;
116
117 Ok(())
118 }
119
120 /// Update the DOM with current state
121 fn update_dom(&self) -> Result<(), JsValue> {
122 let window = window().ok_or("No window")?;
123 let document = window.document().ok_or("No document")?;
124
125 // Update counter display
126 if let Some(counter_element) = document.get_element_by_id("counter-value") {
127 counter_element.set_inner_html(&format!("{}", *self.counter.get()));
128 }
129
130 Ok(())
131 }
132
133 /// Start the render loop
134 #[wasm_bindgen]
135 pub fn start_render_loop(&mut self) -> Result<(), JsValue> {
136 log::info!("Starting render loop");
137
138 // This would typically use requestAnimationFrame
139 // For simplicity, we'll just render once
140 self.render()?;
141
142 Ok(())
143 }
144
145 /// Stop the render loop
146 #[wasm_bindgen]
147 pub fn stop_render_loop(&mut self) {
148 if let Some(id) = self.animation_frame_id.take() {
149 let window = window().expect("No window");
150 window.cancel_animation_frame(id).ok();
151 log::info!("Render loop stopped");
152 }
153 }
154
155 /// Get performance metrics
156 #[wasm_bindgen]
157 pub fn get_metrics(&self) -> JsValue {
158 let metrics = serde_json::json!({
159 "counter": *self.counter.get(),
160 "uptime_ms": self.performance.now(),
161 });
162
163 serde_wasm_bindgen::to_value(&metrics).unwrap_or(JsValue::NULL)
164 }
165}
166 
167/// Build the UI widget tree
168fn build_ui(counter: Arc<Signal<i32>>) -> Box<dyn Widget> {
169 Box::new(
170 Container::new()
171 .background(Color::rgb(0.15, 0.15, 0.20))
172 .padding(40.0)
173 .child(
174 Column::new()
175 .spacing(20.0)
176 .main_axis_alignment(MainAxisAlignment::Center)
177 .cross_axis_alignment(CrossAxisAlignment::Center)
178 .children(vec![
179 // Title
180 Box::new(
181 Text::new("OxideUI WASM Demo")
182 .size(36.0)
183 .color(Color::rgb(1.0, 1.0, 1.0))
184 .weight(FontWeight::Bold)
185 ),
186
187 // Subtitle
188 Box::new(
189 Text::new("A Rust UI Framework for the Web")
190 .size(18.0)
191 .color(Color::rgb(0.7, 0.7, 0.7))
192 ),
193
194 // Spacer
195 Box::new(Container::new().height(20.0)),
196
197 // Counter display
198 Box::new(
199 Container::new()
200 .background(Color::rgb(0.2, 0.2, 0.25))
201 .padding(20.0)
202 .border_radius(10.0)
203 .child(
204 Text::new(format!("Count: {}", *counter.get()))
205 .size(48.0)
206 .color(Color::rgb(0.3, 0.8, 1.0))
207 .weight(FontWeight::Bold)
208 )
209 ),
210
211 // Button row
212 Box::new(
213 Row::new()
214 .spacing(15.0)
215 .main_axis_alignment(MainAxisAlignment::Center)
216 .children(vec![
217 Box::new(
218 Button::new("Decrement")
219 .style(ButtonStyle::Secondary)
220 .on_click({
221 let counter = counter.clone();
222 move || {
223 let current = *counter.get();
224 counter.set(current - 1);
225 }
226 })
227 ),
228 Box::new(
229 Button::new("Reset")
230 .style(ButtonStyle::Danger)
231 .on_click({
232 let counter = counter.clone();
233 move || {
234 counter.set(0);
235 }
236 })
237 ),
238 Box::new(
239 Button::new("Increment")
240 .style(ButtonStyle::Primary)
241 .on_click({
242 let counter = counter.clone();
243 move || {
244 let current = *counter.get();
245 counter.set(current + 1);
246 }
247 })
248 ),
249 ])
250 ),
251
252 // Info text
253 Box::new(
254 Text::new("Built with Rust + WebAssembly")
255 .size(14.0)
256 .color(Color::rgb(0.5, 0.5, 0.5))
257 ),
258 ])
259 )
260 )
261}
262 
263/// Helper function to log to browser console
264#[wasm_bindgen]
265pub fn log_message(message: &str) {
266 web_sys::console::log_1(&message.into());
267}
268 
269/// Helper function to log errors to browser console
270#[wasm_bindgen]
271pub fn log_error(message: &str) {
272 web_sys::console::error_1(&message.into());
273}
274 
275/// Get browser information
276#[wasm_bindgen]
277pub fn get_browser_info() -> JsValue {
278 let window = match window() {
279 Some(w) => w,
280 None => return JsValue::NULL,
281 };
282
283 let navigator = window.navigator();
284 let info = serde_json::json!({
285 "userAgent": navigator.user_agent().unwrap_or_default(),
286 "language": navigator.language().unwrap_or_default(),
287 "platform": navigator.platform().unwrap_or_default(),
288 "cookieEnabled": navigator.cookie_enabled(),
289 });
290
291 serde_wasm_bindgen::to_value(&info).unwrap_or(JsValue::NULL)
292}
293 
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use wasm_bindgen_test::*;
298
299 wasm_bindgen_test_configure!(run_in_browser);
300
301 #[wasm_bindgen_test]
302 fn test_counter_increment() {
303 let mut app = WasmApp::new("test-canvas").expect("Failed to create app");
304
305 assert_eq!(app.counter(), 0);
306
307 app.increment();
308 assert_eq!(app.counter(), 1);
309
310 app.increment();
311 assert_eq!(app.counter(), 2);
312 }
313
314 #[wasm_bindgen_test]
315 fn test_counter_decrement() {
316 let mut app = WasmApp::new("test-canvas").expect("Failed to create app");
317
318 app.increment();
319 app.increment();
320 assert_eq!(app.counter(), 2);
321
322 app.decrement();
323 assert_eq!(app.counter(), 1);
324 }
325
326 #[wasm_bindgen_test]
327 fn test_counter_reset() {
328 let mut app = WasmApp::new("test-canvas").expect("Failed to create app");
329
330 app.increment();
331 app.increment();
332 app.increment();
333 assert_eq!(app.counter(), 3);
334
335 app.reset();
336 assert_eq!(app.counter(), 0);
337 }
338}
339