spitfire_glow/
app.rs

1use crate::{graphics::Graphics, renderer::GlowVertexAttribs};
2use glow::{Context, HasContext};
3#[cfg(not(target_arch = "wasm32"))]
4use glutin::{
5    dpi::LogicalSize,
6    event::{Event, WindowEvent},
7    event_loop::{ControlFlow, EventLoop},
8    platform::run_return::EventLoopExtRunReturn,
9    window::{Fullscreen, Window, WindowBuilder},
10    ContextBuilder, ContextWrapper, PossiblyCurrent,
11};
12#[cfg(target_arch = "wasm32")]
13use web_sys::{wasm_bindgen::JsCast, HtmlCanvasElement, WebGl2RenderingContext};
14#[cfg(target_arch = "wasm32")]
15use winit::{
16    dpi::LogicalSize,
17    event::Event,
18    event_loop::{ControlFlow, EventLoop},
19    window::{Fullscreen, Window, WindowBuilder},
20};
21
22#[allow(unused_variables)]
23pub trait AppState<V: GlowVertexAttribs> {
24    fn on_init(&mut self, graphics: &mut Graphics<V>) {}
25
26    fn on_redraw(&mut self, graphics: &mut Graphics<V>) {}
27
28    fn on_event(&mut self, event: Event<()>, window: &mut Window) -> bool {
29        true
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct AppConfig {
35    pub title: String,
36    pub width: u32,
37    pub height: u32,
38    pub fullscreen: bool,
39    pub maximized: bool,
40    pub vsync: bool,
41    pub decorations: bool,
42    pub transparent: bool,
43    pub double_buffer: Option<bool>,
44    pub hardware_acceleration: Option<bool>,
45    pub refresh_on_event: bool,
46    pub color: [f32; 4],
47}
48
49impl Default for AppConfig {
50    fn default() -> Self {
51        Self {
52            title: "Spitfire Application".to_owned(),
53            width: 1024,
54            height: 576,
55            fullscreen: false,
56            maximized: false,
57            vsync: false,
58            decorations: true,
59            transparent: false,
60            double_buffer: Some(true),
61            hardware_acceleration: Some(true),
62            refresh_on_event: false,
63            color: [1.0, 1.0, 1.0, 1.0],
64        }
65    }
66}
67
68impl AppConfig {
69    pub fn title(mut self, v: impl ToString) -> Self {
70        self.title = v.to_string();
71        self
72    }
73
74    pub fn width(mut self, v: u32) -> Self {
75        self.width = v;
76        self
77    }
78
79    pub fn height(mut self, v: u32) -> Self {
80        self.height = v;
81        self
82    }
83
84    pub fn fullscreen(mut self, v: bool) -> Self {
85        self.fullscreen = v;
86        self
87    }
88
89    pub fn maximized(mut self, v: bool) -> Self {
90        self.maximized = v;
91        self
92    }
93
94    pub fn vsync(mut self, v: bool) -> Self {
95        self.vsync = v;
96        self
97    }
98
99    pub fn decorations(mut self, v: bool) -> Self {
100        self.decorations = v;
101        self
102    }
103
104    pub fn transparent(mut self, v: bool) -> Self {
105        self.transparent = v;
106        self
107    }
108
109    pub fn double_buffer(mut self, v: Option<bool>) -> Self {
110        self.double_buffer = v;
111        self
112    }
113
114    pub fn hardware_acceleration(mut self, v: Option<bool>) -> Self {
115        self.hardware_acceleration = v;
116        self
117    }
118
119    pub fn refresh_on_event(mut self, v: bool) -> Self {
120        self.refresh_on_event = v;
121        self
122    }
123
124    pub fn color(mut self, v: impl Into<[f32; 4]>) -> Self {
125        self.color = v.into();
126        self
127    }
128}
129
130pub struct App<V: GlowVertexAttribs> {
131    #[cfg(not(target_arch = "wasm32"))]
132    width: u32,
133    #[cfg(not(target_arch = "wasm32"))]
134    height: u32,
135    refresh_on_event: bool,
136    event_loop: EventLoop<()>,
137    #[cfg(not(target_arch = "wasm32"))]
138    context_wrapper: ContextWrapper<PossiblyCurrent, Window>,
139    #[cfg(target_arch = "wasm32")]
140    window: Window,
141    graphics: Graphics<V>,
142}
143
144impl<V: GlowVertexAttribs> Default for App<V> {
145    fn default() -> Self {
146        Self::new(Default::default())
147    }
148}
149
150impl<V: GlowVertexAttribs> App<V> {
151    pub fn new(config: AppConfig) -> Self {
152        #[cfg(not(target_arch = "wasm32"))]
153        let AppConfig {
154            title,
155            width,
156            height,
157            fullscreen,
158            maximized,
159            vsync,
160            decorations,
161            transparent,
162            double_buffer,
163            hardware_acceleration,
164            refresh_on_event,
165            color,
166        } = config;
167        #[cfg(target_arch = "wasm32")]
168        let AppConfig {
169            title,
170            width,
171            height,
172            fullscreen,
173            maximized,
174            decorations,
175            transparent,
176            refresh_on_event,
177            color,
178            ..
179        } = config;
180        let fullscreen = if fullscreen {
181            Some(Fullscreen::Borderless(None))
182        } else {
183            None
184        };
185        let event_loop = EventLoop::new();
186        let window_builder = WindowBuilder::new()
187            .with_title(title.as_str())
188            .with_inner_size(LogicalSize::new(width, height))
189            .with_fullscreen(fullscreen)
190            .with_maximized(maximized)
191            .with_decorations(decorations)
192            .with_transparent(transparent);
193        #[cfg(not(target_arch = "wasm32"))]
194        let (context_wrapper, context) = {
195            let context_builder = ContextBuilder::new()
196                .with_vsync(vsync)
197                .with_double_buffer(double_buffer)
198                .with_hardware_acceleration(hardware_acceleration);
199            #[cfg(debug_assertions)]
200            crate::console_log!("* GL {:#?}", context_builder);
201            let context_wrapper = unsafe {
202                context_builder
203                    .build_windowed(window_builder, &event_loop)
204                    .expect("Could not build windowed context wrapper!")
205                    .make_current()
206                    .expect("Could not make windowed context wrapper a current one!")
207            };
208            let context = unsafe {
209                Context::from_loader_function(|name| {
210                    context_wrapper.get_proc_address(name) as *const _
211                })
212            };
213            (context_wrapper, context)
214        };
215        #[cfg(target_arch = "wasm32")]
216        let (window, context) = {
217            use winit::platform::web::WindowBuilderExtWebSys;
218            let canvas = web_sys::window()
219                .unwrap()
220                .document()
221                .unwrap()
222                .get_element_by_id("screen")
223                .unwrap()
224                .dyn_into::<HtmlCanvasElement>()
225                .expect("DOM element is not HtmlCanvasElement");
226            let window = window_builder
227                .with_canvas(Some(canvas.clone()))
228                .build(&event_loop)
229                .expect("Could not build window!");
230            let context = Context::from_webgl2_context(
231                canvas
232                    .get_context("webgl2")
233                    .expect("Could not get WebGL 2 context!")
234                    .expect("Could not get WebGL 2 context!")
235                    .dyn_into::<WebGl2RenderingContext>()
236                    .expect("DOM element is not WebGl2RenderingContext"),
237            );
238            (window, context)
239        };
240        let context_version = context.version();
241        #[cfg(debug_assertions)]
242        crate::console_log!("* GL Version: {:?}", context_version);
243        if context_version.major < 3 {
244            panic!("* Minimum GL version required is 3.0!");
245        }
246        let mut graphics = Graphics::<V>::new(context);
247        graphics.color = color;
248        Self {
249            #[cfg(not(target_arch = "wasm32"))]
250            width,
251            #[cfg(not(target_arch = "wasm32"))]
252            height,
253            refresh_on_event,
254            event_loop,
255            #[cfg(not(target_arch = "wasm32"))]
256            context_wrapper,
257            #[cfg(target_arch = "wasm32")]
258            window,
259            graphics,
260        }
261    }
262
263    pub fn run<S: AppState<V> + 'static>(self, mut state: S) {
264        #[cfg(not(target_arch = "wasm32"))]
265        let App {
266            mut width,
267            mut height,
268            refresh_on_event,
269            mut event_loop,
270            context_wrapper,
271            mut graphics,
272        } = self;
273        #[cfg(target_arch = "wasm32")]
274        let App {
275            refresh_on_event,
276            event_loop,
277            mut window,
278            mut graphics,
279        } = self;
280        #[cfg(not(target_arch = "wasm32"))]
281        let (context, mut window) = unsafe { context_wrapper.split() };
282        state.on_init(&mut graphics);
283        #[cfg(not(target_arch = "wasm32"))]
284        {
285            let mut running = true;
286            while running {
287                event_loop.run_return(|event, _, control_flow| {
288                    *control_flow = if refresh_on_event {
289                        ControlFlow::Wait
290                    } else {
291                        ControlFlow::Poll
292                    };
293                    match &event {
294                        Event::MainEventsCleared => {
295                            unsafe {
296                                graphics
297                                    .context()
298                                    .unwrap()
299                                    .viewport(0, 0, width as _, height as _);
300                            }
301                            graphics.main_camera.screen_size.x = width as _;
302                            graphics.main_camera.screen_size.y = height as _;
303                            let _ = graphics.prepare_frame(true);
304                            state.on_redraw(&mut graphics);
305                            let _ = graphics.draw();
306                            let _ = context.swap_buffers();
307                            *control_flow = ControlFlow::Exit;
308                        }
309                        Event::WindowEvent { event, .. } => match event {
310                            WindowEvent::Resized(physical_size) => {
311                                context.resize(*physical_size);
312                                width = physical_size.width;
313                                height = physical_size.height;
314                            }
315                            WindowEvent::CloseRequested => {
316                                running = false;
317                            }
318                            _ => {}
319                        },
320                        _ => {}
321                    }
322                    if !state.on_event(event, &mut window) {
323                        running = false;
324                    }
325                });
326            }
327            drop(graphics);
328        }
329        #[cfg(target_arch = "wasm32")]
330        {
331            event_loop.run(move |event, _, control_flow| {
332                *control_flow = if refresh_on_event {
333                    ControlFlow::Wait
334                } else {
335                    ControlFlow::Poll
336                };
337                match &event {
338                    Event::MainEventsCleared => {
339                        let dom_window = web_sys::window().unwrap();
340                        let width = dom_window.inner_width().unwrap().as_f64().unwrap().max(1.0);
341                        let height = dom_window
342                            .inner_height()
343                            .unwrap()
344                            .as_f64()
345                            .unwrap()
346                            .max(1.0);
347                        let scaled_width = width * window.scale_factor();
348                        let scaled_height = height * window.scale_factor();
349                        window.set_inner_size(LogicalSize::new(width, height));
350                        graphics.main_camera.screen_size.x = scaled_width as _;
351                        graphics.main_camera.screen_size.y = scaled_height as _;
352                        let _ = graphics.prepare_frame(true);
353                        state.on_redraw(&mut graphics);
354                        let _ = graphics.draw();
355                        window.request_redraw();
356                    }
357                    _ => {}
358                }
359                state.on_event(event, &mut window);
360            });
361        }
362    }
363}