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/application.rs
StratoSDK / crates / strato-platform / src / application.rs
1//! Application management
2 
3use crate::{EventLoop, Window, WindowBuilder};
4use std::collections::HashMap;
5use strato_core::event::Event;
6use strato_widgets::widget::Widget;
7 
8/// Application builder
9pub struct ApplicationBuilder {
10 title: String,
11 initial_window: WindowBuilder,
12 use_taffy: bool,
13}
14 
15impl ApplicationBuilder {
16 /// Create a new application builder
17 pub fn new() -> Self {
18 Self {
19 title: "StratoUI Application".to_string(),
20 initial_window: WindowBuilder::new(),
21 use_taffy: false,
22 }
23 }
24 
25 /// Set application title
26 pub fn title(mut self, title: impl Into<String>) -> Self {
27 let title_string = title.into();
28 self.title = title_string.clone();
29 self.initial_window = self.initial_window.with_title(title_string);
30 self
31 }
32 
33 /// Set initial window configuration
34 pub fn window(mut self, window: WindowBuilder) -> Self {
35 self.initial_window = window;
36 self
37 }
38 
39 /// Enable Taffy layout engine
40 pub fn with_taffy(mut self, enabled: bool) -> Self {
41 self.use_taffy = enabled;
42 self
43 }
44 
45 /// Build the application
46 pub fn build(self) -> Application {
47 let mut app = Application::new(self.title, self.initial_window);
48 if self.use_taffy {
49 app.enable_taffy();
50 }
51 app
52 }
53 
54 /// Run the application with a root widget
55 pub fn run<W: Widget + 'static>(self, root: W) -> ! {
56 let mut app = self.build();
57 app.set_root(Box::new(root));
58 app.run()
59 }
60}
61 
62/// Main application structure
63pub struct Application {
64 title: String,
65 windows: HashMap<u64, Window>,
66 root_widget: Option<Box<dyn Widget>>,
67 event_loop: Option<EventLoop>,
68 initial_window: Option<WindowBuilder>,
69 render_batch: Option<strato_renderer::RenderBatch>,
70 taffy_manager: Option<strato_core::taffy_layout::TaffyLayoutManager>,
71 // Renderer is managed by the event loop to avoid lifetime issues
72}
73 
74impl Application {
75 /// Create a new application
76 pub fn new(title: impl Into<String>, initial_window: WindowBuilder) -> Self {
77 Self {
78 title: title.into(),
79 windows: HashMap::new(),
80 root_widget: None,
81 event_loop: Some(EventLoop::new().expect("Failed to create event loop")),
82 initial_window: Some(initial_window),
83 render_batch: None,
84 taffy_manager: None,
85 }
86 }
87 
88 /// Enable Taffy layout engine
89 pub fn enable_taffy(&mut self) {
90 self.taffy_manager = Some(strato_core::taffy_layout::TaffyLayoutManager::new());
91 }
92 
93 /// Set the root widget
94 pub fn set_root(&mut self, widget: Box<dyn Widget>) {
95 self.root_widget = Some(widget);
96 }
97 
98 /// Add a window
99 pub fn add_window(&mut self, window: Window) {
100 self.windows.insert(window.id(), window);
101 }
102 
103 /// Remove a window
104 pub fn remove_window(&mut self, id: u64) -> Option<Window> {
105 self.windows.remove(&id)
106 }
107 
108 /// Get a window by ID
109 pub fn window(&self, id: u64) -> Option<&Window> {
110 self.windows.get(&id)
111 }
112 
113 /// Get mutable window by ID
114 pub fn window_mut(&mut self, id: u64) -> Option<&mut Window> {
115 self.windows.get_mut(&id)
116 }
117 
118 /// Render the application with a simple approach (no actual GPU rendering)
119 pub fn render_simple(&mut self, window_width: f32, window_height: f32) -> anyhow::Result<()> {
120 if let Some(root_widget) = self.root_widget.as_mut() {
121 let mut batch = strato_renderer::RenderBatch::new();
122 
123 // Compute layout constraints using actual window size
124 let constraints = strato_core::layout::Constraints {
125 min_width: window_width,
126 max_width: window_width,
127 min_height: window_height,
128 max_height: window_height,
129 };
130 
131 // Layout and render the root widget
132 if let Some(taffy_manager) = &mut self.taffy_manager {
133 // Try to use Taffy layout
134 // We need to check if the root widget supports Taffy
135 if let Some(taffy_root) = root_widget.as_taffy() {
136 let size = strato_core::taffy::geometry::Size {
137 width: window_width,
138 height: window_height,
139 };
140 
141 match taffy_manager.compute(taffy_root, size) {
142 Ok(computed_layout) => {
143 // Render using Taffy draw commands
144 // We need to map draw commands to render batch
145 // For now, Taffy doesn't have a direct "render to batch" utility that matches the recursive render() pattern perfectly
146 // because render() expects a mutable batch and recursive calls.
147 // But ComputedLayout gives us a flat list of commands with viewports.
148 // However, the *rendering* logic (drawing rects, text) is inside `Widget::render`.
149 // `Widget::render` expects a `Layout` object.
150 
151 // So we iterate through draw commands, find the widget (by NodeId?? No, we don't have a map from NodeId to Widget reference readily available here unless we traverse).
152 
153 // Wait, TaffyLayoutManager::compute returns ComputedLayout which has NodeIds.
154 // But to call render() on widgets, we need reference to the actual widgets.
155 // Taffy doesn't store widget references.
156 
157 // Alternative: Pass the ComputedLayout TO the recursive render?
158 // OR: Just use the root_widget.render() but with the size calculated by Taffy?
159 
160 // Use Case 1: Root is a TaffyWidget (e.g. Column).
161 // taffy_manager.compute() returns the layout for the whole tree.
162 // But we need to invoke render() on the tree.
163 
164 // In the legacy system:
165 // root.layout(constraints) -> determines size and positions children internally.
166 // root.render(batch, layout) -> renders self and calls children.render().
167 
168 // In Taffy system:
169 // taffy_manager.compute() -> calculates all positions.
170 // BUT `root_widget.render()` still follows legacy pattern: it receives a Layout (pos, size) and renders.
171 // *However*, legacy `render` usually assumes it already knows children positions (stored in the widget state during layout()).
172 // My Taffy implementation separates layout state from widget state.
173 
174 // Implementation detail: `TaffyWidget` has `render`?
175 // No, `TaffyWidget` only has `build_layout`.
176 // `Widget` has `render`.
177 
178 // PROPER SOLUTION:
179 // 1. Compute layout with Taffy.
180 // 2. We need to "apply" the layout to the widgets so they know where they are?
181 // Or pass the Taffy layout map to the render function?
182 // The `ComputedLayout` contains `DrawCommand`s which have `NodeId` and `ValidatedRect`.
183 // It doesn't link back to Widget instances easily.
184 
185 // Actually, `TaffyLayoutManager` builds the tree from the widgets.
186 // The widgets generally don't store their Taffy NodeId (unless we added it? `BaseWidget` has `id: WidgetId`).
187 
188 // This reveals a gap in my Taffy integration plan vs `strato-platform` integration.
189 // If I use Taffy, `root_widget.layout()` is NOT called. So `root_widget` doesn't update its internal layout state.
190 // If `root_widget.render()` relies on that state, it will render at (0,0) or wrong size.
191 
192 // Legacy `Column::render`:
193 // `let child_layout = self.children_layouts[i];`
194 // It uses cached layout from `layout()`.
195 
196 // So, Taffy layout needs to either:
197 // A) Update the widget's internal layout state (requires mutable access to widget tree + mapping Taffy Nodes to Widgets).
198 // B) Be passed down during render. `root.render(batch, layout, &taffy_map)`.
199 
200 // Option B requires changing `Widget::render` signature, which is a breaking change for ALL widgets. I want to avoid that if possible, or do it carefully.
201 // Option A is hard because Taffy NodeId != WidgetId.
202 
203 // Wait, I implemented `TaffyLayoutManager`.
204 // How did I intend to render?
205 // In `task.md`: "Implement TaffyWidget...".
206 // In `walkthrough.md`: "Render using Taffy draw commands... TaffyLayoutManager::compute... for cmd in layout.draw_commands() { // Render widget at cmd.viewport }".
207 // BUT `DrawCommand` only has `NodeId`. It doesn't have the Widget.
208 // So I can't call `widget.render()`.
209 
210 // I need a way to look up the Widget from the NodeId or traversal order.
211 
212 // Crude Fix for `taffy_demo` window:
213 // In `taffy_demo`, I construct the tree manually.
214 
215 // For `Application` integration:
216 // I can't easily map NodeId -> Widget without a map.
217 // `TaffyLayoutManager` doesn't keep a map.
218 
219 // Maybe I should fallback to legacy for now in `render_simple` and NOT use Taffy in `Application` yet,
220 // BUT `taffy_demo` needs to see something.
221 
222 // If I want `taffy_demo` to work, I should implement the render loop IN `taffy_demo` manually,
223 // where I hold both the widget tree and the layout manager.
224 // `taffy_demo` constructs the tree.
225 // It can traverse it and render.
226 
227 // For `Application`, support is blocked by "How to render Taffy layout without widget mapping".
228 
229 // Let's revert `Application` changes regarding Taffy for now?
230 // OR keep `use_taffy` but strictly for "If you provide a Taffy-ready root, we expect... something?"
231 
232 // Actually, look at `crates/strato-widgets/src/layout.rs`.
233 // Does `Column` implement `TaffyWidget`? Yes.
234 // Does it implement `render` using Taffy? No.
235 
236 // So `taffy_demo` CANNOT simply plug into `Application` expecting magic.
237 
238 // The best path for `taffy_demo` windowing is to write a CUSTOM render loop in `taffy_demo` using `winit` and `NonNull` raw pointers or `Rc/RefCell` to map widgets?
239 // Actually, if I traverse the widget tree in standard order (DFS), and Taffy builds in DFS...
240 // Taffy NodeIds are sequential?
241 // If I traverse the widget tree and query Taffy layout by index/order...
242 
243 // Let's stick to the user request: "modifichiamo e riadattiamo tutti gli example".
244 // I should fix `taffy_demo` first.
245 // I will modify `taffy_demo/src/main.rs` to create a window using `winit` directly (copying from `hello_world` but swapping internal logic).
246 // AND defining the render loop there.
247 
248 // So I should undo changes to `Application.rs`? Or leave them as "infrastructure for later"?
249 // Leaving them is fine, but `enable_taffy` won't work yet.
250 // I'll remove the `if let Some(taffy_manager)` block I was about to add.
251 
252 // Let's ABORT the `render_simple` replacement call.
253 // I will KEEP the `taffy_manager` field and builder methods (they are harmless),
254 // but I won't use them in `render_simple` yet.
255 
256 tracing::warn!("Taffy layout enabled but rendering path not fully implemented in Application");
257 // Fallback to legacy
258 let size = root_widget.layout(constraints);
259 let layout =
260 strato_core::layout::Layout::new(glam::Vec2::new(0.0, 0.0), size);
261 root_widget.render(&mut batch, layout);
262 }
263 Err(e) => {
264 tracing::error!("Taffy layout failed: {}", e);
265 }
266 }
267 } else {
268 // Root doesn't support Taffy
269 let size = root_widget.layout(constraints);
270 let layout = strato_core::layout::Layout::new(glam::Vec2::new(0.0, 0.0), size);
271 root_widget.render(&mut batch, layout);
272 }
273 } else {
274 // Legacy
275 let size = root_widget.layout(constraints);
276 let layout = strato_core::layout::Layout::new(glam::Vec2::new(0.0, 0.0), size);
277 root_widget.render(&mut batch, layout);
278 }
279 
280 tracing::info!("Rendered {} vertices in batch", batch.vertices.len());
281 
282 // Return the batch for actual rendering
283 self.render_batch = Some(batch);
284 } else {
285 tracing::warn!("No root widget to render");
286 }
287 Ok(())
288 }
289 
290 /// Get the current render batch
291 pub fn get_render_batch(&mut self) -> Option<strato_renderer::RenderBatch> {
292 self.render_batch.take()
293 }
294 
295 /// Run the application
296 pub fn run(mut self) -> ! {
297 #[cfg(not(target_arch = "wasm32"))]
298 {
299 if let Some(window_builder) = self.initial_window.take() {
300 if let Some(event_loop) = self.event_loop.take() {
301 match event_loop.run_with_window_and_app(window_builder, self, move |_event| {
302 // Event handling is now done inside the event loop
303 }) {
304 Ok(_) => std::process::exit(0),
305 Err(e) => {
306 eprintln!("Event loop error: {}", e);
307 std::process::exit(1);
308 }
309 }
310 } else {
311 eprintln!("No event loop available");
312 std::process::exit(1);
313 }
314 } else {
315 if let Some(event_loop) = self.event_loop.take() {
316 event_loop.run(move |_event| {
317 // Handle event
318 });
319 } else {
320 eprintln!("No event loop available");
321 std::process::exit(1);
322 }
323 }
324 }
325 
326 #[cfg(target_arch = "wasm32")]
327 {
328 panic!("WebAssembly platform not fully implemented");
329 }
330 }
331 
332 /// Handle an event
333 pub fn handle_event(&mut self, event: Event) {
334 // Dispatch event to root widget
335 if let Some(widget) = &mut self.root_widget {
336 widget.handle_event(&event);
337 }
338 
339 // Handle application-level events
340 match event {
341 Event::Window(strato_core::event::WindowEvent::Close) => {
342 // Handle window close - for now, just log it
343 // The application will continue running
344 use strato_core::{logging::LogCategory, strato_info};
345 strato_info!(LogCategory::Platform, "Window close requested");
346 }
347 _ => {}
348 }
349 }
350}
351 
352impl Default for ApplicationBuilder {
353 fn default() -> Self {
354 Self::new()
355 }
356}
357