widgetry/
event_ctx.rs

1use std::collections::VecDeque;
2
3use instant::Instant;
4
5use abstutil::{elapsed_seconds, Timer, TimerSink};
6use geom::{Percent, Polygon};
7
8use crate::{
9    svg, Canvas, CanvasSettings, Color, Drawable, Event, GeomBatch, GfxCtx, HorizontalAlignment,
10    Key, Line, Panel, PanelDims, Prerender, ScreenDims, Style, Text, UserInput, VerticalAlignment,
11    Widget,
12};
13
14#[derive(Clone, PartialEq, Debug)]
15pub enum UpdateType {
16    InputOnly,
17    Game,
18    Pan,
19    ScreenCaptureEverything {
20        dir: String,
21        zoom: f64,
22        dims: ScreenDims,
23    },
24}
25
26pub struct EventCtx<'a> {
27    pub(crate) fake_mouseover: bool,
28    pub input: UserInput,
29    // TODO These two probably shouldn't be public
30    pub canvas: &'a mut Canvas,
31    pub prerender: &'a Prerender,
32    pub(crate) style: &'a mut Style,
33    pub(crate) updates_requested: Vec<UpdateType>,
34    pub(crate) canvas_movement_called: bool,
35
36    /// This widget (in some panel) exclusively owns focus. Don't modify.
37    pub(crate) focus_owned_by: Option<String>,
38    /// While handling an event, this widget (in some panel) this widget declared that it owns
39    /// focus. This will become `focus_owned_by` during the next event.
40    pub(crate) next_focus_owned_by: Option<String>,
41}
42
43impl<'a> EventCtx<'a> {
44    pub fn loading_screen<O, S: Into<String>, F: FnOnce(&mut EventCtx, &mut Timer) -> O>(
45        &mut self,
46        raw_timer_name: S,
47        f: F,
48    ) -> O {
49        let timer_name = raw_timer_name.into();
50        let mut timer = Timer::new_with_sink(
51            &timer_name,
52            Box::new(LoadingScreen::new(
53                self.prerender,
54                self.style.clone(),
55                self.canvas.get_window_dims(),
56                timer_name.clone(),
57            )),
58        );
59        f(self, &mut timer)
60    }
61
62    pub fn request_update(&mut self, update_type: UpdateType) {
63        self.updates_requested.push(update_type);
64    }
65
66    /// Allow panning and zooming on the canvas. Exactly which controls are active (click-and-drag,
67    /// auto-pan at the edge of the screen, using arrow keys, etc) depend on options set. Returns
68    /// true if the canvas moved at all.
69    pub fn canvas_movement(&mut self) -> bool {
70        self.canvas_movement_called = true;
71        let prev = (self.canvas.cam_x, self.canvas.cam_y, self.canvas.cam_zoom);
72        self.updates_requested
73            .extend(self.canvas.handle_event(&mut self.input));
74        prev != (self.canvas.cam_x, self.canvas.cam_y, self.canvas.cam_zoom)
75    }
76
77    // Use to immediately plumb through an (empty) event to something
78    pub fn no_op_event<O, F: FnMut(&mut EventCtx) -> O>(
79        &mut self,
80        fake_mouseover: bool,
81        mut cb: F,
82    ) -> O {
83        let mut tmp = EventCtx {
84            fake_mouseover,
85            input: UserInput::new(Event::NoOp, self.canvas),
86            canvas: self.canvas,
87            prerender: self.prerender,
88            style: self.style,
89            updates_requested: vec![],
90            canvas_movement_called: false,
91            focus_owned_by: None,
92            next_focus_owned_by: None,
93        };
94        let result = cb(&mut tmp);
95        self.updates_requested.extend(tmp.updates_requested);
96        result
97    }
98
99    pub fn redo_mouseover(&self) -> bool {
100        self.fake_mouseover
101            || self.input.window_lost_cursor()
102            || (!self.canvas.is_dragging() && self.input.get_moved_mouse().is_some())
103            || self
104                .input
105                .get_mouse_scroll()
106                .map(|(_, dy)| dy != 0.0)
107                .unwrap_or(false)
108    }
109
110    pub fn normal_left_click(&mut self) -> bool {
111        if self.input.has_been_consumed() {
112            return false;
113        }
114        if !self.canvas.is_dragging() && self.input.left_mouse_button_released() {
115            self.input.consume_event();
116            return true;
117        }
118        false
119    }
120
121    pub fn is_key_down(&self, key: Key) -> bool {
122        self.canvas.keys_held.contains(&key)
123    }
124
125    // Delegation to assets
126    pub fn default_line_height(&self) -> f64 {
127        *self.prerender.assets.default_line_height.borrow()
128    }
129
130    // TODO I can't decide which way the API should go.
131    pub fn upload(&self, batch: GeomBatch) -> Drawable {
132        self.prerender.upload(batch)
133    }
134
135    pub(crate) fn cursor_clickable(&mut self) {
136        self.prerender
137            .inner
138            .set_cursor_icon(winit::window::CursorIcon::Hand);
139    }
140
141    pub(crate) fn cursor_grabbable(&mut self) {
142        self.prerender
143            .inner
144            .set_cursor_icon(winit::window::CursorIcon::Grab);
145    }
146
147    pub(crate) fn cursor_grabbing(&mut self) {
148        self.prerender
149            .inner
150            .set_cursor_icon(winit::window::CursorIcon::Grabbing);
151    }
152
153    pub fn style(&self) -> &Style {
154        self.style
155    }
156
157    pub fn set_style(&mut self, style: Style) {
158        *self.prerender.assets.style.borrow_mut() = style.clone();
159        self.prerender.assets.clear_text_cache();
160        *self.style = style;
161    }
162
163    pub fn make_loading_screen(&mut self, txt: Text) -> Panel {
164        let border = Color::hex("#F4DA22");
165        let (label, bytes) = crate::include_labeled_bytes!("../icons/loading.svg");
166        Panel::new_builder(Widget::row(vec![
167            Widget::custom_col(vec![
168                svg::load_svg_bytes(self.prerender, label, bytes)
169                    .unwrap()
170                    .0
171                    .scale(5.0)
172                    .into_widget(self)
173                    .container()
174                    .bg(Color::BLACK)
175                    .padding(15)
176                    .outline((5.0, border))
177                    .centered_horiz()
178                    .margin_below(5),
179                GeomBatch::from(vec![(Color::grey(0.5), Polygon::rectangle(10.0, 30.0))])
180                    .into_widget(self)
181                    .centered_horiz(),
182                self.style
183                    .loading_tips
184                    .clone()
185                    .default_fg(Color::WHITE)
186                    .wrap_to_pct(self, 25)
187                    .into_widget(self)
188                    .container()
189                    .bg(Color::BLACK)
190                    .padding(15)
191                    .outline((5.0, Color::YELLOW))
192                    .force_width_window_pct(self, Percent::int(30))
193                    .margin_below(5),
194                GeomBatch::from(vec![(Color::grey(0.5), Polygon::rectangle(10.0, 100.0))])
195                    .into_widget(self)
196                    .centered_horiz(),
197            ])
198            .centered_vert(),
199            txt.change_fg(Color::WHITE)
200                .inner_render(&self.prerender.assets, svg::LOW_QUALITY)
201                .into_widget(self)
202                .container()
203                .fill_width()
204                .padding(16)
205                .bg(Color::grey(0.3)),
206        ]))
207        .dims_width(PanelDims::ExactPercent(0.8))
208        .dims_height(PanelDims::ExactPercent(0.8))
209        .aligned(HorizontalAlignment::Center, VerticalAlignment::Center)
210        .build_custom(self)
211    }
212
213    /// Checks if an extra font has previously been loaded with `load_font`. Returns false for
214    /// built-in system fonts.
215    pub fn is_font_loaded(&self, filename: &str) -> bool {
216        self.prerender.assets.is_font_loaded(filename)
217    }
218
219    /// Loads an extra font, used only for automatic fallback of missing glyphs.
220    pub fn load_font(&mut self, filename: &str, bytes: Vec<u8>) {
221        self.prerender.assets.load_font(filename, bytes)
222    }
223
224    pub fn hide_cursor(&self) {
225        self.prerender.inner.set_cursor_visible(false);
226    }
227    pub fn show_cursor(&self) {
228        self.prerender.inner.set_cursor_visible(true);
229    }
230
231    /// The app will need to recreate its panels for this to take effect
232    pub fn set_scale_factor(&mut self, scale_factor: f64) {
233        self.prerender.scale_factor.set(scale_factor);
234        let new_size = self.prerender.window_size();
235        self.prerender.window_resized(new_size);
236        self.canvas.window_width = new_size.width;
237        self.canvas.window_height = new_size.height;
238    }
239
240    /// Only one texture can be loaded at a time; this overwrites anything previously set
241    pub fn set_texture(
242        &mut self,
243        sprite_bytes: Vec<u8>,
244        sprite_dims: (u32, u32),
245        texture_scale: (f32, f32),
246    ) {
247        self.prerender.inner.upload_texture(
248            crate::backend_glow::SpriteTexture::new(sprite_bytes, sprite_dims.0, sprite_dims.1)
249                .expect("failed to format texture sprite sheet"),
250            texture_scale,
251        );
252    }
253}
254
255struct LoadingScreen<'a> {
256    canvas: Canvas,
257    style: Style,
258    prerender: &'a Prerender,
259    lines: VecDeque<String>,
260    max_capacity: usize,
261    last_drawn: Instant,
262    title: String,
263}
264
265impl<'a> LoadingScreen<'a> {
266    fn new(
267        prerender: &'a Prerender,
268        style: Style,
269        initial_size: ScreenDims,
270        title: String,
271    ) -> LoadingScreen<'a> {
272        let canvas = Canvas::new(initial_size, CanvasSettings::new());
273        let max_capacity =
274            (0.8 * initial_size.height / *prerender.assets.default_line_height.borrow()) as usize;
275        LoadingScreen {
276            prerender,
277            lines: VecDeque::new(),
278            max_capacity,
279            // If the loading callback takes less than 0.5s, we don't redraw at all.
280            last_drawn: Instant::now(),
281            title,
282            canvas,
283            style,
284        }
285    }
286
287    fn redraw(&mut self) {
288        // TODO Ideally we wouldn't have to do this, but text rendering is still slow. :)
289        if elapsed_seconds(self.last_drawn) < 0.5 {
290            return;
291        }
292        self.last_drawn = Instant::now();
293        let mut ctx = EventCtx {
294            fake_mouseover: true,
295            input: UserInput::new(Event::NoOp, &self.canvas),
296            canvas: &mut self.canvas,
297            prerender: self.prerender,
298            style: &mut self.style,
299            updates_requested: vec![],
300            canvas_movement_called: false,
301            focus_owned_by: None,
302            next_focus_owned_by: None,
303        };
304
305        let mut txt = Text::from(Line(&self.title).small_heading());
306        for l in &self.lines {
307            txt.add_line(l);
308        }
309        let panel = ctx.make_loading_screen(txt);
310
311        let mut g = GfxCtx::new(self.prerender, &self.canvas, &self.style, false);
312        g.clear(Color::BLACK);
313        panel.draw(&mut g);
314        g.prerender.inner.draw_finished(g.inner);
315    }
316}
317
318impl<'a> TimerSink for LoadingScreen<'a> {
319    // TODO Do word wrap. Assume the window is fixed during loading, if it makes things easier.
320    fn println(&mut self, line: String) {
321        if self.lines.len() == self.max_capacity {
322            self.lines.pop_front();
323        }
324        self.lines.push_back(line);
325        self.redraw();
326    }
327
328    fn reprintln(&mut self, line: String) {
329        self.lines.pop_back();
330        self.lines.push_back(line);
331        self.redraw();
332    }
333}