remove scenario editor and related things. havent used in ages, the scenario abstraction has proven to be wrong anyway, and it blocks cleanup of old ezgui stuff (SliderWithTextBox).

This commit is contained in:
Dustin Carlino 2020-02-27 09:07:12 -08:00
parent ae33beb3c9
commit 01a9986aab
7 changed files with 14 additions and 346 deletions

View File

@ -32,7 +32,7 @@ pub use crate::screen_geom::{ScreenDims, ScreenPt, ScreenRectangle};
pub use crate::text::{Line, Text, TextSpan, HOTKEY_COLOR};
pub use crate::widgets::{
Autocomplete, Button, Choice, Filler, Histogram, ItemSlider, JustDraw, ModalMenu, Plot, Series,
Slider, SliderWithTextBox, Warper, WarpingItemSlider, Wizard, WrappedWizard,
Slider, Warper, WarpingItemSlider, Wizard, WrappedWizard,
};
pub enum InputResult<T: Clone> {

View File

@ -21,6 +21,6 @@ pub use self::no_op::JustDraw;
pub use self::plot::{Plot, Series};
pub(crate) use self::popup_menu::PopupMenu;
pub(crate) use self::screenshot::{screenshot_current, screenshot_everything};
pub use self::slider::{ItemSlider, Slider, SliderWithTextBox, WarpingItemSlider};
pub use self::slider::{ItemSlider, Slider, WarpingItemSlider};
pub use self::warper::Warper;
pub use self::wizard::{Choice, Wizard, WrappedWizard};

View File

@ -1,10 +1,9 @@
use crate::layout::{stack_vertically, ContainerOrientation, Widget};
use crate::widgets::text_box::TextBox;
use crate::{
hotkey, Color, Drawable, EventCtx, EventLoopMode, GeomBatch, GfxCtx, InputResult, Key, Line,
ModalMenu, MultiKey, ScreenDims, ScreenPt, ScreenRectangle, Text, Warper,
hotkey, Color, Drawable, EventCtx, EventLoopMode, GeomBatch, GfxCtx, Key, Line, ModalMenu,
MultiKey, ScreenDims, ScreenPt, ScreenRectangle, Text, Warper,
};
use geom::{Polygon, Pt2D, Time};
use geom::{Polygon, Pt2D};
pub struct Slider {
current_percent: f64,
@ -409,71 +408,3 @@ impl<T: PartialEq> WarpingItemSlider<T> {
self.warper = None;
}
}
// TODO Hardcoded to Times right now...
pub struct SliderWithTextBox {
slider: Slider,
tb: TextBox,
low: Time,
high: Time,
}
impl SliderWithTextBox {
pub fn new(prompt: &str, low: Time, high: Time, ctx: &EventCtx) -> SliderWithTextBox {
SliderWithTextBox {
// TODO Some ratio based on low and high difference
slider: Slider::horizontal(
ctx,
Text::from(Line(prompt)).dims(&ctx.prerender.assets).width,
25.0,
),
tb: TextBox::new(ctx, prompt, Some(low.to_string())),
low,
high,
}
}
pub fn event(&mut self, ctx: &mut EventCtx) -> InputResult<Time> {
stack_vertically(
ContainerOrientation::Centered,
ctx,
vec![&mut self.slider, &mut self.tb],
);
if self.slider.event(ctx) {
let value = self.low + self.slider.get_percent() * (self.high - self.low);
self.tb.set_text(value.to_string());
InputResult::StillActive
} else {
let line_before = self.tb.get_line().to_string();
match self.tb.event(&mut ctx.input) {
InputResult::Done(line, _) => {
if let Ok(t) = Time::parse(&line) {
if t >= self.low && t <= self.high {
return InputResult::Done(line, t);
}
}
println!("Bad input {}", line);
InputResult::Canceled
}
InputResult::StillActive => {
if line_before != self.tb.get_line() {
if let Ok(t) = Time::parse(self.tb.get_line()) {
if t >= self.low && t <= self.high {
self.slider
.set_percent(ctx, (t - self.low) / (self.high - self.low));
}
}
}
InputResult::StillActive
}
InputResult::Canceled => InputResult::Canceled,
}
}
}
pub fn draw(&self, g: &mut GfxCtx) {
self.slider.draw(g);
self.tb.draw(g);
}
}

View File

@ -49,15 +49,6 @@ impl TextBox {
txt
}
pub fn get_line(&self) -> &str {
&self.line
}
pub fn set_text(&mut self, line: String) {
self.line = line;
self.cursor_x = self.line.len();
}
pub fn event(&mut self, input: &mut UserInput) -> InputResult<()> {
let maybe_ev = input.use_event_directly();
if maybe_ev.is_none() {

View File

@ -2,10 +2,9 @@ use crate::widgets::text_box::TextBox;
use crate::widgets::PopupMenu;
use crate::{
hotkey, layout, Button, Color, Composite, EventCtx, GfxCtx, HorizontalAlignment, InputResult,
Key, Line, ManagedWidget, MultiKey, Outcome, SliderWithTextBox, Text, VerticalAlignment,
Key, Line, ManagedWidget, MultiKey, Outcome, Text, VerticalAlignment,
};
use abstutil::Cloneable;
use geom::Time;
use std::collections::VecDeque;
pub struct Wizard {
@ -13,7 +12,6 @@ pub struct Wizard {
tb: Option<TextBox>,
menu_comp: Option<Composite>,
ack: Option<Composite>,
slider: Option<SliderWithTextBox>,
// In the order of queries made
confirmed_state: Vec<Box<dyn Cloneable>>,
@ -26,7 +24,6 @@ impl Wizard {
tb: None,
menu_comp: None,
ack: None,
slider: None,
confirmed_state: Vec::new(),
}
}
@ -41,9 +38,6 @@ impl Wizard {
if let Some(ref s) = self.ack {
s.draw(g);
}
if let Some(ref s) = self.slider {
s.draw(g);
}
}
pub fn wrap<'a, 'b>(&'a mut self, ctx: &'a mut EventCtx<'b>) -> WrappedWizard<'a, 'b> {
@ -114,37 +108,6 @@ impl Wizard {
}
}
}
fn input_time_slider(
&mut self,
query: &str,
low: Time,
high: Time,
ctx: &mut EventCtx,
) -> Option<Time> {
assert!(self.alive);
// Otherwise, we try to use one event for two inputs potentially
if ctx.input.has_been_consumed() {
return None;
}
if self.slider.is_none() {
self.slider = Some(SliderWithTextBox::new(query, low, high, ctx));
}
match self.slider.as_mut().unwrap().event(ctx) {
InputResult::StillActive => None,
InputResult::Canceled => {
self.alive = false;
None
}
InputResult::Done(_, result) => {
self.slider = None;
Some(result)
}
}
}
}
// Lives only for one frame -- bundles up temporary things like UserInput and statefully serve
@ -180,21 +143,6 @@ impl<'a, 'b> WrappedWizard<'a, 'b> {
}
}
pub fn input_time_slider(&mut self, query: &str, low: Time, high: Time) -> Option<Time> {
if !self.ready_results.is_empty() {
let first = self.ready_results.pop_front().unwrap();
// TODO Simplify?
let item: &Time = first.as_any().downcast_ref::<Time>().unwrap();
return Some(*item);
}
if let Some(obj) = self.wizard.input_time_slider(query, low, high, self.ctx) {
self.wizard.confirmed_state.push(Box::new(obj));
Some(obj)
} else {
None
}
}
pub fn input_string(&mut self, query: &str) -> Option<String> {
self.input_something(query, None, Box::new(Some))
}
@ -454,7 +402,6 @@ impl<'a, 'b> WrappedWizard<'a, 'b> {
assert!(self.wizard.tb.is_none());
assert!(self.wizard.menu_comp.is_none());
assert!(self.wizard.ack.is_none());
assert!(self.wizard.slider.is_none());
self.wizard.confirmed_state.clear();
}
}

View File

@ -6,9 +6,7 @@ mod scenario;
use crate::game::{State, Transition, WizardState};
use crate::ui::UI;
use abstutil::Timer;
use ezgui::{hotkey, EventCtx, GfxCtx, Key, ModalMenu, Wizard, WrappedWizard};
use geom::Time;
use sim::Scenario;
use ezgui::{hotkey, EventCtx, GfxCtx, Key, ModalMenu, Wizard};
pub struct MissionEditMode {
menu: ModalMenu,
@ -24,7 +22,6 @@ impl MissionEditMode {
(hotkey(Key::A), "visualize all PSRC trips"),
(hotkey(Key::N), "manage neighborhoods"),
(hotkey(Key::W), "load scenario"),
(None, "create new scenario"),
(hotkey(Key::Escape), "quit"),
],
ctx,
@ -48,8 +45,6 @@ impl State for MissionEditMode {
return Transition::Push(Box::new(neighborhood::NeighborhoodPicker::new()));
} else if self.menu.action("load scenario") {
return Transition::Push(WizardState::new(Box::new(load_scenario)));
} else if self.menu.action("create new scenario") {
return Transition::Push(WizardState::new(Box::new(create_new_scenario)));
}
Transition::Keep
}
@ -72,22 +67,3 @@ fn load_scenario(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option<Tr
scenario::ScenarioManager::new(scenario, ctx, ui),
)))
}
fn create_new_scenario(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option<Transition> {
let name = wiz.wrap(ctx).input_string("Name the scenario")?;
let mut s = Scenario::empty(&ui.primary.map, &name);
s.only_seed_buses = None;
Some(Transition::Replace(Box::new(
scenario::ScenarioManager::new(s, ctx, ui),
)))
}
pub fn pick_time_range(
wizard: &mut WrappedWizard,
low_query: &str,
high_query: &str,
) -> Option<(Time, Time)> {
let t1 = wizard.input_time_slider(low_query, Time::START_OF_DAY, Time::END_OF_DAY)?;
let t2 = wizard.input_time_slider(high_query, t1, Time::END_OF_DAY)?;
Some((t1, t2))
}

View File

@ -2,19 +2,15 @@ use crate::common::{tool_panel, Colorer, CommonState, Warping};
use crate::game::{State, Transition, WizardState};
use crate::helpers::ID;
use crate::managed::{WrappedComposite, WrappedOutcome};
use crate::mission::pick_time_range;
use crate::ui::UI;
use abstutil::{prettyprint_usize, Counter, MultiMap, WeightedUsizeChoice};
use abstutil::{prettyprint_usize, Counter, MultiMap};
use ezgui::{
hotkey, layout, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, ModalMenu,
Slider, Text, Wizard, WrappedWizard,
};
use geom::{Distance, Duration, Line, PolyLine, Polygon, Time};
use map_model::{BuildingID, IntersectionID, Map, Neighborhood};
use sim::{
BorderSpawnOverTime, DrivingGoal, OriginDestination, Scenario, SeedParkedCars, SidewalkPOI,
SidewalkSpot, SpawnOverTime, SpawnTrip,
Slider, Text,
};
use geom::{Distance, Line, PolyLine, Polygon};
use map_model::{BuildingID, IntersectionID, Map};
use sim::{DrivingGoal, Scenario, SidewalkPOI, SidewalkSpot, SpawnTrip};
use std::collections::BTreeSet;
pub struct ScenarioManager {
@ -114,15 +110,7 @@ impl ScenarioManager {
assert!(filled_spots.is_empty());
ScenarioManager {
menu: ModalMenu::new(
"Scenario Editor",
vec![
(hotkey(Key::S), "save"),
(hotkey(Key::E), "edit"),
(hotkey(Key::D), "dot map"),
],
ctx,
),
menu: ModalMenu::new("Scenario Editor", vec![(hotkey(Key::D), "dot map")], ctx),
common: CommonState::new(),
tool_panel: tool_panel(ctx),
scenario,
@ -164,14 +152,7 @@ impl State for ScenarioManager {
if ctx.redo_mouseover() {
ui.recalculate_current_selection(ctx);
}
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(),
}));
} else if self.menu.action("dot map") {
if self.menu.action("dot map") {
return Transition::Push(Box::new(DotMap::new(ctx, ui, &self.scenario)));
}
@ -281,164 +262,6 @@ impl State for ScenarioManager {
}
}
struct ScenarioEditor {
scenario: Scenario,
wizard: Wizard,
}
impl State for ScenarioEditor {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
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
let scenario = self.scenario.clone();
return Transition::PopWithData(Box::new(|state, _, _| {
let mut manager = state.downcast_mut::<ScenarioManager>().unwrap();
manager.scenario = scenario;
// Don't need to update trips_from_bldg or trips_to_bldg, since edit_scenario
// doesn't touch individ_trips.
}));
} else if self.wizard.aborted() {
return Transition::Pop;
}
Transition::Keep
}
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);
}
}
fn edit_scenario(map: &Map, scenario: &mut Scenario, mut wizard: WrappedWizard) -> Option<()> {
let seed_parked = "Seed parked cars";
let spawn = "Spawn agents";
let spawn_border = "Spawn agents from a border";
let randomize = "Randomly spawn stuff from/to every neighborhood";
match wizard
.choose_string("What kind of edit?", || {
vec![seed_parked, spawn, spawn_border, randomize]
})?
.as_str()
{
x if x == seed_parked => {
scenario.seed_parked_cars.push(SeedParkedCars {
neighborhood: choose_neighborhood(
map,
&mut wizard,
"Seed parked cars in what area?",
)?,
cars_per_building: input_weighted_usize(
&mut wizard,
"How many cars per building? (ex: 4,4,2)",
)?,
});
}
x if x == spawn => {
let (start_time, stop_time) =
pick_time_range(&mut wizard, "Start spawning when?", "Stop spawning when?")?;
scenario.spawn_over_time.push(SpawnOverTime {
num_agents: wizard.input_usize("Spawn how many agents?")?,
start_time,
stop_time,
start_from_neighborhood: choose_neighborhood(
map,
&mut wizard,
"Where should the agents start?",
)?,
goal: choose_origin_destination(map, &mut wizard, "Where should the agents go?")?,
percent_biking: wizard
.input_percent("What percent of the walking trips will bike instead?")?,
percent_use_transit: wizard.input_percent(
"What percent of the walking trips will consider taking transit?",
)?,
});
}
x if x == spawn_border => {
let (start_time, stop_time) =
pick_time_range(&mut wizard, "Start spawning when?", "Stop spawning when?")?;
scenario.border_spawn_over_time.push(BorderSpawnOverTime {
num_peds: wizard.input_usize("Spawn how many pedestrians?")?,
num_cars: wizard.input_usize("Spawn how many cars?")?,
num_bikes: wizard.input_usize("Spawn how many bikes?")?,
start_time,
stop_time,
// TODO validate it's a border!
start_from_border: choose_intersection(
&mut wizard,
"Which border should the agents spawn at?",
)
.map(|i| map.get_i(i).some_outgoing_road(map))?,
goal: choose_origin_destination(map, &mut wizard, "Where should the agents go?")?,
percent_use_transit: wizard.input_percent(
"What percent of the walking trips will consider taking transit?",
)?,
});
}
x if x == randomize => {
let neighborhoods = Neighborhood::load_all(map.get_name(), &map.get_gps_bounds());
for (src, _) in &neighborhoods {
for (dst, _) in &neighborhoods {
scenario.spawn_over_time.push(SpawnOverTime {
num_agents: 100,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::minutes(10),
start_from_neighborhood: src.to_string(),
goal: OriginDestination::Neighborhood(dst.to_string()),
percent_biking: 0.1,
percent_use_transit: 0.2,
});
}
}
}
_ => unreachable!(),
};
Some(())
}
fn choose_neighborhood(map: &Map, wizard: &mut WrappedWizard, query: &str) -> Option<String> {
// Load the full object, since we usually visualize the neighborhood when menuing over it
wizard
.choose(query, || {
Choice::from(Neighborhood::load_all(map.get_name(), map.get_gps_bounds()))
})
.map(|(n, _)| n)
}
fn input_weighted_usize(wizard: &mut WrappedWizard, query: &str) -> Option<WeightedUsizeChoice> {
wizard.input_something(
query,
None,
Box::new(|line| WeightedUsizeChoice::parse(&line)),
)
}
// TODO Validate the intersection exists? Let them pick it with the cursor?
fn choose_intersection(wizard: &mut WrappedWizard, query: &str) -> Option<IntersectionID> {
wizard.input_something(
query,
None,
Box::new(|line| usize::from_str_radix(&line, 10).ok().map(IntersectionID)),
)
}
fn choose_origin_destination(
map: &Map,
wizard: &mut WrappedWizard,
query: &str,
) -> Option<OriginDestination> {
let neighborhood = "Neighborhood";
let border = "Intersection";
if wizard.choose_string(query, || vec![neighborhood, border])? == neighborhood {
choose_neighborhood(map, wizard, query).map(OriginDestination::Neighborhood)
} else {
choose_intersection(wizard, query)
.map(|i| OriginDestination::EndOfRoad(map.get_i(i).some_incoming_road(map)))
}
}
// TODO Yet another one of these... something needs to change.
#[derive(PartialEq, Debug, Clone, Copy)]
enum OD {