use std::cell::{Cell, RefCell}; use geom::{Bounds, Polygon, Pt2D}; use crate::assets::Assets; use crate::backend::{GfxCtxInnards, PrerenderInnards}; use crate::{ Canvas, Color, Drawable, EventCtx, GeomBatch, Key, ScreenDims, ScreenPt, ScreenRectangle, Style, Text, }; // We organize major layers of the app with whole number z values, with lower values being more on // top. // // Within each layer, we must only adjust the z-offset of individual polygons within (-1, 0] to // avoid traversing layers. pub(crate) const MAPSPACE_Z: f32 = 1.0; pub(crate) const SCREENSPACE_Z: f32 = 0.0; pub(crate) const MENU_Z: f32 = -1.0; pub(crate) const TOOLTIP_Z: f32 = -2.0; #[derive(Debug)] pub struct Uniforms { // (cam_x, cam_y, cam_zoom) pub transform: [f32; 3], // (window_width, window_height, Z values) pub window: [f32; 3], } impl Uniforms { pub fn new(canvas: &Canvas) -> Uniforms { Uniforms { transform: [ canvas.cam_x as f32, canvas.cam_y as f32, canvas.cam_zoom as f32, ], window: [ canvas.window_width as f32, canvas.window_height as f32, MAPSPACE_Z, ], } } } pub struct GfxCtx<'a> { pub(crate) inner: GfxCtxInnards<'a>, uniforms: Uniforms, screencap_mode: bool, pub(crate) naming_hint: Option, // TODO Don't be pub. Delegate everything. pub canvas: &'a Canvas, pub prerender: &'a Prerender, style: &'a Style, pub(crate) num_draw_calls: usize, pub(crate) num_forks: usize, } impl<'a> GfxCtx<'a> { pub(crate) fn new( prerender: &'a Prerender, canvas: &'a Canvas, style: &'a Style, screencap_mode: bool, ) -> GfxCtx<'a> { let uniforms = Uniforms::new(canvas); GfxCtx { inner: prerender.inner.draw_new_frame(), uniforms, canvas, style, prerender, num_draw_calls: 0, num_forks: 0, screencap_mode, naming_hint: None, } } // Up to the caller to call unfork()! // TODO Canvas doesn't understand this change, so things like text drawing that use // map_to_screen will just be confusing. pub fn fork( &mut self, top_left_map: Pt2D, top_left_screen: ScreenPt, zoom: f64, z: Option, ) { // map_to_screen of top_left_map should be top_left_screen let cam_x = (top_left_map.x() * zoom) - top_left_screen.x; let cam_y = (top_left_map.y() * zoom) - top_left_screen.y; self.uniforms.transform = [cam_x as f32, cam_y as f32, zoom as f32]; self.uniforms.window = [ self.canvas.window_width as f32, self.canvas.window_height as f32, z.unwrap_or(SCREENSPACE_Z), ]; self.num_forks += 1; } pub fn fork_screenspace(&mut self) { self.uniforms.transform = [0.0, 0.0, 1.0]; self.uniforms.window = [ self.canvas.window_width as f32, self.canvas.window_height as f32, SCREENSPACE_Z, ]; self.num_forks += 1; } pub fn unfork(&mut self) { self.uniforms = Uniforms::new(&self.canvas); self.num_forks += 1; } pub fn clear(&mut self, color: Color) { self.inner.clear(color); } // Doesn't take &Polygon, because this is inherently inefficient. If performance matters, // upload, cache, and redraw. pub fn draw_polygon(&mut self, color: Color, poly: Polygon) { GeomBatch::from(vec![(color, poly)]).draw(self); } pub fn redraw(&mut self, obj: &Drawable) { self.inner .redraw(obj, &self.uniforms, &self.prerender.inner); self.num_draw_calls += 1; // println!("{:?}", backtrace::Backtrace::new()); } pub fn redraw_at(&mut self, top_left: ScreenPt, obj: &Drawable) { self.fork(Pt2D::new(0.0, 0.0), top_left, 1.0, None); self.redraw(obj); self.unfork(); } // TODO Stateful API :( pub fn enable_clipping(&mut self, rect: ScreenRectangle) { let scale_factor = self.prerender.get_scale_factor(); self.inner.enable_clipping(rect, scale_factor, self.canvas); } pub fn disable_clipping(&mut self) { let scale_factor = self.prerender.get_scale_factor(); self.inner.disable_clipping(scale_factor, self.canvas); } // Canvas stuff. pub fn draw_mouse_tooltip(&mut self, txt: Text) { // Add some padding let pad = 5.0; let txt_batch = txt.render(self); let raw_dims = txt_batch.get_dims(); let dims = ScreenDims::new(raw_dims.width + 2.0 * pad, raw_dims.height + 2.0 * pad); // TODO Maybe also consider the cursor as a valid center let pt = dims.top_left_for_corner( ScreenPt::new(self.canvas.cursor.x, self.canvas.cursor.y + 20.0), &self.canvas, ); let mut batch = GeomBatch::new(); // TODO Outline? batch.push( Color::BLACK, Polygon::rectangle(dims.width, dims.height).translate(pt.x, pt.y), ); batch.append(txt_batch.translate(pt.x + pad, pt.y + pad)); // fork_screenspace, but with an even more prominent Z self.uniforms.transform = [0.0, 0.0, 1.0]; self.uniforms.window = [ self.canvas.window_width as f32, self.canvas.window_height as f32, TOOLTIP_Z, ]; self.num_forks += 1; // Temporarily disable clipping if needed. let clip = self .inner .take_clip(self.prerender.get_scale_factor(), self.canvas); batch.draw(self); self.unfork(); self.inner.restore_clip(clip); } pub fn get_screen_bounds(&self) -> Bounds { self.canvas.get_screen_bounds() } pub fn screen_to_map(&self, pt: ScreenPt) -> Pt2D { self.canvas.screen_to_map(pt) } pub fn get_cursor_in_map_space(&self) -> Option { self.canvas.get_cursor_in_map_space() } pub(crate) fn get_num_uploads(&self) -> usize { self.prerender.num_uploads.get() } pub fn is_screencap(&self) -> bool { self.screencap_mode } pub fn set_screencap_naming_hint(&mut self, hint: String) { assert!(self.screencap_mode); assert!(self.naming_hint.is_none()); self.naming_hint = Some(hint); } pub fn upload(&mut self, batch: GeomBatch) -> Drawable { self.prerender.upload(batch) } // Delegation to assets pub fn default_line_height(&self) -> f64 { *self.prerender.assets.default_line_height.borrow() } pub fn style(&self) -> &Style { &self.style } pub fn is_key_down(&self, key: Key) -> bool { self.canvas.keys_held.contains(&key) } } // TODO Don't expose this directly // TODO Rename or something maybe. This actually owns all the permanent state of everything. pub struct Prerender { pub(crate) inner: PrerenderInnards, pub(crate) assets: Assets, pub(crate) num_uploads: Cell, pub(crate) scale_factor: RefCell, } impl Prerender { pub fn upload(&self, batch: GeomBatch) -> Drawable { self.actually_upload(true, batch) } pub(crate) fn upload_temporary(&self, batch: GeomBatch) -> Drawable { self.actually_upload(false, batch) } pub fn get_total_bytes_uploaded(&self) -> usize { self.inner.total_bytes_uploaded.get() } fn actually_upload(&self, permanent: bool, batch: GeomBatch) -> Drawable { // println!("{:?}", backtrace::Backtrace::new()); self.num_uploads.set(self.num_uploads.get() + 1); self.inner.actually_upload(permanent, batch) } pub(crate) fn request_redraw(&self) { self.inner.request_redraw() } pub(crate) fn get_scale_factor(&self) -> f64 { *self.scale_factor.borrow() } pub(crate) fn window_size(&self) -> ScreenDims { self.inner.window_size(self.get_scale_factor()) } pub(crate) fn window_resized(&self, new_size: ScreenDims) { self.inner.window_resized(new_size, self.get_scale_factor()) } } impl std::convert::AsRef for GfxCtx<'_> { fn as_ref(&self) -> &Prerender { &self.prerender } } impl std::convert::AsRef for EventCtx<'_> { fn as_ref(&self) -> &Prerender { &self.prerender } }