use widgetry::{
Btn, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, TextExt, Widget,
};
use crate::app::App;
use crate::common::HeatmapOptions;
use crate::game::Transition;
use crate::helpers::{grey_out_map, hotkey_btn};
use crate::sandbox::dashboards;
mod elevation;
pub mod map;
mod pandemic;
mod parking;
mod population;
pub mod traffic;
pub mod transit;
pub trait Layer {
fn name(&self) -> Option<&'static str>;
fn event(&mut self, ctx: &mut EventCtx, app: &mut App, minimap: &Panel)
-> Option<LayerOutcome>;
fn draw(&self, g: &mut GfxCtx, app: &App);
fn draw_minimap(&self, g: &mut GfxCtx);
}
impl dyn Layer {
fn simple_event(
ctx: &mut EventCtx,
minimap: &Panel,
panel: &mut Panel,
) -> Option<LayerOutcome> {
panel.align_above(ctx, minimap);
match panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
"close" => Some(LayerOutcome::Close),
_ => unreachable!(),
},
_ => None,
}
}
}
pub enum LayerOutcome {
Close,
Replace(Box<dyn Layer>),
}
pub struct PickLayer {
panel: Panel,
}
impl PickLayer {
pub fn update(ctx: &mut EventCtx, app: &mut App, minimap: &Panel) -> Option<Transition> {
if app.primary.layer.is_none() {
return None;
}
let mut layer = app.primary.layer.take().unwrap();
match layer.event(ctx, app, minimap) {
Some(LayerOutcome::Close) => {
app.primary.layer = None;
return None;
}
Some(LayerOutcome::Replace(l)) => {
app.primary.layer = Some(l);
return None;
}
None => {}
}
app.primary.layer = Some(layer);
None
}
pub fn pick(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
let mut col = vec![Widget::custom_row(vec![
Line("Layers").small_heading().draw(ctx),
Btn::plaintext("X")
.build(ctx, "close", Key::Escape)
.align_right(),
])];
let current = match app.primary.layer {
None => "None",
Some(ref l) => l.name().unwrap_or(""),
};
let btn = |name: &str, key| {
if name == current {
Btn::text_bg2(name).inactive(ctx)
} else {
hotkey_btn(ctx, app, name, key)
}
};
col.push(btn("None", Key::N));
col.push(
Widget::custom_row(vec![
Widget::col(vec![
"Traffic".draw_text(ctx),
btn("delay", Key::D),
btn("throughput", Key::T),
btn("traffic jams", Key::J),
]),
Widget::col(vec![
"Map".draw_text(ctx),
btn("map edits", Key::E),
btn("parking occupancy", Key::P),
btn("bike network", Key::B),
btn("transit network", Key::U),
btn("population map", Key::X),
btn("no sidewalks", Key::S),
]),
])
.evenly_spaced(),
);
col.push(
Widget::custom_row(vec![
Widget::col(vec![
"Experimental".draw_text(ctx),
btn("amenities", Key::A),
btn("backpressure", Key::Z),
btn("elevation", Key::V),
btn("parking efficiency", Key::O),
btn("blackholes", Key::L),
btn("congestion caps", Key::C),
if app.primary.sim.get_pandemic_model().is_some() {
btn("pandemic model", Key::Y)
} else {
Widget::nothing()
},
]),
Widget::col(vec![
"Data".draw_text(ctx),
btn("traffic signal demand", Key::M),
btn("commuter patterns", Key::R),
]),
])
.evenly_spaced(),
);
Box::new(PickLayer {
panel: Panel::new(Widget::col(col))
.exact_size_percent(35, 70)
.build(ctx),
})
}
}
impl State<App> for PickLayer {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
"close" => {}
"None" => {
app.primary.layer = None;
}
"amenities" => {
app.primary.layer = Some(Box::new(map::Static::amenities(ctx, app)));
}
"backpressure" => {
app.primary.layer = Some(Box::new(traffic::Backpressure::new(ctx, app)));
}
"bike network" => {
app.primary.layer = Some(Box::new(map::BikeNetwork::new(ctx, app)));
}
"delay" => {
app.primary.layer = Some(Box::new(traffic::Delay::new(ctx, app)));
}
"elevation" => {
app.primary.layer = Some(Box::new(elevation::Elevation::new(ctx, app)));
}
"map edits" => {
app.primary.layer = Some(Box::new(map::Static::edits(ctx, app)));
}
"no sidewalks" => {
app.primary.layer = Some(Box::new(map::Static::no_sidewalks(ctx, app)));
}
"pandemic model" => {
app.primary.layer = Some(Box::new(pandemic::Pandemic::new(
ctx,
app,
pandemic::Options {
heatmap: Some(HeatmapOptions::new()),
state: pandemic::SEIR::Infected,
},
)));
}
"blackholes" => {
app.primary.layer = Some(Box::new(map::Static::blackholes(ctx, app)));
}
"congestion caps" => {
app.primary.layer = Some(Box::new(map::CongestionCaps::new(ctx, app)));
}
"parking occupancy" => {
app.primary.layer = Some(Box::new(parking::Occupancy::new(
ctx, app, true, true, true, false, true,
)));
}
"parking efficiency" => {
app.primary.layer = Some(Box::new(parking::Efficiency::new(ctx, app)));
}
"population map" => {
app.primary.layer = Some(Box::new(population::PopulationMap::new(
ctx,
app,
population::Options {
heatmap: Some(HeatmapOptions::new()),
},
)));
}
"throughput" => {
app.primary.layer = Some(Box::new(traffic::Throughput::new(ctx, app)));
}
"traffic jams" => {
app.primary.layer = Some(Box::new(traffic::TrafficJams::new(ctx, app)));
}
"transit network" => {
app.primary.layer = Some(Box::new(transit::TransitNetwork::new(
ctx, app, false, true, true,
)));
}
"traffic signal demand" => {
return Transition::Replace(dashboards::TrafficSignalDemand::new(ctx, app));
}
"commuter patterns" => {
return Transition::Replace(dashboards::CommuterPatterns::new(ctx, app));
}
_ => unreachable!(),
},
_ => {
if self.panel.clicked_outside(ctx) {
return Transition::Pop;
}
return Transition::Keep;
}
}
Transition::Pop
}
fn draw_baselayer(&self) -> DrawBaselayer {
DrawBaselayer::PreviousState
}
fn draw(&self, g: &mut GfxCtx, app: &App) {
grey_out_map(g, app);
self.panel.draw(g);
}
}