Give SimpleApp a way to stash session-wide state. First use case is high scores for the experiment.

This commit is contained in:
Dustin Carlino 2020-12-04 15:08:15 -08:00
parent c9809c805b
commit df04fd7e18
16 changed files with 131 additions and 125 deletions

View File

@ -1,12 +1,11 @@
use abstutil::prettyprint_usize; use abstutil::prettyprint_usize;
use map_gui::SimpleApp;
use widgetry::{ use widgetry::{
Btn, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text, Transition, Btn, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
VerticalAlignment, Widget, VerticalAlignment, Widget,
}; };
use crate::levels::Level; use crate::levels::Level;
use crate::session::Session; use crate::{App, Transition};
const ZOOM: f64 = 2.0; const ZOOM: f64 = 2.0;
@ -20,16 +19,14 @@ pub struct Results {
impl Results { impl Results {
pub fn new( pub fn new(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &mut App,
score: usize, score: usize,
level: &Level, level: &Level,
) -> Box<dyn State<SimpleApp>> { ) -> Box<dyn State<App>> {
ctx.canvas.cam_zoom = ZOOM; ctx.canvas.cam_zoom = ZOOM;
ctx.canvas.center_on_map_pt(app.map.get_bounds().center()); ctx.canvas.center_on_map_pt(app.map.get_bounds().center());
// TODO Store in app app.session.record_score(level.title, score);
let mut session = Session::new();
session.record_score(level.title, score);
let mut txt = Text::new(); let mut txt = Text::new();
txt.add(Line(format!("Results for {}", level.title)).small_heading()); txt.add(Line(format!("Results for {}", level.title)).small_heading());
@ -41,7 +38,7 @@ impl Results {
))); )));
txt.add(Line("")); txt.add(Line(""));
txt.add(Line("High scores:")); txt.add(Line("High scores:"));
for (idx, score) in session.high_scores[level.title].iter().enumerate() { for (idx, score) in app.session.high_scores[level.title].iter().enumerate() {
txt.add(Line(format!("{}) {}", idx + 1, prettyprint_usize(*score)))); txt.add(Line(format!("{}) {}", idx + 1, prettyprint_usize(*score))));
} }
@ -55,8 +52,8 @@ impl Results {
} }
} }
impl State<SimpleApp> for Results { impl State<App> for Results {
fn event(&mut self, ctx: &mut EventCtx, _: &mut SimpleApp) -> Transition<SimpleApp> { fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
ctx.canvas_movement(); ctx.canvas_movement();
match self.panel.event(ctx) { match self.panel.event(ctx) {
@ -72,7 +69,7 @@ impl State<SimpleApp> for Results {
Transition::Keep Transition::Keep
} }
fn draw(&self, g: &mut GfxCtx, _: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, _: &App) {
self.panel.draw(g); self.panel.draw(g);
} }
} }

View File

@ -2,17 +2,18 @@ use std::collections::HashSet;
use abstutil::prettyprint_usize; use abstutil::prettyprint_usize;
use map_gui::load::MapLoader; use map_gui::load::MapLoader;
use map_gui::{SimpleApp, ID}; use map_gui::ID;
use map_model::BuildingID; use map_model::BuildingID;
use widgetry::{ use widgetry::{
Btn, Choice, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Btn, Choice, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State,
Text, TextExt, Transition, VerticalAlignment, Widget, Text, TextExt, VerticalAlignment, Widget,
}; };
use crate::buildings::{BldgState, Buildings}; use crate::buildings::{BldgState, Buildings};
use crate::game::Game; use crate::game::Game;
use crate::levels::Level; use crate::levels::Level;
use crate::vehicles::Vehicle; use crate::vehicles::Vehicle;
use crate::{App, Transition};
const ZOOM: f64 = 2.0; const ZOOM: f64 = 2.0;
@ -24,7 +25,7 @@ pub struct Picker {
} }
impl Picker { impl Picker {
pub fn new(ctx: &mut EventCtx, app: &SimpleApp, level: Level) -> Box<dyn State<SimpleApp>> { pub fn new(ctx: &mut EventCtx, app: &App, level: Level) -> Box<dyn State<App>> {
MapLoader::new( MapLoader::new(
ctx, ctx,
app, app,
@ -89,8 +90,8 @@ impl Picker {
} }
} }
impl State<SimpleApp> for Picker { impl State<App> for Picker {
fn event(&mut self, ctx: &mut EventCtx, app: &mut SimpleApp) -> Transition<SimpleApp> { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement(); ctx.canvas_movement();
if ctx.redo_mouseover() { if ctx.redo_mouseover() {
@ -132,7 +133,7 @@ impl State<SimpleApp> for Picker {
Transition::Keep Transition::Keep
} }
fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, app: &App) {
self.panel.draw(g); self.panel.draw(g);
g.redraw(&self.bldgs.draw_all); g.redraw(&self.bldgs.draw_all);
for b in &self.current_picks { for b in &self.current_picks {

View File

@ -1,9 +1,10 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use map_gui::SimpleApp;
use map_model::{BuildingID, BuildingType}; use map_model::{BuildingID, BuildingType};
use widgetry::{Color, Drawable, EventCtx, GeomBatch, Line, Text}; use widgetry::{Color, Drawable, EventCtx, GeomBatch, Line, Text};
use crate::App;
pub struct Buildings { pub struct Buildings {
// Every building in the map is here, to simplify lookup logic. // Every building in the map is here, to simplify lookup logic.
pub buildings: HashMap<BuildingID, BldgState>, pub buildings: HashMap<BuildingID, BldgState>,
@ -23,7 +24,7 @@ pub enum BldgState {
} }
impl Buildings { impl Buildings {
pub fn new(ctx: &mut EventCtx, app: &SimpleApp, upzones: HashSet<BuildingID>) -> Buildings { pub fn new(ctx: &mut EventCtx, app: &App, upzones: HashSet<BuildingID>) -> Buildings {
let house_color = app.cs.residential_building; let house_color = app.cs.residential_building;
let apartment_color = Color::CYAN; let apartment_color = Color::CYAN;
let store_color = Color::YELLOW; let store_color = Color::YELLOW;

View File

@ -3,11 +3,10 @@ use std::collections::HashSet;
use abstutil::prettyprint_usize; use abstutil::prettyprint_usize;
use geom::{ArrowCap, Circle, Distance, Duration, PolyLine, Pt2D, Time}; use geom::{ArrowCap, Circle, Distance, Duration, PolyLine, Pt2D, Time};
use map_gui::tools::{ChooseSomething, ColorLegend, SimpleMinimap}; use map_gui::tools::{ChooseSomething, ColorLegend, SimpleMinimap};
use map_gui::SimpleApp;
use map_model::BuildingID; use map_model::BuildingID;
use widgetry::{ use widgetry::{
Btn, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Btn, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
Outcome, Panel, State, Text, TextExt, Transition, UpdateType, VerticalAlignment, Widget, Outcome, Panel, State, Text, TextExt, UpdateType, VerticalAlignment, Widget,
}; };
use crate::after_level::Results; use crate::after_level::Results;
@ -17,6 +16,7 @@ use crate::levels::Level;
use crate::meters::{custom_bar, make_bar}; use crate::meters::{custom_bar, make_bar};
use crate::movement::Player; use crate::movement::Player;
use crate::vehicles::Vehicle; use crate::vehicles::Vehicle;
use crate::{App, Transition};
const ACQUIRE_BOOST_RATE: f64 = 0.5; const ACQUIRE_BOOST_RATE: f64 = 0.5;
const BOOST_SPEED_MULTIPLIER: f64 = 2.0; const BOOST_SPEED_MULTIPLIER: f64 = 2.0;
@ -39,11 +39,11 @@ pub struct Game {
impl Game { impl Game {
pub fn new( pub fn new(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
level: Level, level: Level,
vehicle: Vehicle, vehicle: Vehicle,
upzones: HashSet<BuildingID>, upzones: HashSet<BuildingID>,
) -> Box<dyn State<SimpleApp>> { ) -> Box<dyn State<App>> {
let title_panel = Panel::new(Widget::row(vec![ let title_panel = Panel::new(Widget::row(vec![
Btn::svg_def("system/assets/tools/home.svg").build(ctx, "back", Key::Escape), Btn::svg_def("system/assets/tools/home.svg").build(ctx, "back", Key::Escape),
"15 min Santa".draw_text(ctx), "15 min Santa".draw_text(ctx),
@ -153,8 +153,8 @@ impl Game {
} }
} }
impl State<SimpleApp> for Game { impl State<App> for Game {
fn event(&mut self, ctx: &mut EventCtx, app: &mut SimpleApp) -> Transition<SimpleApp> { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
if let Some(dt) = ctx.input.nonblocking_is_update_event() { if let Some(dt) = ctx.input.nonblocking_is_update_event() {
self.time += dt; self.time += dt;
@ -301,7 +301,7 @@ impl State<SimpleApp> for Game {
Transition::Keep Transition::Keep
} }
fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, app: &App) {
self.title_panel.draw(g); self.title_panel.draw(g);
self.status_panel.draw(g); self.status_panel.draw(g);
self.time_panel.draw(g); self.time_panel.draw(g);
@ -364,7 +364,7 @@ struct GameState {
impl GameState { impl GameState {
fn new( fn new(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
level: Level, level: Level,
vehicle: Vehicle, vehicle: Vehicle,
bldgs: Buildings, bldgs: Buildings,
@ -386,7 +386,7 @@ impl GameState {
s s
} }
fn recalc_deliveries(&mut self, ctx: &mut EventCtx, app: &SimpleApp) { fn recalc_deliveries(&mut self, ctx: &mut EventCtx, app: &App) {
let mut batch = GeomBatch::new(); let mut batch = GeomBatch::new();
for (b, state) in &self.bldgs.buildings { for (b, state) in &self.bldgs.buildings {
if let BldgState::Done = state { if let BldgState::Done = state {
@ -398,12 +398,7 @@ impl GameState {
} }
// If something changed, return the update to the score // If something changed, return the update to the score
fn present_dropped( fn present_dropped(&mut self, ctx: &mut EventCtx, app: &App, id: BuildingID) -> Option<usize> {
&mut self,
ctx: &mut EventCtx,
app: &SimpleApp,
id: BuildingID,
) -> Option<usize> {
if !self.has_energy() { if !self.has_energy() {
return None; return None;
} }
@ -442,7 +437,7 @@ impl EnergylessArrow {
fn update( fn update(
&mut self, &mut self,
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
time: Time, time: Time,
sleigh: Pt2D, sleigh: Pt2D,
all_stores: Vec<BuildingID>, all_stores: Vec<BuildingID>,

View File

@ -14,11 +14,19 @@ mod session;
mod title; mod title;
mod vehicles; mod vehicles;
type App = map_gui::SimpleApp<session::Session>;
type Transition = widgetry::Transition<App>;
pub fn main() { pub fn main() {
widgetry::run(widgetry::Settings::new("experiment"), |ctx| { widgetry::run(widgetry::Settings::new("experiment"), |ctx| {
let mut opts = map_gui::options::Options::default(); let mut opts = map_gui::options::Options::default();
opts.color_scheme = map_gui::colors::ColorSchemeChoice::NightMode; opts.color_scheme = map_gui::colors::ColorSchemeChoice::NightMode;
let app = map_gui::SimpleApp::new_with_opts(ctx, abstutil::CmdArgs::new(), opts); let app = map_gui::SimpleApp::new_with_opts(
ctx,
abstutil::CmdArgs::new(),
opts,
session::Session::new(),
);
let states = vec![title::TitleScreen::new(ctx)]; let states = vec![title::TitleScreen::new(ctx)];
(app, states) (app, states)
}); });

View File

@ -2,11 +2,12 @@ use std::collections::{HashMap, HashSet};
use abstutil::MultiMap; use abstutil::MultiMap;
use geom::{Angle, Circle, Distance, Pt2D, Speed}; use geom::{Angle, Circle, Distance, Pt2D, Speed};
use map_gui::{SimpleApp, ID}; use map_gui::ID;
use map_model::{BuildingID, Direction, IntersectionID, LaneType, RoadID}; use map_model::{BuildingID, Direction, IntersectionID, LaneType, RoadID};
use widgetry::EventCtx; use widgetry::EventCtx;
use crate::controls::InstantController; use crate::controls::InstantController;
use crate::App;
pub const ZOOM: f64 = 10.0; pub const ZOOM: f64 = 10.0;
@ -19,7 +20,7 @@ pub struct Player {
} }
impl Player { impl Player {
pub fn new(ctx: &mut EventCtx, app: &SimpleApp, start: IntersectionID) -> Player { pub fn new(ctx: &mut EventCtx, app: &App, start: IntersectionID) -> Player {
ctx.canvas.cam_zoom = ZOOM; ctx.canvas.cam_zoom = ZOOM;
let pos = app.map.get_i(start).polygon.center(); let pos = app.map.get_i(start).polygon.center();
ctx.canvas.center_on_map_pt(pos); ctx.canvas.center_on_map_pt(pos);
@ -37,7 +38,7 @@ impl Player {
pub fn update_with_speed( pub fn update_with_speed(
&mut self, &mut self,
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
speed: Speed, speed: Speed,
) -> Vec<BuildingID> { ) -> Vec<BuildingID> {
let (dx, dy) = self.controls.displacement(ctx, speed); let (dx, dy) = self.controls.displacement(ctx, speed);
@ -52,7 +53,7 @@ impl Player {
fn apply_displacement( fn apply_displacement(
&mut self, &mut self,
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
dx: f64, dx: f64,
dy: f64, dy: f64,
recurse: bool, recurse: bool,
@ -144,7 +145,7 @@ impl Player {
} }
/// Is the player currently on a road with a bus or bike lane? /// Is the player currently on a road with a bus or bike lane?
pub fn on_good_road(&self, app: &SimpleApp) -> bool { pub fn on_good_road(&self, app: &App) -> bool {
if let On::Road(r, _) = self.on { if let On::Road(r, _) = self.on {
for (_, _, lt) in app.map.get_r(r).lanes_ltr() { for (_, _, lt) in app.map.get_r(r).lanes_ltr() {
if lt == LaneType::Biking || lt == LaneType::Bus { if lt == LaneType::Biking || lt == LaneType::Bus {
@ -164,7 +165,7 @@ enum On {
} }
impl On { impl On {
fn get_connections(&self, app: &SimpleApp) -> (HashSet<RoadID>, HashSet<IntersectionID>) { fn get_connections(&self, app: &App) -> (HashSet<RoadID>, HashSet<IntersectionID>) {
let mut valid_roads = HashSet::new(); let mut valid_roads = HashSet::new();
let mut valid_intersections = HashSet::new(); let mut valid_intersections = HashSet::new();
match self { match self {
@ -198,7 +199,7 @@ struct BuildingsAlongRoad {
} }
impl BuildingsAlongRoad { impl BuildingsAlongRoad {
fn new(app: &SimpleApp) -> BuildingsAlongRoad { fn new(app: &App) -> BuildingsAlongRoad {
let mut raw: MultiMap<RoadID, (Distance, BuildingID)> = MultiMap::new(); let mut raw: MultiMap<RoadID, (Distance, BuildingID)> = MultiMap::new();
for b in app.map.all_buildings() { for b in app.map.all_buildings() {
// TODO Happily assuming road and lane length is roughly the same // TODO Happily assuming road and lane length is roughly the same

View File

@ -1,18 +1,17 @@
use map_gui::tools::{open_browser, PopupMsg}; use map_gui::tools::{open_browser, PopupMsg};
use map_gui::SimpleApp;
use widgetry::{ use widgetry::{
Btn, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, Text, Transition, Btn, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, Text, Widget,
Widget,
}; };
use crate::levels::Level; use crate::levels::Level;
use crate::{App, Transition};
pub struct TitleScreen { pub struct TitleScreen {
panel: Panel, panel: Panel,
} }
impl TitleScreen { impl TitleScreen {
pub fn new(ctx: &mut EventCtx) -> Box<dyn State<SimpleApp>> { pub fn new(ctx: &mut EventCtx) -> Box<dyn State<App>> {
let levels = Level::all(); let levels = Level::all();
Box::new(TitleScreen { Box::new(TitleScreen {
@ -47,8 +46,8 @@ impl TitleScreen {
} }
} }
impl State<SimpleApp> for TitleScreen { impl State<App> for TitleScreen {
fn event(&mut self, ctx: &mut EventCtx, app: &mut SimpleApp) -> Transition<SimpleApp> { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) { match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() { Outcome::Clicked(x) => match x.as_ref() {
"quit" => { "quit" => {
@ -102,7 +101,7 @@ impl State<SimpleApp> for TitleScreen {
DrawBaselayer::Custom DrawBaselayer::Custom
} }
fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, app: &App) {
g.clear(app.cs.dialog_bg); g.clear(app.cs.dialog_bg);
self.panel.draw(g); self.panel.draw(g);
} }

View File

@ -3,10 +3,11 @@ use std::collections::HashMap;
use abstutil::MultiMap; use abstutil::MultiMap;
use geom::{Duration, Polygon}; use geom::{Duration, Polygon};
use map_gui::tools::{amenity_type, Grid}; use map_gui::tools::{amenity_type, Grid};
use map_gui::SimpleApp;
use map_model::{connectivity, BuildingID, Map, Path, PathConstraints, PathRequest}; use map_model::{connectivity, BuildingID, Map, Path, PathConstraints, PathRequest};
use widgetry::{Color, Drawable, EventCtx, GeomBatch}; use widgetry::{Color, Drawable, EventCtx, GeomBatch};
use crate::App;
/// Represents the area reachable from a single building. /// Represents the area reachable from a single building.
pub struct Isochrone { pub struct Isochrone {
/// The center of the isochrone /// The center of the isochrone
@ -24,7 +25,7 @@ pub struct Isochrone {
impl Isochrone { impl Isochrone {
pub fn new( pub fn new(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
start: BuildingID, start: BuildingID,
constraints: PathConstraints, constraints: PathConstraints,
) -> Isochrone { ) -> Isochrone {
@ -57,10 +58,7 @@ impl Isochrone {
} }
} }
fn draw_isochrone( fn draw_isochrone(app: &App, time_to_reach_building: &HashMap<BuildingID, Duration>) -> GeomBatch {
app: &SimpleApp,
time_to_reach_building: &HashMap<BuildingID, Duration>,
) -> GeomBatch {
// To generate the polygons covering areas between 0-5 mins, 5-10 mins, etc, we have to feed // To generate the polygons covering areas between 0-5 mins, 5-10 mins, etc, we have to feed
// in a 2D grid of costs. Use a 100x100 meter resolution. // in a 2D grid of costs. Use a 100x100 meter resolution.
let bounds = app.map.get_bounds(); let bounds = app.map.get_bounds();

View File

@ -4,9 +4,11 @@ mod viewer;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
type App = map_gui::SimpleApp<()>;
fn main() { fn main() {
widgetry::run(widgetry::Settings::new("15-minute neighborhoods"), |ctx| { widgetry::run(widgetry::Settings::new("15-minute neighborhoods"), |ctx| {
let app = map_gui::SimpleApp::new(ctx, abstutil::CmdArgs::new()); let app = map_gui::SimpleApp::new(ctx, abstutil::CmdArgs::new(), ());
let states = vec![viewer::Viewer::random_start(ctx, &app)]; let states = vec![viewer::Viewer::random_start(ctx, &app)];
(app, states) (app, states)
}); });

View File

@ -6,7 +6,7 @@
use geom::{Distance, Pt2D}; use geom::{Distance, Pt2D};
use map_gui::tools::{amenity_type, nice_map_name, CityPicker, PopupMsg}; use map_gui::tools::{amenity_type, nice_map_name, CityPicker, PopupMsg};
use map_gui::{Cached, SimpleApp, ID}; use map_gui::{Cached, ID};
use map_model::{Building, BuildingID, PathConstraints}; use map_model::{Building, BuildingID, PathConstraints};
use widgetry::{ use widgetry::{
lctrl, Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, lctrl, Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
@ -14,6 +14,7 @@ use widgetry::{
}; };
use crate::isochrone::Isochrone; use crate::isochrone::Isochrone;
use crate::App;
/// This is the UI state for exploring the isochrone/walkshed from a single building. /// This is the UI state for exploring the isochrone/walkshed from a single building.
pub struct Viewer { pub struct Viewer {
@ -26,17 +27,13 @@ pub struct Viewer {
impl Viewer { impl Viewer {
/// Start with a random building /// Start with a random building
pub fn random_start(ctx: &mut EventCtx, app: &SimpleApp) -> Box<dyn State<SimpleApp>> { pub fn random_start(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
let bldgs = app.map.all_buildings(); let bldgs = app.map.all_buildings();
let start = bldgs[bldgs.len() / 2].id; let start = bldgs[bldgs.len() / 2].id;
Viewer::new(ctx, app, start) Viewer::new(ctx, app, start)
} }
pub fn new( pub fn new(ctx: &mut EventCtx, app: &App, start: BuildingID) -> Box<dyn State<App>> {
ctx: &mut EventCtx,
app: &SimpleApp,
start: BuildingID,
) -> Box<dyn State<SimpleApp>> {
let constraints = PathConstraints::Pedestrian; let constraints = PathConstraints::Pedestrian;
let start = app.map.get_b(start); let start = app.map.get_b(start);
let isochrone = Isochrone::new(ctx, app, start.id, constraints); let isochrone = Isochrone::new(ctx, app, start.id, constraints);
@ -52,8 +49,8 @@ impl Viewer {
} }
} }
impl State<SimpleApp> for Viewer { impl State<App> for Viewer {
fn event(&mut self, ctx: &mut EventCtx, app: &mut SimpleApp) -> Transition<SimpleApp> { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition<App> {
// Allow panning and zooming // Allow panning and zooming
ctx.canvas_movement(); ctx.canvas_movement();
@ -155,7 +152,7 @@ impl State<SimpleApp> for Viewer {
Transition::Keep Transition::Keep
} }
fn draw(&self, g: &mut GfxCtx, _: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, _: &App) {
g.redraw(&self.isochrone.draw); g.redraw(&self.isochrone.draw);
g.redraw(&self.highlight_start); g.redraw(&self.highlight_start);
self.panel.draw(g); self.panel.draw(g);
@ -175,12 +172,7 @@ fn draw_star(ctx: &mut EventCtx, center: Pt2D) -> Drawable {
) )
} }
fn build_panel( fn build_panel(ctx: &mut EventCtx, app: &App, start: &Building, isochrone: &Isochrone) -> Panel {
ctx: &mut EventCtx,
app: &SimpleApp,
start: &Building,
isochrone: &Isochrone,
) -> Panel {
let mut rows = Vec::new(); let mut rows = Vec::new();
rows.push(Widget::row(vec![ rows.push(Widget::row(vec![
@ -236,7 +228,7 @@ struct HoverOnBuilding {
type HoverKey = (BuildingID, f64); type HoverKey = (BuildingID, f64);
impl HoverOnBuilding { impl HoverOnBuilding {
fn key(ctx: &EventCtx, app: &SimpleApp) -> Option<HoverKey> { fn key(ctx: &EventCtx, app: &App) -> Option<HoverKey> {
match app.mouseover_unzoomed_buildings(ctx) { match app.mouseover_unzoomed_buildings(ctx) {
Some(ID::Building(b)) => { Some(ID::Building(b)) => {
let scale_factor = if ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail { let scale_factor = if ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail {
@ -252,7 +244,7 @@ impl HoverOnBuilding {
fn value( fn value(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
key: HoverKey, key: HoverKey,
isochrone: &Isochrone, isochrone: &Isochrone,
) -> HoverOnBuilding { ) -> HoverOnBuilding {

View File

@ -11,20 +11,27 @@ use crate::render::{DrawOptions, Renderable};
use crate::{AppLike, ID}; use crate::{AppLike, ID};
/// Simple app state that just renders a static map, without any dynamic agents on the map. /// Simple app state that just renders a static map, without any dynamic agents on the map.
pub struct SimpleApp { pub struct SimpleApp<T> {
pub map: Map, pub map: Map,
pub draw_map: DrawMap, pub draw_map: DrawMap,
pub cs: ColorScheme, pub cs: ColorScheme,
pub opts: Options, pub opts: Options,
pub current_selection: Option<ID>, pub current_selection: Option<ID>,
/// Custom per-app state can be stored here
pub session: T,
} }
impl SimpleApp { impl<T> SimpleApp<T> {
pub fn new(ctx: &mut EventCtx, args: CmdArgs) -> SimpleApp { pub fn new(ctx: &mut EventCtx, args: CmdArgs, session: T) -> SimpleApp<T> {
SimpleApp::new_with_opts(ctx, args, Options::default()) SimpleApp::new_with_opts(ctx, args, Options::default(), session)
} }
pub fn new_with_opts(ctx: &mut EventCtx, mut args: CmdArgs, mut opts: Options) -> SimpleApp { pub fn new_with_opts(
ctx: &mut EventCtx,
mut args: CmdArgs,
mut opts: Options,
session: T,
) -> SimpleApp<T> {
ctx.loading_screen("load map", |ctx, mut timer| { ctx.loading_screen("load map", |ctx, mut timer| {
opts.update_from_args(&mut args); opts.update_from_args(&mut args);
let map_path = args let map_path = args
@ -42,6 +49,7 @@ impl SimpleApp {
cs, cs,
opts, opts,
current_selection: None, current_selection: None,
session,
} }
}) })
} }
@ -189,7 +197,7 @@ impl SimpleApp {
} }
} }
impl AppLike for SimpleApp { impl<T> AppLike for SimpleApp<T> {
#[inline] #[inline]
fn map(&self) -> &Map { fn map(&self) -> &Map {
&self.map &self.map
@ -244,7 +252,7 @@ impl AppLike for SimpleApp {
pt: Pt2D, pt: Pt2D,
target_cam_zoom: Option<f64>, target_cam_zoom: Option<f64>,
_: Option<ID>, _: Option<ID>,
) -> Box<dyn State<SimpleApp>> { ) -> Box<dyn State<SimpleApp<T>>> {
Box::new(SimpleWarper { Box::new(SimpleWarper {
warper: Warper::new(ctx, pt, target_cam_zoom), warper: Warper::new(ctx, pt, target_cam_zoom),
}) })
@ -264,7 +272,7 @@ impl AppLike for SimpleApp {
} }
} }
impl SharedAppState for SimpleApp { impl<T> SharedAppState for SimpleApp<T> {
fn draw_default(&self, g: &mut GfxCtx) { fn draw_default(&self, g: &mut GfxCtx) {
self.draw_with_opts(g, DrawOptions::new()); self.draw_with_opts(g, DrawOptions::new());
} }
@ -282,8 +290,8 @@ struct SimpleWarper {
warper: Warper, warper: Warper,
} }
impl State<SimpleApp> for SimpleWarper { impl<T> State<SimpleApp<T>> for SimpleWarper {
fn event(&mut self, ctx: &mut EventCtx, _: &mut SimpleApp) -> Transition<SimpleApp> { fn event(&mut self, ctx: &mut EventCtx, _: &mut SimpleApp<T>) -> Transition<SimpleApp<T>> {
if self.warper.event(ctx) { if self.warper.event(ctx) {
Transition::Keep Transition::Keep
} else { } else {
@ -291,7 +299,7 @@ impl State<SimpleApp> for SimpleWarper {
} }
} }
fn draw(&self, _: &mut GfxCtx, _: &SimpleApp) {} fn draw(&self, _: &mut GfxCtx, _: &SimpleApp<T>) {}
} }
/// Store a cached key/value pair, only recalculating when the key changes. /// Store a cached key/value pair, only recalculating when the key changes.

View File

@ -25,7 +25,7 @@ pub struct SimpleMinimap {
} }
impl SimpleMinimap { impl SimpleMinimap {
pub fn new(ctx: &mut EventCtx, app: &SimpleApp, with_zorder: bool) -> SimpleMinimap { pub fn new<T>(ctx: &mut EventCtx, app: &SimpleApp<T>, with_zorder: bool) -> SimpleMinimap {
// Initially pick a zoom to fit the smaller of the entire map's width or height in the // Initially pick a zoom to fit the smaller of the entire map's width or height in the
// minimap. Arbitrary and probably pretty weird. // minimap. Arbitrary and probably pretty weird.
let bounds = app.map.get_bounds(); let bounds = app.map.get_bounds();
@ -49,7 +49,7 @@ impl SimpleMinimap {
m m
} }
pub fn recreate_panel(&mut self, ctx: &mut EventCtx, app: &SimpleApp) { pub fn recreate_panel<T>(&mut self, ctx: &mut EventCtx, app: &SimpleApp<T>) {
if ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail { if ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail {
self.panel = Panel::empty(ctx); self.panel = Panel::empty(ctx);
return; return;
@ -138,7 +138,7 @@ impl SimpleMinimap {
(pct_x, pct_y) (pct_x, pct_y)
} }
pub fn set_zoom(&mut self, ctx: &mut EventCtx, app: &SimpleApp, zoom_lvl: usize) { pub fn set_zoom<T>(&mut self, ctx: &mut EventCtx, app: &SimpleApp<T>, zoom_lvl: usize) {
// Make the frame wind up in the same relative position on the minimap // 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()); let (pct_x, pct_y) = self.map_to_minimap_pct(ctx.canvas.center_to_map_pt());
@ -154,7 +154,7 @@ impl SimpleMinimap {
self.offset_y = map_center.y() * self.zoom - pct_y * inner_rect.height(); self.offset_y = map_center.y() * self.zoom - pct_y * inner_rect.height();
} }
fn recenter(&mut self, ctx: &EventCtx, app: &SimpleApp) { fn recenter<T>(&mut self, ctx: &EventCtx, app: &SimpleApp<T>) {
// Recenter the minimap on the screen bounds // Recenter the minimap on the screen bounds
let map_center = ctx.canvas.center_to_map_pt(); let map_center = ctx.canvas.center_to_map_pt();
let rect = self.panel.rect_of("minimap"); let rect = self.panel.rect_of("minimap");
@ -169,11 +169,11 @@ impl SimpleMinimap {
self.offset_y = off_y.max(0.0).min(bounds.max_y * self.zoom - rect.height()); self.offset_y = off_y.max(0.0).min(bounds.max_y * self.zoom - rect.height());
} }
pub fn event( pub fn event<T: 'static>(
&mut self, &mut self,
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &mut SimpleApp, app: &mut SimpleApp<T>,
) -> Option<Transition<SimpleApp>> { ) -> Option<Transition<SimpleApp<T>>> {
let zoomed = ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail; let zoomed = ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail;
if zoomed != self.zoomed { if zoomed != self.zoomed {
let just_zoomed_in = zoomed && !self.zoomed; let just_zoomed_in = zoomed && !self.zoomed;
@ -303,11 +303,16 @@ impl SimpleMinimap {
None None
} }
pub fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) { pub fn draw<T>(&self, g: &mut GfxCtx, app: &SimpleApp<T>) {
self.draw_with_extra_layers(g, app, Vec::new()); 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<T>(
&self,
g: &mut GfxCtx,
app: &SimpleApp<T>,
extra: Vec<&Drawable>,
) {
self.panel.draw(g); self.panel.draw(g);
if !self.zoomed { if !self.zoomed {
return; return;

View File

@ -2,7 +2,7 @@ mod viewer;
fn main() { fn main() {
widgetry::run(widgetry::Settings::new("OpenStreetMap viewer"), |ctx| { widgetry::run(widgetry::Settings::new("OpenStreetMap viewer"), |ctx| {
let app = map_gui::SimpleApp::new(ctx, abstutil::CmdArgs::new()); let app = map_gui::SimpleApp::new(ctx, abstutil::CmdArgs::new(), ());
let states = vec![viewer::Viewer::new(ctx, &app)]; let states = vec![viewer::Viewer::new(ctx, &app)];
(app, states) (app, states)
}); });

View File

@ -15,6 +15,8 @@ use widgetry::{
VerticalAlignment, Widget, VerticalAlignment, Widget,
}; };
type App = SimpleApp<()>;
pub struct Viewer { pub struct Viewer {
top_panel: Panel, top_panel: Panel,
fixed_object_outline: Option<Drawable>, fixed_object_outline: Option<Drawable>,
@ -23,7 +25,7 @@ pub struct Viewer {
} }
impl Viewer { impl Viewer {
pub fn new(ctx: &mut EventCtx, app: &SimpleApp) -> Box<dyn State<SimpleApp>> { pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
let with_zorder = true; let with_zorder = true;
let mut viewer = Viewer { let mut viewer = Viewer {
fixed_object_outline: None, fixed_object_outline: None,
@ -40,7 +42,7 @@ impl Viewer {
fn recalculate_top_panel( fn recalculate_top_panel(
&mut self, &mut self,
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
biz_search_panel: Option<Widget>, biz_search_panel: Option<Widget>,
) { ) {
let top_panel = Panel::new(Widget::col(vec![ let top_panel = Panel::new(Widget::col(vec![
@ -75,7 +77,7 @@ impl Viewer {
self.top_panel = top_panel; self.top_panel = top_panel;
} }
fn calculate_tags(&self, ctx: &EventCtx, app: &SimpleApp) -> Widget { fn calculate_tags(&self, ctx: &EventCtx, app: &App) -> Widget {
let mut col = Vec::new(); let mut col = Vec::new();
if self.fixed_object_outline.is_some() { if self.fixed_object_outline.is_some() {
col.push("Click something else to examine it".draw_text(ctx)); col.push("Click something else to examine it".draw_text(ctx));
@ -217,8 +219,8 @@ impl Viewer {
} }
} }
impl State<SimpleApp> for Viewer { impl State<App> for Viewer {
fn event(&mut self, ctx: &mut EventCtx, app: &mut SimpleApp) -> Transition<SimpleApp> { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition<App> {
ctx.canvas_movement(); ctx.canvas_movement();
if ctx.redo_mouseover() { if ctx.redo_mouseover() {
let old_id = app.current_selection.clone(); let old_id = app.current_selection.clone();
@ -347,7 +349,7 @@ impl State<SimpleApp> for Viewer {
DrawBaselayer::Custom DrawBaselayer::Custom
} }
fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, app: &App) {
if g.canvas.cam_zoom < app.opts.min_zoom_for_detail { if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
app.draw_unzoomed(g); app.draw_unzoomed(g);
} else { } else {
@ -378,7 +380,7 @@ struct BusinessSearch {
} }
impl BusinessSearch { impl BusinessSearch {
fn new(ctx: &mut EventCtx, app: &SimpleApp) -> BusinessSearch { fn new(ctx: &mut EventCtx, app: &App) -> BusinessSearch {
let mut counts = Counter::new(); let mut counts = Counter::new();
for b in app.map.all_buildings() { for b in app.map.all_buildings() {
for a in &b.amenities { for a in &b.amenities {
@ -400,7 +402,7 @@ impl BusinessSearch {
} }
// Updates the highlighted buildings // Updates the highlighted buildings
fn update(&mut self, ctx: &mut EventCtx, app: &SimpleApp) { fn update(&mut self, ctx: &mut EventCtx, app: &App) {
let mut batch = GeomBatch::new(); let mut batch = GeomBatch::new();
for b in app.map.all_buildings() { for b in app.map.all_buildings() {
if b.amenities if b.amenities
@ -413,12 +415,7 @@ impl BusinessSearch {
self.highlight = ctx.upload(batch); self.highlight = ctx.upload(batch);
} }
fn hovering_on_amenity( fn hovering_on_amenity(&mut self, ctx: &mut EventCtx, app: &App, amenity: Option<String>) {
&mut self,
ctx: &mut EventCtx,
app: &SimpleApp,
amenity: Option<String>,
) {
if amenity.is_none() { if amenity.is_none() {
self.hovering_on_amenity = None; self.hovering_on_amenity = None;
return; return;

View File

@ -2,7 +2,7 @@ mod mapper;
fn main() { fn main() {
widgetry::run(widgetry::Settings::new("OSM parking mapper"), |ctx| { widgetry::run(widgetry::Settings::new("OSM parking mapper"), |ctx| {
let mut app = map_gui::SimpleApp::new(ctx, abstutil::CmdArgs::new()); let mut app = map_gui::SimpleApp::new(ctx, abstutil::CmdArgs::new(), ());
app.opts.min_zoom_for_detail = 2.0; app.opts.min_zoom_for_detail = 2.0;
let states = vec![mapper::ParkingMapper::new(ctx, &app)]; let states = vec![mapper::ParkingMapper::new(ctx, &app)];
(app, states) (app, states)

View File

@ -13,6 +13,8 @@ use widgetry::{
Line, Menu, Outcome, Panel, State, Text, TextExt, Transition, VerticalAlignment, Widget, Line, Menu, Outcome, Panel, State, Text, TextExt, Transition, VerticalAlignment, Widget,
}; };
type App = SimpleApp<()>;
pub struct ParkingMapper { pub struct ParkingMapper {
panel: Panel, panel: Panel,
draw_layer: Drawable, draw_layer: Drawable,
@ -41,16 +43,16 @@ pub enum Value {
} }
impl ParkingMapper { impl ParkingMapper {
pub fn new(ctx: &mut EventCtx, app: &SimpleApp) -> Box<dyn State<SimpleApp>> { pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
ParkingMapper::make(ctx, app, Show::TODO, BTreeMap::new()) ParkingMapper::make(ctx, app, Show::TODO, BTreeMap::new())
} }
fn make( fn make(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
show: Show, show: Show,
data: BTreeMap<WayID, Value>, data: BTreeMap<WayID, Value>,
) -> Box<dyn State<SimpleApp>> { ) -> Box<dyn State<App>> {
let map = &app.map; let map = &app.map;
let color = match show { let color = match show {
@ -189,8 +191,8 @@ impl ParkingMapper {
} }
} }
impl State<SimpleApp> for ParkingMapper { impl State<App> for ParkingMapper {
fn event(&mut self, ctx: &mut EventCtx, app: &mut SimpleApp) -> Transition<SimpleApp> { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition<App> {
let map = &app.map; let map = &app.map;
ctx.canvas_movement(); ctx.canvas_movement();
@ -371,7 +373,7 @@ impl State<SimpleApp> for ParkingMapper {
Transition::Keep Transition::Keep
} }
fn draw(&self, g: &mut GfxCtx, _: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, _: &App) {
g.redraw(&self.draw_layer); g.redraw(&self.draw_layer);
if let Some((_, ref roads)) = self.selected { if let Some((_, ref roads)) = self.selected {
g.redraw(roads); g.redraw(roads);
@ -391,11 +393,11 @@ struct ChangeWay {
impl ChangeWay { impl ChangeWay {
fn new( fn new(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &SimpleApp, app: &App,
selected: &HashSet<RoadID>, selected: &HashSet<RoadID>,
show: Show, show: Show,
data: BTreeMap<WayID, Value>, data: BTreeMap<WayID, Value>,
) -> Box<dyn State<SimpleApp>> { ) -> Box<dyn State<App>> {
let map = &app.map; let map = &app.map;
let osm_way_id = map let osm_way_id = map
.get_r(*selected.iter().next().unwrap()) .get_r(*selected.iter().next().unwrap())
@ -453,8 +455,8 @@ impl ChangeWay {
} }
} }
impl State<SimpleApp> for ChangeWay { impl State<App> for ChangeWay {
fn event(&mut self, ctx: &mut EventCtx, app: &mut SimpleApp) -> Transition<SimpleApp> { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition<App> {
ctx.canvas_movement(); ctx.canvas_movement();
match self.panel.event(ctx) { match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() { Outcome::Clicked(x) => match x.as_ref() {
@ -493,7 +495,7 @@ impl State<SimpleApp> for ChangeWay {
} }
} }
fn draw(&self, g: &mut GfxCtx, _: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, _: &App) {
g.redraw(&self.draw); g.redraw(&self.draw);
self.panel.draw(g); self.panel.draw(g);
} }
@ -597,7 +599,7 @@ fn generate_osmc(
Ok(()) Ok(())
} }
fn find_divided_highways(app: &SimpleApp) -> HashSet<RoadID> { fn find_divided_highways(app: &App) -> HashSet<RoadID> {
let map = &app.map; let map = &app.map;
let mut closest: FindClosest<RoadID> = FindClosest::new(map.get_bounds()); let mut closest: FindClosest<RoadID> = FindClosest::new(map.get_bounds());
// TODO Consider not even filtering by oneway. I keep finding mistakes where people split a // TODO Consider not even filtering by oneway. I keep finding mistakes where people split a
@ -640,7 +642,7 @@ fn find_divided_highways(app: &SimpleApp) -> HashSet<RoadID> {
} }
// TODO Lots of false positives here... why? // TODO Lots of false positives here... why?
fn find_overlapping_stuff(app: &SimpleApp, timer: &mut Timer) -> Vec<Polygon> { fn find_overlapping_stuff(app: &App, timer: &mut Timer) -> Vec<Polygon> {
let map = &app.map; let map = &app.map;
let mut closest: FindClosest<RoadID> = FindClosest::new(map.get_bounds()); let mut closest: FindClosest<RoadID> = FindClosest::new(map.get_bounds());
for r in map.all_roads() { for r in map.all_roads() {