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.
This commit is contained in:
Michael Kirk 2020-07-28 15:39:38 -06:00 committed by Dustin Carlino
parent 9ce7ab1c88
commit f42b7c003c
27 changed files with 163 additions and 292 deletions

View File

@ -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<f64>,
pub scale_factor: RefCell<f64>,
text_cache: RefCell<LruCache<String, GeomBatch>>,
line_height_cache: RefCell<HashMap<(Font, usize), f64>>,
// Keyed by filename, then scale factor mangled into a hashable form. Tuple doesn't work
// because of borrowing.
svg_cache: RefCell<HashMap<String, HashMap<usize, (GeomBatch, Bounds)>>>,
svg_cache: RefCell<HashMap<String, (GeomBatch, Bounds)>>,
#[cfg(not(feature = "wasm-backend"))]
font_to_id: HashMap<Font, fontdb::ID>,
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
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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,

View File

@ -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<usize>,
pub scale_factor: RefCell<f64>,
}
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())
}
}

View File

@ -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<Event> {
pub fn from_winit_event(ev: WindowEvent, scale_factor: f64) -> Option<Event> {
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

View File

@ -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(),

View File

@ -137,23 +137,18 @@ impl GeomBatch {
pub fn from_svg_contents(raw: Vec<u8>) -> 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<I: Into<String>>(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.

View File

@ -101,7 +101,7 @@ impl UserInput {
pub fn is_window_resized(&self) -> bool {
match self.event {
Event::WindowResized(_, _) => true,
Event::WindowResized(_) => true,
_ => false,
}
}

View File

@ -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<I: Into<String>>(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<Node>,
) {
fn get_flexbox(&self, parent: Node, stretch: &mut Stretch, nodes: &mut Vec<Node>) {
if let Some(container) = self.widget.downcast_ref::<Container>() {
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![

View File

@ -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<G: GUI> State<G> {
// 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: 'static + GUI, F: FnOnce(&mut EventCtx) -> 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: 'static + GUI, F: FnOnce(&mut EventCtx) -> 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: 'static + GUI, F: FnOnce(&mut EventCtx) -> 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

View File

@ -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<winit::dpi::LogicalPosition<f64>> for ScreenPt {
fn from(lp: winit::dpi::LogicalPosition<f64>) -> 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<winit::dpi::LogicalSize<f64>> for ScreenDims {
fn from(lp: winit::dpi::LogicalSize<f64>) -> ScreenDims {
ScreenDims {
width: lp.width,
height: lp.height,
}
}
}

View File

@ -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<Bounds, String> {
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()),
]))
}

View File

@ -454,12 +454,7 @@ fn render_line(spans: Vec<TextSpan>, 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),
}

View File

@ -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);

View File

@ -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 {

View File

@ -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);
}

View File

@ -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(),

View File

@ -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()),
);
}

View File

@ -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),
),
);

View File

@ -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");

View File

@ -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()),
);
}

View File

@ -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(),

View File

@ -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(),

View File

@ -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(),

View File

@ -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(),