mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-27 16:36:02 +03:00
storing associated data with menu
This commit is contained in:
parent
de1033ac66
commit
7836df4580
@ -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)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 => {}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user