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-widgets/src/registry.rs
StratoSDK / crates / strato-widgets / src / registry.rs
1use crate::image::{Image, ImageFit, ImageSource};
2use crate::prelude::*;
3use crate::widget::{Widget, WidgetContext, WidgetId};
4use std::collections::HashMap;
5use std::sync::{Arc, Mutex};
6use strato_core::event::{Event, EventResult};
7use strato_core::layout::{Constraints, Layout, Size};
8use strato_core::types::Point;
9use strato_core::ui_node::{PropValue, UiNode, WidgetNode};
10use strato_renderer::batch::RenderBatch;
11 
12/// A builder function that creates a widget from properties.
13type WidgetBuilder = Box<
14 dyn Fn(Vec<(String, PropValue)>, Vec<UiNode>, &WidgetRegistry) -> Box<dyn Widget> + Send + Sync,
15>;
16 
17/// Registry for mapping widget names to their constructors.
18pub struct WidgetRegistry {
19 builders: HashMap<String, WidgetBuilder>,
20}
21 
22/// Wrapper to allow Box<dyn Widget> to satisfy impl Widget
23#[derive(Debug)]
24pub struct BoxedWidget(pub Box<dyn Widget>);
25 
26impl Widget for BoxedWidget {
27 fn id(&self) -> WidgetId {
28 self.0.id()
29 }
30 
31 fn layout(&mut self, constraints: Constraints) -> Size {
32 self.0.layout(constraints)
33 }
34 
35 fn render(&self, batch: &mut RenderBatch, layout: Layout) {
36 self.0.render(batch, layout)
37 }
38 
39 fn handle_event(&mut self, event: &Event) -> EventResult {
40 self.0.handle_event(event)
41 }
42 
43 fn update(&mut self, ctx: &WidgetContext) {
44 self.0.update(ctx)
45 }
46 
47 fn children(&self) -> Vec<&(dyn Widget + '_)> {
48 self.0.children()
49 }
50 fn children_mut(&mut self) -> Vec<&mut (dyn Widget + '_)> {
51 self.0.children_mut()
52 }
53 
54 fn hit_test(&self, point: Point, layout: Layout) -> bool {
55 self.0.hit_test(point, layout)
56 }
57 
58 fn as_any(&self) -> &dyn std::any::Any {
59 self.0.as_any()
60 }
61 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
62 self.0.as_any_mut()
63 }
64 
65 fn clone_widget(&self) -> Box<dyn Widget> {
66 self.0.clone_widget()
67 }
68}
69 
70impl WidgetRegistry {
71 pub fn new() -> Self {
72 let mut registry = Self {
73 builders: HashMap::new(),
74 };
75 registry.register_defaults();
76 registry
77 }
78 
79 /// Register a widget builder.
80 pub fn register<F>(&mut self, name: &str, builder: F)
81 where
82 F: Fn(Vec<(String, PropValue)>, Vec<UiNode>, &WidgetRegistry) -> Box<dyn Widget>
83 + Send
84 + Sync
85 + 'static,
86 {
87 self.builders.insert(name.to_string(), Box::new(builder));
88 }
89 
90 /// Build a widget tree from a UiNode.
91 pub fn build(&self, node: UiNode) -> BoxedWidget {
92 BoxedWidget(match node {
93 UiNode::Widget(node) => self.build_widget(node),
94 UiNode::Text(text) => Box::new(Text::new(text)),
95 UiNode::Fragment(children) => {
96 let mut col = Column::new();
97 for child in children {
98 col = col.child(self.build(child).0);
99 }
100 Box::new(col)
101 }
102 })
103 }
104 
105 fn build_widget(&self, node: WidgetNode) -> Box<dyn Widget> {
106 if let Some(builder) = self.builders.get(&node.name) {
107 builder(node.props, node.children, self)
108 } else {
109 // Fallback for unknown widgets - maybe a red text?
110 Box::new(Text::new(format!("Unknown widget: {}", node.name)))
111 }
112 }
113 
114 fn register_defaults(&mut self) {
115 // Container
116 self.register("Container", |props, children, registry| {
117 let mut widget = Container::new();
118 for (name, value) in props {
119 match (name.as_str(), value) {
120 ("padding", PropValue::Float(v)) => widget = widget.padding(v as f32),
121 ("background", PropValue::Color(c)) => widget = widget.background(c),
122 ("width", PropValue::Float(v)) => widget = widget.width(v as f32),
123 ("height", PropValue::Float(v)) => widget = widget.height(v as f32),
124 ("radius", PropValue::Float(v)) => widget = widget.border_radius(v as f32),
125 ("margin", PropValue::Float(v)) => widget = widget.margin(v as f32),
126 _ => {}
127 }
128 }
129 // Handle "child" logic (Container takes 1 child usually, but our generic AST has list)
130 if let Some(first) = children.first() {
131 widget = widget.child(registry.build(first.clone()));
132 }
133 Box::new(widget)
134 });
135 
136 // Column
137 self.register("Column", |props, children, registry| {
138 let mut widget = Column::new();
139 for (name, value) in props {
140 if name == "spacing" {
141 if let PropValue::Float(v) = value {
142 widget = widget.spacing(v as f32);
143 }
144 }
145 }
146 // Column::children takes Vec<Box<dyn Widget>>.
147 // registry.build returns BoxedWidget.
148 // We need to unwrap or map.
149 let child_widgets: Vec<Box<dyn Widget>> = children
150 .into_iter()
151 .map(|child| registry.build(child).0)
152 .collect();
153 
154 widget = widget.children(child_widgets);
155 Box::new(widget)
156 });
157 
158 // Row
159 self.register("Row", |props, children, registry| {
160 let mut widget = Row::new();
161 for (name, value) in props {
162 if name == "spacing" {
163 if let PropValue::Float(v) = value {
164 widget = widget.spacing(v as f32);
165 }
166 }
167 }
168 let child_widgets: Vec<Box<dyn Widget>> = children
169 .into_iter()
170 .map(|child| registry.build(child).0)
171 .collect();
172 widget = widget.children(child_widgets);
173 Box::new(widget)
174 });
175 
176 // Text
177 self.register("Text", |props, _children, _registry| {
178 let mut text = String::new();
179 
180 // First pass: find text semantic prop
181 for (name, value) in &props {
182 if name == "text" {
183 if let PropValue::String(s) = value {
184 text = s.clone();
185 }
186 }
187 }
188 
189 let mut widget = Text::new(text);
190 
191 // Second pass: apply properties
192 for (name, value) in props {
193 match (name.as_str(), value) {
194 ("color", PropValue::Color(c)) => widget = widget.color(c),
195 ("size", PropValue::Float(v)) => widget = widget.size(v as f32),
196 _ => {}
197 }
198 }
199 
200 Box::new(widget)
201 });
202 
203 // Button
204 self.register("Button", |props, _children, _registry| {
205 let mut label = String::new();
206 for (name, value) in &props {
207 if name == "text" {
208 if let PropValue::String(s) = value {
209 label = s.clone();
210 }
211 }
212 }
213 
214 let widget = Button::new(label);
215 
216 // Button usually doesn't take children in this framework, just text in constructor?
217 // But macro might support `Button { child: Icon }`?
218 // Existing Button::new implementation takes string.
219 // If children present, ignored? or fallback?
220 
221 for (name, value) in props {
222 match (name.as_str(), value) {
223 // disabled?
224 
225 // events?
226 _ => {}
227 }
228 }
229 Box::new(widget)
230 });
231 // Image
232 self.register("Image", |props, _children, _registry| {
233 let mut source = ImageSource::Placeholder {
234 width: 100,
235 height: 100,
236 color: Color::GRAY,
237 };
238 
239 for (name, value) in &props {
240 if name == "source" {
241 if let PropValue::String(s) = value {
242 // Simple heuristic for source type
243 if s.starts_with("http") {
244 source = ImageSource::Url(s.clone());
245 } else if s.starts_with("placeholder") {
246 // Format: placeholder:width:height:hex
247 // Simplified parsing for now: placeholder -> default
248 } else {
249 source = ImageSource::File(std::path::PathBuf::from(s));
250 }
251 }
252 }
253 }
254 
255 let mut widget = Image::new(source);
256 for (name, value) in props {
257 match (name.as_str(), value) {
258 ("fit", PropValue::String(s)) => {
259 let fit = match s.as_str() {
260 "cover" => ImageFit::Cover,
261 "contain" => ImageFit::Contain,
262 "fill" => ImageFit::Fill,
263 _ => ImageFit::None,
264 };
265 widget = widget.fit(fit);
266 }
267 ("opacity", PropValue::Float(v)) => widget = widget.opacity(v as f32),
268 ("radius", PropValue::Float(v)) => widget = widget.border_radius(v as f32),
269 _ => {}
270 }
271 }
272 Box::new(widget)
273 });
274 
275 // TopBar
276 self.register("TopBar", |props, _children, _registry| {
277 let mut title = "".to_string();
278 // First pass for title
279 for (name, value) in &props {
280 if name == "title" {
281 if let PropValue::String(s) = value {
282 title = s.clone();
283 }
284 }
285 }
286 
287 let mut widget = crate::top_bar::TopBar::new(title);
288 
289 for (name, value) in props {
290 match (name.as_str(), value) {
291 ("background", PropValue::Color(c)) => widget = widget.with_background(c),
292 // height handles directly field access if needed, or via method if added
293 _ => {}
294 }
295 }
296 Box::new(widget)
297 });
298 }
299}
300 
301// Global registry instance (lazy static approach usually, but here we instantiate it)
302pub fn create_default_registry() -> WidgetRegistry {
303 WidgetRegistry::new()
304}
305