1use std::cell::Cell;
2use std::panic;
3
4use image::{GenericImageView, Pixel};
5use instant::Instant;
6use winit::window::Icon;
7
8use abstutil::{elapsed_seconds, Timer};
9use geom::Duration;
10
11use crate::app_state::App;
12use crate::assets::Assets;
13use crate::tools::screenshot::screenshot_everything;
14use crate::{
15 Canvas, CanvasSettings, Event, EventCtx, GfxCtx, Prerender, SharedAppState, Style, Text,
16 UpdateType, UserInput,
17};
18
19const UPDATE_FREQUENCY: std::time::Duration = std::time::Duration::from_millis(1000 / 30);
20const DEBUG_PERFORMANCE: bool = false;
22
23pub(crate) struct State<A: SharedAppState> {
25 pub(crate) app: App<A>,
26 pub(crate) canvas: Canvas,
27 style: Style,
28
29 focus_owned_by: Option<String>,
30}
31
32impl<A: 'static + SharedAppState> State<A> {
33 fn event(&mut self, mut ev: Event, prerender: &Prerender) -> (Vec<UpdateType>, bool) {
35 if let Event::MouseWheelScroll(dx, dy) = ev {
36 if self.canvas.settings.invert_scroll {
37 ev = Event::MouseWheelScroll(-dx, -dy);
38 }
39 }
40
41 if let Event::Update(_) = ev {
44 } else {
45 prerender
46 .inner
47 .set_cursor_icon(if self.canvas.drag_canvas_from.is_some() {
48 if matches!(ev, Event::LeftMouseButtonUp { .. }) {
51 winit::window::CursorIcon::Default
52 } else {
53 winit::window::CursorIcon::Grabbing
54 }
55 } else {
56 winit::window::CursorIcon::Default
57 });
58 }
59
60 let input = UserInput::new(ev, &self.canvas);
63
64 {
66 if let Event::WindowResized(new_size) = input.event {
67 let inner_size = prerender.window_size();
73 trace!(
74 "winit event says the window was resized from {}, {} to {:?}. But inner size \
75 is {:?}, so using that",
76 self.canvas.window_width,
77 self.canvas.window_height,
78 new_size,
79 inner_size
80 );
81 prerender.window_resized(inner_size);
82 self.canvas.window_width = inner_size.width;
83 self.canvas.window_height = inner_size.height;
84 }
85
86 if let Event::KeyPress(key) = input.event {
87 self.canvas.keys_held.insert(key);
88 } else if let Event::KeyRelease(key) = input.event {
89 self.canvas.keys_held.remove(&key);
90 }
91
92 if let Some(pt) = input.get_moved_mouse() {
93 self.canvas.cursor = pt;
94 }
95
96 if input.event == Event::WindowGainedCursor {
97 self.canvas.window_has_cursor = true;
98 }
99 if input.window_lost_cursor() {
100 self.canvas.window_has_cursor = false;
101 }
102 }
103
104 match panic::catch_unwind(panic::AssertUnwindSafe(|| {
105 let mut ctx = EventCtx {
106 fake_mouseover: false,
107 input,
108 canvas: &mut self.canvas,
109 prerender,
110 style: &mut self.style,
111 updates_requested: vec![],
112 canvas_movement_called: false,
113
114 focus_owned_by: self.focus_owned_by.take(),
115 next_focus_owned_by: None,
118 };
119 let started = Instant::now();
120 self.app.event(&mut ctx);
121 self.focus_owned_by = ctx.next_focus_owned_by.take();
122 if DEBUG_PERFORMANCE {
123 println!("- event() took {}s", elapsed_seconds(started));
124 }
125
126 if ctx.canvas.drag_canvas_from.is_some() && !ctx.canvas_movement_called {
130 ctx.canvas.drag_canvas_from = None;
131 }
139
140 let input_used = match ev {
144 Event::KeyRelease(_) => ctx.input.has_been_consumed(),
145 _ => true,
146 };
147 (ctx.updates_requested, input_used)
148 })) {
149 Ok(pair) => pair,
150 Err(err) => {
151 self.app.shared_app_state.dump_before_abort(&self.canvas);
152 panic::resume_unwind(err);
153 }
154 }
155 }
156
157 pub(crate) fn draw(&mut self, prerender: &Prerender, screenshot: bool) -> Option<String> {
159 let mut g = GfxCtx::new(prerender, &self.canvas, &self.style, screenshot);
160
161 self.canvas.start_drawing();
162
163 let started = Instant::now();
164 if let Err(err) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
165 self.app.draw(&mut g);
166 })) {
167 self.app.shared_app_state.dump_before_abort(&self.canvas);
168 panic::resume_unwind(err);
169 }
170 let naming_hint = g.naming_hint.take();
171
172 if DEBUG_PERFORMANCE {
173 println!(
174 "----- {} uploads, {} draw calls, {} forks. draw() took {} -----",
175 g.get_num_uploads(),
176 g.num_draw_calls,
177 g.num_forks,
178 elapsed_seconds(started)
179 );
180 }
181
182 prerender.inner.draw_finished(g.inner);
183 naming_hint
184 }
185
186 pub(crate) fn free_memory(&mut self) {
187 self.app.shared_app_state.free_memory();
188 }
189}
190
191pub struct Settings {
193 pub(crate) window_title: String,
194 #[cfg(target_arch = "wasm32")]
195 pub(crate) root_dom_element_id: String,
196 pub(crate) assets_base_url: Option<String>,
197 pub(crate) assets_are_gzipped: bool,
198 dump_raw_events: bool,
199 pub(crate) scale_factor: Option<f64>,
200 require_minimum_width: Option<f64>,
201 window_icon: Option<String>,
202 loading_tips: Option<Text>,
203 load_default_textures: bool,
204 pub(crate) read_svg: Box<dyn Fn(&str) -> Vec<u8>>,
205 pub(crate) canvas_settings: CanvasSettings,
206}
207
208impl Settings {
209 pub fn new(window_title: &str) -> Settings {
211 Settings {
212 window_title: window_title.to_string(),
213 #[cfg(target_arch = "wasm32")]
214 root_dom_element_id: "widgetry-canvas".to_string(),
215 assets_base_url: None,
216 assets_are_gzipped: false,
217 dump_raw_events: false,
218 scale_factor: None,
219 require_minimum_width: None,
220 window_icon: None,
221 loading_tips: None,
222 load_default_textures: true,
223 read_svg: Box::new(|path| {
224 use std::io::Read;
225
226 let mut file =
227 fs_err::File::open(path).unwrap_or_else(|_| panic!("Couldn't read {}", path));
228 let mut buffer = Vec::new();
229 file.read_to_end(&mut buffer)
230 .unwrap_or_else(|_| panic!("Couldn't read all of {}", path));
231 buffer
232 }),
233 canvas_settings: CanvasSettings::new(),
234 }
235 }
236
237 pub fn dump_raw_events(mut self) -> Self {
239 assert!(!self.dump_raw_events);
240 self.dump_raw_events = true;
241 self
242 }
243
244 pub fn scale_factor(mut self, scale_factor: f64) -> Self {
246 self.scale_factor = Some(scale_factor);
247 self
248 }
249
250 #[cfg(target_arch = "wasm32")]
251 pub fn root_dom_element_id(mut self, element_id: String) -> Self {
252 self.root_dom_element_id = element_id;
253 self
254 }
255
256 pub fn require_minimum_width(mut self, width: f64) -> Self {
263 self.require_minimum_width = Some(width);
264 self
265 }
266
267 pub fn window_icon(mut self, path: String) -> Self {
269 self.window_icon = Some(path);
270 self
271 }
272
273 pub fn loading_tips(mut self, txt: Text) -> Self {
276 self.loading_tips = Some(txt);
277 self
278 }
279
280 pub fn read_svg(mut self, function: Box<dyn Fn(&str) -> Vec<u8>>) -> Self {
286 self.read_svg = function;
287 self
288 }
289
290 pub fn assets_base_url(mut self, value: String) -> Self {
291 self.assets_base_url = Some(value);
292 self
293 }
294
295 pub fn assets_are_gzipped(mut self, value: bool) -> Self {
296 self.assets_are_gzipped = value;
297 self
298 }
299
300 pub fn canvas_settings(mut self, settings: CanvasSettings) -> Self {
301 self.canvas_settings = settings;
302 self
303 }
304
305 pub fn load_default_textures(mut self, load_default_textures: bool) -> Self {
306 self.load_default_textures = load_default_textures;
307 self
308 }
309}
310
311pub fn run<
312 A: 'static + SharedAppState,
313 F: FnOnce(&mut EventCtx) -> (A, Vec<Box<dyn crate::app_state::State<A>>>),
314>(
315 settings: Settings,
316 make_app: F,
317) -> ! {
318 let mut timer = Timer::new("setup widgetry");
319 let (prerender_innards, event_loop) = crate::backend::setup(&settings);
320
321 if let Some(ref path) = settings.window_icon {
322 if !cfg!(target_arch = "wasm32") {
323 let image = image::open(path).unwrap();
324 let (width, height) = image.dimensions();
325 let mut rgba = Vec::with_capacity((width * height) as usize * 4);
326 for (_, _, pixel) in image.pixels() {
327 rgba.extend_from_slice(&pixel.to_rgba().0);
328 }
329 let icon = Icon::from_rgba(rgba, width, height).unwrap();
330 prerender_innards.set_window_icon(icon);
331 }
332 }
333
334 let mut style = Style::light_bg();
335 style.loading_tips = settings.loading_tips.unwrap_or_else(Text::new);
336
337 let monitor_scale_factor = prerender_innards.monitor_scale_factor();
338 let prerender = Prerender {
339 assets: Assets::new(
340 style.clone(),
341 settings.assets_base_url,
342 settings.assets_are_gzipped,
343 settings.read_svg,
344 ),
345 num_uploads: Cell::new(0),
346 inner: prerender_innards,
347 scale_factor: Cell::new(settings.scale_factor.unwrap_or(monitor_scale_factor)),
348 };
349 if let Some(min_width) = settings.require_minimum_width {
350 let initial_size = prerender.window_size();
351 if initial_size.width < min_width && settings.scale_factor.is_none() {
352 warn!(
353 "Monitor scale factor is {}, screen window is {}, but the application requires \
354 {}. Overriding the scale factor to 1.",
355 monitor_scale_factor, initial_size.width, min_width
356 );
357 prerender.scale_factor.set(1.0);
358 }
359 }
360
361 let initial_size = prerender.window_size();
362 let mut canvas = Canvas::new(initial_size, settings.canvas_settings);
363 prerender.window_resized(initial_size);
364
365 timer.start("setup app");
366 let (shared_app_state, states) = {
367 let mut ctx = EventCtx {
368 fake_mouseover: true,
369 input: UserInput::new(Event::NoOp, &canvas),
370 canvas: &mut canvas,
371 prerender: &prerender,
372 style: &mut style,
373 updates_requested: vec![],
374 canvas_movement_called: false,
375 focus_owned_by: None,
376 next_focus_owned_by: None,
377 };
378 if settings.load_default_textures {
379 timer.start("load default texture");
380 ctx.set_texture(
381 include_bytes!("../textures/spritesheet.png").to_vec(),
382 (64, 64),
383 (16.0, 16.0),
384 );
385 timer.stop("load default texture");
386 }
387 make_app(&mut ctx)
388 };
389 timer.stop("setup app");
390 let app = App {
391 states,
392 shared_app_state,
393 };
394 timer.done();
395
396 let mut state = State {
397 app,
398 canvas,
399 style,
400 focus_owned_by: None,
401 };
402
403 let dump_raw_events = settings.dump_raw_events;
404
405 let mut running = true;
406 let mut last_update = Instant::now();
407 let mut previous_left_click_at = Instant::now();
409
410 let mut previous_keycode = None;
412 event_loop.run(move |event, _, control_flow| {
413 if dump_raw_events {
414 debug!("Event: {:?}", event);
415 }
416 let ev = match event {
417 winit::event::Event::WindowEvent {
418 event: winit::event::WindowEvent::CloseRequested,
419 ..
420 } => {
421 state.app.shared_app_state.before_quit(&state.canvas);
425 std::process::exit(0);
426 }
427 winit::event::Event::WindowEvent { event, .. } => {
428 use winit::event::VirtualKeyCode;
429
430 if let winit::event::WindowEvent::KeyboardInput { input, .. } = event {
431 if previous_keycode == Some(VirtualKeyCode::LAlt)
435 && input.virtual_keycode == Some(VirtualKeyCode::Tab)
436 {
437 debug!("Skipping alt+tab event");
438 previous_keycode = input.virtual_keycode;
439 return;
440 }
441 previous_keycode = input.virtual_keycode;
442 }
443
444 let scale_factor = prerender.get_scale_factor();
445 if let Some(ev) =
446 Event::from_winit_event(event, scale_factor, previous_left_click_at)
447 {
448 ev
449 } else {
450 return;
452 }
453 }
454 winit::event::Event::RedrawRequested(_) => {
455 state.draw(&prerender, false);
456 prerender.num_uploads.set(0);
457 return;
458 }
459 winit::event::Event::MainEventsCleared => {
460 if running {
462 Event::Update(Duration::realtime_elapsed(last_update))
463 } else {
464 return;
465 }
466 }
467 _ => {
468 return;
469 }
470 };
471
472 match ev {
475 Event::Update(_) => {
476 last_update = Instant::now();
477 *control_flow =
478 winit::event_loop::ControlFlow::WaitUntil(Instant::now() + UPDATE_FREQUENCY);
479 }
480 Event::LeftMouseButtonUp {
481 is_double_click: false,
482 } => {
483 previous_left_click_at = Instant::now();
484 }
485 _ => {}
486 }
487
488 let (mut updates, input_used) = state.event(ev, &prerender);
489
490 if input_used {
491 prerender.request_redraw();
492 }
493
494 if updates.is_empty() {
495 updates.push(UpdateType::InputOnly);
496 }
497 for update in updates {
498 match update {
499 UpdateType::InputOnly => {
500 running = false;
501 *control_flow = winit::event_loop::ControlFlow::Wait;
502 }
503 UpdateType::Game => {
504 if !running {
506 last_update = Instant::now();
507 *control_flow = winit::event_loop::ControlFlow::WaitUntil(
508 Instant::now() + UPDATE_FREQUENCY,
509 );
510 }
511
512 running = true;
513 }
514 UpdateType::Pan => {}
515 UpdateType::ScreenCaptureEverything { dir, zoom, dims } => {
516 if let Err(err) =
517 screenshot_everything(&mut state, &dir, &prerender, zoom, dims)
518 {
519 error!("Couldn't screenshot everything: {}", err);
520 }
521 }
522 }
523 }
524 });
525}