converting mission mode

This commit is contained in:
Dustin Carlino 2019-06-22 13:01:42 -07:00
parent 859429a493
commit 281a0ffcb9
9 changed files with 379 additions and 374 deletions

View File

@ -49,7 +49,7 @@ impl State for PickABTest {
(Transition::Keep, EventLoopMode::InputOnly)
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
}
@ -83,7 +83,7 @@ impl State for ABTestSetup {
(Transition::Keep, EventLoopMode::InputOnly)
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.scroller.draw(g);
self.menu.draw(g);
}

View File

@ -1,7 +1,7 @@
use crate::abtest::setup::PickABTest;
use crate::debug::DebugMode;
use crate::edit::EditMode;
//use crate::mission::MissionEditMode;
use crate::mission::MissionEditMode;
use crate::render::DrawOptions;
use crate::sandbox::SandboxMode;
//use crate::tutorial::TutorialMode;
@ -280,7 +280,7 @@ fn splash_screen(
x if x == edit => Some(Transition::Push(Box::new(EditMode::new(ctx, ui)))),
//x if x == tutorial => break Some(Mode::Tutorial(TutorialMode::new(ctx, ui))),
x if x == debug => Some(Transition::Push(Box::new(DebugMode::new(ctx, ui)))),
//x if x == mission => break Some(Mode::Mission(MissionEditMode::new(ctx, ui))),
x if x == mission => Some(Transition::Push(Box::new(MissionEditMode::new(ctx, ui)))),
x if x == abtest => Some(Transition::Push(Box::new(PickABTest::new()))),
x if x == about => {
if wizard.acknowledge(

View File

@ -4,7 +4,7 @@ mod debug;
mod edit;
mod game;
mod helpers;
//mod mission;
mod mission;
mod render;
mod sandbox;
mod state;

View File

@ -1,5 +1,6 @@
use crate::common::{CommonState, SpeedControls};
use crate::mission::trips::{clip_trips, Trip};
use crate::state::{State, Transition};
use crate::ui::{ShowEverything, UI};
use abstutil::prettyprint_usize;
use ezgui::{
@ -83,9 +84,10 @@ impl TripsVisualizer {
fn current_time(&self) -> Duration {
self.time_slider.get_percent() * Duration::parse("23:59:59.9").unwrap()
}
}
// Returns None if the we're done
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Option<EventLoopMode> {
impl State for TripsVisualizer {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
let time = self.current_time();
let mut txt = Text::prompt("Trips Visualizer");
@ -110,7 +112,7 @@ impl TripsVisualizer {
let thirty_mins = Duration::minutes(30);
if self.menu.action("quit") {
return None;
return (Transition::Pop, EventLoopMode::InputOnly);
} else if time != last_time && self.menu.action("forwards 10 seconds") {
self.time_slider
.set_percent(ctx, (time + ten_secs) / last_time);
@ -135,7 +137,7 @@ impl TripsVisualizer {
self.time_slider
.set_percent(ctx, ((time + dt) / last_time).min(1.0));
} else {
return Some(EventLoopMode::InputOnly);
return (Transition::Keep, EventLoopMode::InputOnly);
}
// TODO Do this more efficiently. ;)
@ -149,13 +151,13 @@ impl TripsVisualizer {
.collect();
if self.speed.is_paused() {
Some(EventLoopMode::InputOnly)
(Transition::Keep, EventLoopMode::InputOnly)
} else {
Some(EventLoopMode::Animation)
(Transition::Keep, EventLoopMode::Animation)
}
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
let time = self.current_time();
let mut batch = GeomBatch::new();
for idx in &self.active_trips {

View File

@ -1,9 +1,11 @@
use crate::common::CommonState;
use crate::helpers::{rotating_color_total, ID};
use crate::state::{State, Transition};
use crate::ui::UI;
use abstutil::{prettyprint_usize, Timer};
use ezgui::{
hotkey, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, ModalMenu, Text, VerticalAlignment,
hotkey, Color, EventCtx, EventLoopMode, GfxCtx, HorizontalAlignment, Key, ModalMenu, Text,
VerticalAlignment,
};
use geom::{Distance, Polygon, Pt2D};
use popdat::{Estimate, PopDat};
@ -55,9 +57,9 @@ impl DataVisualizer {
current_tract: None,
}
}
// Returns true if the we're done
pub fn event(&mut self, ctx: &mut EventCtx, ui: &UI) -> bool {
}
impl State for DataVisualizer {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
let mut txt = Text::prompt("Data Visualizer");
if let Some(ref name) = self.current_tract {
txt.add_line("Census ".to_string());
@ -78,7 +80,7 @@ impl DataVisualizer {
// TODO Remember which dataset we're showing and don't allow reseting to the same.
if self.menu.action("quit") {
return true;
return (Transition::Pop, EventLoopMode::InputOnly);
} else if self.current_dataset != 0 && self.menu.action("household vehicles") {
self.current_dataset = 0;
} else if self.current_dataset != 1 && self.menu.action("commute times") {
@ -101,10 +103,10 @@ impl DataVisualizer {
}
}
false
(Transition::Keep, EventLoopMode::InputOnly)
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
for (name, tract) in &self.tracts {
let color = if Some(name.clone()) == self.current_tract {
ui.cs.get("selected")

View File

@ -1,8 +1,9 @@
use crate::common::CommonState;
use crate::helpers::ID;
use crate::mission::trips::{clip_trips, Trip, TripEndpt};
use crate::state::{State, Transition};
use crate::ui::{ShowEverything, UI};
use ezgui::{hotkey, Color, EventCtx, GfxCtx, ItemSlider, Key, Text};
use ezgui::{hotkey, Color, EventCtx, EventLoopMode, GfxCtx, ItemSlider, Key, Text};
use geom::{Circle, Distance, Line, Speed};
use map_model::BuildingID;
use popdat::psrc;
@ -52,9 +53,10 @@ impl TripsVisualizer {
bldgs,
}
}
}
// Returns true if the we're done
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> bool {
impl State for TripsVisualizer {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
self.slider.event(ctx);
ctx.canvas.handle_event(ctx.input);
@ -68,12 +70,12 @@ impl TripsVisualizer {
}
if self.slider.action("quit") {
return true;
return (Transition::Pop, EventLoopMode::InputOnly);
}
false
(Transition::Keep, EventLoopMode::InputOnly)
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
let (_, trip) = self.slider.get();
let from = trip.from.polygon(&ui.primary.map);
let to = trip.to.polygon(&ui.primary.map);

View File

@ -6,10 +6,8 @@ mod scenario;
mod trips;
use self::trips::{pick_time_range, trips_to_scenario};
use crate::game::{GameState, Mode};
use crate::render::DrawOptions;
use crate::sandbox::SandboxMode;
use crate::ui::ShowEverything;
use crate::state::{State, Transition};
use crate::ui::UI;
use abstutil::Timer;
use ezgui::{hotkey, EventCtx, EventLoopMode, GfxCtx, Key, ModalMenu, Wizard, WrappedWizard};
@ -18,19 +16,7 @@ use map_model::Map;
use sim::Scenario;
pub struct MissionEditMode {
state: State,
}
enum State {
Exploring(ModalMenu),
Neighborhood(neighborhood::NeighborhoodEditor),
LoadScenario(Wizard),
CreateNewScenario(Wizard),
EditScenario(scenario::ScenarioEditor),
DataViz(dataviz::DataVisualizer),
IndividualTrips(individ_trips::TripsVisualizer),
AllTrips(all_trips::TripsVisualizer),
TripsToScenario(Wizard),
menu: ModalMenu,
}
impl MissionEditMode {
@ -39,7 +25,7 @@ impl MissionEditMode {
ui.primary.reset_sim();
MissionEditMode {
state: State::Exploring(ModalMenu::new(
menu: ModalMenu::new(
"Mission Edit Mode",
vec![
(hotkey(Key::Escape), "quit"),
@ -53,170 +39,162 @@ impl MissionEditMode {
(None, "create new scenario"),
],
ctx,
)),
),
}
}
}
pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode {
match state.mode {
Mode::Mission(ref mut mode) => {
match mode.state {
State::Exploring(ref mut menu) => {
menu.handle_event(ctx, None);
ctx.canvas.handle_event(ctx.input);
impl State for MissionEditMode {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
self.menu.handle_event(ctx, None);
ctx.canvas.handle_event(ctx.input);
if menu.action("quit") {
state.mode = Mode::SplashScreen(Wizard::new(), None);
} else if menu.action("visualize population data") {
mode.state =
State::DataViz(dataviz::DataVisualizer::new(ctx, &state.ui));
} else if menu.action("visualize individual PSRC trips") {
mode.state = State::IndividualTrips(
individ_trips::TripsVisualizer::new(ctx, &state.ui),
);
} else if menu.action("visualize all PSRC trips") {
mode.state =
State::AllTrips(all_trips::TripsVisualizer::new(ctx, &state.ui));
} else if menu.action("set up simulation with PSRC trips") {
let scenario = trips_to_scenario(
ctx,
&state.ui,
Duration::ZERO,
Duration::parse("23:59:59.9").unwrap(),
);
ctx.loading_screen("instantiate scenario", |_, timer| {
scenario.instantiate(
&mut state.ui.primary.sim,
&state.ui.primary.map,
&mut state.ui.primary.current_flags.sim_flags.make_rng(),
timer,
);
state
.ui
.primary
.sim
.step(&state.ui.primary.map, Duration::const_seconds(0.1));
});
state.mode = Mode::Sandbox(SandboxMode::new(ctx));
} else if menu.action("create scenario from PSRC trips") {
mode.state = State::TripsToScenario(Wizard::new());
} else if menu.action("manage neighborhoods") {
mode.state = State::Neighborhood(
neighborhood::NeighborhoodEditor::PickNeighborhood(Wizard::new()),
);
} else if menu.action("load scenario") {
mode.state = State::LoadScenario(Wizard::new());
} else if menu.action("create new scenario") {
mode.state = State::CreateNewScenario(Wizard::new());
}
}
State::DataViz(ref mut viz) => {
if viz.event(ctx, &state.ui) {
mode.state = MissionEditMode::new(ctx, &mut state.ui).state;
}
}
State::IndividualTrips(ref mut viz) => {
if viz.event(ctx, &mut state.ui) {
mode.state = MissionEditMode::new(ctx, &mut state.ui).state;
}
}
State::AllTrips(ref mut viz) => {
if let Some(evmode) = viz.event(ctx, &mut state.ui) {
return evmode;
} else {
mode.state = MissionEditMode::new(ctx, &mut state.ui).state;
}
}
State::TripsToScenario(ref mut wizard) => {
if let Some((t1, t2)) = pick_time_range(wizard.wrap(ctx)) {
trips_to_scenario(ctx, &state.ui, t1, t2).save();
mode.state = MissionEditMode::new(ctx, &mut state.ui).state;
} else if wizard.aborted() {
mode.state = MissionEditMode::new(ctx, &mut state.ui).state;
}
}
State::Neighborhood(ref mut editor) => {
if editor.event(ctx, &state.ui) {
mode.state = MissionEditMode::new(ctx, &mut state.ui).state;
}
}
State::LoadScenario(ref mut wizard) => {
if let Some(scenario) =
load_scenario(&state.ui.primary.map, &mut wizard.wrap(ctx))
{
mode.state =
State::EditScenario(scenario::ScenarioEditor::new(scenario, ctx));
} else if wizard.aborted() {
mode.state = MissionEditMode::new(ctx, &mut state.ui).state;
}
}
State::CreateNewScenario(ref mut wizard) => {
let mut wrapped = wizard.wrap(ctx);
if let Some(name) = wrapped.input_string("Name the scenario") {
mode.state = State::EditScenario(scenario::ScenarioEditor::new(
Scenario {
scenario_name: name,
map_name: state.ui.primary.map.get_name().to_string(),
seed_parked_cars: Vec::new(),
spawn_over_time: Vec::new(),
border_spawn_over_time: Vec::new(),
individ_trips: Vec::new(),
},
ctx,
));
} else if wizard.aborted() {
mode.state = MissionEditMode::new(ctx, &mut state.ui).state;
}
}
State::EditScenario(ref mut editor) => {
if let Some(new_mode) = editor.event(ctx, &mut state.ui) {
state.mode = new_mode;
}
}
}
EventLoopMode::InputOnly
}
_ => unreachable!(),
if self.menu.action("quit") {
return (Transition::Pop, EventLoopMode::InputOnly);
} else if self.menu.action("visualize population data") {
return (
Transition::Push(Box::new(dataviz::DataVisualizer::new(ctx, ui))),
EventLoopMode::InputOnly,
);
} else if self.menu.action("visualize individual PSRC trips") {
return (
Transition::Push(Box::new(individ_trips::TripsVisualizer::new(ctx, ui))),
EventLoopMode::InputOnly,
);
} else if self.menu.action("visualize all PSRC trips") {
return (
Transition::Push(Box::new(all_trips::TripsVisualizer::new(ctx, ui))),
EventLoopMode::InputOnly,
);
} else if self.menu.action("set up simulation with PSRC trips") {
let scenario = trips_to_scenario(
ctx,
ui,
Duration::ZERO,
Duration::parse("23:59:59.9").unwrap(),
);
ctx.loading_screen("instantiate scenario", |_, timer| {
scenario.instantiate(
&mut ui.primary.sim,
&ui.primary.map,
&mut ui.primary.current_flags.sim_flags.make_rng(),
timer,
);
ui.primary
.sim
.step(&ui.primary.map, Duration::const_seconds(0.1));
});
return (
Transition::Replace(Box::new(SandboxMode::new(ctx))),
EventLoopMode::InputOnly,
);
} else if self.menu.action("create scenario from PSRC trips") {
return (
Transition::Push(Box::new(TripsToScenario {
wizard: Wizard::new(),
})),
EventLoopMode::InputOnly,
);
} else if self.menu.action("manage neighborhoods") {
return (
Transition::Push(Box::new(neighborhood::NeighborhoodPicker::new())),
EventLoopMode::InputOnly,
);
} else if self.menu.action("load scenario") {
return (
Transition::Push(Box::new(LoadScenario {
wizard: Wizard::new(),
})),
EventLoopMode::InputOnly,
);
} else if self.menu.action("create new scenario") {
return (
Transition::Push(Box::new(CreateNewScenario {
wizard: Wizard::new(),
})),
EventLoopMode::InputOnly,
);
}
(Transition::Keep, EventLoopMode::InputOnly)
}
pub fn draw(state: &GameState, g: &mut GfxCtx) {
state.ui.draw(
g,
DrawOptions::new(),
&state.ui.primary.sim,
&ShowEverything::new(),
);
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.menu.draw(g);
}
}
match state.mode {
Mode::Mission(ref mode) => match mode.state {
State::Exploring(ref menu) => {
menu.draw(g);
}
State::DataViz(ref viz) => {
viz.draw(g, &state.ui);
}
State::IndividualTrips(ref viz) => {
viz.draw(g, &state.ui);
}
State::AllTrips(ref viz) => {
viz.draw(g, &state.ui);
}
State::TripsToScenario(ref wizard) => {
wizard.draw(g);
}
State::Neighborhood(ref editor) => {
editor.draw(g, &state.ui);
}
State::EditScenario(ref editor) => {
editor.draw(g, &state.ui);
}
State::LoadScenario(ref wizard) | State::CreateNewScenario(ref wizard) => {
wizard.draw(g);
}
},
_ => unreachable!(),
struct TripsToScenario {
wizard: Wizard,
}
impl State for TripsToScenario {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
if let Some((t1, t2)) = pick_time_range(self.wizard.wrap(ctx)) {
trips_to_scenario(ctx, ui, t1, t2).save();
return (Transition::Pop, EventLoopMode::InputOnly);
} else if self.wizard.aborted() {
return (Transition::Pop, EventLoopMode::InputOnly);
}
(Transition::Keep, EventLoopMode::InputOnly)
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
}
struct LoadScenario {
wizard: Wizard,
}
impl State for LoadScenario {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
if let Some(scenario) = load_scenario(&ui.primary.map, &mut self.wizard.wrap(ctx)) {
return (
Transition::Replace(Box::new(scenario::ScenarioManager::new(scenario, ctx))),
EventLoopMode::InputOnly,
);
} else if self.wizard.aborted() {
return (Transition::Pop, EventLoopMode::InputOnly);
}
(Transition::Keep, EventLoopMode::InputOnly)
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
}
struct CreateNewScenario {
wizard: Wizard,
}
impl State for CreateNewScenario {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
let mut wrapped = self.wizard.wrap(ctx);
if let Some(name) = wrapped.input_string("Name the scenario") {
return (
Transition::Replace(Box::new(scenario::ScenarioManager::new(
Scenario {
scenario_name: name,
map_name: ui.primary.map.get_name().to_string(),
seed_parked_cars: Vec::new(),
spawn_over_time: Vec::new(),
border_spawn_over_time: Vec::new(),
individ_trips: Vec::new(),
},
ctx,
))),
EventLoopMode::InputOnly,
);
} else if self.wizard.aborted() {
return (Transition::Pop, EventLoopMode::InputOnly);
}
(Transition::Keep, EventLoopMode::InputOnly)
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
}

View File

@ -1,136 +1,145 @@
use crate::state::{State, Transition};
use crate::ui::UI;
use ezgui::{hotkey, Color, EventCtx, GfxCtx, Key, ModalMenu, Wizard, WrappedWizard};
use ezgui::{
hotkey, Color, EventCtx, EventLoopMode, GfxCtx, Key, ModalMenu, Wizard, WrappedWizard,
};
use geom::{Circle, Distance, Line, Polygon, Pt2D};
use map_model::{Map, NeighborhoodBuilder};
const POINT_RADIUS: Distance = Distance::const_meters(10.0);
pub enum NeighborhoodEditor {
PickNeighborhood(Wizard),
// Option<usize> is the point currently being hovered over
EditNeighborhood(ModalMenu, NeighborhoodBuilder, Option<usize>),
// usize is the point being moved
MovingPoint(ModalMenu, NeighborhoodBuilder, usize),
pub struct NeighborhoodPicker {
wizard: Wizard,
}
impl NeighborhoodEditor {
fn modal_menu(ctx: &EventCtx, name: &str) -> ModalMenu {
ModalMenu::new(
&format!("Neighborhood Editor for {}", name),
vec![
(hotkey(Key::Escape), "quit"),
(hotkey(Key::S), "save"),
(hotkey(Key::X), "export as an Osmosis polygon filter"),
(hotkey(Key::P), "add a new point"),
],
ctx,
)
impl NeighborhoodPicker {
pub fn new() -> NeighborhoodPicker {
NeighborhoodPicker {
wizard: Wizard::new(),
}
}
}
impl State for NeighborhoodPicker {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
ctx.canvas.handle_event(ctx.input);
if let Some(n) = pick_neighborhood(&ui.primary.map, self.wizard.wrap(ctx)) {
return (
Transition::Push(Box::new(NeighborhoodEditor {
menu: ModalMenu::new(
&format!("Neighborhood Editor for {}", n.name),
vec![
(hotkey(Key::Escape), "quit"),
(hotkey(Key::S), "save"),
(hotkey(Key::X), "export as an Osmosis polygon filter"),
(hotkey(Key::P), "add a new point"),
],
ctx,
),
neighborhood: n,
mouseover_pt: None,
moving_pt: false,
})),
EventLoopMode::InputOnly,
);
} else if self.wizard.aborted() {
return (Transition::Pop, EventLoopMode::InputOnly);
}
(Transition::Keep, EventLoopMode::InputOnly)
}
// True if done
pub fn event(&mut self, ctx: &mut EventCtx, ui: &UI) -> bool {
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
// TODO is this order wrong?
self.wizard.draw(g);
if let Some(neighborhood) = self.wizard.current_menu_choice::<NeighborhoodBuilder>() {
g.draw_polygon(
ui.cs.get("neighborhood polygon"),
&Polygon::new(
&ui.primary
.map
.get_gps_bounds()
.must_convert(&neighborhood.points),
),
);
}
}
}
struct NeighborhoodEditor {
menu: ModalMenu,
neighborhood: NeighborhoodBuilder,
mouseover_pt: Option<usize>,
moving_pt: bool,
}
impl State for NeighborhoodEditor {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
let gps_bounds = ui.primary.map.get_gps_bounds();
match self {
NeighborhoodEditor::PickNeighborhood(ref mut wizard) => {
ctx.canvas.handle_event(ctx.input);
if let Some(n) = pick_neighborhood(&ui.primary.map, wizard.wrap(ctx)) {
*self = NeighborhoodEditor::EditNeighborhood(
NeighborhoodEditor::modal_menu(ctx, &n.name),
n,
None,
);
} else if wizard.aborted() {
return true;
self.menu.handle_event(ctx, None);
ctx.canvas.handle_event(ctx.input);
if self.moving_pt {
if let Some(pt) = ctx
.canvas
.get_cursor_in_map_space()
.and_then(|c| c.to_gps(gps_bounds))
{
self.neighborhood.points[self.mouseover_pt.unwrap()] = pt;
}
if ctx.input.key_released(Key::LeftControl) {
self.moving_pt = false;
}
} else {
if self.menu.action("quit") {
return (Transition::Pop, EventLoopMode::InputOnly);
} else if self.neighborhood.points.len() >= 3 && self.menu.action("save") {
self.neighborhood.save();
} else if self.neighborhood.points.len() >= 3
&& self.menu.action("export as an Osmosis polygon filter")
{
self.neighborhood.save_as_osmosis().unwrap();
} else if let Some(pt) = ctx
.canvas
.get_cursor_in_map_space()
.and_then(|c| c.to_gps(gps_bounds))
{
if self.menu.action("add a new point") {
self.neighborhood.points.push(pt);
}
}
NeighborhoodEditor::EditNeighborhood(ref mut menu, ref mut n, ref mut current_idx) => {
menu.handle_event(ctx, None);
ctx.canvas.handle_event(ctx.input);
if menu.action("quit") {
return true;
} else if n.points.len() >= 3 && menu.action("save") {
n.save();
return true;
} else if n.points.len() >= 3 && menu.action("export as an Osmosis polygon filter")
{
n.save_as_osmosis().unwrap();
} else if let Some(pt) = ctx
.canvas
.get_cursor_in_map_space()
.and_then(|c| c.to_gps(gps_bounds))
{
if menu.action("add a new point") {
n.points.push(pt);
}
}
if let Some(cursor) = ctx.canvas.get_cursor_in_map_space() {
*current_idx = n.points.iter().position(|pt| {
Circle::new(
Pt2D::from_gps(*pt, gps_bounds).unwrap(),
POINT_RADIUS / ctx.canvas.cam_zoom,
)
.contains_pt(cursor)
});
} else {
*current_idx = None;
}
if let Some(idx) = current_idx {
// TODO mouse dragging might be more intuitive, but it's unclear how to
// override part of canvas.handle_event
if ctx
.input
.key_pressed(Key::LeftControl, "hold to move this point")
{
*self = NeighborhoodEditor::MovingPoint(
NeighborhoodEditor::modal_menu(ctx, &n.name),
n.clone(),
*idx,
);
}
}
if let Some(cursor) = ctx.canvas.get_cursor_in_map_space() {
self.mouseover_pt = self.neighborhood.points.iter().position(|pt| {
Circle::new(
Pt2D::from_gps(*pt, gps_bounds).unwrap(),
POINT_RADIUS / ctx.canvas.cam_zoom,
)
.contains_pt(cursor)
});
} else {
self.mouseover_pt = None;
}
NeighborhoodEditor::MovingPoint(ref mut menu, ref mut n, idx) => {
menu.handle_event(ctx, None);
ctx.canvas.handle_event(ctx.input);
if let Some(pt) = ctx
.canvas
.get_cursor_in_map_space()
.and_then(|c| c.to_gps(gps_bounds))
{
n.points[*idx] = pt;
}
if ctx.input.key_released(Key::LeftControl) {
*self = NeighborhoodEditor::EditNeighborhood(
NeighborhoodEditor::modal_menu(ctx, &n.name),
n.clone(),
Some(*idx),
);
}
// TODO mouse dragging might be more intuitive, but it's unclear how to
// override part of canvas.handle_event
if self.mouseover_pt.is_some()
&& ctx
.input
.key_pressed(Key::LeftControl, "hold to move this point")
{
self.moving_pt = true;
}
}
false
(Transition::Keep, EventLoopMode::InputOnly)
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
let (raw_pts, current_idx) = match self {
NeighborhoodEditor::PickNeighborhood(wizard) => {
// TODO is this order wrong?
wizard.draw(g);
if let Some(neighborhood) = wizard.current_menu_choice::<NeighborhoodBuilder>() {
(&neighborhood.points, None)
} else {
return;
}
}
NeighborhoodEditor::EditNeighborhood(_, n, current_idx) => (&n.points, *current_idx),
NeighborhoodEditor::MovingPoint(_, n, current_idx) => (&n.points, Some(*current_idx)),
};
let gps_bounds = ui.primary.map.get_gps_bounds();
let pts: Vec<Pt2D> = gps_bounds.must_convert(&raw_pts);
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
let pts: Vec<Pt2D> = ui
.primary
.map
.get_gps_bounds()
.must_convert(&self.neighborhood.points);
if pts.len() == 2 {
g.draw_line(
@ -147,7 +156,7 @@ impl NeighborhoodEditor {
);
}
for (idx, pt) in pts.iter().enumerate() {
let color = if Some(idx) == current_idx {
let color = if Some(idx) == self.mouseover_pt {
ui.cs.get_def("neighborhood point to move", Color::CYAN)
} else if idx == pts.len() - 1 {
ui.cs
@ -158,13 +167,7 @@ impl NeighborhoodEditor {
g.draw_circle(color, &Circle::new(*pt, POINT_RADIUS / g.canvas.cam_zoom));
}
match self {
NeighborhoodEditor::EditNeighborhood(ref menu, _, _)
| NeighborhoodEditor::MovingPoint(ref menu, _, _) => {
menu.draw(g);
}
_ => {}
}
self.menu.draw(g);
}
}

View File

@ -1,23 +1,26 @@
use crate::game::Mode;
use crate::mission::{input_time, MissionEditMode};
use crate::mission::input_time;
use crate::sandbox::SandboxMode;
use crate::state::{State, Transition};
use crate::ui::UI;
use abstutil::WeightedUsizeChoice;
use ezgui::{hotkey, EventCtx, GfxCtx, Key, LogScroller, ModalMenu, Wizard, WrappedWizard};
use ezgui::{
hotkey, EventCtx, EventLoopMode, GfxCtx, Key, LogScroller, ModalMenu, Wizard, WrappedWizard,
};
use geom::Duration;
use map_model::{IntersectionID, Map, Neighborhood};
use sim::{BorderSpawnOverTime, OriginDestination, Scenario, SeedParkedCars, SpawnOverTime};
pub enum ScenarioEditor {
ManageScenario(ModalMenu, Scenario, LogScroller),
EditScenario(Scenario, Wizard),
pub struct ScenarioManager {
menu: ModalMenu,
scenario: Scenario,
scroller: LogScroller,
}
impl ScenarioEditor {
pub fn new(scenario: Scenario, ctx: &mut EventCtx) -> ScenarioEditor {
impl ScenarioManager {
pub fn new(scenario: Scenario, ctx: &mut EventCtx) -> ScenarioManager {
let scroller = LogScroller::new(scenario.scenario_name.clone(), scenario.describe());
ScenarioEditor::ManageScenario(
ModalMenu::new(
ScenarioManager {
menu: ModalMenu::new(
&format!("Scenario Editor for {}", scenario.scenario_name),
vec![
(hotkey(Key::Escape), "quit"),
@ -29,58 +32,73 @@ impl ScenarioEditor {
),
scenario,
scroller,
)
}
}
}
impl State for ScenarioManager {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
self.menu.handle_event(ctx, None);
ctx.canvas.handle_event(ctx.input);
if self.menu.action("save") {
self.scenario.save();
} else if self.menu.action("edit") {
return (
Transition::Push(Box::new(ScenarioEditor {
scenario: self.scenario.clone(),
wizard: Wizard::new(),
})),
EventLoopMode::InputOnly,
);
} else if self.menu.action("instantiate") {
ctx.loading_screen("instantiate scenario", |_, timer| {
self.scenario.instantiate(
&mut ui.primary.sim,
&ui.primary.map,
&mut ui.primary.current_flags.sim_flags.make_rng(),
timer,
);
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
});
return (
Transition::Replace(Box::new(SandboxMode::new(ctx))),
EventLoopMode::InputOnly,
);
} else if self.scroller.event(&mut ctx.input) {
return (Transition::Pop, EventLoopMode::InputOnly);
}
(Transition::Keep, EventLoopMode::InputOnly)
}
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Option<Mode> {
match self {
ScenarioEditor::ManageScenario(ref mut menu, scenario, ref mut scroller) => {
menu.handle_event(ctx, None);
ctx.canvas.handle_event(ctx.input);
if menu.action("save") {
scenario.save();
} else if menu.action("edit") {
*self = ScenarioEditor::EditScenario(scenario.clone(), Wizard::new());
} else if menu.action("instantiate") {
ctx.loading_screen("instantiate scenario", |_, timer| {
scenario.instantiate(
&mut ui.primary.sim,
&ui.primary.map,
&mut ui.primary.current_flags.sim_flags.make_rng(),
timer,
);
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
});
return Some(Mode::Sandbox(SandboxMode::new(ctx)));
} else if scroller.event(&mut ctx.input) {
return Some(Mode::Mission(MissionEditMode::new(ctx, ui)));
}
}
ScenarioEditor::EditScenario(ref mut scenario, ref mut wizard) => {
if let Some(()) = edit_scenario(&ui.primary.map, scenario, wizard.wrap(ctx)) {
// TODO autosave, or at least make it clear there are unsaved edits
*self = ScenarioEditor::new(scenario.clone(), ctx);
} else if wizard.aborted() {
*self = ScenarioEditor::new(scenario.clone(), ctx);
}
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
self.scroller.draw(g);
self.menu.draw(g);
}
}
struct ScenarioEditor {
scenario: Scenario,
wizard: Wizard,
}
impl State for ScenarioEditor {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
if let Some(()) = edit_scenario(&ui.primary.map, &mut self.scenario, self.wizard.wrap(ctx))
{
// TODO autosave, or at least make it clear there are unsaved edits
// TODO Need to Refresh the scenario in our parent!
return (Transition::Pop, EventLoopMode::InputOnly);
} else if self.wizard.aborted() {
return (Transition::Pop, EventLoopMode::InputOnly);
}
None
(Transition::Keep, EventLoopMode::InputOnly)
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
match self {
ScenarioEditor::ManageScenario(ref menu, _, scroller) => {
scroller.draw(g);
menu.draw(g);
}
ScenarioEditor::EditScenario(_, wizard) => {
if let Some(neighborhood) = wizard.current_menu_choice::<Neighborhood>() {
g.draw_polygon(ui.cs.get("neighborhood polygon"), &neighborhood.polygon);
}
wizard.draw(g);
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
if let Some(neighborhood) = self.wizard.current_menu_choice::<Neighborhood>() {
g.draw_polygon(ui.cs.get("neighborhood polygon"), &neighborhood.polygon);
}
self.wizard.draw(g);
}
}