use WizardState for some more things, making proper use of bundling state with closures!

This commit is contained in:
Dustin Carlino 2019-08-08 10:20:48 -07:00
parent 02e850f3d7
commit 38ea097b26
7 changed files with 161 additions and 258 deletions

View File

@ -82,10 +82,7 @@ impl State for ABTestSetup {
} else if self.menu.action("run A/B test") {
return Transition::Replace(Box::new(launch_test(&self.ab_test, ui, ctx)));
} else if self.menu.action("load savestate") {
return Transition::Push(Box::new(LoadSavestate {
ab_test: self.ab_test.clone(),
wizard: Wizard::new(),
}));
return Transition::Push(make_load_savestate(self.ab_test.clone()));
}
Transition::Keep
}
@ -96,24 +93,19 @@ impl State for ABTestSetup {
}
}
struct LoadSavestate {
ab_test: ABTest,
wizard: Wizard,
}
impl State for LoadSavestate {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
if let Some(ss) = pick_savestate(&self.ab_test, &mut self.wizard.wrap(ctx)) {
return Transition::Replace(Box::new(launch_savestate(&self.ab_test, ss, ui, ctx)));
} else if self.wizard.aborted() {
return Transition::Pop;
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
fn make_load_savestate(ab_test: ABTest) -> Box<State> {
WizardState::new(Box::new(move |wiz, ctx, ui| {
let ss = wiz.wrap(ctx).choose_string("Load which savestate?", || {
abstutil::list_dir(std::path::Path::new(&abstutil::path1(
&ab_test.map_name,
abstutil::AB_TEST_SAVES,
&ab_test.test_name,
)))
})?;
Some(Transition::Replace(Box::new(launch_savestate(
&ab_test, ss, ui, ctx,
))))
}))
}
fn launch_test(test: &ABTest, ui: &mut UI, ctx: &mut EventCtx) -> ABTestMode {
@ -244,10 +236,3 @@ fn choose_edits(map_name: &str, wizard: &mut WrappedWizard, query: &str) -> Opti
list
})
}
fn pick_savestate(test: &ABTest, wizard: &mut WrappedWizard) -> Option<String> {
let path = abstutil::path1(&test.map_name, abstutil::AB_TEST_SAVES, &test.test_name);
wizard.choose_string("Load which savestate?", || {
abstutil::list_dir(std::path::Path::new(&path))
})
}

View File

@ -1,8 +1,8 @@
use crate::common::CommonState;
use crate::game::{State, Transition};
use crate::game::{State, Transition, WizardState};
use crate::helpers::ID;
use crate::ui::UI;
use ezgui::{EventCtx, GfxCtx, Key, ModalMenu, Text, WarpingItemSlider, Wizard};
use ezgui::{EventCtx, GfxCtx, Key, ModalMenu, Text, WarpingItemSlider};
use geom::Pt2D;
use map_model::{BusRoute, BusRouteID, BusStopID, Map};
@ -32,10 +32,9 @@ impl BusRouteExplorer {
ctx,
)))
} else {
Some(Box::new(BusRoutePicker {
choices: routes.into_iter().map(|r| r.id).collect(),
wizard: Wizard::new(),
}))
Some(make_bus_route_picker(
routes.into_iter().map(|r| r.id).collect(),
))
}
}
@ -83,54 +82,37 @@ impl State for BusRouteExplorer {
}
}
pub struct BusRoutePicker {
choices: Vec<BusRouteID>,
wizard: Wizard,
}
pub struct BusRoutePicker;
impl BusRoutePicker {
pub fn new(ui: &UI, menu: &mut ModalMenu) -> Option<BusRoutePicker> {
pub fn new(ui: &UI, menu: &mut ModalMenu) -> Option<Box<State>> {
if !menu.action("explore a bus route") {
return None;
}
Some(BusRoutePicker {
choices: ui
.primary
Some(make_bus_route_picker(
ui.primary
.map
.get_all_bus_routes()
.iter()
.map(|r| r.id)
.collect(),
wizard: Wizard::new(),
})
))
}
}
impl State for BusRoutePicker {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
let choices = self.choices.clone();
if let Some((_, id)) =
self.wizard
.wrap(ctx)
.choose_something("Explore which bus route?", || {
choices
.into_iter()
.map(|id| (ui.primary.map.get_br(id).name.clone(), id))
.collect()
})
{
return Transition::Replace(Box::new(BusRouteExplorer::for_route(
ui.primary.map.get_br(id),
&ui.primary.map,
ctx,
)));
} else if self.wizard.aborted() {
return Transition::Pop;
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
fn make_bus_route_picker(choices: Vec<BusRouteID>) -> Box<State> {
WizardState::new(Box::new(move |wiz, ctx, ui| {
let (_, id) = wiz
.wrap(ctx)
.choose_something("Explore which bus route?", || {
choices
.iter()
.map(|id| (ui.primary.map.get_br(*id).name.clone(), *id))
.collect()
})?;
Some(Transition::Replace(Box::new(BusRouteExplorer::for_route(
ui.primary.map.get_br(id),
&ui.primary.map,
ctx,
))))
}))
}

View File

@ -275,7 +275,7 @@ impl State for DebugMode {
return Transition::PushWithMode(explorer, EventLoopMode::Animation);
}
if let Some(picker) = bus_explorer::BusRoutePicker::new(ui, &mut self.menu) {
return Transition::Push(Box::new(picker));
return Transition::Push(picker);
}
Transition::Keep

View File

@ -12,7 +12,7 @@ use crate::render::{
use crate::sandbox::SandboxMode;
use crate::ui::{PerMapUI, ShowEverything, UI};
use abstutil::Timer;
use ezgui::{hotkey, lctrl, Color, EventCtx, GfxCtx, Key, ModalMenu, Text, Wizard, WrappedWizard};
use ezgui::{hotkey, lctrl, Color, EventCtx, GfxCtx, Key, ModalMenu, Text, Wizard};
use map_model::{
IntersectionID, Lane, LaneID, LaneType, Map, MapEdits, Road, RoadID, TurnID, TurnType,
};
@ -150,10 +150,7 @@ impl State for EditMode {
.input
.contextual_action(Key::U, "bulk edit lanes on this road")
{
return Transition::Push(Box::new(BulkEditLanes {
road: ui.primary.map.get_l(id).parent,
wizard: Wizard::new(),
}));
return Transition::Push(make_bulk_edit_lanes(ui.primary.map.get_l(id).parent));
} else if orig_edits.lane_overrides.contains_key(&id)
&& ctx.input.contextual_action(Key::R, "revert")
{
@ -474,73 +471,54 @@ pub fn apply_map_edits(
bundle.map.simplify_edits(&mut timer);
}
struct BulkEditLanes {
road: RoadID,
wizard: Wizard,
}
impl State for BulkEditLanes {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
ctx.canvas.handle_event(ctx.input);
if let Some(edits) = bulk_edit(self.road, &mut self.wizard.wrap(ctx), &ui.primary.map) {
apply_map_edits(&mut ui.primary, &ui.cs, ctx, edits);
Transition::Pop
} else if self.wizard.aborted() {
Transition::Pop
} else {
Transition::Keep
}
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
// TODO Still draw the diffs, yo
self.wizard.draw(g);
}
}
fn bulk_edit(r: RoadID, wizard: &mut WrappedWizard, map: &Map) -> Option<MapEdits> {
let (_, from) = wizard.choose_something("Change all lanes of type...", || {
vec![
("driving".to_string(), LaneType::Driving),
("parking".to_string(), LaneType::Parking),
("biking".to_string(), LaneType::Biking),
("bus".to_string(), LaneType::Bus),
]
})?;
let (_, to) = wizard.choose_something("Change to all lanes of type...", || {
vec![
("driving".to_string(), LaneType::Driving),
("parking".to_string(), LaneType::Parking),
("biking".to_string(), LaneType::Biking),
("bus".to_string(), LaneType::Bus),
]
.into_iter()
.filter(|(_, lt)| *lt != from)
.collect()
})?;
// Do the dirty deed. Match by road name; OSM way ID changes a fair bit.
let road_name = map.get_r(r).get_name();
let mut edits = map.get_edits().clone();
let mut cnt = 0;
for l in map.all_lanes() {
if l.lane_type != from {
continue;
}
let parent = map.get_parent(l.id);
if parent.get_name() != road_name {
continue;
}
// TODO This looks at the original state of the map, not with all the edits applied so far!
if can_change_lane_type(parent, l, to, map) {
edits.lane_overrides.insert(l.id, to);
cnt += 1;
}
}
// TODO pop this up. warn about road names changing and being weird. :)
println!(
"Changed {} {:?} lanes to {:?} lanes on {}",
cnt, from, to, road_name
);
Some(edits)
fn make_bulk_edit_lanes(road: RoadID) -> Box<State> {
WizardState::new(Box::new(move |wiz, ctx, ui| {
let mut wizard = wiz.wrap(ctx);
let (_, from) = wizard.choose_something("Change all lanes of type...", || {
vec![
("driving".to_string(), LaneType::Driving),
("parking".to_string(), LaneType::Parking),
("biking".to_string(), LaneType::Biking),
("bus".to_string(), LaneType::Bus),
]
})?;
let (_, to) = wizard.choose_something("Change to all lanes of type...", || {
vec![
("driving".to_string(), LaneType::Driving),
("parking".to_string(), LaneType::Parking),
("biking".to_string(), LaneType::Biking),
("bus".to_string(), LaneType::Bus),
]
.into_iter()
.filter(|(_, lt)| *lt != from)
.collect()
})?;
// Do the dirty deed. Match by road name; OSM way ID changes a fair bit.
let map = &ui.primary.map;
let road_name = map.get_r(road).get_name();
let mut edits = map.get_edits().clone();
let mut cnt = 0;
for l in map.all_lanes() {
if l.lane_type != from {
continue;
}
let parent = map.get_parent(l.id);
if parent.get_name() != road_name {
continue;
}
// TODO This looks at the original state of the map, not with all the edits applied so far!
if can_change_lane_type(parent, l, to, map) {
edits.lane_overrides.insert(l.id, to);
cnt += 1;
}
}
// TODO pop this up. warn about road names changing and being weird. :)
println!(
"Changed {} {:?} lanes to {:?} lanes on {}",
cnt, from, to, road_name
);
apply_map_edits(&mut ui.primary, &ui.cs, ctx, edits);
Some(Transition::Pop)
}))
}

View File

@ -1,13 +1,13 @@
use crate::common::CommonState;
use crate::edit::apply_map_edits;
use crate::game::{State, Transition};
use crate::game::{State, Transition, WizardState};
use crate::helpers::ID;
use crate::render::{draw_signal_cycle, DrawCtx, DrawOptions, DrawTurn, TrafficSignalDiagram};
use crate::ui::{ShowEverything, UI};
use abstutil::Timer;
use ezgui::{hotkey, Color, EventCtx, GeomBatch, GfxCtx, Key, ModalMenu, Wizard, WrappedWizard};
use ezgui::{hotkey, Color, EventCtx, GeomBatch, GfxCtx, Key, ModalMenu};
use geom::Duration;
use map_model::{ControlTrafficSignal, Cycle, IntersectionID, Map, TurnID, TurnPriority, TurnType};
use map_model::{ControlTrafficSignal, Cycle, IntersectionID, TurnID, TurnPriority, TurnType};
// TODO Warn if there are empty cycles or if some turn is completely absent from the signal.
pub struct TrafficSignalEditor {
@ -122,15 +122,11 @@ impl State for TrafficSignalEditor {
}
if self.menu.action("change cycle duration") {
return Transition::Push(Box::new(ChangeCycleDuration {
cycle: signal.cycles[self.diagram.current_cycle()].clone(),
wizard: Wizard::new(),
}));
return Transition::Push(make_change_cycle_duration(
signal.cycles[self.diagram.current_cycle()].duration,
));
} else if self.menu.action("choose a preset signal") {
return Transition::Push(Box::new(ChangePreset {
i: self.diagram.i,
wizard: Wizard::new(),
}));
return Transition::Push(make_change_preset(self.diagram.i));
} else if self.menu.action("reset to original") {
signal = ControlTrafficSignal::get_possible_policies(&ui.primary.map, self.diagram.i)
.remove(0)
@ -198,7 +194,7 @@ impl State for TrafficSignalEditor {
.menu
.action("convert to dedicated pedestrian scramble cycle")
{
convert_to_ped_scramble(&mut signal, self.diagram.i, &ui.primary.map);
signal.convert_to_ped_scramble(&ui.primary.map);
change_traffic_signal(signal, self.diagram.i, ui, ctx);
self.diagram = TrafficSignalDiagram::new(self.diagram.i, 0, &ui.primary.map, ctx);
}
@ -261,51 +257,6 @@ impl State for TrafficSignalEditor {
}
}
fn choose_preset(
map: &Map,
id: IntersectionID,
mut wizard: WrappedWizard,
) -> Option<ControlTrafficSignal> {
wizard
.choose_something("Use which preset for this intersection?", || {
ControlTrafficSignal::get_possible_policies(map, id)
})
.map(|(_, ts)| ts)
}
fn convert_to_ped_scramble(signal: &mut ControlTrafficSignal, i: IntersectionID, map: &Map) {
// Remove Crosswalk turns from existing cycles.
for cycle in signal.cycles.iter_mut() {
// Crosswalks are usually only priority_turns, but also clear out from yield_turns.
for t in map.get_turns_in_intersection(i) {
if t.turn_type == TurnType::Crosswalk {
cycle.priority_turns.remove(&t.id);
cycle.yield_turns.remove(&t.id);
}
}
// Blindly try to promote yield turns to protected, now that crosswalks are gone.
let mut promoted = Vec::new();
for t in &cycle.yield_turns {
if cycle.could_be_priority_turn(*t, map) {
cycle.priority_turns.insert(*t);
promoted.push(*t);
}
}
for t in promoted {
cycle.yield_turns.remove(&t);
}
}
let mut cycle = Cycle::new(i);
for t in map.get_turns_in_intersection(i) {
if t.between_sidewalks() {
cycle.edit_turn(t, TurnPriority::Priority);
}
}
signal.cycles.push(cycle);
}
fn change_traffic_signal(
signal: ControlTrafficSignal,
i: IntersectionID,
@ -322,58 +273,34 @@ fn change_traffic_signal(
apply_map_edits(&mut ui.primary, &ui.cs, ctx, new_edits);
}
struct ChangeCycleDuration {
cycle: Cycle,
wizard: Wizard,
}
impl State for ChangeCycleDuration {
fn event(&mut self, ctx: &mut EventCtx, _: &mut UI) -> Transition {
if let Some(new_duration) = self.wizard.wrap(ctx).input_usize_prefilled(
fn make_change_cycle_duration(current_duration: Duration) -> Box<State> {
WizardState::new(Box::new(move |wiz, ctx, _| {
let new_duration = wiz.wrap(ctx).input_usize_prefilled(
"How long should this cycle be?",
format!("{}", self.cycle.duration.inner_seconds() as usize),
) {
return Transition::PopWithData(Box::new(move |state, ui, ctx| {
let editor = state.downcast_ref::<TrafficSignalEditor>().unwrap();
let mut signal = ui.primary.map.get_traffic_signal(editor.diagram.i).clone();
signal.cycles[editor.diagram.current_cycle()].duration =
Duration::seconds(new_duration as f64);
change_traffic_signal(signal, editor.diagram.i, ui, ctx);
}));
}
if self.wizard.aborted() {
return Transition::Pop;
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
format!("{}", current_duration.inner_seconds() as usize),
)?;
Some(Transition::PopWithData(Box::new(move |state, ui, ctx| {
let mut editor = state.downcast_mut::<TrafficSignalEditor>().unwrap();
let mut signal = ui.primary.map.get_traffic_signal(editor.diagram.i).clone();
let idx = editor.diagram.current_cycle();
signal.cycles[idx].duration = Duration::seconds(new_duration as f64);
change_traffic_signal(signal, editor.diagram.i, ui, ctx);
editor.diagram = TrafficSignalDiagram::new(editor.diagram.i, idx, &ui.primary.map, ctx);
})))
}))
}
struct ChangePreset {
i: IntersectionID,
wizard: Wizard,
}
impl State for ChangePreset {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
if let Some(new_signal) = choose_preset(&ui.primary.map, self.i, self.wizard.wrap(ctx)) {
return Transition::PopWithData(Box::new(move |state, ui, ctx| {
let mut editor = state.downcast_mut::<TrafficSignalEditor>().unwrap();
editor.diagram =
TrafficSignalDiagram::new(editor.diagram.i, 0, &ui.primary.map, ctx);
change_traffic_signal(new_signal, editor.diagram.i, ui, ctx);
}));
}
if self.wizard.aborted() {
return Transition::Pop;
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
fn make_change_preset(i: IntersectionID) -> Box<State> {
WizardState::new(Box::new(move |wiz, ctx, ui| {
let (_, new_signal) = wiz
.wrap(ctx)
.choose_something("Use which preset for this intersection?", || {
ControlTrafficSignal::get_possible_policies(&ui.primary.map, i)
})?;
Some(Transition::PopWithData(Box::new(move |state, ui, ctx| {
let mut editor = state.downcast_mut::<TrafficSignalEditor>().unwrap();
change_traffic_signal(new_signal, editor.diagram.i, ui, ctx);
editor.diagram = TrafficSignalDiagram::new(editor.diagram.i, 0, &ui.primary.map, ctx);
})))
}))
}

View File

@ -196,8 +196,6 @@ pub enum Transition {
ReplaceWithMode(Box<State>, EventLoopMode),
}
// TODO Maybe let callers stash expensive data computed once here, and let the cb borrow it.
// Use cases: both BrowseTrips's
pub struct WizardState {
wizard: Wizard,
// Returning None means stay in this WizardState

View File

@ -375,6 +375,39 @@ impl ControlTrafficSignal {
None
}
}
pub fn convert_to_ped_scramble(&mut self, map: &Map) {
// Remove Crosswalk turns from existing cycles.
for cycle in self.cycles.iter_mut() {
// Crosswalks are usually only priority_turns, but also clear out from yield_turns.
for t in map.get_turns_in_intersection(self.id) {
if t.turn_type == TurnType::Crosswalk {
cycle.priority_turns.remove(&t.id);
cycle.yield_turns.remove(&t.id);
}
}
// Blindly try to promote yield turns to protected, now that crosswalks are gone.
let mut promoted = Vec::new();
for t in &cycle.yield_turns {
if cycle.could_be_priority_turn(*t, map) {
cycle.priority_turns.insert(*t);
promoted.push(*t);
}
}
for t in promoted {
cycle.yield_turns.remove(&t);
}
}
let mut cycle = Cycle::new(self.id);
for t in map.get_turns_in_intersection(self.id) {
if t.between_sidewalks() {
cycle.edit_turn(t, TurnPriority::Priority);
}
}
self.cycles.push(cycle);
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]