mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 07:25:47 +03:00
Move the legend to the minimap in the experiment, by parameterizing the
minimap on a trait to specialize it.
This commit is contained in:
parent
d799b20d93
commit
2153b1605b
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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};
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user