From f42b7c003c6cca2cfc2082162390bc51c7811281 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 28 Jul 2020 15:39:38 -0600 Subject: [PATCH] layout uses logical pixels, not physical Previously it was not clear (to me at least) when a value used in layout was in units of logical pixels vs physical pixels. This lead to some ambiguity about where to scale values, and lead to some values being scaled more than once or sometimes not at all, leading to inconsistent layouts across DPI's. The intent of this change is to solve this ambiguity by having the ui clients work *exlusively* with logical pixels. To achieve this, we consolidate all scaling to the graphics backend. We translate all PhysicalPositions from the windowing libraries to LogicalPixles. Our own types: ScreenPt, ScreenDim, etc. are all in logical units. In some places, I replaced passing raw floats with a corresponding Screen* type to clarify that the units are in logical pixels. --- ezgui/src/assets.rs | 34 ++------- ezgui/src/backend_glium.rs | 49 ++++-------- ezgui/src/backend_glow.rs | 17 +++-- ezgui/src/backend_wasm.rs | 13 ++-- ezgui/src/canvas.rs | 16 ++-- ezgui/src/drawing.rs | 18 ++++- ezgui/src/event.rs | 14 ++-- ezgui/src/event_ctx.rs | 26 ++----- ezgui/src/geom.rs | 13 +--- ezgui/src/input.rs | 2 +- ezgui/src/managed.rs | 76 ++++--------------- ezgui/src/runner.rs | 50 ++++++------ ezgui/src/screen_geom.rs | 19 ++++- ezgui/src/svg.rs | 20 ++--- ezgui/src/text.rs | 7 +- ezgui/src/widgets/button.rs | 6 +- ezgui/src/widgets/just_draw.rs | 12 +-- game/src/common/city_picker.rs | 6 +- game/src/edit/traffic_signals.rs | 6 +- game/src/info/intersection.rs | 2 +- game/src/info/trip.rs | 10 +-- game/src/options.rs | 25 ------ .../src/sandbox/dashboards/traffic_signals.rs | 2 +- game/src/sandbox/dashboards/trip_table.rs | 6 +- game/src/sandbox/gameplay/commute.rs | 2 +- .../sandbox/gameplay/fix_traffic_signals.rs | 2 +- game/src/sandbox/mod.rs | 2 +- 27 files changed, 163 insertions(+), 292 deletions(-) diff --git a/ezgui/src/assets.rs b/ezgui/src/assets.rs index 3f0bb710e3..cc90d323c7 100644 --- a/ezgui/src/assets.rs +++ b/ezgui/src/assets.rs @@ -11,22 +11,20 @@ use usvg::Options; // TODO We don't need refcell maybe? Can we take &mut Assets? pub struct Assets { pub default_line_height: RefCell, - pub scale_factor: RefCell, text_cache: RefCell>, line_height_cache: RefCell>, // Keyed by filename, then scale factor mangled into a hashable form. Tuple doesn't work // because of borrowing. - svg_cache: RefCell>>, + svg_cache: RefCell>, #[cfg(not(feature = "wasm-backend"))] font_to_id: HashMap, pub text_opts: Options, } impl Assets { - pub fn new(font_dir: String, scale_factor: f64) -> Assets { + pub fn new(font_dir: String) -> Assets { let mut a = Assets { default_line_height: RefCell::new(0.0), - scale_factor: RefCell::new(scale_factor), text_cache: RefCell::new(LruCache::new(500)), line_height_cache: RefCell::new(HashMap::new()), svg_cache: RefCell::new(HashMap::new()), @@ -89,7 +87,7 @@ impl Assets { ((ascent - descent) as f64) * scale }) .unwrap(); - let height = text::SCALE_LINE_HEIGHT * *self.scale_factor.borrow() * line_height; + let height = text::SCALE_LINE_HEIGHT * line_height; self.line_height_cache.borrow_mut().insert(key, height); height @@ -113,29 +111,11 @@ impl Assets { self.text_cache.borrow_mut().put(key, geom); } - pub fn get_cached_svg(&self, key: &str, scale_factor: f64) -> Option<(GeomBatch, Bounds)> { - self.svg_cache - .borrow() - .get(key) - .and_then(|m| m.get(&key_scale_factor(scale_factor)).cloned()) - } - pub fn cache_svg(&self, key: String, scale_factor: f64, geom: GeomBatch, bounds: Bounds) { - self.svg_cache - .borrow_mut() - .entry(key) - .or_insert_with(HashMap::new) - .insert(key_scale_factor(scale_factor), (geom, bounds)); + pub fn get_cached_svg(&self, key: &str) -> Option<(GeomBatch, Bounds)> { + self.svg_cache.borrow().get(key).cloned() } - pub fn set_scale_factor(&self, scale_factor: f64) { - *self.scale_factor.borrow_mut() = scale_factor; - self.text_cache.borrow_mut().clear(); - self.line_height_cache.borrow_mut().clear(); - *self.default_line_height.borrow_mut() = - self.line_height(text::DEFAULT_FONT, text::DEFAULT_FONT_SIZE); + pub fn cache_svg(&self, key: String, geom: GeomBatch, bounds: Bounds) { + self.svg_cache.borrow_mut().insert(key, (geom, bounds)); } } - -fn key_scale_factor(x: f64) -> usize { - (x * 100.0) as usize -} diff --git a/ezgui/src/backend_glium.rs b/ezgui/src/backend_glium.rs index 7a0956fed6..99db894291 100644 --- a/ezgui/src/backend_glium.rs +++ b/ezgui/src/backend_glium.rs @@ -4,13 +4,7 @@ use glium::uniforms::UniformValue; use glium::Surface; use std::cell::Cell; -pub fn setup( - window_title: &str, -) -> ( - PrerenderInnards, - winit::event_loop::EventLoop<()>, - ScreenDims, -) { +pub fn setup(window_title: &str) -> (PrerenderInnards, winit::event_loop::EventLoop<()>) { let event_loop = winit::event_loop::EventLoop::new(); let display = match glium::Display::new( winit::window::WindowBuilder::new() @@ -105,19 +99,6 @@ pub fn setup( ) .unwrap(); - let inner_window = display.gl_window().window().inner_size(); - let monitor = event_loop.primary_monitor().size(); - let initial_size = if cfg!(target_os = "linux") { - monitor - } else { - inner_window - }; - println!( - "Inner window size is {:?}, monitor is {:?}, scale factor is {}", - inner_window, - monitor, - display.gl_window().window().scale_factor() - ); ( PrerenderInnards { display, @@ -125,7 +106,6 @@ pub fn setup( total_bytes_uploaded: Cell::new(0), }, event_loop, - ScreenDims::new(initial_size.width.into(), initial_size.height.into()), ) } @@ -166,18 +146,15 @@ impl<'a> GfxCtxInnards<'a> { .unwrap(); } - pub fn enable_clipping(&mut self, rect: ScreenRectangle, canvas: &Canvas) { + pub fn enable_clipping(&mut self, rect: ScreenRectangle, scale_factor: f64, canvas: &Canvas) { assert!(self.params.scissor.is_none()); - // The scissor rectangle has to be in device coordinates, so you would think some transform - // by scale factor (previously called HiDPI factor) has to happen here. But actually, - // window dimensions and the rectangle passed in are already scaled up. So don't do - // anything here! + // The scissor rectangle is in units of physical pixles, as opposed to logical pixels self.params.scissor = Some(glium::Rect { - left: rect.x1 as u32, + left: (rect.x1 * scale_factor) as u32, // Y-inversion - bottom: (canvas.window_height - rect.y2) as u32, - width: (rect.x2 - rect.x1) as u32, - height: (rect.y2 - rect.y1) as u32, + bottom: ((canvas.window_height - rect.y2) * scale_factor) as u32, + width: ((rect.x2 - rect.x1) * scale_factor) as u32, + height: ((rect.y2 - rect.y1) * scale_factor) as u32, }); } @@ -300,11 +277,15 @@ impl PrerenderInnards { } } - pub fn window_resized(&self, _: f64, _: f64) {} + pub fn window_resized(&self, _new_size: ScreenDims) {} - pub fn get_inner_size(&self) -> (f64, f64) { - let size = self.display.gl_window().window().inner_size(); - (size.width.into(), size.height.into()) + pub fn window_size(&self, scale_factor: f64) -> ScreenDims { + self.display + .gl_window() + .window() + .inner_size() + .to_logical(scale_factor) + .into() } pub fn set_window_icon(&self, icon: winit::window::Icon) { diff --git a/ezgui/src/backend_glow.rs b/ezgui/src/backend_glow.rs index 162439976f..751fa16cbf 100644 --- a/ezgui/src/backend_glow.rs +++ b/ezgui/src/backend_glow.rs @@ -312,14 +312,15 @@ impl PrerenderInnards { } } - pub fn window_resized(&self, width: f64, height: f64) { - self.windowed_context - .resize(winit::dpi::PhysicalSize::new(width as u32, height as u32)); - unsafe { - self.gl.viewport(0, 0, width as i32, height as i32); - // I think it's safe to assume there's not a clip right now. - self.gl.scissor(0, 0, width as i32, height as i32); - } + pub fn window_resized(&self, new_size: ScreenDims) { + todo!("DPI TODO: support other backends"); + //self.windowed_context + // .resize(winit::dpi::PhysicalSize::new(width as u32, height as u32)); + //unsafe { + // self.gl.viewport(0, 0, width as i32, height as i32); + // // I think it's safe to assume there's not a clip right now. + // self.gl.scissor(0, 0, width as i32, height as i32); + //} } pub fn get_inner_size(&self) -> (f64, f64) { diff --git a/ezgui/src/backend_wasm.rs b/ezgui/src/backend_wasm.rs index 0b40c31b2d..345a49a144 100644 --- a/ezgui/src/backend_wasm.rs +++ b/ezgui/src/backend_wasm.rs @@ -316,12 +316,13 @@ impl PrerenderInnards { } } - pub fn window_resized(&self, width: f64, height: f64) { - unsafe { - self.gl.viewport(0, 0, width as i32, height as i32); - // I think it's safe to assume there's not a clip right now. - self.gl.scissor(0, 0, width as i32, height as i32); - } + pub fn window_resized(&self, new_size: ScreenDims) { + todo!("DPI TODO: support other backends"); + //unsafe { + // self.gl.viewport(0, 0, width as i32, height as i32); + // // I think it's safe to assume there's not a clip right now. + // self.gl.scissor(0, 0, width as i32, height as i32); + //} } pub fn get_inner_size(&self) -> (f64, f64) { diff --git a/ezgui/src/canvas.rs b/ezgui/src/canvas.rs index db252a3aa3..e6858f2313 100644 --- a/ezgui/src/canvas.rs +++ b/ezgui/src/canvas.rs @@ -1,4 +1,3 @@ -use crate::assets::Assets; use crate::{Key, ScreenDims, ScreenPt, ScreenRectangle, UpdateType, UserInput}; use abstutil::Timer; use geom::{Bounds, Pt2D}; @@ -49,7 +48,7 @@ pub struct Canvas { } impl Canvas { - pub(crate) fn new(initial_width: f64, initial_height: f64) -> Canvas { + pub(crate) fn new(initial_dims: ScreenDims) -> Canvas { Canvas { cam_x: 0.0, cam_y: 0.0, @@ -62,8 +61,8 @@ impl Canvas { drag_canvas_from: None, drag_just_ended: false, - window_width: initial_width, - window_height: initial_height, + window_width: initial_dims.width, + window_height: initial_dims.height, map_dims: (0.0, 0.0), invert_scroll: false, @@ -265,6 +264,10 @@ impl Canvas { b } + pub fn get_window_dims(&self) -> ScreenDims { + ScreenDims::new(self.window_width, self.window_height) + } + fn get_map_bounds(&self) -> Bounds { let mut b = Bounds::new(); b.update(Pt2D::new(0.0, 0.0)); @@ -306,7 +309,6 @@ impl Canvas { pub(crate) fn align_window( &self, - assets: &Assets, dims: ScreenDims, horiz: HorizontalAlignment, vert: VerticalAlignment, @@ -323,9 +325,7 @@ impl Canvas { VerticalAlignment::Center => (self.window_height - dims.height) / 2.0, VerticalAlignment::Bottom => self.window_height - dims.height, // TODO Hack - VerticalAlignment::BottomAboveOSD => { - self.window_height - dims.height - 60.0 * *assets.scale_factor.borrow() - } + VerticalAlignment::BottomAboveOSD => self.window_height - dims.height - 60.0, VerticalAlignment::Percent(pct) => pct * self.window_height, VerticalAlignment::Above(y) => y - dims.height, VerticalAlignment::Below(y) => y, diff --git a/ezgui/src/drawing.rs b/ezgui/src/drawing.rs index 2791bd5048..e113965cc7 100644 --- a/ezgui/src/drawing.rs +++ b/ezgui/src/drawing.rs @@ -4,7 +4,7 @@ use crate::{ Canvas, Color, Drawable, GeomBatch, ScreenDims, ScreenPt, ScreenRectangle, Style, Text, }; use geom::{Bounds, Polygon, Pt2D}; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; // Lower is more on top const MAPSPACE_Z: f32 = 1.0; @@ -136,7 +136,8 @@ impl<'a> GfxCtx<'a> { // TODO Stateful API :( pub fn enable_clipping(&mut self, rect: ScreenRectangle) { - self.inner.enable_clipping(rect, self.canvas); + let scale_factor = self.prerender.get_scale_factor(); + self.inner.enable_clipping(rect, scale_factor, self.canvas); } pub fn disable_clipping(&mut self) { @@ -227,6 +228,7 @@ pub struct Prerender { pub(crate) inner: PrerenderInnards, pub(crate) assets: Assets, pub(crate) num_uploads: Cell, + pub scale_factor: RefCell, } impl Prerender { @@ -251,4 +253,16 @@ impl Prerender { pub(crate) fn request_redraw(&self) { self.inner.request_redraw() } + + pub fn get_scale_factor(&self) -> f64 { + *self.scale_factor.borrow() + } + + pub fn set_scale_factor(&self, scale_factor: f64) { + *self.scale_factor.borrow_mut() = scale_factor; + } + + pub fn window_size(&self) -> ScreenDims { + self.inner.window_size(self.get_scale_factor()) + } } diff --git a/ezgui/src/event.rs b/ezgui/src/event.rs index 26c7572d1f..767a9b08a6 100644 --- a/ezgui/src/event.rs +++ b/ezgui/src/event.rs @@ -1,4 +1,4 @@ -use crate::ScreenPt; +use crate::{ScreenDims, ScreenPt}; use geom::Duration; use winit::event::{ ElementState, KeyboardInput, MouseButton, MouseScrollDelta, VirtualKeyCode, WindowEvent, @@ -23,11 +23,11 @@ pub enum Event { WindowLostCursor, WindowGainedCursor, MouseWheelScroll(f64, f64), - WindowResized(f64, f64), + WindowResized(ScreenDims), } impl Event { - pub fn from_winit_event(ev: WindowEvent) -> Option { + pub fn from_winit_event(ev: WindowEvent, scale_factor: f64) -> Option { match ev { WindowEvent::MouseInput { state, button, .. } => match (button, state) { (MouseButton::Left, ElementState::Pressed) => Some(Event::LeftMouseButtonDown), @@ -47,9 +47,9 @@ impl Event { None } } - WindowEvent::CursorMoved { position, .. } => { - Some(Event::MouseMovedTo(ScreenPt::new(position.x, position.y))) - } + WindowEvent::CursorMoved { position, .. } => Some(Event::MouseMovedTo( + position.to_logical(scale_factor).into(), + )), WindowEvent::MouseWheel { delta, .. } => match delta { MouseScrollDelta::LineDelta(dx, dy) => { if dx == 0.0 && dy == 0.0 { @@ -72,7 +72,7 @@ impl Event { } }, WindowEvent::Resized(size) => { - Some(Event::WindowResized(size.width.into(), size.height.into())) + Some(Event::WindowResized(size.to_logical(scale_factor).into())) } WindowEvent::Focused(gained) => Some(if gained { Event::WindowGainedCursor diff --git a/ezgui/src/event_ctx.rs b/ezgui/src/event_ctx.rs index 71ba9cf43a..1250dc4074 100644 --- a/ezgui/src/event_ctx.rs +++ b/ezgui/src/event_ctx.rs @@ -1,6 +1,6 @@ use crate::{ - svg, text, Canvas, Color, Drawable, Event, GeomBatch, GfxCtx, Line, Prerender, ScreenPt, Style, - Text, UserInput, + svg, text, Canvas, Color, Drawable, Event, GeomBatch, GfxCtx, Line, Prerender, ScreenDims, + ScreenPt, Style, Text, UserInput, }; use abstutil::{elapsed_seconds, Timer, TimerSink}; use geom::Polygon; @@ -41,8 +41,7 @@ impl<'a> EventCtx<'a> { &timer_name, Box::new(LoadingScreen::new( self.prerender, - self.canvas.window_width, - self.canvas.window_height, + self.canvas.get_window_dims(), timer_name.clone(), )), ); @@ -113,18 +112,6 @@ impl<'a> EventCtx<'a> { self.prerender.upload(batch) } - pub fn set_scale_factor(&self, scale: f64) { - self.prerender.assets.set_scale_factor(scale) - } - - pub fn get_scale_factor(&self) -> f64 { - *self.prerender.assets.scale_factor.borrow() - } - - pub fn monitor_scale_factor(&self) -> f64 { - self.prerender.inner.monitor_scale_factor() - } - pub(crate) fn cursor_clickable(&mut self) { self.prerender .inner @@ -153,13 +140,12 @@ pub struct LoadingScreen<'a> { impl<'a> LoadingScreen<'a> { pub fn new( prerender: &'a Prerender, - initial_width: f64, - initial_height: f64, + initial_size: ScreenDims, title: String, ) -> LoadingScreen<'a> { - let canvas = Canvas::new(initial_width, initial_height); + let canvas = Canvas::new(initial_size); let max_capacity = - (0.8 * initial_height / *prerender.assets.default_line_height.borrow()) as usize; + (0.8 * initial_size.height / *prerender.assets.default_line_height.borrow()) as usize; LoadingScreen { prerender, lines: VecDeque::new(), diff --git a/ezgui/src/geom.rs b/ezgui/src/geom.rs index f2aafebc38..e96738e01c 100644 --- a/ezgui/src/geom.rs +++ b/ezgui/src/geom.rs @@ -137,23 +137,18 @@ impl GeomBatch { pub fn from_svg_contents(raw: Vec) -> GeomBatch { let mut batch = GeomBatch::new(); let svg_tree = usvg::Tree::from_data(&raw, &usvg::Options::default()).unwrap(); - svg::add_svg_inner(&mut batch, svg_tree, svg::HIGH_QUALITY, 1.0).unwrap(); + svg::add_svg_inner(&mut batch, svg_tree, svg::HIGH_QUALITY).unwrap(); batch } /// Returns a batch containing an SVG from a file. pub fn mapspace_svg(prerender: &Prerender, filename: &str) -> GeomBatch { - svg::load_svg(prerender, filename, 1.0).0 + svg::load_svg(prerender, filename).0 } - /// Returns a batch containing an SVG from a file. Uses the current screen's scale factor. + /// Returns a batch containing an SVG from a file. pub fn screenspace_svg>(prerender: &Prerender, filename: I) -> GeomBatch { - svg::load_svg( - prerender, - &filename.into(), - *prerender.assets.scale_factor.borrow(), - ) - .0 + svg::load_svg(prerender, &filename.into()).0 } /// Transforms all colors in a batch. diff --git a/ezgui/src/input.rs b/ezgui/src/input.rs index a97a9879ab..915d12f134 100644 --- a/ezgui/src/input.rs +++ b/ezgui/src/input.rs @@ -101,7 +101,7 @@ impl UserInput { pub fn is_window_resized(&self) -> bool { match self.event { - Event::WindowResized(_, _) => true, + Event::WindowResized(_) => true, _ => false, } } diff --git a/ezgui/src/managed.rs b/ezgui/src/managed.rs index d99c80b9cd..9f10ee6869 100644 --- a/ezgui/src/managed.rs +++ b/ezgui/src/managed.rs @@ -230,7 +230,7 @@ impl Widget { // TODO These are literally just convenient APIs to avoid importing JustDraw. Do we want this // or not? pub fn draw_batch(ctx: &EventCtx, batch: GeomBatch) -> Widget { - JustDraw::wrap(ctx, batch.scale(ctx.get_scale_factor())) + JustDraw::wrap(ctx, batch) } pub fn draw_svg>(ctx: &EventCtx, filename: I) -> Widget { JustDraw::svg(ctx, filename.into()) @@ -307,9 +307,8 @@ impl Widget { // TODO 35 is a sad magic number. By default, Composites have padding of 16, so assuming // this geometry is going in one of those, it makes sense to subtract 32. But that still // caused some scrolling in a test, so snip away a few more pixels. - self.layout.style.min_size.width = Dimension::Points( - (w * ctx.canvas.window_width * ctx.get_scale_factor()) as f32 - 35.0, - ); + self.layout.style.min_size.width = + Dimension::Points((w * ctx.canvas.window_width) as f32 - 35.0); } // Pretend we're in a Composite and basically copy recompute_layout @@ -325,12 +324,7 @@ impl Widget { .unwrap(); let mut nodes = vec![]; - self.get_flexbox( - root, - ctx.get_scale_factor() as f32, - &mut stretch, - &mut nodes, - ); + self.get_flexbox(root, &mut stretch, &mut nodes); nodes.reverse(); let container_size = Size { @@ -371,13 +365,7 @@ impl Widget { } // Populate a flattened list of Nodes, matching the traversal order - fn get_flexbox( - &self, - parent: Node, - scale_factor: f32, - stretch: &mut Stretch, - nodes: &mut Vec, - ) { + fn get_flexbox(&self, parent: Node, stretch: &mut Stretch, nodes: &mut Vec) { if let Some(container) = self.widget.downcast_ref::() { let mut style = self.layout.style.clone(); style.flex_direction = if container.is_row { @@ -388,7 +376,7 @@ impl Widget { let node = stretch.new_node(style, Vec::new()).unwrap(); nodes.push(node); for widget in &container.members { - widget.get_flexbox(node, scale_factor, stretch, nodes); + widget.get_flexbox(node, stretch, nodes); } stretch.add_child(parent, node).unwrap(); return; @@ -398,32 +386,6 @@ impl Widget { width: Dimension::Points(self.widget.get_dims().width as f32), height: Dimension::Points(self.widget.get_dims().height as f32), }; - if scale_factor != 1.0 { - if let Dimension::Points(ref mut px) = style.padding.start { - *px *= scale_factor; - } - if let Dimension::Points(ref mut px) = style.padding.end { - *px *= scale_factor; - } - if let Dimension::Points(ref mut px) = style.padding.top { - *px *= scale_factor; - } - if let Dimension::Points(ref mut px) = style.padding.bottom { - *px *= scale_factor; - } - if let Dimension::Points(ref mut px) = style.margin.start { - *px *= scale_factor; - } - if let Dimension::Points(ref mut px) = style.margin.end { - *px *= scale_factor; - } - if let Dimension::Points(ref mut px) = style.margin.top { - *px *= scale_factor; - } - if let Dimension::Points(ref mut px) = style.margin.bottom { - *px *= scale_factor; - } - } let node = stretch.new_node(style, Vec::new()).unwrap(); stretch.add_child(parent, node).unwrap(); nodes.push(node); @@ -660,12 +622,7 @@ impl Composite { .unwrap(); let mut nodes = vec![]; - self.top_level.get_flexbox( - root, - ctx.get_scale_factor() as f32, - &mut stretch, - &mut nodes, - ); + self.top_level.get_flexbox(root, &mut stretch, &mut nodes); nodes.reverse(); // TODO Express more simply. Constraining this seems useless. @@ -682,9 +639,9 @@ impl Composite { let result = stretch.layout(root).unwrap(); ScreenDims::new(result.size.width.into(), result.size.height.into()) }; - let top_left = - ctx.canvas - .align_window(&ctx.prerender.assets, effective_dims, self.horiz, self.vert); + let top_left = ctx + .canvas + .align_window(effective_dims, self.horiz, self.vert); let offset = self.scroll_offset(); self.top_level.apply_flexbox( &stretch, @@ -792,12 +749,9 @@ impl Composite { g.fork_screenspace(); g.draw_polygon(Color::RED.alpha(0.5), self.top_level.rect.to_polygon()); - let top_left = g.canvas.align_window( - &g.prerender.assets, - self.container_dims, - self.horiz, - self.vert, - ); + let top_left = g + .canvas + .align_window(self.container_dims, self.horiz, self.vert); g.draw_polygon( Color::BLUE.alpha(0.5), Polygon::rectangle(self.container_dims.width, self.container_dims.height) @@ -1006,9 +960,7 @@ impl CompositeBuilder { }; // If the panel fits without a scrollbar, don't add one. - let top_left = - ctx.canvas - .align_window(&ctx.prerender.assets, c.container_dims, c.horiz, c.vert); + let top_left = ctx.canvas.align_window(c.container_dims, c.horiz, c.vert); if c.contents_dims.width > c.container_dims.width { c.scrollable_x = true; c.top_level = Widget::custom_col(vec![ diff --git a/ezgui/src/runner.rs b/ezgui/src/runner.rs index 89351e1227..2db6316a95 100644 --- a/ezgui/src/runner.rs +++ b/ezgui/src/runner.rs @@ -4,7 +4,7 @@ use crate::{Canvas, Event, EventCtx, GfxCtx, Key, Prerender, Style, UpdateType, use geom::Duration; use image::{GenericImageView, Pixel}; use instant::Instant; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::panic; use winit::window::Icon; @@ -59,22 +59,16 @@ impl State { // Update some ezgui state that's stashed in Canvas for sad reasons. { - if let Event::WindowResized(width, height) = input.event { - let inner_size = prerender.inner.get_inner_size(); + if let Event::WindowResized(new_size) = input.event { + let inner_size = prerender.window_size(); println!( - "winit event says the window was resized from {}, {} to {}, {}. But inner \ - size is {}, {}, so using that", - self.canvas.window_width, - self.canvas.window_height, - width, - height, - inner_size.0, - inner_size.1 + "winit event says the window was resized from {}, {} to {:?}. But inner size \ + is {:?}, so using that", + self.canvas.window_width, self.canvas.window_height, new_size, inner_size ); - let (width, height) = inner_size; - prerender.inner.window_resized(width, height); - self.canvas.window_width = width; - self.canvas.window_height = height; + prerender.inner.window_resized(new_size); + self.canvas.window_width = inner_size.width; + self.canvas.window_height = inner_size.height; } if input.event == Event::KeyPress(Key::LeftControl) { @@ -197,11 +191,8 @@ impl Settings { } pub fn run G>(settings: Settings, make_gui: F) -> ! { - let (prerender_innards, event_loop, window_size) = - crate::backend::setup(&settings.window_title); + let (prerender_innards, event_loop) = crate::backend::setup(&settings.window_title); - let mut canvas = Canvas::new(window_size.width, window_size.height); - prerender_innards.window_resized(canvas.window_width, canvas.window_height); if let Some(ref path) = settings.window_icon { if !cfg!(target_arch = "wasm32") { let image = image::open(path).unwrap(); @@ -214,18 +205,24 @@ pub fn run G>(settings: Settings, prerender_innards.set_window_icon(icon); } } + + let monitor_scale_factor = prerender_innards.monitor_scale_factor(); let prerender = Prerender { - assets: Assets::new( - abstutil::path("system/fonts"), - settings - .scale_factor - .unwrap_or_else(|| prerender_innards.monitor_scale_factor()), - ), + assets: Assets::new(abstutil::path("system/fonts")), num_uploads: Cell::new(0), inner: prerender_innards, + scale_factor: RefCell::new(settings.scale_factor.unwrap_or(monitor_scale_factor)), }; let mut style = Style::standard(); + // DPI TODO: This is going to cause a regression for devs on linux - since not all UI elements + // properly resize, e.g. the minimap. However, since only dev's are launching directly to the + // simulation, in practice this shouldn't cause much of an issue until we can get the minimap + // to resize itself. + let initial_size = prerender.window_size(); + let mut canvas = Canvas::new(initial_size); + prerender.inner.window_resized(initial_size); + let gui = make_gui(&mut EventCtx { fake_mouseover: true, input: UserInput::new(Event::NoOp, &canvas), @@ -265,7 +262,8 @@ pub fn run G>(settings: Settings, std::process::exit(0); } winit::event::Event::WindowEvent { event, .. } => { - if let Some(ev) = Event::from_winit_event(event) { + let scale_factor = prerender.get_scale_factor(); + if let Some(ev) = Event::from_winit_event(event, scale_factor) { ev } else { // Don't touch control_flow if we got an irrelevant event diff --git a/ezgui/src/screen_geom.rs b/ezgui/src/screen_geom.rs index 3a88e137d1..4a52bcc1ac 100644 --- a/ezgui/src/screen_geom.rs +++ b/ezgui/src/screen_geom.rs @@ -2,6 +2,7 @@ use crate::Canvas; use geom::{trim_f64, Polygon, Pt2D}; use serde::{Deserialize, Serialize}; +/// ScreenPt is in units of logical pixels, as opposed to physical pixels. #[derive(Debug, Clone, Copy, PartialEq)] pub struct ScreenPt { pub x: f64, @@ -20,6 +21,13 @@ impl ScreenPt { } } +impl From> for ScreenPt { + fn from(lp: winit::dpi::LogicalPosition) -> ScreenPt { + ScreenPt { x: lp.x, y: lp.y } + } +} + +/// ScreenRectangle is in units of logical pixels, as opposed to physical pixels. #[derive(Clone, Debug)] pub struct ScreenRectangle { pub x1: f64, @@ -87,7 +95,7 @@ impl ScreenRectangle { } } -// TODO Everything screen-space should probably just be usize, can't have fractional pixels? +/// ScreenDims is in units of logical pixels, as opposed to physical pixels. #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct ScreenDims { pub width: f64, @@ -125,3 +133,12 @@ impl ScreenDims { } } } + +impl From> for ScreenDims { + fn from(lp: winit::dpi::LogicalSize) -> ScreenDims { + ScreenDims { + width: lp.width, + height: lp.height, + } + } +} diff --git a/ezgui/src/svg.rs b/ezgui/src/svg.rs index b7b1300bed..59b2838387 100644 --- a/ezgui/src/svg.rs +++ b/ezgui/src/svg.rs @@ -12,8 +12,8 @@ pub const LOW_QUALITY: f32 = 1.0; // Code here adapted from // https://github.com/nical/lyon/blob/0d0ee771180fb317b986d9cf30266722e0773e01/examples/wgpu_svg/src/main.rs -pub fn load_svg(prerender: &Prerender, filename: &str, scale_factor: f64) -> (GeomBatch, Bounds) { - if let Some(pair) = prerender.assets.get_cached_svg(filename, scale_factor) { +pub fn load_svg(prerender: &Prerender, filename: &str) -> (GeomBatch, Bounds) { + if let Some(pair) = prerender.assets.get_cached_svg(filename) { return pair; } @@ -25,14 +25,11 @@ pub fn load_svg(prerender: &Prerender, filename: &str, scale_factor: f64) -> (Ge }; let svg_tree = usvg::Tree::from_data(&raw, &usvg::Options::default()).unwrap(); let mut batch = GeomBatch::new(); - match add_svg_inner(&mut batch, svg_tree, HIGH_QUALITY, scale_factor) { + match add_svg_inner(&mut batch, svg_tree, HIGH_QUALITY) { Ok(bounds) => { - prerender.assets.cache_svg( - filename.to_string(), - scale_factor, - batch.clone(), - bounds.clone(), - ); + prerender + .assets + .cache_svg(filename.to_string(), batch.clone(), bounds.clone()); (batch, bounds) } Err(err) => panic!("{}: {}", filename, err), @@ -46,7 +43,6 @@ pub fn add_svg_inner( batch: &mut GeomBatch, svg_tree: usvg::Tree, tolerance: f32, - scale: f64, ) -> Result { let mut fill_tess = tessellation::FillTessellator::new(); let mut stroke_tess = tessellation::StrokeTessellator::new(); @@ -88,7 +84,7 @@ pub fn add_svg_inner( Polygon::precomputed( mesh.vertices .into_iter() - .map(|v| Pt2D::new(scale * f64::from(v.x), scale * f64::from(v.y))) + .map(|v| Pt2D::new(f64::from(v.x), f64::from(v.y))) .collect(), mesh.indices.into_iter().map(|idx| idx as usize).collect(), ), @@ -97,7 +93,7 @@ pub fn add_svg_inner( let size = svg_tree.svg_node().size; Ok(Bounds::from(&vec![ Pt2D::new(0.0, 0.0), - Pt2D::new(scale * size.width(), scale * size.height()), + Pt2D::new(size.width(), size.height()), ])) } diff --git a/ezgui/src/text.rs b/ezgui/src/text.rs index 1bf8eee558..39e751512f 100644 --- a/ezgui/src/text.rs +++ b/ezgui/src/text.rs @@ -454,12 +454,7 @@ fn render_line(spans: Vec, tolerance: f32, assets: &Assets) -> GeomBat Err(err) => panic!("render_line({}): {}", contents, err), }; let mut batch = GeomBatch::new(); - match crate::svg::add_svg_inner( - &mut batch, - svg_tree, - tolerance, - *assets.scale_factor.borrow(), - ) { + match crate::svg::add_svg_inner(&mut batch, svg_tree, tolerance) { Ok(_) => batch, Err(err) => panic!("render_line({}): {}", contents, err), } diff --git a/ezgui/src/widgets/button.rs b/ezgui/src/widgets/button.rs index 9e81d1c637..249ca9d8b9 100644 --- a/ezgui/src/widgets/button.rs +++ b/ezgui/src/widgets/button.rs @@ -255,11 +255,7 @@ impl BtnBuilder { rewrite_hover, maybe_tooltip, } => { - let (normal, bounds) = svg::load_svg( - ctx.prerender, - &path, - *ctx.prerender.assets.scale_factor.borrow(), - ); + let (normal, bounds) = svg::load_svg(ctx.prerender, &path); let geom = Polygon::rectangle(bounds.width(), bounds.height()); let hovered = normal.clone().color(rewrite_hover); diff --git a/ezgui/src/widgets/just_draw.rs b/ezgui/src/widgets/just_draw.rs index cb5b2c1f77..38910e7198 100644 --- a/ezgui/src/widgets/just_draw.rs +++ b/ezgui/src/widgets/just_draw.rs @@ -22,11 +22,7 @@ impl JustDraw { } pub fn svg(ctx: &EventCtx, filename: String) -> Widget { - let (batch, bounds) = svg::load_svg( - ctx.prerender, - &filename, - *ctx.prerender.assets.scale_factor.borrow(), - ); + let (batch, bounds) = svg::load_svg(ctx.prerender, &filename); // TODO The dims will be wrong; it'll only look at geometry, not the padding in the image. Widget::new(Box::new(JustDraw { dims: ScreenDims::new(bounds.width(), bounds.height()), @@ -35,11 +31,7 @@ impl JustDraw { })) } pub fn svg_transform(ctx: &EventCtx, filename: &str, rewrite: RewriteColor) -> Widget { - let (batch, bounds) = svg::load_svg( - ctx.prerender, - filename, - *ctx.prerender.assets.scale_factor.borrow(), - ); + let (batch, bounds) = svg::load_svg(ctx.prerender, filename); let batch = batch.color(rewrite); // TODO The dims will be wrong; it'll only look at geometry, not the padding in the image. Widget::new(Box::new(JustDraw { diff --git a/game/src/common/city_picker.rs b/game/src/common/city_picker.rs index 6e00b08840..780fed4536 100644 --- a/game/src/common/city_picker.rs +++ b/game/src/common/city_picker.rs @@ -36,9 +36,9 @@ impl CityPicker { &mut abstutil::Timer::throwaway(), ) { let bounds = city.boundary.get_bounds(); - let zoom_no_scale_factor = (0.8 * ctx.canvas.window_width / bounds.width()) + + let zoom = (0.8 * ctx.canvas.window_width / bounds.width()) .min(0.8 * ctx.canvas.window_height / bounds.height()); - let zoom = zoom_no_scale_factor / ctx.get_scale_factor(); batch.push(app.cs.map_background, city.boundary); for (area_type, polygon) in city.areas { @@ -56,7 +56,7 @@ impl CityPicker { } else { batch.push(color, polygon.to_outline(Distance::meters(200.0)).unwrap()); } - regions.push((name, color, polygon.scale(zoom_no_scale_factor))); + regions.push((name, color, polygon.scale(zoom))); } batch = batch.scale(zoom); } diff --git a/game/src/edit/traffic_signals.rs b/game/src/edit/traffic_signals.rs index 46d1f36a17..c4c4eb4b17 100644 --- a/game/src/edit/traffic_signals.rs +++ b/game/src/edit/traffic_signals.rs @@ -932,9 +932,7 @@ fn make_signal_diagram( ctx, GeomBatch::from(vec![( Color::WHITE, - // TODO draw_batch will scale up, but that's inappropriate here, since we're - // depending on window width, which already factors in scale - Polygon::rectangle(0.2 * ctx.canvas.window_width / ctx.get_scale_factor(), 2.0), + Polygon::rectangle(0.2 * ctx.canvas.window_width, 2.0), )]), ) .centered_horiz(), @@ -1029,7 +1027,7 @@ fn make_signal_diagram( ctx, GeomBatch::from(vec![( Color::WHITE, - Polygon::rectangle(0.2 * ctx.canvas.window_width / ctx.get_scale_factor(), 2.0), + Polygon::rectangle(0.2 * ctx.canvas.window_width, 2.0), )]), ) .centered_horiz(), diff --git a/game/src/info/intersection.rs b/game/src/info/intersection.rs index c3c55d4db6..8be965ea9e 100644 --- a/game/src/info/intersection.rs +++ b/game/src/info/intersection.rs @@ -167,7 +167,7 @@ pub fn current_demand( txt_batch.append( Text::from(Line(prettyprint_usize(demand)).fg(Color::RED)) .render_ctx(ctx) - .scale(0.15 / ctx.get_scale_factor()) + .scale(0.15) .centered_on(pl.middle()), ); } diff --git a/game/src/info/trip.rs b/game/src/info/trip.rs index a4debd439d..aea237ef9c 100644 --- a/game/src/info/trip.rs +++ b/game/src/info/trip.rs @@ -496,12 +496,8 @@ fn make_timeline( (100.0 * percent_duration) as usize ))); - // TODO We're manually mixing screenspace_svg, our own geometry, and a centered_on(). Be - // careful about the scale factor. I'm confused about some of the math here, figured it out - // by trial and error. - let scale = ctx.get_scale_factor(); let phase_width = total_width * percent_duration; - let rect = Polygon::rectangle(phase_width * scale, 15.0 * scale); + let rect = Polygon::rectangle(phase_width, 15.0); let mut normal = GeomBatch::from(vec![(color, rect.clone())]); if idx == num_phases - 1 { if let Some(p) = progress_along_path { @@ -510,7 +506,7 @@ fn make_timeline( ctx.prerender, "system/assets/timeline/current_pos.svg", ) - .centered_on(Pt2D::new(p * phase_width * scale, 7.5 * scale)), + .centered_on(Pt2D::new(p * phase_width, 7.5)), ); } } @@ -534,7 +530,7 @@ fn make_timeline( ) .centered_on( // TODO Hardcoded layouting... - Pt2D::new(0.5 * phase_width * scale, -20.0), + Pt2D::new(0.5 * phase_width, -20.0), ), ); diff --git a/game/src/options.rs b/game/src/options.rs index 320a19386d..c688c56960 100644 --- a/game/src/options.rs +++ b/game/src/options.rs @@ -145,26 +145,6 @@ impl OptionsPanel { ColorSchemeChoice::choices(), ), ]), - Widget::row(vec![ - format!( - "Scale factor for text / UI elements (your monitor is {}):", - ctx.monitor_scale_factor() - ) - .draw_text(ctx), - Widget::dropdown(ctx, "Scale factor", ctx.get_scale_factor(), { - let mut choices = vec![ - Choice::new("0.5", 0.5), - Choice::new("1.0", 1.0), - Choice::new("1.5", 1.5), - Choice::new("2.0", 2.0), - ]; - let native = ctx.monitor_scale_factor(); - if !choices.iter().any(|c| c.data == native) { - choices.push(Choice::new(native.to_string(), native)); - } - choices - }), - ]), Widget::row(vec![ "Camera zoom to switch to unzoomed view".draw_text(ctx), Widget::dropdown( @@ -253,11 +233,6 @@ impl State for OptionsPanel { app.switch_map(ctx, app.primary.current_flags.sim_flags.load.clone()); } - let factor = self.composite.dropdown_value("Scale factor"); - if ctx.get_scale_factor() != factor { - ctx.set_scale_factor(factor); - } - app.opts.min_zoom_for_detail = self.composite.dropdown_value("min zoom"); app.opts.large_unzoomed_agents = self.composite.is_checked("Draw enlarged unzoomed agents"); diff --git a/game/src/sandbox/dashboards/traffic_signals.rs b/game/src/sandbox/dashboards/traffic_signals.rs index 5155f25859..74d7f53adb 100644 --- a/game/src/sandbox/dashboards/traffic_signals.rs +++ b/game/src/sandbox/dashboards/traffic_signals.rs @@ -180,7 +180,7 @@ impl Demand { txt_batch.append( Text::from(Line(prettyprint_usize(demand)).fg(Color::RED)) .render_ctx(ctx) - .scale(0.15 / ctx.get_scale_factor()) + .scale(0.15) .centered_on(pl.middle()), ); } diff --git a/game/src/sandbox/dashboards/trip_table.rs b/game/src/sandbox/dashboards/trip_table.rs index 90cd2f63f5..ad06af0dfb 100644 --- a/game/src/sandbox/dashboards/trip_table.rs +++ b/game/src/sandbox/dashboards/trip_table.rs @@ -448,12 +448,10 @@ pub fn make_table( .enumerate() .map(|(idx, w)| { let margin = extra_margin + width_per_col[idx] - w.get_width_for_forcing(); - // TODO margin_right scales up, so we have to cancel that out. Otherwise here we're - // already working in physical pixels. Sigh. if idx == width_per_col.len() - 1 { - w.margin_right(((margin - extra_margin) / ctx.get_scale_factor()) as usize) + w.margin_right((margin - extra_margin) as usize) } else { - w.margin_right((margin / ctx.get_scale_factor()) as usize) + w.margin_right(margin as usize) } }) .collect(), diff --git a/game/src/sandbox/gameplay/commute.rs b/game/src/sandbox/gameplay/commute.rs index 63df1d6032..f25bb36471 100644 --- a/game/src/sandbox/gameplay/commute.rs +++ b/game/src/sandbox/gameplay/commute.rs @@ -248,7 +248,7 @@ fn make_meter( ctx, GeomBatch::from(vec![( Color::WHITE, - Polygon::rectangle(0.2 * ctx.canvas.window_width / ctx.get_scale_factor(), 2.0), + Polygon::rectangle(0.2 * ctx.canvas.window_width, 2.0), )]), ) .centered_horiz(), diff --git a/game/src/sandbox/gameplay/fix_traffic_signals.rs b/game/src/sandbox/gameplay/fix_traffic_signals.rs index 2262bc8ecf..ad06fd000c 100644 --- a/game/src/sandbox/gameplay/fix_traffic_signals.rs +++ b/game/src/sandbox/gameplay/fix_traffic_signals.rs @@ -293,7 +293,7 @@ fn make_meter( ctx, GeomBatch::from(vec![( Color::WHITE, - Polygon::rectangle(0.2 * ctx.canvas.window_width / ctx.get_scale_factor(), 2.0), + Polygon::rectangle(0.2 * ctx.canvas.window_width, 2.0), )]), ) .centered_horiz(), diff --git a/game/src/sandbox/mod.rs b/game/src/sandbox/mod.rs index 20c9e32a94..dc12ad1a1a 100644 --- a/game/src/sandbox/mod.rs +++ b/game/src/sandbox/mod.rs @@ -330,7 +330,7 @@ impl AgentMeter { ctx, GeomBatch::from(vec![( Color::WHITE, - Polygon::rectangle(0.2 * ctx.canvas.window_width / ctx.get_scale_factor(), 2.0), + Polygon::rectangle(0.2 * ctx.canvas.window_width, 2.0), )]), ) .centered_horiz(),