storing associated data with menu

This commit is contained in:
Dustin Carlino 2018-09-23 14:26:06 -07:00
parent de1033ac66
commit 7836df4580
9 changed files with 107 additions and 109 deletions

View File

@ -6,7 +6,6 @@ use graphics;
use objects::SETTINGS; use objects::SETTINGS;
use piston::input::{Key, MouseCursorEvent}; use piston::input::{Key, MouseCursorEvent};
use plugins::Colorizer; use plugins::Colorizer;
use std::str::FromStr;
use std::string::ToString; use std::string::ToString;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@ -18,7 +17,7 @@ const TILE_DIMS: u32 = 2;
// TODO parts of this should be in ezgui // TODO parts of this should be in ezgui
pub enum ColorPicker { pub enum ColorPicker {
Inactive, Inactive,
Choosing(Menu), Choosing(Menu<Colors>),
// Remember the original color, in case we revert // Remember the original color, in case we revert
PickingColor(Colors, graphics::types::Color), PickingColor(Colors, graphics::types::Color),
} }
@ -35,7 +34,7 @@ impl ColorPicker {
if input.unimportant_key_pressed(Key::D8, SETTINGS, "configure colors") { if input.unimportant_key_pressed(Key::D8, SETTINGS, "configure colors") {
new_state = Some(ColorPicker::Choosing(Menu::new( new_state = Some(ColorPicker::Choosing(Menu::new(
"Pick a color to change", "Pick a color to change",
Colors::iter().map(|c| c.to_string()).collect(), Colors::iter().map(|c| (c.to_string(), c)).collect(),
))); )));
} }
} }
@ -45,9 +44,8 @@ impl ColorPicker {
new_state = Some(ColorPicker::Inactive); new_state = Some(ColorPicker::Inactive);
} }
InputResult::StillActive => {} InputResult::StillActive => {}
InputResult::Done(choice) => { InputResult::Done(_, color) => {
let c = Colors::from_str(&choice).unwrap(); new_state = Some(ColorPicker::PickingColor(color, cs.get(color)));
new_state = Some(ColorPicker::PickingColor(c, cs.get(c)));
} }
}; };
} }

View File

@ -6,7 +6,6 @@ use objects::EDIT_MAP;
use piston::input::{Button, Key, ReleaseEvent}; use piston::input::{Button, Key, ReleaseEvent};
use plugins::Colorizer; use plugins::Colorizer;
use polygons; use polygons;
use std::collections::BTreeMap;
const POINT_RADIUS: f64 = 2.0; const POINT_RADIUS: f64 = 2.0;
@ -18,7 +17,7 @@ pub enum DrawPolygonState {
MovingPoint(Vec<Pt2D>, usize, String), MovingPoint(Vec<Pt2D>, usize, String),
NamingPolygon(TextBox, Vec<Pt2D>), NamingPolygon(TextBox, Vec<Pt2D>),
// String name to each choice, pre-loaded // String name to each choice, pre-loaded
ListingPolygons(Menu, BTreeMap<String, polygons::PolygonSelection>), ListingPolygons(Menu<polygons::PolygonSelection>),
} }
impl DrawPolygonState { impl DrawPolygonState {
@ -53,10 +52,10 @@ impl DrawPolygonState {
if polygons.is_empty() { if polygons.is_empty() {
warn!("Sorry, no existing polygons"); warn!("Sorry, no existing polygons");
} else { } else {
new_state = Some(DrawPolygonState::ListingPolygons( new_state = Some(DrawPolygonState::ListingPolygons(Menu::new(
Menu::new("Load which polygon?", polygons.keys().cloned().collect()), "Load which polygon?",
polygons, polygons,
)); )));
} }
} else if input.key_pressed(Key::Escape, "throw away this neighborhood polygon") { } else if input.key_pressed(Key::Escape, "throw away this neighborhood polygon") {
new_state = Some(DrawPolygonState::Empty); new_state = Some(DrawPolygonState::Empty);
@ -109,7 +108,7 @@ impl DrawPolygonState {
info!("Never mind!"); info!("Never mind!");
new_state = Some(DrawPolygonState::Empty); new_state = Some(DrawPolygonState::Empty);
} }
InputResult::Done(name) => { InputResult::Done(name, _) => {
let path = format!("../data/polygons/{}/{}", map.get_name(), name); let path = format!("../data/polygons/{}/{}", map.get_name(), name);
abstutil::write_json( abstutil::write_json(
&path, &path,
@ -123,17 +122,17 @@ impl DrawPolygonState {
} }
InputResult::StillActive => {} InputResult::StillActive => {}
}, },
DrawPolygonState::ListingPolygons(ref mut menu, polygons) => { DrawPolygonState::ListingPolygons(ref mut menu) => {
match menu.event(input) { match menu.event(input) {
InputResult::Canceled => { InputResult::Canceled => {
new_state = Some(DrawPolygonState::Empty); new_state = Some(DrawPolygonState::Empty);
} }
InputResult::StillActive => {} InputResult::StillActive => {}
InputResult::Done(choice) => { InputResult::Done(name, poly) => {
new_state = Some(DrawPolygonState::DrawingPoints( new_state = Some(DrawPolygonState::DrawingPoints(
polygons[&choice].points.clone(), poly.points.clone(),
None, None,
choice, name,
)); ));
} }
}; };
@ -166,9 +165,9 @@ impl DrawPolygonState {
tb.draw(g, canvas); tb.draw(g, canvas);
return; return;
} }
DrawPolygonState::ListingPolygons(menu, polygons) => { DrawPolygonState::ListingPolygons(menu) => {
menu.draw(g, canvas); menu.draw(g, canvas);
(&polygons[menu.current_choice()].points, None) (&menu.current_choice().points, None)
} }
}; };

View File

@ -40,7 +40,7 @@ impl SearchState {
InputResult::Canceled => { InputResult::Canceled => {
new_state = Some(SearchState::Empty); new_state = Some(SearchState::Empty);
} }
InputResult::Done(filter) => { InputResult::Done(filter, _) => {
new_state = Some(SearchState::FilterOSM(filter)); new_state = Some(SearchState::FilterOSM(filter));
} }
InputResult::StillActive => {} InputResult::StillActive => {}

View File

@ -36,7 +36,7 @@ impl WarpState {
InputResult::Canceled => { InputResult::Canceled => {
new_state = Some(WarpState::Empty); new_state = Some(WarpState::Empty);
} }
InputResult::Done(to) => { InputResult::Done(to, _) => {
warp(to, map, sim, canvas, selected); warp(to, map, sim, canvas, selected);
new_state = Some(WarpState::Empty); new_state = Some(WarpState::Empty);
} }

View File

@ -6,7 +6,6 @@ use piston::input::Key;
use plugins::Colorizer; use plugins::Colorizer;
use polygons; use polygons;
use sim::Tick; use sim::Tick;
use std::collections::BTreeMap;
use std::collections::VecDeque; use std::collections::VecDeque;
#[derive(Debug)] #[derive(Debug)]
@ -66,14 +65,15 @@ impl WizardSample {
pub fn draw(&self, g: &mut GfxCtx, canvas: &Canvas) { pub fn draw(&self, g: &mut GfxCtx, canvas: &Canvas) {
if let WizardSample::Active(wizard) = self { if let WizardSample::Active(wizard) = self {
if let Some(ref menu) = wizard.menu { if let Some(ref menu) = wizard.string_menu {
menu.draw(g, canvas); menu.draw(g, canvas);
if let Some(ref polygons) = wizard.polygons { }
g.draw_polygon( if let Some(ref menu) = wizard.polygon_menu {
[0.0, 0.0, 1.0, 0.6], menu.draw(g, canvas);
&Polygon::new(&polygons[menu.current_choice()].points), g.draw_polygon(
); [0.0, 0.0, 1.0, 0.6],
} &Polygon::new(&menu.current_choice().points),
);
} }
if let Some(ref tb) = wizard.tb { if let Some(ref tb) = wizard.tb {
tb.draw(g, canvas); tb.draw(g, canvas);
@ -101,9 +101,8 @@ fn workflow(mut wizard: WrappedWizard) -> Option<SpawnOverTime> {
pub struct Wizard { pub struct Wizard {
alive: bool, alive: bool,
tb: Option<TextBox>, tb: Option<TextBox>,
menu: Option<Menu>, string_menu: Option<Menu<()>>,
// If this is present, menu also is. polygon_menu: Option<Menu<polygons::PolygonSelection>>,
polygons: Option<BTreeMap<String, polygons::PolygonSelection>>,
state_usize: Vec<usize>, state_usize: Vec<usize>,
state_tick: Vec<Tick>, state_tick: Vec<Tick>,
@ -116,8 +115,8 @@ impl Wizard {
Wizard { Wizard {
alive: true, alive: true,
tb: None, tb: None,
menu: None, string_menu: None,
polygons: None, polygon_menu: None,
state_usize: Vec::new(), state_usize: Vec::new(),
state_tick: Vec::new(), state_tick: Vec::new(),
state_percent: Vec::new(), state_percent: Vec::new(),
@ -147,37 +146,6 @@ impl Wizard {
!self.alive !self.alive
} }
fn input_with_menu(
&mut self,
query: &str,
choices: Vec<String>,
input: &mut UserInput,
) -> Option<String> {
assert!(self.alive);
// Otherwise, we try to use one event for two inputs potentially
if input.has_been_consumed() {
return None;
}
if self.menu.is_none() {
self.menu = Some(Menu::new(query, choices));
}
match self.menu.as_mut().unwrap().event(input) {
InputResult::Canceled => {
self.menu = None;
self.alive = false;
None
}
InputResult::StillActive => None,
InputResult::Done(choice) => {
self.menu = None;
Some(choice)
}
}
}
fn input_with_text_box<R>( fn input_with_text_box<R>(
&mut self, &mut self,
query: &str, query: &str,
@ -201,7 +169,7 @@ impl Wizard {
self.alive = false; self.alive = false;
None None
} }
InputResult::Done(line) => { InputResult::Done(line, _) => {
self.tb = None; self.tb = None;
if let Some(result) = parser(line.clone()) { if let Some(result) = parser(line.clone()) {
Some(result) Some(result)
@ -288,9 +256,17 @@ impl<'a> WrappedWizard<'a> {
if !self.ready_choices.is_empty() { if !self.ready_choices.is_empty() {
return self.ready_choices.pop_front(); return self.ready_choices.pop_front();
} }
if let Some(choice) = self.wizard.input_with_menu(
query, if self.wizard.string_menu.is_none() {
choices.iter().map(|s| s.to_string()).collect(), self.wizard.string_menu = Some(Menu::new(
query,
choices.into_iter().map(|s| (s.to_string(), ())).collect(),
));
}
if let Some((choice, _)) = input_with_menu(
&mut self.wizard.string_menu,
&mut self.wizard.alive,
self.input, self.input,
) { ) {
self.wizard.state_choices.push(choice.clone()); self.wizard.state_choices.push(choice.clone());
@ -304,28 +280,51 @@ impl<'a> WrappedWizard<'a> {
if !self.ready_choices.is_empty() { if !self.ready_choices.is_empty() {
return self.ready_choices.pop_front(); return self.ready_choices.pop_front();
} }
if self.wizard.polygons.is_none() {
self.wizard.polygons = Some(polygons::load_all_polygons(self.map.get_name())); if self.wizard.polygon_menu.is_none() {
} self.wizard.polygon_menu = Some(Menu::new(
let names = self query,
.wizard polygons::load_all_polygons(self.map.get_name()),
.polygons ));
.as_ref()
.unwrap()
.keys()
.cloned()
.collect();
let result = if let Some(choice) = self.wizard.input_with_menu(query, names, self.input) {
self.wizard.state_choices.push(choice.clone());
Some(choice)
} else {
None
};
// TODO Very weird to manage this coupled state like this...
if self.wizard.menu.is_none() {
self.wizard.polygons = None;
} }
result if let Some((name, _)) = input_with_menu(
&mut self.wizard.polygon_menu,
&mut self.wizard.alive,
self.input,
) {
self.wizard.state_choices.push(name.clone());
Some(name)
} else {
None
}
}
}
// The caller initializes the menu, if needed. Pass in Option that must be Some().
// Bit weird to be a free function, but need to borrow a different menu and also the alive bit.
fn input_with_menu<T: Clone>(
menu: &mut Option<Menu<T>>,
alive: &mut bool,
input: &mut UserInput,
) -> Option<(String, T)> {
assert!(*alive);
// Otherwise, we try to use one event for two inputs potentially
if input.has_been_consumed() {
return None;
}
match menu.as_mut().unwrap().event(input) {
InputResult::Canceled => {
*menu = None;
*alive = false;
None
}
InputResult::StillActive => None,
InputResult::Done(name, poly) => {
*menu = None;
Some((name, poly))
}
} }
} }

View File

@ -4,21 +4,19 @@ use std;
use std::collections::BTreeMap; use std::collections::BTreeMap;
// Named polygonal regions // Named polygonal regions
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PolygonSelection { pub struct PolygonSelection {
pub name: String, pub name: String,
pub points: Vec<Pt2D>, pub points: Vec<Pt2D>,
} }
pub fn load_all_polygons(map_name: &str) -> BTreeMap<String, PolygonSelection> { pub fn load_all_polygons(map_name: &str) -> Vec<(String, PolygonSelection)> {
let mut results: BTreeMap<String, PolygonSelection> = BTreeMap::new(); let mut tree: BTreeMap<String, PolygonSelection> = BTreeMap::new();
for entry in std::fs::read_dir(format!("../data/polygons/{}/", map_name)).unwrap() { for entry in std::fs::read_dir(format!("../data/polygons/{}/", map_name)).unwrap() {
let name = entry.unwrap().file_name().into_string().unwrap(); let name = entry.unwrap().file_name().into_string().unwrap();
let load: PolygonSelection = let load: PolygonSelection =
abstutil::read_json(&format!("../data/polygons/{}/{}", map_name, name)).unwrap(); abstutil::read_json(&format!("../data/polygons/{}/{}", map_name, name)).unwrap();
results.insert(name, load); tree.insert(name, load);
} }
results tree.into_iter().collect()
} }
// TODO maybe a wrapper type for the BTreeMap or a way to hook it up easily to a Menu

View File

@ -220,8 +220,8 @@ fn line_to_array(l: &geom::Line) -> [f64; 4] {
[l.pt1().x(), l.pt1().y(), l.pt2().x(), l.pt2().y()] [l.pt1().x(), l.pt1().y(), l.pt2().x(), l.pt2().y()]
} }
pub enum InputResult { pub enum InputResult<T: Clone> {
Canceled, Canceled,
StillActive, StillActive,
Done(String), Done(String, T),
} }

View File

@ -1,14 +1,15 @@
use piston::input::{Button, Key, PressEvent}; use piston::input::{Button, Key, PressEvent};
use {text, Canvas, GfxCtx, InputResult, TextOSD, UserInput}; use {text, Canvas, GfxCtx, InputResult, TextOSD, UserInput};
pub struct Menu { // Stores some associated data with each choice
pub struct Menu<T: Clone> {
prompt: String, prompt: String,
choices: Vec<String>, choices: Vec<(String, T)>,
current_idx: usize, current_idx: usize,
} }
impl Menu { impl<T: Clone> Menu<T> {
pub fn new(prompt: &str, choices: Vec<String>) -> Menu { pub fn new(prompt: &str, choices: Vec<(String, T)>) -> Menu<T> {
Menu { Menu {
prompt: prompt.to_string(), prompt: prompt.to_string(),
choices, choices,
@ -16,7 +17,7 @@ impl Menu {
} }
} }
pub fn event(&mut self, input: &mut UserInput) -> InputResult { pub fn event(&mut self, input: &mut UserInput) -> InputResult<T> {
let ev = input.use_event_directly().clone(); let ev = input.use_event_directly().clone();
input.consume_event(); input.consume_event();
@ -25,7 +26,10 @@ impl Menu {
} }
if let Some(Button::Keyboard(Key::Return)) = ev.press_args() { if let Some(Button::Keyboard(Key::Return)) = ev.press_args() {
return InputResult::Done(self.choices[self.current_idx].clone()); // TODO instead of requiring clone, we could drain choices to take ownership of the
// item. but without consuming self here, it's a bit sketchy to do that.
let (name, item) = self.choices[self.current_idx].clone();
return InputResult::Done(name, item);
} }
if let Some(Button::Keyboard(Key::Up)) = ev.press_args() { if let Some(Button::Keyboard(Key::Up)) = ev.press_args() {
@ -79,7 +83,7 @@ impl Menu {
} }
}; };
for (idx, line) in self.choices.iter().enumerate() { for (idx, (line, _)) in self.choices.iter().enumerate() {
if idx < low_idx || idx > low_idx + can_fit { if idx < low_idx || idx > low_idx + can_fit {
continue; continue;
} }
@ -97,7 +101,7 @@ impl Menu {
canvas.draw_centered_text(g, osd); canvas.draw_centered_text(g, osd);
} }
pub fn current_choice(&self) -> &String { pub fn current_choice(&self) -> &T {
&self.choices[self.current_idx] &self.choices[self.current_idx].1
} }
} }

View File

@ -58,7 +58,7 @@ impl TextBox {
canvas.draw_centered_text(g, osd); canvas.draw_centered_text(g, osd);
} }
pub fn event(&mut self, input: &mut UserInput) -> InputResult { pub fn event(&mut self, input: &mut UserInput) -> InputResult<()> {
let ev = input.use_event_directly().clone(); let ev = input.use_event_directly().clone();
input.consume_event(); input.consume_event();
@ -68,7 +68,7 @@ impl TextBox {
// Done? // Done?
if let Some(Button::Keyboard(Key::Return)) = ev.press_args() { if let Some(Button::Keyboard(Key::Return)) = ev.press_args() {
return InputResult::Done(self.line.clone()); return InputResult::Done(self.line.clone(), ());
} }
// Key state tracking // Key state tracking