orbit/
platform.rs

1// Platform adapters for the Orbit UI framework
2
3/// Trait for platform adapters
4pub trait PlatformAdapter {
5    /// Initialize the platform adapter
6    fn init(&mut self) -> Result<(), crate::Error>;
7
8    /// Start the main application loop
9    fn run(&mut self) -> Result<(), crate::Error>;
10
11    /// Shutdown the platform adapter
12    fn shutdown(&mut self) -> Result<(), crate::Error>;
13}
14
15/// WebAssembly platform adapter
16#[cfg(feature = "web")]
17pub mod web {
18    use super::PlatformAdapter;
19    // Remove unused import
20    // use wasm_bindgen::prelude::*;
21
22    /// WebAssembly platform adapter
23    pub struct WebAdapter {
24        // Web-specific resources
25    }
26
27    impl WebAdapter {
28        /// Create a new WebAssembly adapter
29        pub fn new() -> Self {
30            Self {}
31        }
32    }
33
34    impl Default for WebAdapter {
35        fn default() -> Self {
36            Self::new()
37        }
38    }
39
40    impl PlatformAdapter for WebAdapter {
41        fn init(&mut self) -> Result<(), crate::Error> {
42            // Initialize web platform
43            Ok(())
44        }
45
46        fn run(&mut self) -> Result<(), crate::Error> {
47            // Set up web event loop
48            Ok(())
49        }
50
51        fn shutdown(&mut self) -> Result<(), crate::Error> {
52            // Clean up web resources
53            Ok(())
54        }
55    }
56}
57
58/// Desktop platform adapter
59#[cfg(feature = "desktop")]
60pub mod desktop {
61    use super::PlatformAdapter;
62    use crate::renderer::{Renderer, RendererType};
63
64    // Add Rc and RefCell for shared mutable state
65    use std::cell::RefCell;
66    use std::rc::Rc;
67
68    use glium::Surface;
69    use glutin::config::GlConfig;
70    use glutin::context::NotCurrentGlContext;
71    use glutin::display::{GetGlDisplay, GlDisplay};
72    use glutin::surface::WindowSurface;
73    use glutin_winit::GlWindow;
74    use winit::dpi::LogicalSize;
75    use winit::event::{Event, WindowEvent};
76    use winit::event_loop::{ControlFlow, EventLoop};
77    use winit::window::WindowBuilder;
78
79    /// Desktop platform adapter
80    pub struct DesktopAdapter {
81        // Desktop-specific resources
82        renderer: Rc<RefCell<Box<dyn Renderer>>>, // Changed to Rc<RefCell<...>>
83        display: Option<glium::Display<WindowSurface>>,
84        event_loop: Option<EventLoop<()>>,
85        running: bool,
86        start_time: std::time::Instant,
87    }
88
89    impl DesktopAdapter {
90        /// Create a new desktop adapter
91        pub fn new() -> Self {
92            let renderer_result = crate::renderer::create_renderer(RendererType::Auto);
93            let renderer = match renderer_result {
94                Ok(r) => r,
95                Err(e) => {
96                    // Fall back to Skia renderer if Auto selection fails
97                    eprintln!("Failed to create Auto renderer: {e}, falling back to Skia");
98                    crate::renderer::create_renderer(RendererType::Skia)
99                        .expect("Failed to create fallback Skia renderer")
100                }
101            };
102
103            Self {
104                renderer: Rc::new(RefCell::new(renderer)), // Wrap in Rc<RefCell<...>>
105                display: None,
106                event_loop: None,
107                running: false,
108                start_time: std::time::Instant::now(),
109            }
110        }
111
112        /// Create a new desktop adapter with a specific renderer
113        pub fn new_with_renderer(renderer_type: RendererType) -> Self {
114            let renderer_result = crate::renderer::create_renderer(renderer_type);
115            let renderer = match renderer_result {
116                Ok(r) => r,
117                Err(e) => {
118                    // Fall back to Skia renderer if requested renderer fails
119                    eprintln!(
120                        "Failed to create {renderer_type:?} renderer: {e}, falling back to Skia"
121                    );
122                    crate::renderer::create_renderer(RendererType::Skia)
123                        .expect("Failed to create fallback Skia renderer")
124                }
125            };
126
127            Self {
128                renderer: Rc::new(RefCell::new(renderer)), // Wrap in Rc<RefCell<...>>
129                display: None,
130                event_loop: None,
131                running: false,
132                start_time: std::time::Instant::now(),
133            }
134        }
135
136        /// Initialize the window
137        fn init_window(&mut self) -> Result<(), crate::Error> {
138            // Create an event loop
139            let event_loop = EventLoop::new()
140                .map_err(|e| crate::Error::Platform(format!("Failed to create event loop: {e}")))?;
141
142            // Window configuration
143            let window_builder = WindowBuilder::new()
144                .with_title("Orbit UI Application")
145                .with_inner_size(LogicalSize::new(800.0, 600.0));
146
147            // Create the basic config for glutin - using default sensible values
148            let template = glutin::config::ConfigTemplateBuilder::new()
149                .with_alpha_size(8)
150                .with_stencil_size(8)
151                .with_depth_size(24);
152
153            // Create a builder for the glutin-winit integration
154            let display_builder =
155                glutin_winit::DisplayBuilder::new().with_window_builder(Some(window_builder));
156
157            // Build the configs with the event loop
158            let (mut window, gl_config) = display_builder
159                .build(&event_loop, template, |configs| {
160                    // Choose the config with the maximum number of samples
161                    configs
162                        .reduce(|accum, config| {
163                            let transparency_check =
164                                config.supports_transparency().unwrap_or(false)
165                                    & !accum.supports_transparency().unwrap_or(false);
166
167                            if transparency_check || config.num_samples() > accum.num_samples() {
168                                config
169                            } else {
170                                accum
171                            }
172                        })
173                        .unwrap()
174                })
175                .map_err(|e| crate::Error::Platform(format!("Failed to build display: {e:?}")))?;
176
177            // Set up the context attributes with defaults
178            let context_attribs = glutin::context::ContextAttributesBuilder::new().build(None);
179
180            // Create the surfaced context - this handles a lot of the compatibility issues
181            // and is safer than manually creating all the pieces
182            let (gl_context, gl_surface) = match window.take() {
183                Some(window) => {
184                    // No need to get window size, it's handled automatically by build_surface_attributes
185
186                    // Create the GL context
187                    let not_current_context = unsafe {
188                        gl_config
189                            .display()
190                            .create_context(&gl_config, &context_attribs)
191                            .map_err(|e| {
192                                crate::Error::Platform(format!(
193                                    "Failed to create GL context: {e:?}"
194                                ))
195                            })?
196                    };
197
198                    // Build surface attributes from the window
199                    let attrs = window.build_surface_attributes(<_>::default());
200
201                    // Create the surface from the window
202                    let surface = unsafe {
203                        gl_config
204                            .display()
205                            .create_window_surface(&gl_config, &attrs)
206                            .map_err(|e| {
207                                crate::Error::Platform(format!(
208                                    "Failed to create window surface: {e:?}"
209                                ))
210                            })?
211                    };
212
213                    // Make the context current with the surface
214                    let context = not_current_context.make_current(&surface).map_err(|e| {
215                        crate::Error::Platform(format!("Failed to make context current: {e:?}"))
216                    })?;
217
218                    (context, surface)
219                }
220                None => {
221                    return Err(crate::Error::Platform("Window creation failed".into()));
222                }
223            };
224
225            // Create the glium display from the context and surface
226            let display =
227                glium::Display::from_context_surface(gl_context, gl_surface).map_err(|e| {
228                    crate::Error::Platform(format!("Failed to create Glium display: {e:?}"))
229                })?;
230
231            self.display = Some(display);
232            self.event_loop = Some(event_loop);
233
234            Ok(())
235        }
236    }
237
238    impl Default for DesktopAdapter {
239        fn default() -> Self {
240            Self::new()
241        }
242    }
243
244    impl PlatformAdapter for DesktopAdapter {
245        fn init(&mut self) -> Result<(), crate::Error> {
246            // Initialize desktop platform
247            self.init_window()?;
248            self.renderer.borrow_mut().init()?; // Use borrow_mut()
249
250            Ok(())
251        }
252
253        fn run(&mut self) -> Result<(), crate::Error> {
254            // Set flag indicating we're running
255            self.running = true;
256            let event_loop = match self.event_loop.take() {
257                Some(el) => el,
258                None => return Err(crate::Error::Platform("Event loop not initialized".into())),
259            };
260
261            // Get a clone of the display
262            let display_clone = match &self.display {
263                // Renamed for clarity
264                Some(display) => display.clone(),
265                None => return Err(crate::Error::Platform("Display not initialized".into())),
266            };
267
268            // Clone start_time for the closure (Instant is Copy)
269            let start_time_clone = self.start_time; // Renamed for clarity
270
271            // Clone the Rc for the renderer to move into the closure
272            let renderer_rc = self.renderer.clone();
273
274            // Run the event loop with the newer API
275            let _ = event_loop.run(move |event, window_target| {
276                window_target.set_control_flow(ControlFlow::Poll);
277
278                match event {
279                    Event::WindowEvent {
280                        event: WindowEvent::CloseRequested,
281                        ..
282                    } => {
283                        window_target.exit();
284                    }
285                    Event::AboutToWait => {
286                        // Draw a frame
287                        let mut target = display_clone.draw(); // Use cloned display
288                        target.clear_color(0.2, 0.2, 0.2, 1.0);
289
290                        // Get the elapsed time for animation
291                        let _elapsed = start_time_clone.elapsed().as_secs_f32(); // Use cloned start_time
292
293                        // Create a dummy node for demonstration
294                        let node = crate::component::Node::default();
295
296                        // We should construct a proper node tree based on the elapsed time
297                        // and component definitions, but for now we're just using a dummy node
298
299                        // Create render context with default dimensions
300                        let mut render_context = crate::renderer::RenderContext::new(800, 600);
301
302                        // Render the UI
303                        if let Err(e) = renderer_rc.borrow_mut().render(&node, &mut render_context)
304                        {
305                            // Use cloned Rc and borrow_mut()
306                            eprintln!("Rendering error: {e}");
307                        }
308
309                        // Flush changes
310                        if let Err(e) = renderer_rc.borrow_mut().flush() {
311                            // Use cloned Rc and borrow_mut()
312                            eprintln!("Flush error: {e}");
313                        }
314
315                        // Finish drawing
316                        target.finish().unwrap();
317                    }
318                    _ => (),
319                }
320            });
321
322            // We shouldn't reach here due to event_loop.run() consuming event_loop
323            Ok(())
324        }
325
326        fn shutdown(&mut self) -> Result<(), crate::Error> {
327            // Clean up desktop resources
328            self.running = false;
329
330            // Clean up renderer
331            self.renderer.borrow_mut().cleanup()?; // Use borrow_mut()
332
333            // Display is cleaned up automatically
334            self.display = None;
335
336            Ok(())
337        }
338    }
339}
340
341/// Factory function to create the appropriate platform adapter
342pub fn create_adapter(platform_type: PlatformType) -> Box<dyn PlatformAdapter> {
343    match platform_type {
344        #[cfg(feature = "web")]
345        PlatformType::Web => Box::new(web::WebAdapter::new()),
346        #[cfg(not(feature = "web"))]
347        PlatformType::Web => panic!("Web platform not supported in this build"),
348
349        #[cfg(feature = "desktop")]
350        PlatformType::Desktop => Box::new(desktop::DesktopAdapter::new()),
351        #[cfg(not(feature = "desktop"))]
352        PlatformType::Desktop => panic!("Desktop platform not supported in this build"),
353
354        PlatformType::Auto => {
355            // Logic to detect the current platform
356            #[cfg(target_arch = "wasm32")]
357            {
358                #[cfg(feature = "web")]
359                return Box::new(web::WebAdapter::new());
360                #[cfg(not(feature = "web"))]
361                panic!("Web platform not supported in this build");
362            }
363
364            #[cfg(not(target_arch = "wasm32"))]
365            {
366                #[cfg(feature = "desktop")]
367                return Box::new(desktop::DesktopAdapter::new());
368                #[cfg(not(feature = "desktop"))]
369                panic!("Desktop platform not supported in this build");
370            }
371        }
372    }
373}
374
375/// Types of platforms available
376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377pub enum PlatformType {
378    /// Web platform (WASM)
379    Web,
380    /// Desktop platform (Windows, macOS, Linux)
381    Desktop,
382    /// Automatically detect platform
383    Auto,
384}