diff --git a/experiment/src/game.rs b/experiment/src/game.rs index a2d6e21bfb..08e04170c7 100644 --- a/experiment/src/game.rs +++ b/experiment/src/game.rs @@ -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, 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 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() + } +} diff --git a/experiment/src/session.rs b/experiment/src/session.rs index 4c0d33713d..a4edb2a2ae 100644 --- a/experiment/src/session.rs +++ b/experiment/src/session.rs @@ -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"), diff --git a/map_gui/src/colors.rs b/map_gui/src/colors.rs index 4da15b43c6..0cdfe03fbe 100644 --- a/map_gui/src/colors.rs +++ b/map_gui/src/colors.rs @@ -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; diff --git a/map_gui/src/tools/minimap.rs b/map_gui/src/tools/minimap.rs index 1c65e190e0..752ef0b4da 100644 --- a/map_gui/src/tools/minimap.rs +++ b/map_gui/src/tools/minimap.rs @@ -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> { + controls: T, + app_type: PhantomData, + 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(ctx: &mut EventCtx, app: &SimpleApp, with_zorder: bool) -> SimpleMinimap { +pub trait MinimapControls { + fn has_zorder(&self, app: &A) -> bool; + fn make_legend(&self, ctx: &mut EventCtx, app: &A) -> Widget; +} + +impl> Minimap { + pub fn new(ctx: &mut EventCtx, app: &A, controls: T) -> Minimap { // 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(&mut self, ctx: &mut EventCtx, app: &SimpleApp) { - 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(&mut self, ctx: &mut EventCtx, app: &SimpleApp, 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(&mut self, ctx: &EventCtx, app: &SimpleApp) { + 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( - &mut self, - ctx: &mut EventCtx, - app: &mut SimpleApp, - ) -> Option>> { - let zoomed = ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail; + pub fn event(&mut self, ctx: &mut EventCtx, app: &mut A) -> Option> { + 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(&self, g: &mut GfxCtx, app: &SimpleApp) { + pub fn draw(&self, g: &mut GfxCtx, app: &A) { self.draw_with_extra_layers(g, app, Vec::new()); } - pub fn draw_with_extra_layers( - &self, - g: &mut GfxCtx, - app: &SimpleApp, - 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), diff --git a/map_gui/src/tools/mod.rs b/map_gui/src/tools/mod.rs index 7e15c8360a..a217cb58e4 100644 --- a/map_gui/src/tools/mod.rs +++ b/map_gui/src/tools/mod.rs @@ -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}; diff --git a/osm_viewer/src/viewer.rs b/osm_viewer/src/viewer.rs index e897707c05..e16157cbe0 100644 --- a/osm_viewer/src/viewer.rs +++ b/osm_viewer/src/viewer.rs @@ -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, - minimap: SimpleMinimap, + minimap: Minimap, businesses: Option, } impl Viewer { pub fn new(ctx: &mut EventCtx, app: &App) -> Box> { - 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 for MinimapController { + fn has_zorder(&self, _: &App) -> bool { + true + } + + fn make_legend(&self, _: &mut EventCtx, _: &App) -> Widget { + Widget::nothing() + } +}