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};
pub enum EditMode {
ViewingDiffs(CommonState),
ViewingDiffs(CommonState, NewModalMenu),
Saving(Wizard),
Loading(Wizard),
EditingStopSign(IntersectionID, NewModalMenu),
@ -25,37 +25,39 @@ pub enum EditMode {
}
impl EditMode {
pub fn new() -> EditMode {
EditMode::ViewingDiffs(CommonState::new())
pub fn new(ctx: &EventCtx) -> EditMode {
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 {
ctx.canvas.handle_event(ctx.input);
// Common functionality
let mut txt = Text::prompt("Map Edit Mode");
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("Right-click a lane or intersection to start editing".to_string());
match state.mode {
Mode::Edit(EditMode::ViewingDiffs(_))
| Mode::Edit(EditMode::Saving(_))
| Mode::Edit(EditMode::Loading(_)) => {
Mode::Edit(EditMode::ViewingDiffs(ref mut common, ref mut menu)) => {
let mut txt = Text::prompt("Map Edit Mode");
// 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") {
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("Right-click a lane or intersection to start editing".to_string());
menu.update_prompt(txt, ctx);
menu.handle_event(ctx);
if menu.action("quit") {
// TODO Warn about unsaved edits
state.mode = Mode::SplashScreen(Wizard::new(), None);
return EventLoopMode::InputOnly;
}
}
_ => {}
}
match state.mode {
Mode::Edit(EditMode::ViewingDiffs(ref mut common)) => {
ctx.canvas.handle_event(ctx.input);
// 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
// directly.
@ -72,10 +74,10 @@ impl EditMode {
}
// 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()));
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()));
return EventLoopMode::InputOnly;
}
@ -131,7 +133,7 @@ impl EditMode {
.is_some()
|| wizard.aborted()
{
state.mode = Mode::Edit(EditMode::new());
state.mode = Mode::Edit(EditMode::new(ctx));
}
}
Mode::Edit(EditMode::Loading(ref mut wizard)) => {
@ -141,13 +143,14 @@ impl EditMode {
"Load which map 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() {
state.mode = Mode::Edit(EditMode::new());
state.mode = Mode::Edit(EditMode::new(ctx));
}
}
Mode::Edit(EditMode::EditingStopSign(i, ref mut menu)) => {
menu.handle_event(ctx);
ctx.canvas.handle_event(ctx.input);
state.ui.primary.current_selection = state.ui.handle_mouseover(
ctx,
@ -181,7 +184,7 @@ impl EditMode {
apply_map_edits(&mut state.ui, ctx, new_edits);
}
} 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") {
let mut new_edits = state.ui.primary.map.get_edits().clone();
new_edits.stop_sign_overrides.remove(&i);
@ -190,7 +193,7 @@ impl EditMode {
}
Mode::Edit(EditMode::EditingTrafficSignal(ref mut editor)) => {
if editor.event(ctx, &mut state.ui) {
state.mode = Mode::Edit(EditMode::new());
state.mode = Mode::Edit(EditMode::new(ctx));
}
}
_ => unreachable!(),
@ -201,7 +204,7 @@ impl EditMode {
pub fn draw(state: &GameState, g: &mut GfxCtx) {
match state.mode {
Mode::Edit(EditMode::ViewingDiffs(ref common)) => {
Mode::Edit(EditMode::ViewingDiffs(ref common, ref menu)) => {
state.ui.draw(
g,
common.draw_options(&state.ui),
@ -209,6 +212,7 @@ impl EditMode {
&ShowEverything::new(),
);
common.draw(g, &state.ui);
menu.draw(g);
// 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

View File

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

View File

@ -40,7 +40,7 @@ impl GameState {
let splash = !flags.no_splash;
let mut rng = flags.sim_flags.make_rng();
let mut game = GameState {
mode: Mode::Sandbox(SandboxMode::new()),
mode: Mode::Sandbox(SandboxMode::new(canvas)),
ui: UI::new(flags, prerender, canvas),
};
if splash {
@ -71,45 +71,6 @@ impl GameState {
impl GUI for GameState {
fn modal_menus(&self) -> Vec<ModalMenu> {
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(
"Debug Mode",
vec![
@ -345,7 +306,7 @@ fn splash_screen(
)?
.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 => {
let current_map = ui.primary.map.get_name().to_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();
flags.sim_flags.load = PathBuf::from(format!("../data/maps/{}.abst", name));
*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() {
break Some(Mode::SplashScreen(Wizard::new(), maybe_screensaver.take()));
} else {
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 => {
break Some(Mode::Tutorial(TutorialMode::Part1(
ctx.canvas.center_to_map_pt(),

View File

@ -48,7 +48,7 @@ impl ScenarioEditor {
&mut ui.primary.current_flags.sim_flags.make_rng(),
&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") {
let neighborhoods = Neighborhood::load_all(
ui.primary.map.get_name(),

View File

@ -8,7 +8,7 @@ use crate::game::{GameState, Mode};
use crate::render::DrawOptions;
use crate::ui::ShowEverything;
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 sim::{Benchmark, Sim, TripID};
use std::time::Instant;
@ -24,6 +24,7 @@ pub struct SandboxMode {
state: State,
// TODO Not while Spawning or TimeTraveling...
common: CommonState,
menu: NewModalMenu,
}
enum State {
@ -38,33 +39,44 @@ enum State {
}
impl SandboxMode {
pub fn new() -> SandboxMode {
pub fn new(canvas: &Canvas) -> SandboxMode {
SandboxMode {
desired_speed: 1.0,
state: State::Paused,
following: None,
route_viewer: route_viewer::RouteViewer::Inactive,
show_activity: show_activity::ShowActivity::Inactive,
time_travel: time_travel::TimeTravel::new(),
time_travel: time_travel::TimeTravel::new(canvas),
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 {
match state.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 spawner.event(ctx, &mut state.ui) {
mode.state = State::Paused;
@ -110,10 +122,24 @@ impl SandboxMode {
txt.add_line("Showing active traffic".to_string());
}
}
ctx.input
.set_mode_with_new_prompt("Sandbox Mode", txt, ctx.canvas);
mode.menu.update_prompt(txt, ctx);
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);
return EventLoopMode::InputOnly;
}
@ -148,13 +174,13 @@ impl SandboxMode {
// get_canonical_point_for_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.route_viewer.event(ctx, &mut state.ui);
mode.show_activity.event(ctx, &mut state.ui);
if ctx.input.modal_action("start time traveling") {
mode.route_viewer.event(ctx, &mut state.ui, &mut mode.menu);
mode.show_activity.event(ctx, &mut state.ui, &mut mode.menu);
if mode.menu.action("start time traveling") {
mode.state = State::TimeTraveling;
mode.time_travel.start(state.ui.primary.sim.time());
// Do this again, in case recording was previously disabled.
@ -162,7 +188,7 @@ impl SandboxMode {
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
// sharing it in the old structure.
state.ui.primary.sim = Sim::new(
@ -174,14 +200,14 @@ impl SandboxMode {
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 = 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;
}
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
state.ui.primary.sim = Sim::new(
&state.ui.primary.map,
@ -193,10 +219,10 @@ impl SandboxMode {
match mode.state {
State::Paused => {
if ctx.input.modal_action("save sim state") {
if mode.menu.action("save sim state") {
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
.ui
.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
.ui
.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 {
last_step: Instant::now(),
benchmark: state.ui.primary.sim.start_benchmark(),
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);
//*ctx.recalculate_current_selection = true;
}
@ -250,7 +276,7 @@ impl SandboxMode {
ref mut benchmark,
ref mut speed,
} => {
if ctx.input.modal_action("run/pause sim") {
if mode.menu.action("run/pause sim") {
mode.state = State::Paused;
} else if ctx.input.nonblocking_is_update_event() {
// TODO https://gafferongames.com/post/fix_your_timestep/
@ -293,6 +319,7 @@ impl SandboxMode {
&mode.time_travel,
&ShowEverything::new(),
);
mode.time_travel.draw(g);
}
_ => {
state.ui.draw(
@ -302,6 +329,7 @@ impl SandboxMode {
&ShowEverything::new(),
);
mode.common.draw(g, &state.ui);
mode.menu.draw(g);
mode.route_viewer.draw(g, &state.ui);
mode.show_activity.draw(g, &state.ui);
}

View File

@ -1,6 +1,6 @@
use crate::helpers::ID;
use crate::ui::UI;
use ezgui::{Color, EventCtx, GfxCtx, Key};
use ezgui::{Color, EventCtx, GfxCtx, Key, NewModalMenu};
use geom::{Duration, PolyLine};
use map_model::LANE_THICKNESS;
use sim::{AgentID, TripID};
@ -13,14 +13,14 @@ pub enum 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 {
RouteViewer::Inactive => {
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) {
*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);
}
}
@ -56,14 +56,14 @@ impl RouteViewer {
}
RouteViewer::Active(time, trip, _) => {
// 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;
} else if *time != ui.primary.sim.time() {
*self = show_route(*trip, ui);
}
}
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;
} else if *time != ui.primary.sim.time() {
*self = debug_all_routes(ui);

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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 {
menu: Menu<Key>,
@ -8,6 +8,15 @@ pub struct NewModalMenu {
impl 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(
Some(Text::prompt(prompt_line)),
choices
@ -17,7 +26,7 @@ impl NewModalMenu {
false,
true,
Position::TopRightOfScreen,
ctx.canvas,
canvas,
);
menu.mark_all_inactive();
NewModalMenu {
@ -62,6 +71,11 @@ impl NewModalMenu {
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) {
self.menu.draw(g);
}