StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | //! Application management |
| 2 | |
| 3 | use crate::{EventLoop, Window, WindowBuilder}; |
| 4 | use std::collections::HashMap; |
| 5 | use strato_core::event::Event; |
| 6 | use strato_widgets::widget::Widget; |
| 7 | |
| 8 | /// Application builder |
| 9 | pub struct ApplicationBuilder { |
| 10 | title: String, |
| 11 | initial_window: WindowBuilder, |
| 12 | use_taffy: bool, |
| 13 | } |
| 14 | |
| 15 | impl 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 |
| 63 | pub 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 | |
| 74 | impl 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 | |
| 352 | impl Default for ApplicationBuilder { |
| 353 | fn default() -> Self { |
| 354 | Self::new() |
| 355 | } |
| 356 | } |
| 357 |