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-core/src/widget.rs
StratoSDK / crates / strato-core / src / widget.rs
1//! Core widget traits and types for StratoUI
2//!
3//! This module provides the fundamental widget system that all UI components
4//! are built upon. It defines the core traits and types needed for widget
5//! creation, rendering, and interaction.
6 
7use crate::{
8 error::StratoResult,
9 event::Event,
10 layout::{LayoutConstraints, Size},
11 types::Rect,
12};
13use std::{any::Any, collections::HashMap, fmt::Debug};
14 
15/// Unique identifier for widgets
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct WidgetId(pub u64);
18 
19impl WidgetId {
20 /// Create a new widget ID
21 pub fn new() -> Self {
22 use std::sync::atomic::{AtomicU64, Ordering};
23 static COUNTER: AtomicU64 = AtomicU64::new(1);
24 Self(COUNTER.fetch_add(1, Ordering::Relaxed))
25 }
26}
27 
28impl Default for WidgetId {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33 
34/// Widget context containing shared state and services
35#[derive(Debug)]
36pub struct WidgetContext {
37 /// Widget's unique identifier
38 pub id: WidgetId,
39 /// Current layout bounds
40 pub bounds: Rect,
41 /// Whether the widget is focused
42 pub is_focused: bool,
43 /// Whether the widget is hovered
44 pub is_hovered: bool,
45 /// Whether the widget is pressed
46 pub is_pressed: bool,
47 /// Custom properties
48 pub properties: HashMap<String, Box<dyn Any>>,
49}
50 
51impl WidgetContext {
52 /// Create a new widget context
53 pub fn new(id: WidgetId) -> Self {
54 Self {
55 id,
56 bounds: Rect::default(),
57 is_focused: false,
58 is_hovered: false,
59 is_pressed: false,
60 properties: HashMap::new(),
61 }
62 }
63 
64 /// Set a custom property
65 pub fn set_property<T: Any>(&mut self, key: &str, value: T) {
66 self.properties.insert(key.to_string(), Box::new(value));
67 }
68 
69 /// Get a custom property
70 pub fn get_property<T: Any>(&self, key: &str) -> Option<&T> {
71 self.properties.get(key)?.downcast_ref()
72 }
73}
74 
75/// Core widget trait that all widgets must implement
76pub trait Widget: Debug + Send + Sync {
77 /// Get the widget's unique identifier
78 fn id(&self) -> WidgetId;
79 
80 /// Handle an event
81 fn handle_event(&mut self, event: &Event, context: &mut WidgetContext) -> StratoResult<bool>;
82 
83 /// Update the widget's state
84 fn update(&mut self, context: &mut WidgetContext) -> StratoResult<()>;
85 
86 /// Calculate the widget's layout
87 fn layout(
88 &mut self,
89 constraints: &LayoutConstraints,
90 context: &mut WidgetContext,
91 ) -> StratoResult<Size>;
92 
93 /// Render the widget
94 fn render(&self, context: &WidgetContext) -> StratoResult<()>;
95 
96 /// Get the widget as Any for downcasting
97 fn as_any(&self) -> &dyn Any;
98 
99 /// Get the widget as mutable Any for downcasting
100 fn as_any_mut(&mut self) -> &mut dyn Any;
101 
102 /// Get child widgets
103 fn children(&self) -> Vec<&dyn Widget> {
104 Vec::new()
105 }
106 
107 /// Get mutable child widgets
108 fn children_mut(&mut self) -> Vec<&mut dyn Widget> {
109 Vec::new()
110 }
111 
112 /// Check if the widget can receive focus
113 fn can_focus(&self) -> bool {
114 false
115 }
116 
117 /// Check if the widget is visible
118 fn is_visible(&self) -> bool {
119 true
120 }
121 
122 /// Get the widget's preferred size
123 fn preferred_size(&self) -> Option<Size> {
124 None
125 }
126 
127 /// Get the widget's minimum size
128 fn min_size(&self) -> Size {
129 Size::new(0.0, 0.0)
130 }
131 
132 /// Get the widget's maximum size
133 fn max_size(&self) -> Size {
134 Size::new(f32::INFINITY, f32::INFINITY)
135 }
136 
137 /// Get the widget as TaffyWidget if supported
138 fn as_taffy(&self) -> Option<&dyn crate::taffy_layout::TaffyWidget> {
139 None
140 }
141}
142 
143/// Implement Widget for Box<T> to allow boxed widgets to be treated as widgets
144impl<T: Widget + ?Sized> Widget for Box<T> {
145 fn id(&self) -> WidgetId {
146 (**self).id()
147 }
148 
149 fn handle_event(&mut self, event: &Event, context: &mut WidgetContext) -> StratoResult<bool> {
150 (**self).handle_event(event, context)
151 }
152 
153 fn update(&mut self, context: &mut WidgetContext) -> StratoResult<()> {
154 (**self).update(context)
155 }
156 
157 fn layout(
158 &mut self,
159 constraints: &LayoutConstraints,
160 context: &mut WidgetContext,
161 ) -> StratoResult<Size> {
162 (**self).layout(constraints, context)
163 }
164 
165 fn render(&self, context: &WidgetContext) -> StratoResult<()> {
166 (**self).render(context)
167 }
168 
169 fn as_any(&self) -> &dyn Any {
170 (**self).as_any()
171 }
172 
173 fn as_any_mut(&mut self) -> &mut dyn Any {
174 (**self).as_any_mut()
175 }
176 
177 fn children(&self) -> Vec<&dyn Widget> {
178 (**self).children()
179 }
180 
181 fn children_mut(&mut self) -> Vec<&mut dyn Widget> {
182 (**self).children_mut()
183 }
184 
185 fn can_focus(&self) -> bool {
186 (**self).can_focus()
187 }
188 
189 fn is_visible(&self) -> bool {
190 (**self).is_visible()
191 }
192 
193 fn preferred_size(&self) -> Option<Size> {
194 (**self).preferred_size()
195 }
196 
197 fn min_size(&self) -> Size {
198 (**self).min_size()
199 }
200 
201 fn max_size(&self) -> Size {
202 (**self).max_size()
203 }
204 
205 fn as_taffy(&self) -> Option<&dyn crate::taffy_layout::TaffyWidget> {
206 (**self).as_taffy()
207 }
208}
209 
210/// Widget builder trait for creating widgets with a fluent API
211pub trait WidgetBuilder<T: Widget> {
212 /// Build the widget
213 fn build(self) -> T;
214}
215 
216/// Widget tree for managing widget hierarchy
217#[derive(Debug)]
218pub struct WidgetTree {
219 root: Option<Box<dyn Widget>>,
220 widgets: HashMap<WidgetId, Box<dyn Widget>>,
221}
222 
223impl WidgetTree {
224 /// Create a new widget tree
225 pub fn new() -> Self {
226 Self {
227 root: None,
228 widgets: HashMap::new(),
229 }
230 }
231 
232 /// Set the root widget
233 pub fn set_root(&mut self, widget: Box<dyn Widget>) {
234 let id = widget.id();
235 self.widgets.insert(id, widget);
236 self.root = self.widgets.remove(&id);
237 }
238 
239 /// Get the root widget
240 pub fn root(&self) -> Option<&dyn Widget> {
241 self.root.as_ref().map(|w| w.as_ref())
242 }
243 
244 /// Get a widget by ID
245 pub fn get_widget(&self, id: WidgetId) -> Option<&dyn Widget> {
246 self.widgets.get(&id).map(|w| w.as_ref())
247 }
248 
249 /// Get a mutable widget by ID
250 pub fn get_widget_mut(&mut self, id: WidgetId) -> Option<&mut Box<dyn Widget>> {
251 self.widgets.get_mut(&id)
252 }
253 
254 /// Add a widget to the tree
255 pub fn add_widget(&mut self, widget: Box<dyn Widget>) {
256 let id = widget.id();
257 self.widgets.insert(id, widget);
258 }
259 
260 /// Remove a widget from the tree
261 pub fn remove_widget(&mut self, id: WidgetId) -> Option<Box<dyn Widget>> {
262 self.widgets.remove(&id)
263 }
264 
265 /// Get all widget IDs
266 pub fn widget_ids(&self) -> Vec<WidgetId> {
267 self.widgets.keys().copied().collect()
268 }
269 
270 /// Clear all widgets
271 pub fn clear(&mut self) {
272 self.widgets.clear();
273 self.root = None;
274 }
275}
276 
277impl Default for WidgetTree {
278 fn default() -> Self {
279 Self::new()
280 }
281}
282 
283/// Widget manager for handling widget lifecycle
284#[derive(Debug)]
285pub struct WidgetManager {
286 tree: WidgetTree,
287 contexts: HashMap<WidgetId, WidgetContext>,
288 focused_widget: Option<WidgetId>,
289}
290 
291impl WidgetManager {
292 /// Create a new widget manager
293 pub fn new() -> Self {
294 Self {
295 tree: WidgetTree::new(),
296 contexts: HashMap::new(),
297 focused_widget: None,
298 }
299 }
300 
301 /// Set the root widget
302 pub fn set_root(&mut self, widget: Box<dyn Widget>) {
303 let id = widget.id();
304 let context = WidgetContext::new(id);
305 self.contexts.insert(id, context);
306 self.tree.set_root(widget);
307 }
308 
309 /// Handle an event
310 pub fn handle_event(&mut self, event: &Event) -> StratoResult<bool> {
311 if let Some(root) = self.tree.root() {
312 let id = root.id();
313 if let (Some(widget), Some(context)) =
314 (self.tree.get_widget_mut(id), self.contexts.get_mut(&id))
315 {
316 return widget.handle_event(event, context);
317 }
318 }
319 Ok(false)
320 }
321 
322 /// Update all widgets
323 pub fn update(&mut self) -> StratoResult<()> {
324 for id in self.tree.widget_ids() {
325 if let (Some(widget), Some(context)) =
326 (self.tree.get_widget_mut(id), self.contexts.get_mut(&id))
327 {
328 widget.update(context)?;
329 }
330 }
331 Ok(())
332 }
333 
334 /// Layout all widgets
335 pub fn layout(&mut self, constraints: &LayoutConstraints) -> StratoResult<()> {
336 if let Some(root) = self.tree.root() {
337 let id = root.id();
338 if let (Some(widget), Some(context)) =
339 (self.tree.get_widget_mut(id), self.contexts.get_mut(&id))
340 {
341 widget.layout(constraints, context)?;
342 }
343 }
344 Ok(())
345 }
346 
347 /// Render all widgets
348 pub fn render(&self) -> StratoResult<()> {
349 for id in self.tree.widget_ids() {
350 if let (Some(widget), Some(context)) =
351 (self.tree.get_widget(id), self.contexts.get(&id))
352 {
353 widget.render(context)?;
354 }
355 }
356 Ok(())
357 }
358 
359 /// Set focus to a widget
360 pub fn set_focus(&mut self, id: Option<WidgetId>) -> StratoResult<()> {
361 // Clear previous focus
362 if let Some(old_id) = self.focused_widget {
363 if let Some(context) = self.contexts.get_mut(&old_id) {
364 context.is_focused = false;
365 }
366 }
367 
368 // Set new focus
369 if let Some(new_id) = id {
370 if let Some(context) = self.contexts.get_mut(&new_id) {
371 context.is_focused = true;
372 }
373 }
374 
375 self.focused_widget = id;
376 Ok(())
377 }
378 
379 /// Get the currently focused widget
380 pub fn focused_widget(&self) -> Option<WidgetId> {
381 self.focused_widget
382 }
383 
384 /// Get widget context
385 pub fn get_context(&self, id: WidgetId) -> Option<&WidgetContext> {
386 self.contexts.get(&id)
387 }
388 
389 /// Get mutable widget context
390 pub fn get_context_mut(&mut self, id: WidgetId) -> Option<&mut WidgetContext> {
391 self.contexts.get_mut(&id)
392 }
393}
394 
395impl Default for WidgetManager {
396 fn default() -> Self {
397 Self::new()
398 }
399}
400 
401#[cfg(test)]
402mod tests {
403 use super::*;
404 
405 #[derive(Debug)]
406 struct TestWidget {
407 id: WidgetId,
408 }
409 
410 impl TestWidget {
411 fn new() -> Self {
412 Self {
413 id: WidgetId::new(),
414 }
415 }
416 }
417 
418 impl Widget for TestWidget {
419 fn id(&self) -> WidgetId {
420 self.id
421 }
422 
423 fn handle_event(
424 &mut self,
425 _event: &Event,
426 _context: &mut WidgetContext,
427 ) -> StratoResult<bool> {
428 Ok(false)
429 }
430 
431 fn update(&mut self, _context: &mut WidgetContext) -> StratoResult<()> {
432 Ok(())
433 }
434 
435 fn layout(
436 &mut self,
437 _constraints: &LayoutConstraints,
438 _context: &mut WidgetContext,
439 ) -> StratoResult<Size> {
440 Ok(Size::new(100.0, 50.0))
441 }
442 
443 fn render(&self, _context: &WidgetContext) -> StratoResult<()> {
444 Ok(())
445 }
446 
447 fn as_any(&self) -> &dyn Any {
448 self
449 }
450 
451 fn as_any_mut(&mut self) -> &mut dyn Any {
452 self
453 }
454 }
455 
456 #[test]
457 fn test_widget_id_generation() {
458 let id1 = WidgetId::new();
459 let id2 = WidgetId::new();
460 assert_ne!(id1, id2);
461 }
462 
463 #[test]
464 fn test_widget_context() {
465 let id = WidgetId::new();
466 let mut context = WidgetContext::new(id);
467 
468 context.set_property("test", 42i32);
469 assert_eq!(context.get_property::<i32>("test"), Some(&42));
470 }
471 
472 #[test]
473 fn test_widget_tree() {
474 let mut tree = WidgetTree::new();
475 let widget = Box::new(TestWidget::new());
476 let id = widget.id();
477 
478 tree.add_widget(widget);
479 assert!(tree.get_widget(id).is_some());
480 
481 tree.remove_widget(id);
482 assert!(tree.get_widget(id).is_none());
483 }
484 
485 #[test]
486 fn test_widget_manager() {
487 let mut manager = WidgetManager::new();
488 let widget = Box::new(TestWidget::new());
489 let id = widget.id();
490 
491 manager.set_root(widget);
492 assert!(manager.get_context(id).is_some());
493 
494 manager.set_focus(Some(id)).unwrap();
495 assert_eq!(manager.focused_widget(), Some(id));
496 }
497}
498