widgetry/
drawing.rs

1use std::cell::Cell;
2
3use geom::{Bounds, Polygon, Pt2D, Tessellation};
4
5use crate::assets::Assets;
6use crate::backend::{GfxCtxInnards, PrerenderInnards};
7use crate::{
8    Canvas, Color, Drawable, EventCtx, GeomBatch, Key, ScreenDims, ScreenPt, ScreenRectangle,
9    Style, Text,
10};
11
12// We organize major layers of the app with whole number z values, with lower values being more on
13// top.
14//
15// Within each layer, we must only adjust the z-offset of individual polygons within (-1, 0] to
16// avoid traversing layers.
17pub(crate) const MAPSPACE_Z: f32 = 1.0;
18pub(crate) const SCREENSPACE_Z: f32 = 0.0;
19pub(crate) const MENU_Z: f32 = -1.0;
20pub(crate) const TOOLTIP_Z: f32 = -2.0;
21
22#[derive(Debug)]
23pub struct Uniforms {
24    /// (cam_x, cam_y, cam_zoom)
25    pub transform: [f32; 3],
26    /// (window_width, window_height, Z values)
27    pub window: [f32; 3],
28}
29
30impl Uniforms {
31    pub fn new(canvas: &Canvas) -> Uniforms {
32        Uniforms {
33            transform: [
34                canvas.cam_x as f32,
35                canvas.cam_y as f32,
36                canvas.cam_zoom as f32,
37            ],
38            window: [
39                canvas.window_width as f32,
40                canvas.window_height as f32,
41                MAPSPACE_Z,
42            ],
43        }
44    }
45}
46
47pub struct GfxCtx<'a> {
48    pub(crate) inner: GfxCtxInnards<'a>,
49    uniforms: Uniforms,
50
51    screencap_mode: bool,
52    pub(crate) naming_hint: Option<String>,
53
54    // TODO Don't be pub. Delegate everything.
55    pub canvas: &'a Canvas,
56    pub prerender: &'a Prerender,
57    style: &'a Style,
58
59    pub(crate) num_draw_calls: usize,
60    pub(crate) num_forks: usize,
61}
62
63impl<'a> GfxCtx<'a> {
64    pub(crate) fn new(
65        prerender: &'a Prerender,
66        canvas: &'a Canvas,
67        style: &'a Style,
68        screencap_mode: bool,
69    ) -> GfxCtx<'a> {
70        let uniforms = Uniforms::new(canvas);
71        GfxCtx {
72            inner: prerender.inner.draw_new_frame(),
73            uniforms,
74            canvas,
75            style,
76            prerender,
77            num_draw_calls: 0,
78            num_forks: 0,
79            screencap_mode,
80            naming_hint: None,
81        }
82    }
83
84    // Up to the caller to call unfork()!
85    // TODO Canvas doesn't understand this change, so things like text drawing that use
86    // map_to_screen will just be confusing.
87    pub fn fork(
88        &mut self,
89        top_left_map: Pt2D,
90        top_left_screen: ScreenPt,
91        zoom: f64,
92        z: Option<f32>,
93    ) {
94        // map_to_screen of top_left_map should be top_left_screen
95        let cam_x = (top_left_map.x() * zoom) - top_left_screen.x;
96        let cam_y = (top_left_map.y() * zoom) - top_left_screen.y;
97
98        self.uniforms.transform = [cam_x as f32, cam_y as f32, zoom as f32];
99        self.uniforms.window = [
100            self.canvas.window_width as f32,
101            self.canvas.window_height as f32,
102            z.unwrap_or(SCREENSPACE_Z),
103        ];
104        self.num_forks += 1;
105    }
106
107    pub fn fork_screenspace(&mut self) {
108        self.uniforms.transform = [0.0, 0.0, 1.0];
109        self.uniforms.window = [
110            self.canvas.window_width as f32,
111            self.canvas.window_height as f32,
112            SCREENSPACE_Z,
113        ];
114        self.num_forks += 1;
115    }
116
117    pub fn unfork(&mut self) {
118        self.uniforms = Uniforms::new(self.canvas);
119        self.num_forks += 1;
120
121        // println!("{:?}", backtrace::Backtrace::new());
122    }
123
124    pub fn clear(&mut self, color: Color) {
125        self.inner.clear(color);
126    }
127
128    // Doesn't take &Polygon, because this is inherently inefficient. If performance matters,
129    // upload, cache, and redraw.
130    pub fn draw_polygon<T: Into<Tessellation>>(&mut self, color: Color, poly: T) {
131        GeomBatch::from(vec![(color, poly)]).draw(self);
132    }
133
134    pub fn redraw(&mut self, obj: &Drawable) {
135        self.inner
136            .redraw(obj, &self.uniforms, &self.prerender.inner);
137        self.num_draw_calls += 1;
138
139        // println!("{:?}", backtrace::Backtrace::new());
140    }
141
142    pub fn redraw_at(&mut self, top_left: ScreenPt, obj: &Drawable) {
143        self.fork(Pt2D::new(0.0, 0.0), top_left, 1.0, None);
144        self.redraw(obj);
145        self.unfork();
146    }
147
148    // TODO Stateful API :(
149    pub fn enable_clipping(&mut self, rect: ScreenRectangle) {
150        let scale_factor = self.prerender.get_scale_factor();
151        self.inner.enable_clipping(rect, scale_factor, self.canvas);
152    }
153
154    pub fn disable_clipping(&mut self) {
155        let scale_factor = self.prerender.get_scale_factor();
156        self.inner.disable_clipping(scale_factor, self.canvas);
157    }
158
159    // Canvas stuff.
160
161    /// Draw a tooltip where the mouse is
162    pub fn draw_mouse_tooltip(&mut self, txt: Text) {
163        self.draw_tooltip_at(
164            txt,
165            ScreenPt::new(self.canvas.cursor.x, self.canvas.cursor.y + 20.0),
166        )
167    }
168
169    /// Draw a tooltip somewhere on the screen
170    pub fn draw_tooltip_at(&mut self, txt: Text, center: ScreenPt) {
171        if txt.is_empty() {
172            return;
173        }
174
175        // Add some padding
176        let pad = 5.0;
177        let txt = txt.default_fg(self.style.text_tooltip_color);
178        let txt_batch = txt.render(self);
179        let raw_dims = txt_batch.get_dims();
180        let dims = ScreenDims::new(raw_dims.width + 2.0 * pad, raw_dims.height + 2.0 * pad);
181
182        // TODO Maybe also consider the cursor as a valid center
183        let pt = dims.top_left_for_corner(center, self.canvas);
184        let mut batch = GeomBatch::new();
185        // TODO Outline?
186        batch.push(
187            Color::BLACK,
188            Polygon::rectangle(dims.width, dims.height).translate(pt.x, pt.y),
189        );
190        batch.append(txt_batch.translate(pt.x + pad, pt.y + pad));
191
192        // fork_screenspace, but with an even more prominent Z
193        self.uniforms.transform = [0.0, 0.0, 1.0];
194        self.uniforms.window = [
195            self.canvas.window_width as f32,
196            self.canvas.window_height as f32,
197            TOOLTIP_Z,
198        ];
199        self.num_forks += 1;
200        // Temporarily disable clipping if needed.
201        let clip = self
202            .inner
203            .take_clip(self.prerender.get_scale_factor(), self.canvas);
204        batch.draw(self);
205        self.unfork();
206        self.inner.restore_clip(clip);
207    }
208
209    pub fn get_screen_bounds(&self) -> Bounds {
210        self.canvas.get_screen_bounds()
211    }
212
213    pub fn screen_to_map(&self, pt: ScreenPt) -> Pt2D {
214        self.canvas.screen_to_map(pt)
215    }
216
217    pub fn get_cursor_in_map_space(&self) -> Option<Pt2D> {
218        self.canvas.get_cursor_in_map_space()
219    }
220
221    pub(crate) fn get_num_uploads(&self) -> usize {
222        self.prerender.num_uploads.get()
223    }
224
225    pub fn is_screencap(&self) -> bool {
226        self.screencap_mode
227    }
228
229    pub fn set_screencap_naming_hint(&mut self, hint: String) {
230        assert!(self.screencap_mode);
231        assert!(self.naming_hint.is_none());
232        self.naming_hint = Some(hint);
233    }
234
235    pub fn upload(&mut self, batch: GeomBatch) -> Drawable {
236        self.prerender.upload(batch)
237    }
238
239    // Delegation to assets
240    pub fn default_line_height(&self) -> f64 {
241        *self.prerender.assets.default_line_height.borrow()
242    }
243
244    pub fn style(&self) -> &Style {
245        self.style
246    }
247
248    pub fn is_key_down(&self, key: Key) -> bool {
249        self.canvas.keys_held.contains(&key)
250    }
251}
252
253// TODO Don't expose this directly
254// TODO Rename or something maybe. This actually owns all the permanent state of everything.
255pub struct Prerender {
256    pub(crate) inner: PrerenderInnards,
257    pub(crate) assets: Assets,
258    pub(crate) num_uploads: Cell<usize>,
259    pub(crate) scale_factor: Cell<f64>,
260}
261
262impl Prerender {
263    pub fn upload(&self, batch: GeomBatch) -> Drawable {
264        self.actually_upload(true, batch)
265    }
266
267    pub(crate) fn upload_temporary(&self, batch: GeomBatch) -> Drawable {
268        self.actually_upload(false, batch)
269    }
270
271    pub fn get_total_bytes_uploaded(&self) -> usize {
272        self.inner.total_bytes_uploaded.get()
273    }
274
275    fn actually_upload(&self, permanent: bool, batch: GeomBatch) -> Drawable {
276        self.num_uploads.set(self.num_uploads.get() + 1);
277        self.inner.actually_upload(permanent, batch)
278
279        // println!("{:?}", backtrace::Backtrace::new());
280    }
281
282    pub(crate) fn request_redraw(&self) {
283        self.inner.request_redraw()
284    }
285
286    pub fn get_scale_factor(&self) -> f64 {
287        self.scale_factor.get()
288    }
289
290    pub(crate) fn window_size(&self) -> ScreenDims {
291        self.inner.window_size(self.get_scale_factor())
292    }
293
294    pub(crate) fn window_resized(&self, new_size: ScreenDims) {
295        self.inner.window_resized(new_size, self.get_scale_factor())
296    }
297
298    pub fn assets_base_url(&self) -> Option<&str> {
299        self.assets.base_url()
300    }
301
302    pub fn assets_are_gzipped(&self) -> bool {
303        self.assets.are_gzipped()
304    }
305}
306
307impl std::convert::AsRef<Prerender> for GfxCtx<'_> {
308    fn as_ref(&self) -> &Prerender {
309        self.prerender
310    }
311}
312
313impl std::convert::AsRef<Prerender> for EventCtx<'_> {
314    fn as_ref(&self) -> &Prerender {
315        self.prerender
316    }
317}
318
319impl std::convert::AsRef<Prerender> for Prerender {
320    fn as_ref(&self) -> &Prerender {
321        self
322    }
323}