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 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 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 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
100 let size = app.graphics().size;
101 app.graphics_mut().resize(size)
102 }
103 Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
105 Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
107 }
108 }
109 Event::MainEventsCleared => {
110 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}