Move the legend to the minimap in the experiment, by parameterizing the

minimap on a trait to specialize it.
This commit is contained in:
Dustin Carlino 2020-12-07 14:42:49 -08:00
parent d799b20d93
commit 2153b1605b
6 changed files with 86 additions and 58 deletions

View File

@ -2,7 +2,7 @@ use std::collections::HashSet;
use abstutil::prettyprint_usize;
use geom::{ArrowCap, Circle, Distance, Duration, PolyLine, Pt2D, Time};
use map_gui::tools::{ChooseSomething, ColorLegend, SimpleMinimap};
use map_gui::tools::{ChooseSomething, ColorLegend, Minimap, MinimapControls};
use map_model::BuildingID;
use widgetry::{
Btn, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
@ -26,7 +26,7 @@ pub struct Game {
status_panel: Panel,
time_panel: Panel,
boost_panel: Panel,
minimap: SimpleMinimap,
minimap: Minimap<App, MinimapController>,
animator: Animator,
snow: SnowEffect,
@ -60,10 +60,6 @@ impl Game {
Widget::draw_batch(ctx, GeomBatch::new()).named("score"),
"Remaining Gifts:".draw_text(ctx),
Widget::draw_batch(ctx, GeomBatch::new()).named("energy"),
Widget::horiz_separator(ctx, 0.2),
ColorLegend::row(ctx, app.session.colors.house, "single-family house"),
ColorLegend::row(ctx, app.session.colors.apartment, "apartment building"),
ColorLegend::row(ctx, app.session.colors.store, "store"),
]))
.aligned(HorizontalAlignment::RightInset, VerticalAlignment::TopInset)
.build(ctx);
@ -95,13 +91,12 @@ impl Game {
let bldgs = Buildings::new(ctx, app, upzones);
let state = GameState::new(ctx, app, level, vehicle, bldgs);
let with_zorder = false;
let mut game = Game {
title_panel,
status_panel,
time_panel,
boost_panel,
minimap: SimpleMinimap::new(ctx, app, with_zorder),
minimap: Minimap::new(ctx, app, MinimapController),
animator: Animator::new(ctx),
snow: SnowEffect::new(ctx),
@ -475,3 +470,20 @@ impl EnergylessArrow {
self.draw = ctx.upload(GeomBatch::from(vec![(Color::RED.alpha(0.8), arrow)]));
}
}
struct MinimapController;
impl MinimapControls<App> for MinimapController {
fn has_zorder(&self, _: &App) -> bool {
false
}
fn make_legend(&self, ctx: &mut EventCtx, app: &App) -> Widget {
Widget::row(vec![
ColorLegend::row(ctx, app.session.colors.house, "house"),
ColorLegend::row(ctx, app.session.colors.apartment, "apartment"),
ColorLegend::row(ctx, app.session.colors.store, "store"),
])
.evenly_spaced()
}
}

View File

@ -58,9 +58,9 @@ impl Session {
Session {
levels,
colors: ColorScheme {
house: Color::hex("#5E8962"),
apartment: Color::CYAN,
store: Color::YELLOW,
house: Color::hex("#688865"),
apartment: Color::hex("#C0F879"),
store: Color::hex("#F4DF4D"),
visited: Color::BLACK,
score: Color::hex("#83AA51"),

View File

@ -400,6 +400,7 @@ impl ColorScheme {
cs.unzoomed_arterial = cs.sidewalk;
cs.unzoomed_highway = cs.parking_lane;
cs.unzoomed_residential = cs.driving_lane;
cs.stop_sign = Color::rgb_f(0.67, 0.55, 0.55);
cs.panel_bg = Color::hex("#003046").alpha(0.8);
cs.gui_style.panel_bg = cs.panel_bg;

View File

@ -1,3 +1,5 @@
use std::marker::PhantomData;
use abstutil::clamp;
use geom::{Distance, Polygon, Pt2D, Ring};
use widgetry::{
@ -6,15 +8,17 @@ use widgetry::{
};
use crate::tools::Navigator;
use crate::SimpleApp;
use crate::AppLike;
// TODO Some of the math in here might assume map bound minimums start at (0, 0).
pub struct SimpleMinimap {
pub struct Minimap<A: AppLike, T: MinimapControls<A>> {
controls: T,
app_type: PhantomData<A>,
dragging: bool,
pub(crate) panel: Panel,
// Update panel when other things change
zoomed: bool,
with_zorder: bool,
// [0, 3], with 0 meaning the most unzoomed
zoom_lvl: usize,
@ -24,17 +28,24 @@ pub struct SimpleMinimap {
offset_y: f64,
}
impl SimpleMinimap {
pub fn new<T>(ctx: &mut EventCtx, app: &SimpleApp<T>, with_zorder: bool) -> SimpleMinimap {
pub trait MinimapControls<A: AppLike> {
fn has_zorder(&self, app: &A) -> bool;
fn make_legend(&self, ctx: &mut EventCtx, app: &A) -> Widget;
}
impl<A: AppLike + 'static, T: MinimapControls<A>> Minimap<A, T> {
pub fn new(ctx: &mut EventCtx, app: &A, controls: T) -> Minimap<A, T> {
// Initially pick a zoom to fit the smaller of the entire map's width or height in the
// minimap. Arbitrary and probably pretty weird.
let bounds = app.map.get_bounds();
let bounds = app.map().get_bounds();
let base_zoom = 0.15 * ctx.canvas.window_width / bounds.width().min(bounds.height());
let mut m = SimpleMinimap {
let mut m = Minimap {
controls,
app_type: PhantomData,
dragging: false,
panel: Panel::empty(ctx),
zoomed: ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail,
with_zorder,
zoomed: ctx.canvas.cam_zoom >= app.opts().min_zoom_for_detail,
zoom_lvl: 0,
base_zoom,
@ -49,8 +60,8 @@ impl SimpleMinimap {
m
}
pub fn recreate_panel<T>(&mut self, ctx: &mut EventCtx, app: &SimpleApp<T>) {
if ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail {
pub fn recreate_panel(&mut self, ctx: &mut EventCtx, app: &A) {
if ctx.canvas.cam_zoom < app.opts().min_zoom_for_detail {
self.panel = Panel::empty(ctx);
return;
}
@ -69,7 +80,7 @@ impl SimpleMinimap {
col.push(
Btn::custom(
GeomBatch::from(vec![(color, rect.clone())]),
GeomBatch::from(vec![(app.cs.hovering, rect.clone())]),
GeomBatch::from(vec![(app.cs().hovering, rect.clone())]),
rect,
None,
)
@ -85,11 +96,11 @@ impl SimpleMinimap {
// pan up arrow. Also, double column to avoid the background color
// stretching to the bottom of the row.
Widget::custom_col(vec![
Widget::custom_col(col).padding(10).bg(app.cs.inner_panel),
if self.with_zorder {
Widget::custom_col(col).padding(10).bg(app.cs().inner_panel),
if self.controls.has_zorder(app) {
Widget::col(vec![
Line("Z-order:").small().draw(ctx),
Spinner::new(ctx, app.draw_map.zorder_range, app.draw_map.show_zorder)
Spinner::new(ctx, app.draw_map().zorder_range, app.draw_map().show_zorder)
.named("zorder"),
])
.margin_above(10)
@ -119,11 +130,11 @@ impl SimpleMinimap {
]);
self.panel = Panel::new(Widget::row(vec![Widget::col(vec![
// TODO Remove
Widget::row(vec![minimap_controls, zoom_col]),
self.controls.make_legend(ctx, app),
])
.padding(16)
.bg(app.cs.panel_bg)]))
.bg(app.cs().panel_bg)]))
.aligned(
HorizontalAlignment::Right,
VerticalAlignment::BottomAboveOSD,
@ -138,7 +149,7 @@ impl SimpleMinimap {
(pct_x, pct_y)
}
pub fn set_zoom<T>(&mut self, ctx: &mut EventCtx, app: &SimpleApp<T>, zoom_lvl: usize) {
pub fn set_zoom(&mut self, ctx: &mut EventCtx, app: &A, zoom_lvl: usize) {
// Make the frame wind up in the same relative position on the minimap
let (pct_x, pct_y) = self.map_to_minimap_pct(ctx.canvas.center_to_map_pt());
@ -154,7 +165,7 @@ impl SimpleMinimap {
self.offset_y = map_center.y() * self.zoom - pct_y * inner_rect.height();
}
fn recenter<T>(&mut self, ctx: &EventCtx, app: &SimpleApp<T>) {
fn recenter(&mut self, ctx: &EventCtx, app: &A) {
// Recenter the minimap on the screen bounds
let map_center = ctx.canvas.center_to_map_pt();
let rect = self.panel.rect_of("minimap");
@ -162,19 +173,15 @@ impl SimpleMinimap {
let off_y = map_center.y() * self.zoom - rect.height() / 2.0;
// Don't go out of bounds.
let bounds = app.map.get_bounds();
let bounds = app.map().get_bounds();
// TODO For boundaries without rectangular shapes, it'd be even nicer to clamp to the
// boundary.
self.offset_x = off_x.max(0.0).min(bounds.max_x * self.zoom - rect.width());
self.offset_y = off_y.max(0.0).min(bounds.max_y * self.zoom - rect.height());
}
pub fn event<T: 'static>(
&mut self,
ctx: &mut EventCtx,
app: &mut SimpleApp<T>,
) -> Option<Transition<SimpleApp<T>>> {
let zoomed = ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail;
pub fn event(&mut self, ctx: &mut EventCtx, app: &mut A) -> Option<Transition<A>> {
let zoomed = ctx.canvas.cam_zoom >= app.opts().min_zoom_for_detail;
if zoomed != self.zoomed {
let just_zoomed_in = zoomed && !self.zoomed;
@ -206,7 +213,7 @@ impl SimpleMinimap {
// When the window is resized, just reset completely. This is important when the window
// size at startup is incorrect and immediately corrected by the window manager after
// Minimap::new happens.
let bounds = app.map.get_bounds();
let bounds = app.map().get_bounds();
// On Windows, apparently minimizing can cause some resize events with 0, 0 dimensions!
self.base_zoom =
(0.15 * ctx.canvas.window_width / bounds.width().min(bounds.height())).max(0.0001);
@ -265,7 +272,7 @@ impl SimpleMinimap {
_ => unreachable!(),
},
Outcome::Changed => {
app.draw_map.show_zorder = self.panel.spinner("zorder");
app.mut_draw_map().show_zorder = self.panel.spinner("zorder");
self.recreate_panel(ctx, app);
}
_ => {}
@ -303,16 +310,11 @@ impl SimpleMinimap {
None
}
pub fn draw<T>(&self, g: &mut GfxCtx, app: &SimpleApp<T>) {
pub fn draw(&self, g: &mut GfxCtx, app: &A) {
self.draw_with_extra_layers(g, app, Vec::new());
}
pub fn draw_with_extra_layers<T>(
&self,
g: &mut GfxCtx,
app: &SimpleApp<T>,
extra: Vec<&Drawable>,
) {
pub fn draw_with_extra_layers(&self, g: &mut GfxCtx, app: &A, extra: Vec<&Drawable>) {
self.panel.draw(g);
if !self.zoomed {
return;
@ -320,7 +322,7 @@ impl SimpleMinimap {
let inner_rect = self.panel.rect_of("minimap").clone();
let mut map_bounds = app.map.get_bounds().clone();
let mut map_bounds = app.map().get_bounds().clone();
// Adjust bounds to account for the current pan and zoom
map_bounds.min_x = (map_bounds.min_x + self.offset_x) / self.zoom;
map_bounds.min_y = (map_bounds.min_y + self.offset_y) / self.zoom;
@ -334,11 +336,12 @@ impl SimpleMinimap {
None,
);
g.enable_clipping(inner_rect);
g.redraw(&app.draw_map.boundary_polygon);
g.redraw(&app.draw_map.draw_all_areas);
g.redraw(&app.draw_map.draw_all_unzoomed_parking_lots);
g.redraw(&app.draw_map.draw_all_unzoomed_roads_and_intersections);
g.redraw(&app.draw_map.draw_all_buildings);
let draw_map = app.draw_map();
g.redraw(&draw_map.boundary_polygon);
g.redraw(&draw_map.draw_all_areas);
g.redraw(&draw_map.draw_all_unzoomed_parking_lots);
g.redraw(&draw_map.draw_all_unzoomed_roads_and_intersections);
g.redraw(&draw_map.draw_all_buildings);
for draw in extra {
g.redraw(draw);
}
@ -356,7 +359,7 @@ impl SimpleMinimap {
(pt.x(), pt.y())
};
g.draw_polygon(
app.cs.minimap_cursor,
app.cs().minimap_cursor,
Ring::must_new(vec![
Pt2D::new(x1, y1),
Pt2D::new(x2, y1),

View File

@ -7,7 +7,7 @@ use widgetry::{GfxCtx, Line, Text};
pub use self::city_picker::CityPicker;
pub use self::colors::{ColorDiscrete, ColorLegend, ColorNetwork, ColorScale, DivergingScale};
pub use self::heatmap::{make_heatmap, Grid, HeatmapOptions};
pub use self::minimap::SimpleMinimap;
pub use self::minimap::{Minimap, MinimapControls};
pub use self::navigate::Navigator;
pub use self::turn_explorer::TurnExplorer;
pub use self::ui::{ChooseSomething, PopupMsg, PromptInput};

View File

@ -5,7 +5,8 @@ use geom::ArrowCap;
use map_gui::options::OptionsPanel;
use map_gui::render::{DrawOptions, BIG_ARROW_THICKNESS};
use map_gui::tools::{
nice_map_name, open_browser, CityPicker, Navigator, PopupMsg, SimpleMinimap, TurnExplorer,
nice_map_name, open_browser, CityPicker, Minimap, MinimapControls, Navigator, PopupMsg,
TurnExplorer,
};
use map_gui::{SimpleApp, ID};
use map_model::osm;
@ -20,16 +21,15 @@ type App = SimpleApp<()>;
pub struct Viewer {
top_panel: Panel,
fixed_object_outline: Option<Drawable>,
minimap: SimpleMinimap,
minimap: Minimap<App, MinimapController>,
businesses: Option<BusinessSearch>,
}
impl Viewer {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
let with_zorder = true;
let mut viewer = Viewer {
fixed_object_outline: None,
minimap: SimpleMinimap::new(ctx, app, with_zorder),
minimap: Minimap::new(ctx, app, MinimapController),
businesses: None,
top_panel: Panel::empty(ctx),
};
@ -460,3 +460,15 @@ impl BusinessSearch {
Widget::col(col)
}
}
struct MinimapController;
impl MinimapControls<App> for MinimapController {
fn has_zorder(&self, _: &App) -> bool {
true
}
fn make_legend(&self, _: &mut EventCtx, _: &App) -> Widget {
Widget::nothing()
}
}