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

View File

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

View File

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

View File

@ -6,7 +6,6 @@ use piston::input::Key;
use plugins::Colorizer;
use polygons;
use sim::Tick;
use std::collections::BTreeMap;
use std::collections::VecDeque;
#[derive(Debug)]
@ -66,14 +65,15 @@ impl WizardSample {
pub fn draw(&self, g: &mut GfxCtx, canvas: &Canvas) {
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);
if let Some(ref polygons) = wizard.polygons {
g.draw_polygon(
[0.0, 0.0, 1.0, 0.6],
&Polygon::new(&polygons[menu.current_choice()].points),
);
}
}
if let Some(ref menu) = wizard.polygon_menu {
menu.draw(g, canvas);
g.draw_polygon(
[0.0, 0.0, 1.0, 0.6],
&Polygon::new(&menu.current_choice().points),
);
}
if let Some(ref tb) = wizard.tb {
tb.draw(g, canvas);
@ -101,9 +101,8 @@ fn workflow(mut wizard: WrappedWizard) -> Option<SpawnOverTime> {
pub struct Wizard {
alive: bool,
tb: Option<TextBox>,
menu: Option<Menu>,
// If this is present, menu also is.
polygons: Option<BTreeMap<String, polygons::PolygonSelection>>,
string_menu: Option<Menu<()>>,
polygon_menu: Option<Menu<polygons::PolygonSelection>>,
state_usize: Vec<usize>,
state_tick: Vec<Tick>,
@ -116,8 +115,8 @@ impl Wizard {
Wizard {
alive: true,
tb: None,
menu: None,
polygons: None,
string_menu: None,
polygon_menu: None,
state_usize: Vec::new(),
state_tick: Vec::new(),
state_percent: Vec::new(),
@ -147,37 +146,6 @@ impl Wizard {
!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>(
&mut self,
query: &str,
@ -201,7 +169,7 @@ impl Wizard {
self.alive = false;
None
}
InputResult::Done(line) => {
InputResult::Done(line, _) => {
self.tb = None;
if let Some(result) = parser(line.clone()) {
Some(result)
@ -288,9 +256,17 @@ impl<'a> WrappedWizard<'a> {
if !self.ready_choices.is_empty() {
return self.ready_choices.pop_front();
}
if let Some(choice) = self.wizard.input_with_menu(
query,
choices.iter().map(|s| s.to_string()).collect(),
if self.wizard.string_menu.is_none() {
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.wizard.state_choices.push(choice.clone());
@ -304,28 +280,51 @@ impl<'a> WrappedWizard<'a> {
if !self.ready_choices.is_empty() {
return self.ready_choices.pop_front();
}
if self.wizard.polygons.is_none() {
self.wizard.polygons = Some(polygons::load_all_polygons(self.map.get_name()));
}
let names = self
.wizard
.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;
if self.wizard.polygon_menu.is_none() {
self.wizard.polygon_menu = Some(Menu::new(
query,
polygons::load_all_polygons(self.map.get_name()),
));
}
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;
// Named polygonal regions
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PolygonSelection {
pub name: String,
pub points: Vec<Pt2D>,
}
pub fn load_all_polygons(map_name: &str) -> BTreeMap<String, PolygonSelection> {
let mut results: BTreeMap<String, PolygonSelection> = BTreeMap::new();
pub fn load_all_polygons(map_name: &str) -> Vec<(String, PolygonSelection)> {
let mut tree: BTreeMap<String, PolygonSelection> = BTreeMap::new();
for entry in std::fs::read_dir(format!("../data/polygons/{}/", map_name)).unwrap() {
let name = entry.unwrap().file_name().into_string().unwrap();
let load: PolygonSelection =
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()]
}
pub enum InputResult {
pub enum InputResult<T: Clone> {
Canceled,
StillActive,
Done(String),
Done(String, T),
}

View File

@ -1,14 +1,15 @@
use piston::input::{Button, Key, PressEvent};
use {text, Canvas, GfxCtx, InputResult, TextOSD, UserInput};
pub struct Menu {
// Stores some associated data with each choice
pub struct Menu<T: Clone> {
prompt: String,
choices: Vec<String>,
choices: Vec<(String, T)>,
current_idx: usize,
}
impl Menu {
pub fn new(prompt: &str, choices: Vec<String>) -> Menu {
impl<T: Clone> Menu<T> {
pub fn new(prompt: &str, choices: Vec<(String, T)>) -> Menu<T> {
Menu {
prompt: prompt.to_string(),
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();
input.consume_event();
@ -25,7 +26,10 @@ impl Menu {
}
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() {
@ -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 {
continue;
}
@ -97,7 +101,7 @@ impl Menu {
canvas.draw_centered_text(g, osd);
}
pub fn current_choice(&self) -> &String {
&self.choices[self.current_idx]
pub fn current_choice(&self) -> &T {
&self.choices[self.current_idx].1
}
}

View File

@ -58,7 +58,7 @@ impl TextBox {
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();
input.consume_event();
@ -68,7 +68,7 @@ impl TextBox {
// Done?
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