mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-01 02:33:54 +03:00
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:
parent
9ce7ab1c88
commit
f42b7c003c
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
@ -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.
|
||||
|
@ -101,7 +101,7 @@ impl UserInput {
|
||||
|
||||
pub fn is_window_resized(&self) -> bool {
|
||||
match self.event {
|
||||
Event::WindowResized(_, _) => true,
|
||||
Event::WindowResized(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -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![
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()),
|
||||
]))
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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()),
|
||||
);
|
||||
}
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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()),
|
||||
);
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
Loading…
Reference in New Issue
Block a user