ellipsoid/
lib.rs

1#![feature(async_fn_in_trait)]
2
3use std::time;
4
5use serde::{Deserialize, Serialize};
6pub use winit::event::WindowEvent;
7use winit::{
8    event::*,
9    event_loop::{ControlFlow, EventLoop},
10    window::{Window, WindowBuilder},
11};
12
13#[cfg(target_arch = "wasm32")]
14use wasm_bindgen::prelude::*;
15
16mod graphics;
17pub use graphics::{Color, GTransform, Geometry, Graphics, Shape, Textures};
18
19pub trait App<T: Textures> {
20    async fn new(window: Window) -> Self;
21    fn graphics(&self) -> &Graphics<T>;
22    fn graphics_mut(&mut self) -> &mut Graphics<T>;
23    fn input(&mut self, _event: &WindowEvent) -> bool {
24        false
25    }
26    fn update(&mut self, dt: f32);
27    fn draw(&mut self);
28}
29
30pub async fn run<T: Textures, A: App<T> + 'static>() {
31    cfg_if::cfg_if! {
32        if #[cfg(target_arch = "wasm32")] {
33            std::panic::set_hook(Box::new(console_error_panic_hook::hook));
34            console_log::init_with_level(log::Level::Debug).expect("Could't initialize logger");
35        } else {
36            env_logger::init();
37        }
38    }
39
40    let event_loop = EventLoop::new();
41    let window = WindowBuilder::new().build(&event_loop).unwrap();
42
43    #[cfg(target_arch = "wasm32")]
44    {
45        // Winit prevents sizing with CSS, so we have to set
46        // the size manually when on web.
47        use winit::dpi::PhysicalSize;
48        window.set_inner_size(PhysicalSize::new(1280, 720));
49
50        use winit::platform::web::WindowExtWebSys;
51        web_sys::window()
52            .and_then(|win| win.document())
53            .and_then(|doc| {
54                let dst = doc.get_element_by_id("ellipsoid-container")?;
55                let canvas = web_sys::Element::from(window.canvas());
56                dst.append_child(&canvas).ok()?;
57                Some(())
58            })
59            .expect("Couldn't append canvas to document body.");
60    }
61
62    let mut app = A::new(window).await;
63
64    let mut last_update = now();
65
66    event_loop.run(move |event, _, control_flow| {
67        app.graphics_mut().handle_raw_event(&event);
68        match event {
69            Event::WindowEvent {
70                ref event,
71                window_id,
72            } if window_id == app.graphics().window().id() => {
73                if !app.input(event) {
74                    match event {
75                        WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
76                        WindowEvent::Resized(physical_size) => {
77                            app.graphics_mut().resize(*physical_size);
78                        }
79                        WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
80                            // new_inner_size is &mut so w have to dereference it twice
81                            app.graphics_mut().resize(**new_inner_size);
82                        }
83                        _ => {}
84                    }
85                }
86            }
87            Event::RedrawRequested(window_id) if window_id == app.graphics().window().id() => {
88                let now = now();
89                let dt = (now - last_update).as_secs_f32();
90                last_update = now;
91
92                app.graphics_mut().update();
93                app.update(dt);
94                app.draw();
95
96                match app.graphics_mut().render() {
97                    Ok(_) => {}
98                    // Reconfigure the surface if it's lost or outdated
99                    Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
100                        let size = app.graphics().size;
101                        app.graphics_mut().resize(size)
102                    }
103                    // The system is out of memory, we should probably quit
104                    Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
105                    // We're ignoring timeouts
106                    Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
107                }
108            }
109            Event::MainEventsCleared => {
110                // RedrawRequested will only trigger once, unless we manually
111                // request it.
112                app.graphics().window().request_redraw();
113            }
114            _ => {}
115        }
116    });
117}
118
119#[derive(Serialize, Deserialize)]
120pub struct Interval {
121    last: time::Duration,
122    interval: time::Duration,
123}
124
125impl Interval {
126    pub fn new(interval: time::Duration) -> Self {
127        Self {
128            last: now(),
129            interval,
130        }
131    }
132    pub fn check(&mut self) -> bool {
133        let now = now();
134        if now - self.last > self.interval {
135            self.last = now;
136            true
137        } else {
138            false
139        }
140    }
141}
142
143pub fn now() -> time::Duration {
144    time::Duration::from_millis(chrono::Local::now().timestamp_millis() as u64)
145}
146
147pub mod prelude {
148    pub use crate::{App, Color, GTransform, Geometry, Graphics, Shape, Textures};
149    pub use async_trait::async_trait;
150    pub use egui;
151    pub use glam::{self, vec2, Vec2};
152    pub use image::ImageFormat;
153    pub use winit::{self, event::WindowEvent};
154}