make Warp and Navigate use nested states inside CommonState

This commit is contained in:
Dustin Carlino 2019-06-22 18:09:31 -07:00
parent 8250bd5a1e
commit 551866169a
7 changed files with 158 additions and 147 deletions

View File

@ -88,8 +88,8 @@ impl State for ABTestMode {
false,
);
}
if let Some(evmode) = self.common.event(ctx, ui, &mut self.menu) {
return (Transition::Keep, evmode);
if let Some(pair) = self.common.event(ctx, ui, &mut self.menu) {
return pair;
}
if self.menu.action("quit") {

View File

@ -3,6 +3,7 @@ mod navigate;
mod turn_cycler;
mod warp;
use crate::game::Transition;
use crate::helpers::ID;
use crate::render::DrawOptions;
use crate::ui::UI;
@ -18,8 +19,6 @@ use std::time::Instant;
pub struct CommonState {
associated: associated::ShowAssociatedState,
turn_cycler: turn_cycler::TurnCyclerState,
warp: Option<warp::WarpState>,
navigate: Option<navigate::Navigator>,
}
impl CommonState {
@ -27,8 +26,6 @@ impl CommonState {
CommonState {
associated: associated::ShowAssociatedState::Inactive,
turn_cycler: turn_cycler::TurnCyclerState::new(),
warp: None,
navigate: None,
}
}
@ -41,48 +38,35 @@ impl CommonState {
]
}
// If this returns something, then this common state should prevent other things from
// happening.
pub fn event(
&mut self,
ctx: &mut EventCtx,
ui: &mut UI,
menu: &mut ModalMenu,
) -> Option<EventLoopMode> {
if let Some(ref mut warp) = self.warp {
if let Some(evmode) = warp.event(ctx, ui) {
return Some(evmode);
}
self.warp = None;
}
) -> Option<(Transition, EventLoopMode)> {
if menu.action("warp") {
self.warp = Some(warp::WarpState::new());
}
if let Some(ref mut navigate) = self.navigate {
if let Some(evmode) = navigate.event(ctx, ui) {
return Some(evmode);
}
self.navigate = None;
return Some((
Transition::Push(Box::new(warp::EnteringWarp::new())),
EventLoopMode::InputOnly,
));
}
if menu.action("navigate") {
self.navigate = Some(navigate::Navigator::new(ui));
return Some((
Transition::Push(Box::new(navigate::Navigator::new(ui))),
EventLoopMode::InputOnly,
));
}
self.associated.event(ui);
self.turn_cycler.event(ctx, ui);
if menu.action("take a screenshot") {
return Some(EventLoopMode::ScreenCaptureCurrentShot);
return Some((Transition::Keep, EventLoopMode::ScreenCaptureCurrentShot));
}
None
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
if let Some(ref warp) = self.warp {
warp.draw(g);
}
if let Some(ref navigate) = self.navigate {
navigate.draw(g);
}
self.turn_cycler.draw(g, ui);
CommonState::draw_osd(g, ui, ui.primary.current_selection);

View File

@ -1,111 +1,123 @@
use crate::common::warp::Warping;
use crate::game::{State, Transition};
use crate::helpers::ID;
use crate::ui::UI;
use ezgui::{Autocomplete, EventCtx, EventLoopMode, GfxCtx, InputResult, Warper};
use map_model::RoadID;
use std::collections::HashSet;
pub enum Navigator {
FirstStreet(Autocomplete<RoadID>),
CrossStreet(RoadID, Autocomplete<RoadID>),
Warping(Warper, ID),
pub struct Navigator {
autocomplete: Autocomplete<RoadID>,
}
impl Navigator {
pub fn new(ui: &UI) -> Navigator {
// TODO Canonicalize names, handling abbreviations like east/e and street/st
Navigator::FirstStreet(Autocomplete::new(
"Warp where?",
ui.primary
.map
.all_roads()
.iter()
.map(|r| (r.get_name(), r.id))
.collect(),
))
Navigator {
autocomplete: Autocomplete::new(
"Warp where?",
ui.primary
.map
.all_roads()
.iter()
.map(|r| (r.get_name(), r.id))
.collect(),
),
}
}
}
// When None, this is done.
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Option<EventLoopMode> {
impl State for Navigator {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
let map = &ui.primary.map;
match self {
Navigator::FirstStreet(autocomplete) => match autocomplete.event(ctx.input) {
InputResult::Canceled => None,
InputResult::Done(name, ids) => {
// Roads share intersections, so of course there'll be overlap here.
let mut cross_streets = HashSet::new();
for r in &ids {
let road = map.get_r(*r);
for i in &[road.src_i, road.dst_i] {
for cross in &map.get_i(*i).roads {
if !ids.contains(cross) {
cross_streets.insert(*cross);
}
match self.autocomplete.event(ctx.input) {
InputResult::Canceled => (Transition::Pop, EventLoopMode::InputOnly),
InputResult::Done(name, ids) => {
// Roads share intersections, so of course there'll be overlap here.
let mut cross_streets = HashSet::new();
for r in &ids {
let road = map.get_r(*r);
for i in &[road.src_i, road.dst_i] {
for cross in &map.get_i(*i).roads {
if !ids.contains(cross) {
cross_streets.insert(*cross);
}
}
}
*self = Navigator::CrossStreet(
*ids.iter().next().unwrap(),
Autocomplete::new(
}
(
Transition::Replace(Box::new(CrossStreet {
first: *ids.iter().next().unwrap(),
autocomplete: Autocomplete::new(
&format!("{} and what?", name),
cross_streets
.into_iter()
.map(|r| (map.get_r(r).get_name(), r))
.collect(),
),
);
Some(EventLoopMode::InputOnly)
}
InputResult::StillActive => Some(EventLoopMode::InputOnly),
},
Navigator::CrossStreet(first_id, autocomplete) => match autocomplete.event(ctx.input) {
InputResult::Canceled => {
// Just warp to somewhere on the first road
let road = map.get_r(*first_id);
println!("Warping to {}", road.get_name());
*self = Navigator::Warping(
Warper::new(
ctx,
road.center_pts.dist_along(road.center_pts.length() / 2.0).0,
),
ID::Lane(road.all_lanes()[0]),
);
Some(EventLoopMode::Animation)
}
InputResult::Done(name, ids) => {
println!(
"Warping to {} and {}",
map.get_r(*first_id).get_name(),
name
);
let road = map.get_r(*ids.iter().next().unwrap());
let pt = if map.get_i(road.src_i).roads.contains(first_id) {
map.get_i(road.src_i).polygon.center()
} else {
map.get_i(road.dst_i).polygon.center()
};
*self = Navigator::Warping(Warper::new(ctx, pt), ID::Lane(road.all_lanes()[0]));
Some(EventLoopMode::Animation)
}
InputResult::StillActive => Some(EventLoopMode::InputOnly),
},
Navigator::Warping(ref warper, id) => {
let result = warper.event(ctx);
if result.is_none() {
ui.primary.current_selection = Some(*id);
}
result
})),
EventLoopMode::InputOnly,
)
}
InputResult::StillActive => (Transition::Keep, EventLoopMode::InputOnly),
}
}
pub fn draw(&self, g: &mut GfxCtx) {
match self {
Navigator::FirstStreet(ref autocomplete)
| Navigator::CrossStreet(_, ref autocomplete) => {
autocomplete.draw(g);
}
Navigator::Warping(_, _) => {}
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.autocomplete.draw(g);
}
}
struct CrossStreet {
first: RoadID,
autocomplete: Autocomplete<RoadID>,
}
impl State for CrossStreet {
// When None, this is done.
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
let map = &ui.primary.map;
match self.autocomplete.event(ctx.input) {
InputResult::Canceled => {
// Just warp to somewhere on the first road
let road = map.get_r(self.first);
println!("Warping to {}", road.get_name());
(
Transition::Replace(Box::new(Warping {
warper: Warper::new(
ctx,
road.center_pts.dist_along(road.center_pts.length() / 2.0).0,
),
id: ID::Lane(road.all_lanes()[0]),
})),
EventLoopMode::Animation,
)
}
InputResult::Done(name, ids) => {
println!(
"Warping to {} and {}",
map.get_r(self.first).get_name(),
name
);
let road = map.get_r(*ids.iter().next().unwrap());
let pt = if map.get_i(road.src_i).roads.contains(&self.first) {
map.get_i(road.src_i).polygon.center()
} else {
map.get_i(road.dst_i).polygon.center()
};
(
Transition::Replace(Box::new(Warping {
warper: Warper::new(ctx, pt),
id: ID::Lane(road.all_lanes()[0]),
})),
EventLoopMode::Animation,
)
}
InputResult::StillActive => (Transition::Keep, EventLoopMode::InputOnly),
}
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.autocomplete.draw(g);
}
}

View File

@ -1,3 +1,4 @@
use crate::game::{State, Transition};
use crate::helpers::ID;
use crate::ui::{PerMapUI, UI};
use ezgui::{EventCtx, EventLoopMode, GfxCtx, InputResult, TextBox, Warper};
@ -6,46 +7,60 @@ use map_model::{raw_data, AreaID, BuildingID, IntersectionID, LaneID, RoadID};
use sim::{PedestrianID, TripID};
use std::usize;
pub enum WarpState {
EnteringSearch(TextBox),
Warping(Warper, ID),
pub struct EnteringWarp {
tb: TextBox,
}
impl WarpState {
pub fn new() -> WarpState {
WarpState::EnteringSearch(TextBox::new("Warp to what?", None))
impl EnteringWarp {
pub fn new() -> EnteringWarp {
EnteringWarp {
tb: TextBox::new("Warp to what?", None),
}
}
}
// When None, this is done.
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Option<EventLoopMode> {
match self {
WarpState::EnteringSearch(tb) => match tb.event(ctx.input) {
InputResult::Canceled => None,
InputResult::Done(to, _) => {
if let Some((id, pt)) = warp_point(to, &ui.primary) {
*self = WarpState::Warping(Warper::new(ctx, pt), id);
Some(EventLoopMode::Animation)
} else {
None
}
impl State for EnteringWarp {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
match self.tb.event(ctx.input) {
InputResult::Canceled => (Transition::Pop, EventLoopMode::InputOnly),
InputResult::Done(to, _) => {
if let Some((id, pt)) = warp_point(to, &ui.primary) {
(
Transition::Replace(Box::new(Warping {
warper: Warper::new(ctx, pt),
id,
})),
EventLoopMode::Animation,
)
} else {
(Transition::Pop, EventLoopMode::InputOnly)
}
InputResult::StillActive => Some(EventLoopMode::InputOnly),
},
WarpState::Warping(ref warper, id) => {
let result = warper.event(ctx);
if result.is_none() {
ui.primary.current_selection = Some(*id);
}
result
}
InputResult::StillActive => (Transition::Keep, EventLoopMode::InputOnly),
}
}
pub fn draw(&self, g: &mut GfxCtx) {
if let WarpState::EnteringSearch(tb) = self {
tb.draw(g);
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.tb.draw(g);
}
}
pub struct Warping {
pub warper: Warper,
pub id: ID,
}
impl State for Warping {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
if let Some(evmode) = self.warper.event(ctx) {
(Transition::Keep, evmode)
} else {
ui.primary.current_selection = Some(self.id);
(Transition::Pop, EventLoopMode::InputOnly)
}
}
fn draw(&self, _: &mut GfxCtx, _: &UI) {}
}
fn warp_point(line: String, primary: &PerMapUI) -> Option<(ID, Pt2D)> {

View File

@ -127,8 +127,8 @@ impl State for DebugMode {
self.menu.handle_event(ctx, Some(txt));
ctx.canvas.handle_event(ctx.input);
if let Some(evmode) = self.common.event(ctx, ui, &mut self.menu) {
return (Transition::Keep, evmode);
if let Some(pair) = self.common.event(ctx, ui, &mut self.menu) {
return pair;
}
if self.menu.action("quit") {

View File

@ -87,8 +87,8 @@ impl State for EditMode {
false,
);
}
if let Some(evmode) = self.common.event(ctx, ui, &mut self.menu) {
return (Transition::Keep, evmode);
if let Some(pair) = self.common.event(ctx, ui, &mut self.menu) {
return pair;
}
if self.menu.action("quit") {

View File

@ -106,8 +106,8 @@ impl State for SandboxMode {
false,
);
}
if let Some(evmode) = self.common.event(ctx, ui, &mut self.menu) {
return (Transition::Keep, evmode);
if let Some(pair) = self.common.event(ctx, ui, &mut self.menu) {
return pair;
}
if let Some(spawner) = spawner::AgentSpawner::new(ctx, ui, &mut self.menu) {