convert more to NewModalMenu

This commit is contained in:
Dustin Carlino 2019-05-01 14:02:13 -07:00
parent d10cbd9b7c
commit 36df2e7908
10 changed files with 171 additions and 134 deletions

View File

@ -17,7 +17,7 @@ use map_model::{
use std::collections::{BTreeSet, HashMap}; use std::collections::{BTreeSet, HashMap};
pub enum EditMode { pub enum EditMode {
ViewingDiffs(CommonState), ViewingDiffs(CommonState, NewModalMenu),
Saving(Wizard), Saving(Wizard),
Loading(Wizard), Loading(Wizard),
EditingStopSign(IntersectionID, NewModalMenu), EditingStopSign(IntersectionID, NewModalMenu),
@ -25,37 +25,39 @@ pub enum EditMode {
} }
impl EditMode { impl EditMode {
pub fn new() -> EditMode { pub fn new(ctx: &EventCtx) -> EditMode {
EditMode::ViewingDiffs(CommonState::new()) EditMode::ViewingDiffs(
CommonState::new(),
NewModalMenu::new(
"Map Edit Mode",
vec![
(Key::Escape, "quit"),
(Key::S, "save edits"),
(Key::L, "load different edits"),
],
ctx,
),
)
} }
pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode { pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode {
ctx.canvas.handle_event(ctx.input); match state.mode {
Mode::Edit(EditMode::ViewingDiffs(ref mut common, ref mut menu)) => {
// Common functionality
let mut txt = Text::prompt("Map Edit Mode"); let mut txt = Text::prompt("Map Edit Mode");
// TODO Display info/hints on more lines.
txt.add_line(state.ui.primary.map.get_edits().edits_name.clone()); txt.add_line(state.ui.primary.map.get_edits().edits_name.clone());
txt.add_line(state.ui.primary.map.get_edits().describe()); txt.add_line(state.ui.primary.map.get_edits().describe());
txt.add_line("Right-click a lane or intersection to start editing".to_string()); txt.add_line("Right-click a lane or intersection to start editing".to_string());
match state.mode { menu.update_prompt(txt, ctx);
Mode::Edit(EditMode::ViewingDiffs(_)) menu.handle_event(ctx);
| Mode::Edit(EditMode::Saving(_)) if menu.action("quit") {
| Mode::Edit(EditMode::Loading(_)) => {
// TODO Display info/hints on more lines.
ctx.input
.set_mode_with_new_prompt("Map Edit Mode", txt, ctx.canvas);
// TODO Clicking this works, but the key doesn't
if ctx.input.modal_action("quit") {
// TODO Warn about unsaved edits // TODO Warn about unsaved edits
state.mode = Mode::SplashScreen(Wizard::new(), None); state.mode = Mode::SplashScreen(Wizard::new(), None);
return EventLoopMode::InputOnly; return EventLoopMode::InputOnly;
} }
}
_ => {}
}
match state.mode { ctx.canvas.handle_event(ctx.input);
Mode::Edit(EditMode::ViewingDiffs(ref mut common)) => {
// TODO Reset when transitioning in/out of this state? Or maybe we just don't draw // TODO Reset when transitioning in/out of this state? Or maybe we just don't draw
// the effects of it. Or eventually, the Option<ID> itself will live in here // the effects of it. Or eventually, the Option<ID> itself will live in here
// directly. // directly.
@ -72,10 +74,10 @@ impl EditMode {
} }
// TODO Only if current edits are unsaved // TODO Only if current edits are unsaved
if ctx.input.modal_action("save edits") { if menu.action("save edits") {
state.mode = Mode::Edit(EditMode::Saving(Wizard::new())); state.mode = Mode::Edit(EditMode::Saving(Wizard::new()));
return EventLoopMode::InputOnly; return EventLoopMode::InputOnly;
} else if ctx.input.modal_action("load different edits") { } else if menu.action("load different edits") {
state.mode = Mode::Edit(EditMode::Loading(Wizard::new())); state.mode = Mode::Edit(EditMode::Loading(Wizard::new()));
return EventLoopMode::InputOnly; return EventLoopMode::InputOnly;
} }
@ -131,7 +133,7 @@ impl EditMode {
.is_some() .is_some()
|| wizard.aborted() || wizard.aborted()
{ {
state.mode = Mode::Edit(EditMode::new()); state.mode = Mode::Edit(EditMode::new(ctx));
} }
} }
Mode::Edit(EditMode::Loading(ref mut wizard)) => { Mode::Edit(EditMode::Loading(ref mut wizard)) => {
@ -141,13 +143,14 @@ impl EditMode {
"Load which map edits?", "Load which map edits?",
) { ) {
apply_map_edits(&mut state.ui, ctx, new_edits); apply_map_edits(&mut state.ui, ctx, new_edits);
state.mode = Mode::Edit(EditMode::new()); state.mode = Mode::Edit(EditMode::new(ctx));
} else if wizard.aborted() { } else if wizard.aborted() {
state.mode = Mode::Edit(EditMode::new()); state.mode = Mode::Edit(EditMode::new(ctx));
} }
} }
Mode::Edit(EditMode::EditingStopSign(i, ref mut menu)) => { Mode::Edit(EditMode::EditingStopSign(i, ref mut menu)) => {
menu.handle_event(ctx); menu.handle_event(ctx);
ctx.canvas.handle_event(ctx.input);
state.ui.primary.current_selection = state.ui.handle_mouseover( state.ui.primary.current_selection = state.ui.handle_mouseover(
ctx, ctx,
@ -181,7 +184,7 @@ impl EditMode {
apply_map_edits(&mut state.ui, ctx, new_edits); apply_map_edits(&mut state.ui, ctx, new_edits);
} }
} else if menu.action("quit") { } else if menu.action("quit") {
state.mode = Mode::Edit(EditMode::new()); state.mode = Mode::Edit(EditMode::new(ctx));
} else if menu.action("reset to default") { } else if menu.action("reset to default") {
let mut new_edits = state.ui.primary.map.get_edits().clone(); let mut new_edits = state.ui.primary.map.get_edits().clone();
new_edits.stop_sign_overrides.remove(&i); new_edits.stop_sign_overrides.remove(&i);
@ -190,7 +193,7 @@ impl EditMode {
} }
Mode::Edit(EditMode::EditingTrafficSignal(ref mut editor)) => { Mode::Edit(EditMode::EditingTrafficSignal(ref mut editor)) => {
if editor.event(ctx, &mut state.ui) { if editor.event(ctx, &mut state.ui) {
state.mode = Mode::Edit(EditMode::new()); state.mode = Mode::Edit(EditMode::new(ctx));
} }
} }
_ => unreachable!(), _ => unreachable!(),
@ -201,7 +204,7 @@ impl EditMode {
pub fn draw(state: &GameState, g: &mut GfxCtx) { pub fn draw(state: &GameState, g: &mut GfxCtx) {
match state.mode { match state.mode {
Mode::Edit(EditMode::ViewingDiffs(ref common)) => { Mode::Edit(EditMode::ViewingDiffs(ref common, ref menu)) => {
state.ui.draw( state.ui.draw(
g, g,
common.draw_options(&state.ui), common.draw_options(&state.ui),
@ -209,6 +212,7 @@ impl EditMode {
&ShowEverything::new(), &ShowEverything::new(),
); );
common.draw(g, &state.ui); common.draw(g, &state.ui);
menu.draw(g);
// TODO Similar to drawing areas with traffic or not -- would be convenient to just // TODO Similar to drawing areas with traffic or not -- would be convenient to just
// supply a set of things to highlight and have something else take care of drawing // supply a set of things to highlight and have something else take care of drawing

View File

@ -56,6 +56,7 @@ impl TrafficSignalEditor {
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> bool { pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> bool {
self.menu.handle_event(ctx); self.menu.handle_event(ctx);
self.diagram_top_left = self.menu.get_bottom_left(ctx); self.diagram_top_left = self.menu.get_bottom_left(ctx);
ctx.canvas.handle_event(ctx.input);
ui.primary.current_selection = ui.handle_mouseover( ui.primary.current_selection = ui.handle_mouseover(
ctx, ctx,

View File

@ -40,7 +40,7 @@ impl GameState {
let splash = !flags.no_splash; let splash = !flags.no_splash;
let mut rng = flags.sim_flags.make_rng(); let mut rng = flags.sim_flags.make_rng();
let mut game = GameState { let mut game = GameState {
mode: Mode::Sandbox(SandboxMode::new()), mode: Mode::Sandbox(SandboxMode::new(canvas)),
ui: UI::new(flags, prerender, canvas), ui: UI::new(flags, prerender, canvas),
}; };
if splash { if splash {
@ -71,45 +71,6 @@ impl GameState {
impl GUI for GameState { impl GUI for GameState {
fn modal_menus(&self) -> Vec<ModalMenu> { fn modal_menus(&self) -> Vec<ModalMenu> {
vec![ vec![
ModalMenu::new(
"Map Edit Mode",
vec![
(Key::Escape, "quit"),
(Key::S, "save edits"),
(Key::L, "load different edits"),
],
),
ModalMenu::new(
"Sandbox Mode",
vec![
(Key::Escape, "quit"),
(Key::LeftBracket, "slow down sim"),
(Key::RightBracket, "speed up sim"),
(Key::O, "save sim state"),
(Key::Y, "load previous sim state"),
(Key::U, "load next sim state"),
(Key::Space, "run/pause sim"),
(Key::M, "run one step of sim"),
(Key::X, "reset sim"),
(Key::S, "seed the sim with agents"),
// TODO Strange to always have this. Really it's a case of stacked modal?
(Key::F, "stop following agent"),
(Key::R, "stop showing agent's route"),
// TODO This should probably be a debug thing instead
(Key::L, "show/hide route for all agents"),
(Key::A, "show/hide active traffic"),
(Key::T, "start time traveling"),
],
),
ModalMenu::new("Agent Spawner", vec![(Key::Escape, "quit")]),
ModalMenu::new(
"Time Traveler",
vec![
(Key::Escape, "quit"),
(Key::Comma, "rewind"),
(Key::Dot, "forwards"),
],
),
ModalMenu::new( ModalMenu::new(
"Debug Mode", "Debug Mode",
vec![ vec![
@ -345,7 +306,7 @@ fn splash_screen(
)? )?
.as_str() .as_str()
{ {
x if x == sandbox => break Some(Mode::Sandbox(SandboxMode::new())), x if x == sandbox => break Some(Mode::Sandbox(SandboxMode::new(ctx.canvas))),
x if x == load_map => { x if x == load_map => {
let current_map = ui.primary.map.get_name().to_string(); let current_map = ui.primary.map.get_name().to_string();
if let Some((name, _)) = wizard.choose_something_no_keys::<String>( if let Some((name, _)) = wizard.choose_something_no_keys::<String>(
@ -361,14 +322,14 @@ fn splash_screen(
let mut flags = ui.primary.current_flags.clone(); let mut flags = ui.primary.current_flags.clone();
flags.sim_flags.load = PathBuf::from(format!("../data/maps/{}.abst", name)); flags.sim_flags.load = PathBuf::from(format!("../data/maps/{}.abst", name));
*ui = UI::new(flags, ctx.prerender, ctx.canvas); *ui = UI::new(flags, ctx.prerender, ctx.canvas);
break Some(Mode::Sandbox(SandboxMode::new())); break Some(Mode::Sandbox(SandboxMode::new(ctx.canvas)));
} else if wizard.aborted() { } else if wizard.aborted() {
break Some(Mode::SplashScreen(Wizard::new(), maybe_screensaver.take())); break Some(Mode::SplashScreen(Wizard::new(), maybe_screensaver.take()));
} else { } else {
break None; break None;
} }
} }
x if x == edit => break Some(Mode::Edit(EditMode::new())), x if x == edit => break Some(Mode::Edit(EditMode::new(ctx))),
x if x == tutorial => { x if x == tutorial => {
break Some(Mode::Tutorial(TutorialMode::Part1( break Some(Mode::Tutorial(TutorialMode::Part1(
ctx.canvas.center_to_map_pt(), ctx.canvas.center_to_map_pt(),

View File

@ -48,7 +48,7 @@ impl ScenarioEditor {
&mut ui.primary.current_flags.sim_flags.make_rng(), &mut ui.primary.current_flags.sim_flags.make_rng(),
&mut Timer::new("instantiate scenario"), &mut Timer::new("instantiate scenario"),
); );
return Some(Mode::Sandbox(SandboxMode::new())); return Some(Mode::Sandbox(SandboxMode::new(ctx.canvas)));
} else if ctx.input.modal_action("visualize") { } else if ctx.input.modal_action("visualize") {
let neighborhoods = Neighborhood::load_all( let neighborhoods = Neighborhood::load_all(
ui.primary.map.get_name(), ui.primary.map.get_name(),

View File

@ -8,7 +8,7 @@ use crate::game::{GameState, Mode};
use crate::render::DrawOptions; use crate::render::DrawOptions;
use crate::ui::ShowEverything; use crate::ui::ShowEverything;
use abstutil::elapsed_seconds; use abstutil::elapsed_seconds;
use ezgui::{EventCtx, EventLoopMode, GfxCtx, Key, Text, Wizard}; use ezgui::{Canvas, EventCtx, EventLoopMode, GfxCtx, Key, NewModalMenu, Text, Wizard};
use geom::Duration; use geom::Duration;
use sim::{Benchmark, Sim, TripID}; use sim::{Benchmark, Sim, TripID};
use std::time::Instant; use std::time::Instant;
@ -24,6 +24,7 @@ pub struct SandboxMode {
state: State, state: State,
// TODO Not while Spawning or TimeTraveling... // TODO Not while Spawning or TimeTraveling...
common: CommonState, common: CommonState,
menu: NewModalMenu,
} }
enum State { enum State {
@ -38,33 +39,44 @@ enum State {
} }
impl SandboxMode { impl SandboxMode {
pub fn new() -> SandboxMode { pub fn new(canvas: &Canvas) -> SandboxMode {
SandboxMode { SandboxMode {
desired_speed: 1.0, desired_speed: 1.0,
state: State::Paused, state: State::Paused,
following: None, following: None,
route_viewer: route_viewer::RouteViewer::Inactive, route_viewer: route_viewer::RouteViewer::Inactive,
show_activity: show_activity::ShowActivity::Inactive, show_activity: show_activity::ShowActivity::Inactive,
time_travel: time_travel::TimeTravel::new(), time_travel: time_travel::TimeTravel::new(canvas),
common: CommonState::new(), common: CommonState::new(),
menu: NewModalMenu::hacky_new(
"Sandbox Mode",
vec![
(Key::Escape, "quit"),
(Key::LeftBracket, "slow down sim"),
(Key::RightBracket, "speed up sim"),
(Key::O, "save sim state"),
(Key::Y, "load previous sim state"),
(Key::U, "load next sim state"),
(Key::Space, "run/pause sim"),
(Key::M, "run one step of sim"),
(Key::X, "reset sim"),
(Key::S, "seed the sim with agents"),
// TODO Strange to always have this. Really it's a case of stacked modal?
(Key::F, "stop following agent"),
(Key::R, "stop showing agent's route"),
// TODO This should probably be a debug thing instead
(Key::L, "show/hide route for all agents"),
(Key::A, "show/hide active traffic"),
(Key::T, "start time traveling"),
],
canvas,
),
} }
} }
pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode { pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode {
match state.mode { match state.mode {
Mode::Sandbox(ref mut mode) => { Mode::Sandbox(ref mut mode) => {
ctx.canvas.handle_event(ctx.input);
state.ui.primary.current_selection = state.ui.handle_mouseover(
ctx,
None,
&state.ui.primary.sim,
&ShowEverything::new(),
false,
);
if let Some(evmode) = mode.common.event(ctx, &state.ui) {
return evmode;
}
if let State::Spawning(ref mut spawner) = mode.state { if let State::Spawning(ref mut spawner) = mode.state {
if spawner.event(ctx, &mut state.ui) { if spawner.event(ctx, &mut state.ui) {
mode.state = State::Paused; mode.state = State::Paused;
@ -110,10 +122,24 @@ impl SandboxMode {
txt.add_line("Showing active traffic".to_string()); txt.add_line("Showing active traffic".to_string());
} }
} }
ctx.input mode.menu.update_prompt(txt, ctx);
.set_mode_with_new_prompt("Sandbox Mode", txt, ctx.canvas); mode.menu.handle_event(ctx);
if let Some(spawner) = spawner::AgentSpawner::new(ctx, &mut state.ui) { ctx.canvas.handle_event(ctx.input);
state.ui.primary.current_selection = state.ui.handle_mouseover(
ctx,
None,
&state.ui.primary.sim,
&ShowEverything::new(),
false,
);
if let Some(evmode) = mode.common.event(ctx, &state.ui) {
return evmode;
}
if let Some(spawner) =
spawner::AgentSpawner::new(ctx, &mut state.ui, &mut mode.menu)
{
mode.state = State::Spawning(spawner); mode.state = State::Spawning(spawner);
return EventLoopMode::InputOnly; return EventLoopMode::InputOnly;
} }
@ -148,13 +174,13 @@ impl SandboxMode {
// get_canonical_point_for_trip // get_canonical_point_for_trip
println!("{} is gone... temporarily or not?", trip); println!("{} is gone... temporarily or not?", trip);
} }
if ctx.input.modal_action("stop following agent") { if mode.menu.action("stop following agent") {
mode.following = None; mode.following = None;
} }
} }
mode.route_viewer.event(ctx, &mut state.ui); mode.route_viewer.event(ctx, &mut state.ui, &mut mode.menu);
mode.show_activity.event(ctx, &mut state.ui); mode.show_activity.event(ctx, &mut state.ui, &mut mode.menu);
if ctx.input.modal_action("start time traveling") { if mode.menu.action("start time traveling") {
mode.state = State::TimeTraveling; mode.state = State::TimeTraveling;
mode.time_travel.start(state.ui.primary.sim.time()); mode.time_travel.start(state.ui.primary.sim.time());
// Do this again, in case recording was previously disabled. // Do this again, in case recording was previously disabled.
@ -162,7 +188,7 @@ impl SandboxMode {
return EventLoopMode::InputOnly; return EventLoopMode::InputOnly;
} }
if ctx.input.modal_action("quit") { if mode.menu.action("quit") {
// TODO This shouldn't be necessary when we plumb state around instead of // TODO This shouldn't be necessary when we plumb state around instead of
// sharing it in the old structure. // sharing it in the old structure.
state.ui.primary.sim = Sim::new( state.ui.primary.sim = Sim::new(
@ -174,14 +200,14 @@ impl SandboxMode {
return EventLoopMode::InputOnly; return EventLoopMode::InputOnly;
} }
if ctx.input.modal_action("slow down sim") { if mode.menu.action("slow down sim") {
mode.desired_speed -= ADJUST_SPEED; mode.desired_speed -= ADJUST_SPEED;
mode.desired_speed = mode.desired_speed.max(0.0); mode.desired_speed = mode.desired_speed.max(0.0);
} }
if ctx.input.modal_action("speed up sim") { if mode.menu.action("speed up sim") {
mode.desired_speed += ADJUST_SPEED; mode.desired_speed += ADJUST_SPEED;
} }
if !state.ui.primary.sim.is_empty() && ctx.input.modal_action("reset sim") { if !state.ui.primary.sim.is_empty() && mode.menu.action("reset sim") {
// TODO savestate_every gets lost // TODO savestate_every gets lost
state.ui.primary.sim = Sim::new( state.ui.primary.sim = Sim::new(
&state.ui.primary.map, &state.ui.primary.map,
@ -193,10 +219,10 @@ impl SandboxMode {
match mode.state { match mode.state {
State::Paused => { State::Paused => {
if ctx.input.modal_action("save sim state") { if mode.menu.action("save sim state") {
state.ui.primary.sim.save(); state.ui.primary.sim.save();
} }
if ctx.input.modal_action("load previous sim state") { if mode.menu.action("load previous sim state") {
let prev_state = state let prev_state = state
.ui .ui
.primary .primary
@ -215,7 +241,7 @@ impl SandboxMode {
} }
} }
} }
if ctx.input.modal_action("load next sim state") { if mode.menu.action("load next sim state") {
let next_state = state let next_state = state
.ui .ui
.primary .primary
@ -233,13 +259,13 @@ impl SandboxMode {
} }
} }
if ctx.input.modal_action("run/pause sim") { if mode.menu.action("run/pause sim") {
mode.state = State::Running { mode.state = State::Running {
last_step: Instant::now(), last_step: Instant::now(),
benchmark: state.ui.primary.sim.start_benchmark(), benchmark: state.ui.primary.sim.start_benchmark(),
speed: "...".to_string(), speed: "...".to_string(),
}; };
} else if ctx.input.modal_action("run one step of sim") { } else if mode.menu.action("run one step of sim") {
state.ui.primary.sim.step(&state.ui.primary.map); state.ui.primary.sim.step(&state.ui.primary.map);
//*ctx.recalculate_current_selection = true; //*ctx.recalculate_current_selection = true;
} }
@ -250,7 +276,7 @@ impl SandboxMode {
ref mut benchmark, ref mut benchmark,
ref mut speed, ref mut speed,
} => { } => {
if ctx.input.modal_action("run/pause sim") { if mode.menu.action("run/pause sim") {
mode.state = State::Paused; mode.state = State::Paused;
} else if ctx.input.nonblocking_is_update_event() { } else if ctx.input.nonblocking_is_update_event() {
// TODO https://gafferongames.com/post/fix_your_timestep/ // TODO https://gafferongames.com/post/fix_your_timestep/
@ -293,6 +319,7 @@ impl SandboxMode {
&mode.time_travel, &mode.time_travel,
&ShowEverything::new(), &ShowEverything::new(),
); );
mode.time_travel.draw(g);
} }
_ => { _ => {
state.ui.draw( state.ui.draw(
@ -302,6 +329,7 @@ impl SandboxMode {
&ShowEverything::new(), &ShowEverything::new(),
); );
mode.common.draw(g, &state.ui); mode.common.draw(g, &state.ui);
mode.menu.draw(g);
mode.route_viewer.draw(g, &state.ui); mode.route_viewer.draw(g, &state.ui);
mode.show_activity.draw(g, &state.ui); mode.show_activity.draw(g, &state.ui);
} }

View File

@ -1,6 +1,6 @@
use crate::helpers::ID; use crate::helpers::ID;
use crate::ui::UI; use crate::ui::UI;
use ezgui::{Color, EventCtx, GfxCtx, Key}; use ezgui::{Color, EventCtx, GfxCtx, Key, NewModalMenu};
use geom::{Duration, PolyLine}; use geom::{Duration, PolyLine};
use map_model::LANE_THICKNESS; use map_model::LANE_THICKNESS;
use sim::{AgentID, TripID}; use sim::{AgentID, TripID};
@ -13,14 +13,14 @@ pub enum RouteViewer {
} }
impl RouteViewer { impl RouteViewer {
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) { pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI, menu: &mut NewModalMenu) {
match self { match self {
RouteViewer::Inactive => { RouteViewer::Inactive => {
if let Some(agent) = ui.primary.current_selection.and_then(|id| id.agent_id()) { if let Some(agent) = ui.primary.current_selection.and_then(|id| id.agent_id()) {
if let Some(trace) = ui.primary.sim.trace_route(agent, &ui.primary.map, None) { if let Some(trace) = ui.primary.sim.trace_route(agent, &ui.primary.map, None) {
*self = RouteViewer::Hovering(ui.primary.sim.time(), agent, trace); *self = RouteViewer::Hovering(ui.primary.sim.time(), agent, trace);
} }
} else if ctx.input.modal_action("show/hide route for all agents") { } else if menu.action("show/hide route for all agents") {
*self = debug_all_routes(ui); *self = debug_all_routes(ui);
} }
} }
@ -56,14 +56,14 @@ impl RouteViewer {
} }
RouteViewer::Active(time, trip, _) => { RouteViewer::Active(time, trip, _) => {
// TODO Using the modal menu from parent is weird... // TODO Using the modal menu from parent is weird...
if ctx.input.modal_action("stop showing agent's route") { if menu.action("stop showing agent's route") {
*self = RouteViewer::Inactive; *self = RouteViewer::Inactive;
} else if *time != ui.primary.sim.time() { } else if *time != ui.primary.sim.time() {
*self = show_route(*trip, ui); *self = show_route(*trip, ui);
} }
} }
RouteViewer::DebugAllRoutes(time, _) => { RouteViewer::DebugAllRoutes(time, _) => {
if ctx.input.modal_action("show/hide route for all agents") { if menu.action("show/hide route for all agents") {
*self = RouteViewer::Inactive; *self = RouteViewer::Inactive;
} else if *time != ui.primary.sim.time() { } else if *time != ui.primary.sim.time() {
*self = debug_all_routes(ui); *self = debug_all_routes(ui);

View File

@ -1,6 +1,6 @@
use crate::render::MIN_ZOOM_FOR_DETAIL; use crate::render::MIN_ZOOM_FOR_DETAIL;
use crate::ui::UI; use crate::ui::UI;
use ezgui::{Color, EventCtx, GfxCtx}; use ezgui::{Color, EventCtx, GfxCtx, NewModalMenu};
use geom::{Bounds, Duration, Polygon, Pt2D}; use geom::{Bounds, Duration, Polygon, Pt2D};
use map_model::{RoadID, Traversable}; use map_model::{RoadID, Traversable};
use std::collections::HashMap; use std::collections::HashMap;
@ -12,18 +12,18 @@ pub enum ShowActivity {
} }
impl ShowActivity { impl ShowActivity {
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) { pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI, menu: &mut NewModalMenu) {
let zoomed = ctx.canvas.cam_zoom >= MIN_ZOOM_FOR_DETAIL; let zoomed = ctx.canvas.cam_zoom >= MIN_ZOOM_FOR_DETAIL;
// If we survive past this, recompute current state. // If we survive past this, recompute current state.
match self { match self {
ShowActivity::Inactive => { ShowActivity::Inactive => {
if !ctx.input.modal_action("show/hide active traffic") { if !menu.action("show/hide active traffic") {
return; return;
} }
} }
ShowActivity::Zoomed(time, ref heatmap) => { ShowActivity::Zoomed(time, ref heatmap) => {
if ctx.input.modal_action("show/hide active traffic") { if menu.action("show/hide active traffic") {
*self = ShowActivity::Inactive; *self = ShowActivity::Inactive;
return; return;
} }
@ -35,7 +35,7 @@ impl ShowActivity {
} }
} }
ShowActivity::Unzoomed(time, _) => { ShowActivity::Unzoomed(time, _) => {
if ctx.input.modal_action("show/hide active traffic") { if menu.action("show/hide active traffic") {
*self = ShowActivity::Inactive; *self = ShowActivity::Inactive;
return; return;
} }

View File

@ -2,7 +2,7 @@ use crate::helpers::ID;
use crate::render::DrawOptions; use crate::render::DrawOptions;
use crate::ui::{ShowEverything, UI}; use crate::ui::{ShowEverything, UI};
use abstutil::Timer; use abstutil::Timer;
use ezgui::{EventCtx, GfxCtx, Key}; use ezgui::{EventCtx, GfxCtx, Key, NewModalMenu};
use geom::PolyLine; use geom::PolyLine;
use map_model::{ use map_model::{
BuildingID, IntersectionID, IntersectionType, LaneType, PathRequest, Position, LANE_THICKNESS, BuildingID, IntersectionID, IntersectionType, LaneType, PathRequest, Position, LANE_THICKNESS,
@ -11,6 +11,7 @@ use rand::seq::SliceRandom;
use sim::{DrivingGoal, Scenario, SidewalkSpot, TripSpec}; use sim::{DrivingGoal, Scenario, SidewalkSpot, TripSpec};
pub struct AgentSpawner { pub struct AgentSpawner {
menu: NewModalMenu,
from: Source, from: Source,
maybe_goal: Option<(Goal, Option<PolyLine>)>, maybe_goal: Option<(Goal, Option<PolyLine>)>,
} }
@ -28,7 +29,12 @@ enum Goal {
} }
impl AgentSpawner { impl AgentSpawner {
pub fn new(ctx: &mut EventCtx, ui: &mut UI) -> Option<AgentSpawner> { pub fn new(
ctx: &mut EventCtx,
ui: &mut UI,
sandbox_menu: &mut NewModalMenu,
) -> Option<AgentSpawner> {
let menu = NewModalMenu::new("Agent Spawner", vec![(Key::Escape, "quit")], ctx);
let map = &ui.primary.map; let map = &ui.primary.map;
match ui.primary.current_selection { match ui.primary.current_selection {
Some(ID::Building(id)) => { Some(ID::Building(id)) => {
@ -37,6 +43,7 @@ impl AgentSpawner {
.contextual_action(Key::F3, "spawn a pedestrian starting here") .contextual_action(Key::F3, "spawn a pedestrian starting here")
{ {
return Some(AgentSpawner { return Some(AgentSpawner {
menu,
from: Source::Walking(id), from: Source::Walking(id),
maybe_goal: None, maybe_goal: None,
}); });
@ -50,6 +57,7 @@ impl AgentSpawner {
.contextual_action(Key::F4, "spawn a car starting here") .contextual_action(Key::F4, "spawn a car starting here")
{ {
return Some(AgentSpawner { return Some(AgentSpawner {
menu,
from: Source::Driving( from: Source::Driving(
b.front_path.sidewalk.equiv_pos(driving_lane, map), b.front_path.sidewalk.equiv_pos(driving_lane, map),
), ),
@ -65,6 +73,7 @@ impl AgentSpawner {
.contextual_action(Key::F3, "spawn an agent starting here") .contextual_action(Key::F3, "spawn an agent starting here")
{ {
return Some(AgentSpawner { return Some(AgentSpawner {
menu,
from: Source::Driving(Position::new(id, map.get_l(id).length() / 2.0)), from: Source::Driving(Position::new(id, map.get_l(id).length() / 2.0)),
maybe_goal: None, maybe_goal: None,
}); });
@ -80,8 +89,7 @@ impl AgentSpawner {
} }
None => { None => {
if ui.primary.sim.is_empty() { if ui.primary.sim.is_empty() {
// TODO Weird. This mode belongs to the parent SpawnMode, not AgentSpawner. if sandbox_menu.action("seed the sim with agents") {
if ctx.input.modal_action("seed the sim with agents") {
Scenario::scaled_run(map, ui.primary.current_flags.num_agents).instantiate( Scenario::scaled_run(map, ui.primary.current_flags.num_agents).instantiate(
&mut ui.primary.sim, &mut ui.primary.sim,
map, map,
@ -100,11 +108,15 @@ impl AgentSpawner {
// Returns true if the spawner editor is done and we should go back to main sandbox mode. // Returns true if the spawner editor is done and we should go back to main sandbox mode.
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> bool { pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> bool {
// TODO Instructions to select target building/lane // TODO Instructions to select target building/lane
ctx.input.set_mode("Agent Spawner", ctx.canvas); self.menu.handle_event(ctx);
if ctx.input.modal_action("quit") { if self.menu.action("quit") {
return true; return true;
} }
ctx.canvas.handle_event(ctx.input);
ui.primary.current_selection =
ui.handle_mouseover(ctx, None, &ui.primary.sim, &ShowEverything::new(), false);
let map = &ui.primary.map; let map = &ui.primary.map;
let new_goal = match ui.primary.current_selection { let new_goal = match ui.primary.current_selection {
@ -236,6 +248,8 @@ impl AgentSpawner {
opts.override_colors.insert(src, ui.cs.get("selected")); opts.override_colors.insert(src, ui.cs.get("selected"));
ui.draw(g, opts, &ui.primary.sim, &ShowEverything::new()); ui.draw(g, opts, &ui.primary.sim, &ShowEverything::new());
self.menu.draw(g);
if let Some((_, Some(ref trace))) = self.maybe_goal { if let Some((_, Some(ref trace))) = self.maybe_goal {
g.draw_polygon(ui.cs.get("route"), &trace.make_polygons(LANE_THICKNESS)); g.draw_polygon(ui.cs.get("route"), &trace.make_polygons(LANE_THICKNESS));
} }

View File

@ -1,12 +1,13 @@
use crate::ui::UI; use crate::ui::UI;
use abstutil::MultiMap; use abstutil::MultiMap;
use ezgui::EventCtx; use ezgui::{Canvas, EventCtx, GfxCtx, Key, NewModalMenu, Text};
use geom::Duration; use geom::Duration;
use map_model::{Map, Traversable}; use map_model::{Map, Traversable};
use sim::{CarID, DrawCarInput, DrawPedestrianInput, GetDrawAgents, PedestrianID, TIMESTEP}; use sim::{CarID, DrawCarInput, DrawPedestrianInput, GetDrawAgents, PedestrianID, TIMESTEP};
use std::collections::BTreeMap; use std::collections::BTreeMap;
pub struct TimeTravel { pub struct TimeTravel {
menu: NewModalMenu,
// TODO Could be more efficient // TODO Could be more efficient
state_per_time: BTreeMap<Duration, StateAtTime>, state_per_time: BTreeMap<Duration, StateAtTime>,
pub current_time: Option<Duration>, pub current_time: Option<Duration>,
@ -23,7 +24,7 @@ struct StateAtTime {
} }
impl TimeTravel { impl TimeTravel {
pub fn new() -> TimeTravel { pub fn new(canvas: &Canvas) -> TimeTravel {
TimeTravel { TimeTravel {
state_per_time: BTreeMap::new(), state_per_time: BTreeMap::new(),
current_time: None, current_time: None,
@ -31,6 +32,15 @@ impl TimeTravel {
first_time: Duration::ZERO, first_time: Duration::ZERO,
last_time: Duration::ZERO, last_time: Duration::ZERO,
should_record: false, should_record: false,
menu: NewModalMenu::hacky_new(
"Time Traveler",
vec![
(Key::Escape, "quit"),
(Key::Comma, "rewind"),
(Key::Dot, "forwards"),
],
canvas,
),
} }
} }
@ -81,22 +91,27 @@ impl TimeTravel {
// Returns true if done. // Returns true if done.
pub fn event(&mut self, ctx: &mut EventCtx) -> bool { pub fn event(&mut self, ctx: &mut EventCtx) -> bool {
let time = self.current_time.unwrap(); let time = self.current_time.unwrap();
ctx.input.set_mode_with_prompt( self.menu.handle_event(ctx);
"Time Traveler", self.menu
format!("Time Traveler at {}", time), .update_prompt(Text::prompt(&format!("Time Traveler at {}", time)), ctx);
&ctx.canvas,
); ctx.canvas.handle_event(ctx.input);
if time > self.first_time && ctx.input.modal_action("rewind") {
if time > self.first_time && self.menu.action("rewind") {
self.current_time = Some(time - TIMESTEP); self.current_time = Some(time - TIMESTEP);
} else if time < self.last_time && ctx.input.modal_action("forwards") { } else if time < self.last_time && self.menu.action("forwards") {
self.current_time = Some(time + TIMESTEP); self.current_time = Some(time + TIMESTEP);
} else if ctx.input.modal_action("quit") { } else if self.menu.action("quit") {
self.current_time = None; self.current_time = None;
return true; return true;
} }
false false
} }
pub fn draw(&self, g: &mut GfxCtx) {
self.menu.draw(g);
}
fn get_current_state(&self) -> &StateAtTime { fn get_current_state(&self) -> &StateAtTime {
&self.state_per_time[&self.current_time.unwrap()] &self.state_per_time[&self.current_time.unwrap()]
} }

View File

@ -1,5 +1,5 @@
use crate::widgets::{Menu, Position}; use crate::widgets::{Menu, Position};
use crate::{EventCtx, GfxCtx, InputResult, Key, ScreenPt, Text}; use crate::{Canvas, EventCtx, GfxCtx, InputResult, Key, ScreenPt, Text};
pub struct NewModalMenu { pub struct NewModalMenu {
menu: Menu<Key>, menu: Menu<Key>,
@ -8,6 +8,15 @@ pub struct NewModalMenu {
impl NewModalMenu { impl NewModalMenu {
pub fn new(prompt_line: &str, choices: Vec<(Key, &str)>, ctx: &EventCtx) -> NewModalMenu { pub fn new(prompt_line: &str, choices: Vec<(Key, &str)>, ctx: &EventCtx) -> NewModalMenu {
NewModalMenu::hacky_new(prompt_line, choices, ctx.canvas)
}
// TODO Pass EventCtx when constructing the GUI?
pub fn hacky_new(
prompt_line: &str,
choices: Vec<(Key, &str)>,
canvas: &Canvas,
) -> NewModalMenu {
let mut menu = Menu::new( let mut menu = Menu::new(
Some(Text::prompt(prompt_line)), Some(Text::prompt(prompt_line)),
choices choices
@ -17,7 +26,7 @@ impl NewModalMenu {
false, false,
true, true,
Position::TopRightOfScreen, Position::TopRightOfScreen,
ctx.canvas, canvas,
); );
menu.mark_all_inactive(); menu.mark_all_inactive();
NewModalMenu { NewModalMenu {
@ -62,6 +71,11 @@ impl NewModalMenu {
false false
} }
pub fn update_prompt(&mut self, txt: Text, _: &EventCtx) {
// TODO Do need to recalculate geometry
self.menu.change_prompt(txt);
}
pub fn draw(&self, g: &mut GfxCtx) { pub fn draw(&self, g: &mut GfxCtx) {
self.menu.draw(g); self.menu.draw(g);
} }