mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-25 11:44:25 +03:00
new warp function that autocompletes street names
This commit is contained in:
parent
30db2e7fc2
commit
3b2c1a1d73
@ -1,4 +1,5 @@
|
||||
mod associated;
|
||||
mod navigate;
|
||||
mod turn_cycler;
|
||||
mod warp;
|
||||
|
||||
@ -11,6 +12,7 @@ pub struct CommonState {
|
||||
associated: associated::ShowAssociatedState,
|
||||
turn_cycler: turn_cycler::TurnCyclerState,
|
||||
warp: Option<warp::WarpState>,
|
||||
navigate: Option<navigate::Navigator>,
|
||||
}
|
||||
|
||||
impl CommonState {
|
||||
@ -19,6 +21,7 @@ impl CommonState {
|
||||
associated: associated::ShowAssociatedState::Inactive,
|
||||
turn_cycler: turn_cycler::TurnCyclerState::new(),
|
||||
warp: None,
|
||||
navigate: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +37,16 @@ impl CommonState {
|
||||
if ctx.input.unimportant_key_pressed(Key::J, "warp") {
|
||||
self.warp = Some(warp::WarpState::new());
|
||||
}
|
||||
if let Some(ref mut navigate) = self.navigate {
|
||||
if let Some(evmode) = navigate.event(ctx, ui) {
|
||||
return Some(evmode);
|
||||
}
|
||||
self.navigate = None;
|
||||
}
|
||||
// TODO This definitely conflicts with some modes.
|
||||
if ctx.input.unimportant_key_pressed(Key::K, "navigate") {
|
||||
self.navigate = Some(navigate::Navigator::new(ui));
|
||||
}
|
||||
|
||||
self.associated.event(ui);
|
||||
self.turn_cycler.event(ctx, ui);
|
||||
@ -51,6 +64,9 @@ impl CommonState {
|
||||
if let Some(ref warp) = self.warp {
|
||||
warp.draw(g);
|
||||
}
|
||||
if let Some(ref navigate) = self.navigate {
|
||||
navigate.draw(g);
|
||||
}
|
||||
self.turn_cycler.draw(g, ui);
|
||||
}
|
||||
|
||||
|
45
editor/src/common/navigate.rs
Normal file
45
editor/src/common/navigate.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use crate::ui::UI;
|
||||
use ezgui::{Autocomplete, EventCtx, EventLoopMode, GfxCtx, InputResult};
|
||||
use map_model::RoadID;
|
||||
|
||||
pub enum Navigator {
|
||||
// TODO Ask for a cross-street after the first one
|
||||
Searching(Autocomplete<RoadID>),
|
||||
}
|
||||
|
||||
impl Navigator {
|
||||
pub fn new(ui: &UI) -> Navigator {
|
||||
// TODO Canonicalize names, handling abbreviations like east/e and street/st
|
||||
Navigator::Searching(Autocomplete::new(
|
||||
"Warp to what?",
|
||||
ui.primary
|
||||
.map
|
||||
.all_roads()
|
||||
.iter()
|
||||
.map(|r| (r.get_name(), r.id))
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
// When None, this is done.
|
||||
pub fn event(&mut self, ctx: &mut EventCtx, ui: &UI) -> Option<EventLoopMode> {
|
||||
match self {
|
||||
Navigator::Searching(autocomplete) => match autocomplete.event(ctx.input) {
|
||||
InputResult::Canceled => None,
|
||||
InputResult::Done(name, ids) => {
|
||||
println!("Search for '{}' yielded {:?}", name, ids);
|
||||
None
|
||||
}
|
||||
InputResult::StillActive => Some(EventLoopMode::InputOnly),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
match self {
|
||||
Navigator::Searching(ref autocomplete) => {
|
||||
autocomplete.draw(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -101,13 +101,7 @@ impl ID {
|
||||
ID::Road(id) => {
|
||||
let r = map.get_r(id);
|
||||
txt.add_line(format!("{} (originally {}) is ", r.id, r.stable_id));
|
||||
txt.append(
|
||||
r.osm_tags
|
||||
.get("name")
|
||||
.unwrap_or(&"???".to_string())
|
||||
.to_string(),
|
||||
Some(Color::CYAN),
|
||||
);
|
||||
txt.append(r.get_name(), Some(Color::CYAN));
|
||||
txt.add_line(format!("From OSM way {}", r.osm_way_id));
|
||||
}
|
||||
ID::Lane(id) => {
|
||||
@ -117,13 +111,7 @@ impl ID {
|
||||
let i2 = map.get_destination_intersection(id);
|
||||
|
||||
txt.add_line(format!("{} is ", l.id));
|
||||
txt.append(
|
||||
r.osm_tags
|
||||
.get("name")
|
||||
.unwrap_or(&"???".to_string())
|
||||
.to_string(),
|
||||
Some(Color::CYAN),
|
||||
);
|
||||
txt.append(r.get_name(), Some(Color::CYAN));
|
||||
txt.add_line(format!("From OSM way {}", r.osm_way_id));
|
||||
txt.add_line(format!(
|
||||
"Parent {} (originally {}) points to {}",
|
||||
|
@ -61,6 +61,9 @@ impl SandboxMode {
|
||||
&ShowEverything::new(),
|
||||
false,
|
||||
);
|
||||
if let Some(evmode) = mode.common.event(ctx, &state.ui) {
|
||||
return evmode;
|
||||
}
|
||||
|
||||
if let State::Spawning(ref mut spawner) = mode.state {
|
||||
if spawner.event(ctx, &mut state.ui) {
|
||||
@ -111,10 +114,6 @@ impl SandboxMode {
|
||||
ctx.input
|
||||
.set_mode_with_new_prompt("Sandbox Mode", txt, ctx.canvas);
|
||||
|
||||
if let Some(evmode) = mode.common.event(ctx, &state.ui) {
|
||||
return evmode;
|
||||
}
|
||||
|
||||
if let Some(spawner) = spawner::AgentSpawner::new(ctx, &mut state.ui) {
|
||||
mode.state = State::Spawning(spawner);
|
||||
return EventLoopMode::InputOnly;
|
||||
|
@ -13,6 +13,7 @@ glutin = "0.20.0"
|
||||
palette = "0.4"
|
||||
serde = "1.0.89"
|
||||
serde_derive = "1.0.89"
|
||||
simsearch = "0.1.4"
|
||||
textwrap = "0.11"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
|
@ -19,7 +19,7 @@ pub use crate::runner::{run, EventLoopMode, GUI};
|
||||
pub use crate::screen_geom::ScreenPt;
|
||||
pub use crate::text::Text;
|
||||
pub use crate::widgets::{
|
||||
Folder, LogScroller, ScrollingMenu, TextBox, TopMenu, Wizard, WrappedWizard,
|
||||
Autocomplete, Folder, LogScroller, ScrollingMenu, TextBox, TopMenu, Wizard, WrappedWizard,
|
||||
};
|
||||
|
||||
pub enum InputResult<T: Clone> {
|
||||
|
138
ezgui/src/widgets/autocomplete.rs
Normal file
138
ezgui/src/widgets/autocomplete.rs
Normal file
@ -0,0 +1,138 @@
|
||||
use crate::{text, Event, GfxCtx, InputResult, Key, Text, UserInput, CENTERED};
|
||||
use simsearch::SimSearch;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::hash::Hash;
|
||||
|
||||
const NUM_SEARCH_RESULTS: usize = 5;
|
||||
|
||||
pub struct Autocomplete<T: Clone + Hash + Eq> {
|
||||
prompt: String,
|
||||
choices: BTreeMap<String, HashSet<T>>,
|
||||
// Maps index to choice
|
||||
search_map: Vec<String>,
|
||||
search: SimSearch<usize>,
|
||||
|
||||
line: String,
|
||||
cursor_x: usize,
|
||||
shift_pressed: bool,
|
||||
current_results: Vec<usize>,
|
||||
cursor_y: usize,
|
||||
}
|
||||
|
||||
impl<T: Clone + Hash + Eq> Autocomplete<T> {
|
||||
pub fn new(prompt: &str, choices_list: Vec<(String, T)>) -> Autocomplete<T> {
|
||||
let mut choices = BTreeMap::new();
|
||||
for (name, data) in choices_list {
|
||||
if !choices.contains_key(&name) {
|
||||
choices.insert(name.clone(), HashSet::new());
|
||||
}
|
||||
choices.get_mut(&name).unwrap().insert(data);
|
||||
}
|
||||
let mut search_map = Vec::new();
|
||||
let mut search = SimSearch::new();
|
||||
let mut current_results = Vec::new();
|
||||
for (idx, name) in choices.keys().enumerate() {
|
||||
search_map.push(name.to_string());
|
||||
search.insert(idx, name);
|
||||
if idx < NUM_SEARCH_RESULTS {
|
||||
current_results.push(idx);
|
||||
}
|
||||
}
|
||||
|
||||
Autocomplete {
|
||||
prompt: prompt.to_string(),
|
||||
choices,
|
||||
search_map,
|
||||
search,
|
||||
|
||||
line: String::new(),
|
||||
cursor_x: 0,
|
||||
shift_pressed: false,
|
||||
current_results,
|
||||
cursor_y: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
let mut txt = Text::new();
|
||||
txt.add_styled_line(self.prompt.clone(), None, Some(text::PROMPT_COLOR), None);
|
||||
|
||||
txt.add_line(self.line[0..self.cursor_x].to_string());
|
||||
if self.cursor_x < self.line.len() {
|
||||
// TODO This "cursor" looks awful!
|
||||
txt.append("|".to_string(), Some(text::SELECTED_COLOR));
|
||||
txt.append(self.line[self.cursor_x..=self.cursor_x].to_string(), None);
|
||||
txt.append(self.line[self.cursor_x + 1..].to_string(), None);
|
||||
} else {
|
||||
txt.append("|".to_string(), Some(text::SELECTED_COLOR));
|
||||
}
|
||||
|
||||
for (idx, id) in self.current_results.iter().enumerate() {
|
||||
if idx == self.cursor_y {
|
||||
txt.add_styled_line(
|
||||
self.search_map[*id].clone(),
|
||||
None,
|
||||
Some(text::SELECTED_COLOR),
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
txt.add_line(self.search_map[*id].clone());
|
||||
}
|
||||
}
|
||||
|
||||
g.draw_blocking_text(&txt, CENTERED);
|
||||
}
|
||||
|
||||
pub fn event(&mut self, input: &mut UserInput) -> InputResult<HashSet<T>> {
|
||||
let maybe_ev = input.use_event_directly();
|
||||
if maybe_ev.is_none() {
|
||||
return InputResult::StillActive;
|
||||
}
|
||||
let ev = maybe_ev.unwrap();
|
||||
|
||||
if ev == Event::KeyPress(Key::Escape) {
|
||||
return InputResult::Canceled;
|
||||
} else if ev == Event::KeyPress(Key::Enter) {
|
||||
if self.current_results.is_empty() {
|
||||
return InputResult::Canceled;
|
||||
}
|
||||
let name = &self.search_map[self.current_results[self.cursor_y]];
|
||||
return InputResult::Done(name.to_string(), self.choices.remove(name).unwrap());
|
||||
} else if ev == Event::KeyPress(Key::LeftShift) {
|
||||
self.shift_pressed = true;
|
||||
} else if ev == Event::KeyRelease(Key::LeftShift) {
|
||||
self.shift_pressed = false;
|
||||
} else if ev == Event::KeyPress(Key::LeftArrow) {
|
||||
if self.cursor_x > 0 {
|
||||
self.cursor_x -= 1;
|
||||
}
|
||||
} else if ev == Event::KeyPress(Key::RightArrow) {
|
||||
self.cursor_x = (self.cursor_x + 1).min(self.line.len());
|
||||
} else if ev == Event::KeyPress(Key::UpArrow) {
|
||||
if self.cursor_y > 0 {
|
||||
self.cursor_y -= 1;
|
||||
}
|
||||
} else if ev == Event::KeyPress(Key::DownArrow) {
|
||||
self.cursor_y = (self.cursor_y + 1).min(self.current_results.len() - 1);
|
||||
} else if ev == Event::KeyPress(Key::Backspace) {
|
||||
if self.cursor_x > 0 {
|
||||
self.line.remove(self.cursor_x - 1);
|
||||
self.cursor_x -= 1;
|
||||
|
||||
self.current_results = self.search.search(&self.line);
|
||||
self.current_results.truncate(NUM_SEARCH_RESULTS);
|
||||
self.cursor_y = 0;
|
||||
}
|
||||
} else if let Event::KeyPress(key) = ev {
|
||||
if let Some(c) = key.to_char(self.shift_pressed) {
|
||||
self.line.insert(self.cursor_x, c);
|
||||
self.cursor_x += 1;
|
||||
|
||||
self.current_results = self.search.search(&self.line);
|
||||
self.current_results.truncate(NUM_SEARCH_RESULTS);
|
||||
self.cursor_y = 0;
|
||||
}
|
||||
};
|
||||
InputResult::StillActive
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
mod autocomplete;
|
||||
mod log_scroller;
|
||||
mod menu;
|
||||
mod screenshot;
|
||||
@ -6,6 +7,7 @@ mod text_box;
|
||||
mod top_menu;
|
||||
mod wizard;
|
||||
|
||||
pub use self::autocomplete::Autocomplete;
|
||||
pub use self::log_scroller::LogScroller;
|
||||
pub use self::menu::{Menu, Position};
|
||||
pub(crate) use self::screenshot::{screenshot_current, screenshot_everything};
|
||||
|
@ -5,8 +5,7 @@ use crate::{text, Event, GfxCtx, InputResult, Key, Text, UserInput, CENTERED};
|
||||
pub struct TextBox {
|
||||
prompt: String,
|
||||
// TODO A rope would be cool.
|
||||
// TODO dont be pub
|
||||
pub line: String,
|
||||
line: String,
|
||||
cursor_x: usize,
|
||||
shift_pressed: bool,
|
||||
}
|
||||
|
@ -331,4 +331,11 @@ impl Road {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> String {
|
||||
self.osm_tags
|
||||
.get("name")
|
||||
.unwrap_or(&"???".to_string())
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user