new warp function that autocompletes street names

This commit is contained in:
Dustin Carlino 2019-04-29 14:52:04 -07:00
parent 30db2e7fc2
commit 3b2c1a1d73
10 changed files with 216 additions and 21 deletions

View File

@ -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);
}

View 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);
}
}
}
}

View File

@ -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 {}",

View File

@ -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;

View File

@ -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]

View File

@ -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> {

View 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
}
}

View File

@ -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};

View File

@ -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,
}

View File

@ -331,4 +331,11 @@ impl Road {
)
}
}
pub fn get_name(&self) -> String {
self.osm_tags
.get("name")
.unwrap_or(&"???".to_string())
.to_string()
}
}