mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 20:29:04 +03:00
enter signal metadata via a form with multiple textboxes at once. needs
work.
This commit is contained in:
parent
1ad434f3d6
commit
0fd4de749d
@ -6,8 +6,7 @@ use geom::{Angle, Duration, Polygon, Pt2D};
|
|||||||
|
|
||||||
// TODO Add text to the logo (showing zoom)
|
// TODO Add text to the logo (showing zoom)
|
||||||
// TODO Some kind of plot?!
|
// TODO Some kind of plot?!
|
||||||
// TODO Some popup dialogs?
|
// TODO Some popup dialogs with form entry, even some scrolling
|
||||||
// TODO Something scrolling
|
|
||||||
// TODO Loading screen with timer?
|
// TODO Loading screen with timer?
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
|
@ -41,6 +41,18 @@ impl UserInput {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn any_key_pressed(&mut self) -> Option<Key> {
|
||||||
|
if self.event_consumed {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Event::KeyPress(key) = self.event {
|
||||||
|
self.consume_event();
|
||||||
|
return Some(key);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn unimportant_key_pressed(&mut self, key: Key, action: &str) -> bool {
|
pub fn unimportant_key_pressed(&mut self, key: Key, action: &str) -> bool {
|
||||||
self.reserve_key(key, action);
|
self.reserve_key(key, action);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::layout::Widget;
|
use crate::layout::Widget;
|
||||||
use crate::widgets::{Checkbox, PopupMenu};
|
use crate::text;
|
||||||
|
use crate::widgets::{Checkbox, PopupMenu, TextBox};
|
||||||
use crate::{
|
use crate::{
|
||||||
Button, Color, Drawable, EventCtx, Filler, GeomBatch, GfxCtx, Histogram, HorizontalAlignment,
|
Button, Color, Drawable, EventCtx, Filler, GeomBatch, GfxCtx, Histogram, HorizontalAlignment,
|
||||||
JustDraw, MultiKey, Plot, RewriteColor, ScreenDims, ScreenPt, ScreenRectangle, Slider, Text,
|
JustDraw, MultiKey, Plot, RewriteColor, ScreenDims, ScreenPt, ScreenRectangle, Slider, Text,
|
||||||
@ -30,6 +31,7 @@ enum WidgetType {
|
|||||||
Draw(JustDraw),
|
Draw(JustDraw),
|
||||||
Btn(Button),
|
Btn(Button),
|
||||||
Checkbox(Checkbox),
|
Checkbox(Checkbox),
|
||||||
|
TextBox(TextBox),
|
||||||
Slider(String),
|
Slider(String),
|
||||||
Menu(String),
|
Menu(String),
|
||||||
Filler(String),
|
Filler(String),
|
||||||
@ -289,6 +291,11 @@ impl ManagedWidget {
|
|||||||
.named(label)
|
.named(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn text_entry(ctx: &EventCtx, prefilled: String) -> ManagedWidget {
|
||||||
|
// TODO Hardcoded style, max chars
|
||||||
|
ManagedWidget::new(WidgetType::TextBox(TextBox::new(ctx, 50, prefilled))).bg(text::BG_COLOR)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn duration_plot(plot: Plot<Duration>) -> ManagedWidget {
|
pub(crate) fn duration_plot(plot: Plot<Duration>) -> ManagedWidget {
|
||||||
ManagedWidget::new(WidgetType::DurationPlot(plot))
|
ManagedWidget::new(WidgetType::DurationPlot(plot))
|
||||||
}
|
}
|
||||||
@ -329,6 +336,9 @@ impl ManagedWidget {
|
|||||||
WidgetType::Checkbox(ref mut checkbox) => {
|
WidgetType::Checkbox(ref mut checkbox) => {
|
||||||
checkbox.event(ctx);
|
checkbox.event(ctx);
|
||||||
}
|
}
|
||||||
|
WidgetType::TextBox(ref mut textbox) => {
|
||||||
|
textbox.event(ctx);
|
||||||
|
}
|
||||||
WidgetType::Slider(ref name) => {
|
WidgetType::Slider(ref name) => {
|
||||||
sliders.get_mut(name).unwrap().event(ctx);
|
sliders.get_mut(name).unwrap().event(ctx);
|
||||||
}
|
}
|
||||||
@ -364,6 +374,7 @@ impl ManagedWidget {
|
|||||||
WidgetType::Draw(ref j) => j.draw(g),
|
WidgetType::Draw(ref j) => j.draw(g),
|
||||||
WidgetType::Btn(ref btn) => btn.draw(g),
|
WidgetType::Btn(ref btn) => btn.draw(g),
|
||||||
WidgetType::Checkbox(ref checkbox) => checkbox.draw(g),
|
WidgetType::Checkbox(ref checkbox) => checkbox.draw(g),
|
||||||
|
WidgetType::TextBox(ref textbox) => textbox.draw(g),
|
||||||
WidgetType::Slider(ref name) => {
|
WidgetType::Slider(ref name) => {
|
||||||
if name != "horiz scrollbar" && name != "vert scrollbar" {
|
if name != "horiz scrollbar" && name != "vert scrollbar" {
|
||||||
sliders[name].draw(g);
|
sliders[name].draw(g);
|
||||||
@ -397,6 +408,7 @@ impl ManagedWidget {
|
|||||||
WidgetType::Draw(ref widget) => widget,
|
WidgetType::Draw(ref widget) => widget,
|
||||||
WidgetType::Btn(ref widget) => widget,
|
WidgetType::Btn(ref widget) => widget,
|
||||||
WidgetType::Checkbox(ref widget) => widget,
|
WidgetType::Checkbox(ref widget) => widget,
|
||||||
|
WidgetType::TextBox(ref widget) => widget,
|
||||||
WidgetType::Slider(ref name) => &sliders[name],
|
WidgetType::Slider(ref name) => &sliders[name],
|
||||||
WidgetType::Menu(ref name) => &menus[name],
|
WidgetType::Menu(ref name) => &menus[name],
|
||||||
WidgetType::Filler(ref name) => &fillers[name],
|
WidgetType::Filler(ref name) => &fillers[name],
|
||||||
@ -505,6 +517,9 @@ impl ManagedWidget {
|
|||||||
WidgetType::Checkbox(ref mut widget) => {
|
WidgetType::Checkbox(ref mut widget) => {
|
||||||
widget.set_pos(top_left);
|
widget.set_pos(top_left);
|
||||||
}
|
}
|
||||||
|
WidgetType::TextBox(ref mut widget) => {
|
||||||
|
widget.set_pos(top_left);
|
||||||
|
}
|
||||||
WidgetType::Slider(ref name) => {
|
WidgetType::Slider(ref name) => {
|
||||||
sliders.get_mut(name).unwrap().set_pos(top_left);
|
sliders.get_mut(name).unwrap().set_pos(top_left);
|
||||||
}
|
}
|
||||||
@ -566,6 +581,7 @@ impl ManagedWidget {
|
|||||||
| WidgetType::Menu(_)
|
| WidgetType::Menu(_)
|
||||||
| WidgetType::Filler(_)
|
| WidgetType::Filler(_)
|
||||||
| WidgetType::Checkbox(_)
|
| WidgetType::Checkbox(_)
|
||||||
|
| WidgetType::TextBox(_)
|
||||||
| WidgetType::DurationPlot(_)
|
| WidgetType::DurationPlot(_)
|
||||||
| WidgetType::UsizePlot(_) => {}
|
| WidgetType::UsizePlot(_) => {}
|
||||||
WidgetType::Histogram(_) => {}
|
WidgetType::Histogram(_) => {}
|
||||||
@ -597,7 +613,9 @@ impl ManagedWidget {
|
|||||||
fn find(&self, name: &str) -> Option<&ManagedWidget> {
|
fn find(&self, name: &str) -> Option<&ManagedWidget> {
|
||||||
let found = match self.widget {
|
let found = match self.widget {
|
||||||
// TODO Consolidate and just do this
|
// TODO Consolidate and just do this
|
||||||
WidgetType::Draw(_) | WidgetType::Checkbox(_) => self.id == Some(name.to_string()),
|
WidgetType::Draw(_) | WidgetType::Checkbox(_) | WidgetType::TextBox(_) => {
|
||||||
|
self.id == Some(name.to_string())
|
||||||
|
}
|
||||||
WidgetType::Btn(ref btn) => btn.action == name,
|
WidgetType::Btn(ref btn) => btn.action == name,
|
||||||
WidgetType::Slider(ref n) => n == name,
|
WidgetType::Slider(ref n) => n == name,
|
||||||
WidgetType::Menu(ref n) => n == name,
|
WidgetType::Menu(ref n) => n == name,
|
||||||
@ -623,7 +641,9 @@ impl ManagedWidget {
|
|||||||
fn find_mut(&mut self, name: &str) -> Option<&mut ManagedWidget> {
|
fn find_mut(&mut self, name: &str) -> Option<&mut ManagedWidget> {
|
||||||
let found = match self.widget {
|
let found = match self.widget {
|
||||||
// TODO Consolidate and just do this
|
// TODO Consolidate and just do this
|
||||||
WidgetType::Draw(_) | WidgetType::Checkbox(_) => self.id == Some(name.to_string()),
|
WidgetType::Draw(_) | WidgetType::Checkbox(_) | WidgetType::TextBox(_) => {
|
||||||
|
self.id == Some(name.to_string())
|
||||||
|
}
|
||||||
WidgetType::Btn(ref btn) => btn.action == name,
|
WidgetType::Btn(ref btn) => btn.action == name,
|
||||||
WidgetType::Slider(ref n) => n == name,
|
WidgetType::Slider(ref n) => n == name,
|
||||||
WidgetType::Menu(ref n) => n == name,
|
WidgetType::Menu(ref n) => n == name,
|
||||||
@ -925,6 +945,13 @@ impl Composite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn text_box(&self, name: &str) -> String {
|
||||||
|
match self.find(name).widget {
|
||||||
|
WidgetType::TextBox(ref textbox) => textbox.get_entry(),
|
||||||
|
_ => panic!("{} isn't a textbox", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn filler_rect(&self, name: &str) -> ScreenRectangle {
|
pub fn filler_rect(&self, name: &str) -> ScreenRectangle {
|
||||||
let f = &self.fillers[name];
|
let f = &self.fillers[name];
|
||||||
ScreenRectangle::top_left(f.top_left, f.dims)
|
ScreenRectangle::top_left(f.top_left, f.dims)
|
||||||
|
@ -14,7 +14,7 @@ pub const INACTIVE_CHOICE_COLOR: Color = Color::grey(0.4);
|
|||||||
pub const SCALE_LINE_HEIGHT: f64 = 1.2;
|
pub const SCALE_LINE_HEIGHT: f64 = 1.2;
|
||||||
|
|
||||||
// TODO Don't do this!
|
// TODO Don't do this!
|
||||||
const MAX_CHAR_WIDTH: f64 = 25.0;
|
pub const MAX_CHAR_WIDTH: f64 = 25.0;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum Font {
|
pub enum Font {
|
||||||
|
@ -24,5 +24,6 @@ pub use self::plot::{Plot, PlotOptions, Series};
|
|||||||
pub(crate) use self::popup_menu::PopupMenu;
|
pub(crate) use self::popup_menu::PopupMenu;
|
||||||
pub(crate) use self::screenshot::{screenshot_current, screenshot_everything};
|
pub(crate) use self::screenshot::{screenshot_current, screenshot_everything};
|
||||||
pub use self::slider::{ItemSlider, Slider, WarpingItemSlider};
|
pub use self::slider::{ItemSlider, Slider, WarpingItemSlider};
|
||||||
|
pub(crate) use self::text_box::TextBox;
|
||||||
pub use self::warper::Warper;
|
pub use self::warper::Warper;
|
||||||
pub use self::wizard::{Choice, Wizard, WrappedWizard};
|
pub use self::wizard::{Choice, Wizard, WrappedWizard};
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
use crate::layout::Widget;
|
use crate::layout::Widget;
|
||||||
use crate::{
|
use crate::{text, EventCtx, GfxCtx, Key, Line, ScreenDims, ScreenPt, Text};
|
||||||
text, Event, EventCtx, GfxCtx, InputResult, Key, Line, ScreenDims, ScreenPt, Text, UserInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO right now, only a single line
|
// TODO right now, only a single line
|
||||||
|
|
||||||
pub struct TextBox {
|
pub struct TextBox {
|
||||||
prompt: String,
|
|
||||||
// TODO A rope would be cool.
|
// TODO A rope would be cool.
|
||||||
line: String,
|
line: String,
|
||||||
cursor_x: usize,
|
cursor_x: usize,
|
||||||
@ -17,25 +14,65 @@ pub struct TextBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TextBox {
|
impl TextBox {
|
||||||
pub fn new(ctx: &EventCtx, prompt: &str, prefilled: Option<String>) -> TextBox {
|
pub fn new(ctx: &EventCtx, max_chars: usize, prefilled: String) -> TextBox {
|
||||||
let line = prefilled.unwrap_or_else(String::new);
|
TextBox {
|
||||||
let mut tb = TextBox {
|
cursor_x: prefilled.len(),
|
||||||
prompt: prompt.to_string(),
|
line: prefilled,
|
||||||
cursor_x: line.len(),
|
|
||||||
line,
|
|
||||||
shift_pressed: false,
|
shift_pressed: false,
|
||||||
|
|
||||||
top_left: ScreenPt::new(0.0, 0.0),
|
top_left: ScreenPt::new(0.0, 0.0),
|
||||||
dims: ScreenDims::new(0.0, 0.0),
|
dims: ScreenDims::new(
|
||||||
};
|
(max_chars as f64) * text::MAX_CHAR_WIDTH,
|
||||||
// TODO Assume the dims never exceed the prompt width?
|
ctx.default_line_height(),
|
||||||
tb.dims = tb.get_text().dims(&ctx.prerender.assets);
|
),
|
||||||
tb
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_text(&self) -> Text {
|
pub fn event(&mut self, ctx: &mut EventCtx) {
|
||||||
let mut txt = Text::from(Line(&self.prompt).roboto_bold()).with_bg();
|
if let Some(key) = ctx.input.any_key_pressed() {
|
||||||
txt.add(Line(&self.line[0..self.cursor_x]));
|
match key {
|
||||||
|
Key::LeftShift => {
|
||||||
|
self.shift_pressed = true;
|
||||||
|
}
|
||||||
|
Key::LeftArrow => {
|
||||||
|
if self.cursor_x > 0 {
|
||||||
|
self.cursor_x -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Key::RightArrow => {
|
||||||
|
self.cursor_x = (self.cursor_x + 1).min(self.line.len());
|
||||||
|
}
|
||||||
|
Key::Backspace => {
|
||||||
|
if self.cursor_x > 0 {
|
||||||
|
self.line.remove(self.cursor_x - 1);
|
||||||
|
self.cursor_x -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(c) = key.to_char(self.shift_pressed) {
|
||||||
|
self.line.insert(self.cursor_x, c);
|
||||||
|
self.cursor_x += 1;
|
||||||
|
} else {
|
||||||
|
ctx.input.unconsume_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ctx.input.key_released(Key::LeftShift) {
|
||||||
|
self.shift_pressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, g: &mut GfxCtx) {
|
||||||
|
g.draw_blocking_text_at_screenspace_topleft(self.calculate_text(), self.top_left);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_entry(&self) -> String {
|
||||||
|
self.line.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_text(&self) -> Text {
|
||||||
|
let mut txt = Text::from(Line(&self.line[0..self.cursor_x]));
|
||||||
if self.cursor_x < self.line.len() {
|
if self.cursor_x < self.line.len() {
|
||||||
// TODO This "cursor" looks awful!
|
// TODO This "cursor" looks awful!
|
||||||
txt.append_all(vec![
|
txt.append_all(vec![
|
||||||
@ -48,45 +85,6 @@ impl TextBox {
|
|||||||
}
|
}
|
||||||
txt
|
txt
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event(&mut self, input: &mut UserInput) -> InputResult<()> {
|
|
||||||
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) {
|
|
||||||
return InputResult::Done(self.line.clone(), ());
|
|
||||||
} 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::Backspace) {
|
|
||||||
if self.cursor_x > 0 {
|
|
||||||
self.line.remove(self.cursor_x - 1);
|
|
||||||
self.cursor_x -= 1;
|
|
||||||
}
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
InputResult::StillActive
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(&self, g: &mut GfxCtx) {
|
|
||||||
g.draw_blocking_text_at_screenspace_topleft(self.get_text(), self.top_left);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for TextBox {
|
impl Widget for TextBox {
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
use crate::widgets::text_box::TextBox;
|
|
||||||
use crate::widgets::PopupMenu;
|
use crate::widgets::PopupMenu;
|
||||||
use crate::{
|
use crate::{
|
||||||
hotkey, layout, Button, Color, Composite, EventCtx, GfxCtx, HorizontalAlignment, InputResult,
|
hotkey, Button, Color, Composite, EventCtx, GfxCtx, HorizontalAlignment, InputResult, Key,
|
||||||
Key, Line, ManagedWidget, MultiKey, Outcome, Text, VerticalAlignment,
|
Line, ManagedWidget, MultiKey, Outcome, Text, VerticalAlignment,
|
||||||
};
|
};
|
||||||
use abstutil::Cloneable;
|
use abstutil::Cloneable;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
pub struct Wizard {
|
pub struct Wizard {
|
||||||
alive: bool,
|
alive: bool,
|
||||||
tb: Option<TextBox>,
|
tb_comp: Option<Composite>,
|
||||||
menu_comp: Option<Composite>,
|
menu_comp: Option<Composite>,
|
||||||
ack: Option<Composite>,
|
ack: Option<Composite>,
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ impl Wizard {
|
|||||||
pub fn new() -> Wizard {
|
pub fn new() -> Wizard {
|
||||||
Wizard {
|
Wizard {
|
||||||
alive: true,
|
alive: true,
|
||||||
tb: None,
|
tb_comp: None,
|
||||||
menu_comp: None,
|
menu_comp: None,
|
||||||
ack: None,
|
ack: None,
|
||||||
confirmed_state: Vec::new(),
|
confirmed_state: Vec::new(),
|
||||||
@ -32,8 +31,8 @@ impl Wizard {
|
|||||||
if let Some(ref comp) = self.menu_comp {
|
if let Some(ref comp) = self.menu_comp {
|
||||||
comp.draw(g);
|
comp.draw(g);
|
||||||
}
|
}
|
||||||
if let Some(ref tb) = self.tb {
|
if let Some(ref comp) = self.tb_comp {
|
||||||
tb.draw(g);
|
comp.draw(g);
|
||||||
}
|
}
|
||||||
if let Some(ref s) = self.ack {
|
if let Some(ref s) = self.ack {
|
||||||
s.draw(g);
|
s.draw(g);
|
||||||
@ -82,30 +81,69 @@ impl Wizard {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.tb.is_none() {
|
if self.tb_comp.is_none() {
|
||||||
self.tb = Some(TextBox::new(ctx, query, prefilled));
|
self.tb_comp = Some(
|
||||||
|
Composite::new(
|
||||||
|
ManagedWidget::col(vec![
|
||||||
|
ManagedWidget::row(vec![
|
||||||
|
ManagedWidget::draw_text(ctx, Text::from(Line(query).roboto_bold())),
|
||||||
|
// TODO nice text button
|
||||||
|
ManagedWidget::btn(Button::text_bg(
|
||||||
|
Text::from(Line("X").fg(Color::BLACK)),
|
||||||
|
Color::WHITE,
|
||||||
|
Color::ORANGE,
|
||||||
|
hotkey(Key::Escape),
|
||||||
|
"quit",
|
||||||
|
ctx,
|
||||||
|
))
|
||||||
|
.margin(5)
|
||||||
|
.align_right(),
|
||||||
|
]),
|
||||||
|
ManagedWidget::text_entry(ctx, prefilled.unwrap_or_else(String::new))
|
||||||
|
.named("input"),
|
||||||
|
ManagedWidget::btn(Button::text_bg(
|
||||||
|
Text::from(Line("Done").fg(Color::BLACK)),
|
||||||
|
Color::WHITE,
|
||||||
|
Color::ORANGE,
|
||||||
|
hotkey(Key::Enter),
|
||||||
|
"done",
|
||||||
|
ctx,
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
.bg(Color::grey(0.4))
|
||||||
|
.outline(5.0, Color::WHITE)
|
||||||
|
.padding(5),
|
||||||
|
)
|
||||||
|
.build(ctx),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
layout::stack_vertically(
|
|
||||||
layout::ContainerOrientation::Centered,
|
|
||||||
ctx,
|
|
||||||
vec![self.tb.as_mut().unwrap()],
|
|
||||||
);
|
|
||||||
|
|
||||||
match self.tb.as_mut().unwrap().event(&mut ctx.input) {
|
assert!(self.alive);
|
||||||
InputResult::StillActive => None,
|
|
||||||
InputResult::Canceled => {
|
// Otherwise, we try to use one event for two inputs potentially
|
||||||
self.alive = false;
|
if ctx.input.has_been_consumed() {
|
||||||
None
|
return None;
|
||||||
}
|
}
|
||||||
InputResult::Done(line, _) => {
|
|
||||||
self.tb = None;
|
match self.tb_comp.as_mut().unwrap().event(ctx) {
|
||||||
if let Some(result) = parser(line.clone()) {
|
Some(Outcome::Clicked(x)) => match x.as_ref() {
|
||||||
Some(result)
|
"quit" => {
|
||||||
} else {
|
self.alive = false;
|
||||||
println!("Invalid input {}", line);
|
self.tb_comp = None;
|
||||||
None
|
return None;
|
||||||
}
|
}
|
||||||
}
|
"done" => {
|
||||||
|
let line = self.tb_comp.take().unwrap().text_box("input");
|
||||||
|
if let Some(result) = parser(line.clone()) {
|
||||||
|
Some(result)
|
||||||
|
} else {
|
||||||
|
println!("Invalid input {}", line);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -399,7 +437,7 @@ impl<'a, 'b> WrappedWizard<'a, 'b> {
|
|||||||
|
|
||||||
// If the control flow through a wizard block needs to change, might need to call this.
|
// If the control flow through a wizard block needs to change, might need to call this.
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
assert!(self.wizard.tb.is_none());
|
assert!(self.wizard.tb_comp.is_none());
|
||||||
assert!(self.wizard.menu_comp.is_none());
|
assert!(self.wizard.menu_comp.is_none());
|
||||||
assert!(self.wizard.ack.is_none());
|
assert!(self.wizard.ack.is_none());
|
||||||
self.wizard.confirmed_state.clear();
|
self.wizard.confirmed_state.clear();
|
||||||
|
@ -177,7 +177,12 @@ impl State for TrafficSignalEditor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
"Edit metadata" => {
|
"Edit metadata" => {
|
||||||
return Transition::Push(edit_md(self.i));
|
// TODO Not sure which one I prefer usability or code wise...
|
||||||
|
if true {
|
||||||
|
return Transition::Push(Box::new(EditMetadata::new(ctx, app, self.i)));
|
||||||
|
} else {
|
||||||
|
return Transition::Push(edit_md(self.i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"Export" => {
|
"Export" => {
|
||||||
if orig_signal.observation_md.is_none() {
|
if orig_signal.observation_md.is_none() {
|
||||||
@ -879,6 +884,140 @@ impl State for PreviewTrafficSignal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct EditMetadata {
|
||||||
|
composite: Composite,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditMetadata {
|
||||||
|
fn new(ctx: &mut EventCtx, app: &App, i: IntersectionID) -> EditMetadata {
|
||||||
|
let default = traffic_signals::Metadata {
|
||||||
|
author: "Anonymous".to_string(),
|
||||||
|
datetime: "MM/DD/YYYY HH:MM:SS".to_string(),
|
||||||
|
notes: "no notes".to_string(),
|
||||||
|
};
|
||||||
|
let prev_observed = app
|
||||||
|
.primary
|
||||||
|
.map
|
||||||
|
.get_traffic_signal(i)
|
||||||
|
.observation_md
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| default.clone());
|
||||||
|
// TODO Always ask this?
|
||||||
|
let prev_audited = app
|
||||||
|
.primary
|
||||||
|
.map
|
||||||
|
.get_traffic_signal(i)
|
||||||
|
.audit_md
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| default.clone());
|
||||||
|
|
||||||
|
EditMetadata {
|
||||||
|
composite: Composite::new(
|
||||||
|
ManagedWidget::col(vec![
|
||||||
|
ManagedWidget::row(vec![
|
||||||
|
ManagedWidget::draw_text(
|
||||||
|
ctx,
|
||||||
|
Text::from(Line("Metadata about the traffic signal").roboto_bold()),
|
||||||
|
),
|
||||||
|
WrappedComposite::text_button(ctx, "X", hotkey(Key::Escape)).align_right(),
|
||||||
|
]),
|
||||||
|
ManagedWidget::draw_text(ctx, Text::from(Line("The mapper").roboto_bold())),
|
||||||
|
ManagedWidget::draw_text(
|
||||||
|
ctx,
|
||||||
|
Text::from(Line(
|
||||||
|
"Who mapped this signal? (Feel free to remain anonymous.)",
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
ManagedWidget::text_entry(ctx, prev_observed.author).named("observed author"),
|
||||||
|
ManagedWidget::draw_text(
|
||||||
|
ctx,
|
||||||
|
Text::from(Line("When was this signal mapped? TODO format")),
|
||||||
|
),
|
||||||
|
ManagedWidget::text_entry(ctx, prev_observed.datetime)
|
||||||
|
.named("observed datetime"),
|
||||||
|
ManagedWidget::draw_text(
|
||||||
|
ctx,
|
||||||
|
Text::from(Line("Any other observations about the signal?")),
|
||||||
|
),
|
||||||
|
ManagedWidget::text_entry(ctx, prev_observed.notes).named("observed notes"),
|
||||||
|
ManagedWidget::draw_text(
|
||||||
|
ctx,
|
||||||
|
Text::from(
|
||||||
|
Line("The last person to audit the mapped signal").roboto_bold(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ManagedWidget::draw_text(
|
||||||
|
ctx,
|
||||||
|
Text::from(Line(
|
||||||
|
"Who audited this signal? (Feel free to remain anonymous.)",
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
ManagedWidget::text_entry(ctx, prev_audited.author).named("audited author"),
|
||||||
|
ManagedWidget::draw_text(
|
||||||
|
ctx,
|
||||||
|
Text::from(Line("When was this signal audited? TODO format")),
|
||||||
|
),
|
||||||
|
ManagedWidget::text_entry(ctx, prev_audited.datetime).named("audited datetime"),
|
||||||
|
ManagedWidget::draw_text(
|
||||||
|
ctx,
|
||||||
|
Text::from(Line("Any other notes about auditing the signal?")),
|
||||||
|
),
|
||||||
|
ManagedWidget::text_entry(ctx, prev_audited.notes).named("audited notes"),
|
||||||
|
WrappedComposite::text_bg_button(ctx, "Done", hotkey(Key::Enter))
|
||||||
|
.centered_horiz(),
|
||||||
|
])
|
||||||
|
.bg(colors::PANEL_BG),
|
||||||
|
)
|
||||||
|
.build(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State for EditMetadata {
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
|
||||||
|
match self.composite.event(ctx) {
|
||||||
|
Some(Outcome::Clicked(x)) => match x.as_ref() {
|
||||||
|
"X" => {
|
||||||
|
return Transition::Pop;
|
||||||
|
}
|
||||||
|
"Done" => {
|
||||||
|
// This feels like overkill...
|
||||||
|
let observed = traffic_signals::Metadata {
|
||||||
|
author: self.composite.text_box("observed author"),
|
||||||
|
datetime: self.composite.text_box("observed datetime"),
|
||||||
|
notes: self.composite.text_box("observed notes"),
|
||||||
|
};
|
||||||
|
let audited = traffic_signals::Metadata {
|
||||||
|
author: self.composite.text_box("audited author"),
|
||||||
|
datetime: self.composite.text_box("audited datetime"),
|
||||||
|
notes: self.composite.text_box("audited notes"),
|
||||||
|
};
|
||||||
|
return Transition::PopWithData(Box::new(move |state, app, ctx| {
|
||||||
|
let editor = state.downcast_mut::<TrafficSignalEditor>().unwrap();
|
||||||
|
let orig_signal = app.primary.map.get_traffic_signal(editor.i);
|
||||||
|
let mut new_signal = orig_signal.clone();
|
||||||
|
new_signal.observation_md = Some(observed);
|
||||||
|
new_signal.audit_md = Some(audited);
|
||||||
|
|
||||||
|
editor.command_stack.push(orig_signal.clone());
|
||||||
|
editor.redo_stack.clear();
|
||||||
|
editor.top_panel = make_top_panel(ctx, app, true, false);
|
||||||
|
change_traffic_signal(new_signal, app, ctx);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Transition::Keep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||||
|
self.composite.draw(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn edit_md(i: IntersectionID) -> Box<dyn State> {
|
fn edit_md(i: IntersectionID) -> Box<dyn State> {
|
||||||
WizardState::new(Box::new(move |wiz, ctx, app| {
|
WizardState::new(Box::new(move |wiz, ctx, app| {
|
||||||
let mut wizard = wiz.wrap(ctx);
|
let mut wizard = wiz.wrap(ctx);
|
||||||
|
Loading…
Reference in New Issue
Block a user