1pub trait PlatformAdapter {
5 fn init(&mut self) -> Result<(), crate::Error>;
7
8 fn run(&mut self) -> Result<(), crate::Error>;
10
11 fn shutdown(&mut self) -> Result<(), crate::Error>;
13}
14
15#[cfg(feature = "web")]
17pub mod web {
18 use super::PlatformAdapter;
19 pub struct WebAdapter {
24 }
26
27 impl WebAdapter {
28 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 Ok(())
44 }
45
46 fn run(&mut self) -> Result<(), crate::Error> {
47 Ok(())
49 }
50
51 fn shutdown(&mut self) -> Result<(), crate::Error> {
52 Ok(())
54 }
55 }
56}
57
58#[cfg(feature = "desktop")]
60pub mod desktop {
61 use super::PlatformAdapter;
62 use crate::renderer::{Renderer, RendererType};
63
64 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 pub struct DesktopAdapter {
81 renderer: Rc<RefCell<Box<dyn Renderer>>>, 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 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 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)), display: None,
106 event_loop: None,
107 running: false,
108 start_time: std::time::Instant::now(),
109 }
110 }
111
112 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 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)), display: None,
130 event_loop: None,
131 running: false,
132 start_time: std::time::Instant::now(),
133 }
134 }
135
136 fn init_window(&mut self) -> Result<(), crate::Error> {
138 let event_loop = EventLoop::new()
140 .map_err(|e| crate::Error::Platform(format!("Failed to create event loop: {e}")))?;
141
142 let window_builder = WindowBuilder::new()
144 .with_title("Orbit UI Application")
145 .with_inner_size(LogicalSize::new(800.0, 600.0));
146
147 let template = glutin::config::ConfigTemplateBuilder::new()
149 .with_alpha_size(8)
150 .with_stencil_size(8)
151 .with_depth_size(24);
152
153 let display_builder =
155 glutin_winit::DisplayBuilder::new().with_window_builder(Some(window_builder));
156
157 let (mut window, gl_config) = display_builder
159 .build(&event_loop, template, |configs| {
160 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 let context_attribs = glutin::context::ContextAttributesBuilder::new().build(None);
179
180 let (gl_context, gl_surface) = match window.take() {
183 Some(window) => {
184 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 let attrs = window.build_surface_attributes(<_>::default());
200
201 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 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 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 self.init_window()?;
248 self.renderer.borrow_mut().init()?; Ok(())
251 }
252
253 fn run(&mut self) -> Result<(), crate::Error> {
254 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 let display_clone = match &self.display {
263 Some(display) => display.clone(),
265 None => return Err(crate::Error::Platform("Display not initialized".into())),
266 };
267
268 let start_time_clone = self.start_time; let renderer_rc = self.renderer.clone();
273
274 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 let mut target = display_clone.draw(); target.clear_color(0.2, 0.2, 0.2, 1.0);
289
290 let _elapsed = start_time_clone.elapsed().as_secs_f32(); let node = crate::component::Node::default();
295
296 let mut render_context = crate::renderer::RenderContext::new(800, 600);
301
302 if let Err(e) = renderer_rc.borrow_mut().render(&node, &mut render_context)
304 {
305 eprintln!("Rendering error: {e}");
307 }
308
309 if let Err(e) = renderer_rc.borrow_mut().flush() {
311 eprintln!("Flush error: {e}");
313 }
314
315 target.finish().unwrap();
317 }
318 _ => (),
319 }
320 });
321
322 Ok(())
324 }
325
326 fn shutdown(&mut self) -> Result<(), crate::Error> {
327 self.running = false;
329
330 self.renderer.borrow_mut().cleanup()?; self.display = None;
335
336 Ok(())
337 }
338 }
339}
340
341pub 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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377pub enum PlatformType {
378 Web,
380 Desktop,
382 Auto,
384}