mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
remove last users of ModalMenu. so long, (some variant of) the original widget.
This commit is contained in:
parent
b70037b4e8
commit
4df64b4c83
@ -24,7 +24,6 @@ htmlescape = "0.3.1"
|
||||
instant = "0.1.2"
|
||||
lru = "0.4.3"
|
||||
lyon = "0.15.6"
|
||||
ordered-float = "1.0.1"
|
||||
serde = "1.0.98"
|
||||
serde_derive = "1.0.98"
|
||||
simsearch = "0.2.0"
|
||||
|
@ -191,15 +191,6 @@ impl<'a> GfxCtx<'a> {
|
||||
self.redraw_at(top_left, &draw);
|
||||
}
|
||||
|
||||
// TODO Super close to deleting this.
|
||||
pub(crate) fn draw_blocking_text_at_screenspace_topleft(&mut self, txt: Text, pt: ScreenPt) {
|
||||
let batch = txt.render_g(self);
|
||||
self.canvas
|
||||
.mark_covered_area(ScreenRectangle::top_left(pt, batch.get_dims()));
|
||||
let draw = self.upload(batch);
|
||||
self.redraw_at(pt, &draw);
|
||||
}
|
||||
|
||||
// TODO Rename these draw_nonblocking_text_*
|
||||
// TODO Super close to deleting this.
|
||||
pub fn draw_text_at(&mut self, txt: Text, map_pt: Pt2D) {
|
||||
|
@ -402,7 +402,6 @@ impl MultiKey {
|
||||
}
|
||||
}
|
||||
|
||||
// For easy ModalMenu construction
|
||||
pub fn hotkey(key: Key) -> Option<MultiKey> {
|
||||
Some(MultiKey::Normal(key))
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ pub use crate::widgets::checkbox::Checkbox;
|
||||
pub(crate) use crate::widgets::dropdown::Dropdown;
|
||||
pub use crate::widgets::filler::Filler;
|
||||
pub use crate::widgets::histogram::Histogram;
|
||||
pub use crate::widgets::modal_menu::ModalMenu;
|
||||
pub use crate::widgets::no_op::JustDraw;
|
||||
pub use crate::widgets::plot::{Plot, PlotOptions, Series};
|
||||
pub(crate) use crate::widgets::popup_menu::PopupMenu;
|
||||
|
@ -197,7 +197,7 @@ impl Widget {
|
||||
|
||||
// Convenient?? constructors
|
||||
impl Widget {
|
||||
fn new(widget: Box<dyn WidgetImpl>) -> Widget {
|
||||
pub fn new(widget: Box<dyn WidgetImpl>) -> Widget {
|
||||
Widget {
|
||||
widget,
|
||||
style: LayoutStyle {
|
||||
|
@ -5,7 +5,6 @@ pub mod containers;
|
||||
pub mod dropdown;
|
||||
pub mod filler;
|
||||
pub mod histogram;
|
||||
pub mod modal_menu;
|
||||
pub mod no_op;
|
||||
pub mod plot;
|
||||
pub mod popup_menu;
|
||||
@ -14,65 +13,27 @@ pub mod text_box;
|
||||
pub mod wizard;
|
||||
|
||||
use crate::{EventCtx, GfxCtx, Outcome, ScreenDims, ScreenPt, ScreenRectangle};
|
||||
use ordered_float::NotNan;
|
||||
|
||||
/// Create a new widget by implementing this trait. You can instantiate your widget by calling
|
||||
/// `Widget::new(Box::new(instance of your new widget))`, which gives you the usual style options.
|
||||
pub trait WidgetImpl: downcast_rs::Downcast {
|
||||
/// What width and height does the widget occupy? If this changes, be sure to set
|
||||
/// `redo_layout` to true in `event`.
|
||||
fn get_dims(&self) -> ScreenDims;
|
||||
/// Your widget's top left corner should be here. Handle mouse events and draw appropriately.
|
||||
fn set_pos(&mut self, top_left: ScreenPt);
|
||||
|
||||
// TODO Require everyone to implement it
|
||||
// TODO I think we can scrap rect
|
||||
/// Your chance to react to an event. If this event should trigger layouting to be recalculated
|
||||
/// (because this widget changes dimensions), set `redo_layout` to true. Most widgets should
|
||||
/// return `None` instead of an `Outcome`.
|
||||
fn event(
|
||||
&mut self,
|
||||
_ctx: &mut EventCtx,
|
||||
_rect: &ScreenRectangle,
|
||||
_redo_layout: &mut bool,
|
||||
) -> Option<Outcome> {
|
||||
None
|
||||
}
|
||||
fn draw(&self, _g: &mut GfxCtx) {}
|
||||
ctx: &mut EventCtx,
|
||||
rect: &ScreenRectangle,
|
||||
redo_layout: &mut bool,
|
||||
) -> Option<Outcome>;
|
||||
/// Draw the widget. Be sure to draw relative to the top-left specified by `set_pos`.
|
||||
fn draw(&self, g: &mut GfxCtx);
|
||||
}
|
||||
|
||||
downcast_rs::impl_downcast!(WidgetImpl);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ContainerOrientation {
|
||||
TopLeft,
|
||||
TopRight,
|
||||
TopLeftButDownABit(f64),
|
||||
Centered,
|
||||
// Place the widget this percentage along the width of the screen
|
||||
Top(f64),
|
||||
}
|
||||
|
||||
pub fn stack_vertically(
|
||||
orientation: ContainerOrientation,
|
||||
ctx: &EventCtx,
|
||||
widgets: Vec<&mut dyn WidgetImpl>,
|
||||
) {
|
||||
assert!(!widgets.is_empty());
|
||||
|
||||
let dims_per_widget: Vec<ScreenDims> = widgets.iter().map(|w| w.get_dims()).collect();
|
||||
let total_width = dims_per_widget
|
||||
.iter()
|
||||
.map(|d| d.width)
|
||||
.max_by_key(|x| NotNan::new(*x).unwrap())
|
||||
.unwrap();
|
||||
let total_height: f64 = dims_per_widget.iter().map(|d| d.height).sum();
|
||||
|
||||
let mut top_left = match orientation {
|
||||
ContainerOrientation::TopLeft => ScreenPt::new(0.0, 0.0),
|
||||
ContainerOrientation::TopRight => ScreenPt::new(ctx.canvas.window_width - total_width, 0.0),
|
||||
ContainerOrientation::TopLeftButDownABit(y1) => ScreenPt::new(0.0, y1),
|
||||
ContainerOrientation::Centered => {
|
||||
let mut pt = ctx.canvas.center_to_screen_pt();
|
||||
pt.x -= total_width / 2.0;
|
||||
pt.y -= total_height / 2.0;
|
||||
pt
|
||||
}
|
||||
ContainerOrientation::Top(percent) => ScreenPt::new(ctx.canvas.window_width * percent, 0.0),
|
||||
};
|
||||
for (w, dims) in widgets.into_iter().zip(dims_per_widget) {
|
||||
w.set_pos(top_left);
|
||||
top_left.y += dims.height;
|
||||
}
|
||||
}
|
||||
|
@ -1,253 +0,0 @@
|
||||
use crate::widgets::{stack_vertically, ContainerOrientation};
|
||||
use crate::{
|
||||
text, EventCtx, GfxCtx, Line, MultiKey, ScreenDims, ScreenPt, ScreenRectangle, Text, WidgetImpl,
|
||||
};
|
||||
|
||||
pub struct ModalMenu {
|
||||
title: String,
|
||||
info: Text,
|
||||
chosen_action: Option<String>,
|
||||
choices: Vec<Choice>,
|
||||
// This can be inactive entries too.
|
||||
hovering_idx: Option<usize>,
|
||||
standalone_widgets: Option<ContainerOrientation>,
|
||||
|
||||
top_left: ScreenPt,
|
||||
dims: ScreenDims,
|
||||
}
|
||||
|
||||
struct Choice {
|
||||
hotkey: Option<MultiKey>,
|
||||
label: String,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl ModalMenu {
|
||||
pub fn new<S1: Into<String>, S2: Into<String>>(
|
||||
title: S1,
|
||||
raw_choices: Vec<(Option<MultiKey>, S2)>,
|
||||
ctx: &EventCtx,
|
||||
) -> ModalMenu {
|
||||
let mut m = ModalMenu {
|
||||
title: title.into(),
|
||||
info: Text::new(),
|
||||
chosen_action: None,
|
||||
choices: raw_choices
|
||||
.into_iter()
|
||||
.map(|(hotkey, label)| Choice {
|
||||
hotkey,
|
||||
label: label.into(),
|
||||
active: false,
|
||||
})
|
||||
.collect(),
|
||||
hovering_idx: None,
|
||||
standalone_widgets: Some(ContainerOrientation::TopRight),
|
||||
|
||||
top_left: ScreenPt::new(0.0, 0.0),
|
||||
dims: ScreenDims::new(0.0, 0.0),
|
||||
};
|
||||
m.dims = m.calculate_txt().dims(&ctx.prerender.assets);
|
||||
|
||||
m
|
||||
}
|
||||
|
||||
// It's part of something bigger
|
||||
pub fn disable_standalone_widgets(mut self) -> ModalMenu {
|
||||
assert!(self.standalone_widgets.is_some());
|
||||
self.standalone_widgets = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_standalone_widgets(mut self, widgets: ContainerOrientation) -> ModalMenu {
|
||||
self.standalone_widgets = Some(widgets);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_info(&mut self, ctx: &EventCtx, info: Text) {
|
||||
self.info = info.with_bg();
|
||||
self.dims = self.calculate_txt().dims(&ctx.prerender.assets);
|
||||
}
|
||||
|
||||
pub fn event(&mut self, ctx: &mut EventCtx) {
|
||||
if let Some(ref action) = self.chosen_action {
|
||||
panic!("Caller didn't consume modal action '{}'", action);
|
||||
}
|
||||
|
||||
if let Some(o) = self.standalone_widgets {
|
||||
stack_vertically(o, ctx, vec![self]);
|
||||
self.dims = self.calculate_txt().dims(&ctx.prerender.assets);
|
||||
}
|
||||
|
||||
// Handle the mouse
|
||||
if ctx.redo_mouseover() {
|
||||
self.hovering_idx = None;
|
||||
if let Some(cursor) = ctx.canvas.get_cursor_in_screen_space() {
|
||||
let mut top_left = self.top_left;
|
||||
top_left.y += self.info.clone().dims(&ctx.prerender.assets).height;
|
||||
if !self.title.is_empty() {
|
||||
top_left.y += ctx.default_line_height();
|
||||
}
|
||||
for idx in 0..self.choices.len() {
|
||||
let rect = ScreenRectangle {
|
||||
x1: top_left.x,
|
||||
y1: top_left.y,
|
||||
x2: top_left.x + self.dims.width,
|
||||
y2: top_left.y + ctx.default_line_height(),
|
||||
};
|
||||
if rect.contains(cursor) {
|
||||
self.hovering_idx = Some(idx);
|
||||
break;
|
||||
}
|
||||
top_left.y += ctx.default_line_height();
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(idx) = self.hovering_idx {
|
||||
if ctx.normal_left_click() && self.choices[idx].active {
|
||||
self.chosen_action = Some(self.choices[idx].label.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle hotkeys
|
||||
for choice in &self.choices {
|
||||
if !choice.active {
|
||||
continue;
|
||||
}
|
||||
if let Some(ref hotkey) = choice.hotkey {
|
||||
if ctx.input.new_was_pressed(hotkey) {
|
||||
self.chosen_action = Some(choice.label.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset for next round
|
||||
for choice in self.choices.iter_mut() {
|
||||
choice.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_action(&mut self, ctx: &EventCtx, hotkey: Option<MultiKey>, label: &str) {
|
||||
self.choices.push(Choice {
|
||||
hotkey,
|
||||
label: label.to_string(),
|
||||
active: false,
|
||||
});
|
||||
self.dims = self.calculate_txt().dims(&ctx.prerender.assets);
|
||||
}
|
||||
|
||||
pub fn remove_action(&mut self, ctx: &EventCtx, label: &str) {
|
||||
self.choices.retain(|c| c.label != label);
|
||||
self.dims = self.calculate_txt().dims(&ctx.prerender.assets);
|
||||
}
|
||||
|
||||
pub fn change_action(&mut self, ctx: &EventCtx, old_label: &str, new_label: &str) {
|
||||
for c in self.choices.iter_mut() {
|
||||
if c.label == old_label {
|
||||
c.label = new_label.to_string();
|
||||
self.dims = self.calculate_txt().dims(&ctx.prerender.assets);
|
||||
return;
|
||||
}
|
||||
}
|
||||
panic!("Menu doesn't have {}", old_label);
|
||||
}
|
||||
|
||||
pub fn maybe_change_action(&mut self, ctx: &EventCtx, old_label: &str, new_label: &str) {
|
||||
for c in self.choices.iter_mut() {
|
||||
if c.label == old_label {
|
||||
c.label = new_label.to_string();
|
||||
self.dims = self.calculate_txt().dims(&ctx.prerender.assets);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Don't panic
|
||||
}
|
||||
|
||||
pub fn swap_action(&mut self, ctx: &EventCtx, old_label: &str, new_label: &str) -> bool {
|
||||
if self.action(old_label) {
|
||||
self.change_action(ctx, old_label, new_label);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume_action(&mut self, ctx: &EventCtx, name: &str) -> bool {
|
||||
if self.action(name) {
|
||||
self.remove_action(ctx, name);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action(&mut self, label: &str) -> bool {
|
||||
if let Some(ref action) = self.chosen_action {
|
||||
if label == action {
|
||||
self.chosen_action = None;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for c in self.choices.iter_mut() {
|
||||
if c.label == label {
|
||||
c.active = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
panic!("Menu doesn't have action {}", label);
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
g.draw_blocking_text_at_screenspace_topleft(self.calculate_txt(), self.top_left);
|
||||
}
|
||||
|
||||
fn calculate_txt(&self) -> Text {
|
||||
let mut txt = if self.title.is_empty() {
|
||||
Text::new()
|
||||
} else {
|
||||
Text::from(Line(&self.title).roboto_bold())
|
||||
}
|
||||
.with_bg();
|
||||
txt.extend(&self.info);
|
||||
|
||||
for (idx, choice) in self.choices.iter().enumerate() {
|
||||
if choice.active {
|
||||
if let Some(ref key) = choice.hotkey {
|
||||
txt.add_appended(vec![
|
||||
Line(key.describe()).fg(text::HOTKEY_COLOR),
|
||||
Line(format!(" - {}", choice.label)),
|
||||
]);
|
||||
} else {
|
||||
txt.add(Line(&choice.label));
|
||||
}
|
||||
|
||||
// TODO BG color should be on the TextSpan, so this isn't so terrible?
|
||||
if Some(idx) == self.hovering_idx {
|
||||
txt.highlight_last_line(text::SELECTED_COLOR);
|
||||
}
|
||||
} else {
|
||||
if let Some(ref key) = choice.hotkey {
|
||||
txt.add(
|
||||
Line(format!("{} - {}", key.describe(), choice.label))
|
||||
.fg(text::INACTIVE_CHOICE_COLOR),
|
||||
);
|
||||
} else {
|
||||
txt.add(Line(&choice.label).fg(text::INACTIVE_CHOICE_COLOR));
|
||||
}
|
||||
}
|
||||
}
|
||||
txt
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for ModalMenu {
|
||||
fn get_dims(&self) -> ScreenDims {
|
||||
ScreenDims::new(self.dims.width, self.dims.height)
|
||||
}
|
||||
|
||||
fn set_pos(&mut self, top_left: ScreenPt) {
|
||||
self.top_left = top_left;
|
||||
}
|
||||
}
|
@ -7,15 +7,14 @@ use crate::game::{State, Transition};
|
||||
use crate::managed::{WrappedComposite, WrappedOutcome};
|
||||
use crate::render::MIN_ZOOM_FOR_DETAIL;
|
||||
use abstutil::Timer;
|
||||
use ezgui::{hotkey, lctrl, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, ModalMenu, Text};
|
||||
use ezgui::{lctrl, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Text};
|
||||
use geom::{Circle, Distance, Line, PolyLine};
|
||||
use map_model::{Map, NORMAL_LANE_THICKNESS};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sim::{Sim, SimOptions, TripID, TripMode};
|
||||
use sim::{Sim, SimOptions, TripID};
|
||||
|
||||
// TODO I took out speed controls
|
||||
// TODO Controls have been removed.
|
||||
pub struct ABTestMode {
|
||||
menu: ModalMenu,
|
||||
diff_trip: Option<DiffOneTrip>,
|
||||
diff_all: Option<DiffAllTrips>,
|
||||
common: CommonState,
|
||||
@ -29,17 +28,6 @@ impl ABTestMode {
|
||||
app.primary.current_selection = None;
|
||||
|
||||
ABTestMode {
|
||||
menu: ModalMenu::new(
|
||||
"A/B Test Mode",
|
||||
vec![
|
||||
(hotkey(Key::S), "swap"),
|
||||
(hotkey(Key::D), "diff all trips"),
|
||||
(hotkey(Key::A), "stop diffing trips"),
|
||||
(hotkey(Key::O), "save state"),
|
||||
// TODO load arbitrary savestate
|
||||
],
|
||||
ctx,
|
||||
),
|
||||
diff_trip: None,
|
||||
diff_all: None,
|
||||
common: CommonState::new(),
|
||||
@ -72,19 +60,8 @@ impl State for ABTestMode {
|
||||
diff.lines.len()
|
||||
)));
|
||||
}
|
||||
let (finished, unfinished, by_mode) = app.primary.sim.num_trips();
|
||||
txt.add(Line(format!("Finished trips: {}", finished)));
|
||||
txt.add(Line(format!("Unfinished trips: {}", unfinished)));
|
||||
txt.add(Line(format!(
|
||||
"Peds {}, Bikes {}, Cars {}, Buses {}",
|
||||
by_mode[&TripMode::Walk],
|
||||
by_mode[&TripMode::Bike],
|
||||
by_mode[&TripMode::Drive],
|
||||
by_mode[&TripMode::Transit]
|
||||
)));
|
||||
self.menu.set_info(ctx, txt);
|
||||
// TODO Stick this info somewhere
|
||||
}
|
||||
self.menu.event(ctx);
|
||||
|
||||
ctx.canvas_movement();
|
||||
if ctx.redo_mouseover() {
|
||||
@ -95,7 +72,8 @@ impl State for ABTestMode {
|
||||
return Transition::Push(Box::new(DebugMode::new(ctx)));
|
||||
}
|
||||
|
||||
if self.menu.action("swap") {
|
||||
if false {
|
||||
// swap
|
||||
let secondary = app.secondary.take().unwrap();
|
||||
let primary = std::mem::replace(&mut app.primary, secondary);
|
||||
app.secondary = Some(primary);
|
||||
@ -104,7 +82,8 @@ impl State for ABTestMode {
|
||||
self.flipped = !self.flipped;
|
||||
}
|
||||
|
||||
if self.menu.action("save state") {
|
||||
if false {
|
||||
// save state
|
||||
ctx.loading_screen("savestate", |_, timer| {
|
||||
timer.start("save all state");
|
||||
self.savestate(app);
|
||||
@ -113,15 +92,18 @@ impl State for ABTestMode {
|
||||
}
|
||||
|
||||
if self.diff_trip.is_some() {
|
||||
if self.menu.action("stop diffing trips") {
|
||||
if false {
|
||||
// stop diffing trips
|
||||
self.diff_trip = None;
|
||||
}
|
||||
} else if self.diff_all.is_some() {
|
||||
if self.menu.action("stop diffing trips") {
|
||||
if false {
|
||||
// stop diffing trips
|
||||
self.diff_all = None;
|
||||
}
|
||||
} else {
|
||||
if app.primary.current_selection.is_none() && self.menu.action("diff all trips") {
|
||||
if app.primary.current_selection.is_none() && false {
|
||||
// diff all trips
|
||||
self.diff_all = Some(DiffAllTrips::new(
|
||||
&mut app.primary,
|
||||
app.secondary.as_mut().unwrap(),
|
||||
@ -180,7 +162,6 @@ impl State for ABTestMode {
|
||||
if let Some(ref diff) = self.diff_all {
|
||||
diff.draw(g, app);
|
||||
}
|
||||
self.menu.draw(g);
|
||||
}
|
||||
|
||||
fn on_suspend(&mut self, _: &mut EventCtx, _: &mut App) {
|
||||
|
@ -3,7 +3,7 @@ use crate::app::{App, Flags, PerMap};
|
||||
use crate::edit::apply_map_edits;
|
||||
use crate::game::{State, Transition, WizardState};
|
||||
use crate::render::DrawMap;
|
||||
use ezgui::{hotkey, Choice, EventCtx, GfxCtx, Key, Line, ModalMenu, Text, Wizard, WrappedWizard};
|
||||
use ezgui::{Choice, EventCtx, GfxCtx, Wizard, WrappedWizard};
|
||||
use geom::Duration;
|
||||
use map_model::MapEdits;
|
||||
use sim::{ABTest, Scenario, SimFlags, SimOptions};
|
||||
@ -58,48 +58,32 @@ fn pick_ab_test(wiz: &mut Wizard, ctx: &mut EventCtx, app: &mut App) -> Option<T
|
||||
t
|
||||
};
|
||||
|
||||
let mut menu = ModalMenu::new(
|
||||
"A/B Test Editor",
|
||||
vec![
|
||||
(hotkey(Key::Escape), "quit"),
|
||||
(hotkey(Key::R), "run A/B test"),
|
||||
(hotkey(Key::L), "load savestate"),
|
||||
],
|
||||
ctx,
|
||||
);
|
||||
let mut txt = Text::new();
|
||||
txt.add(Line(&ab_test.test_name));
|
||||
for line in ab_test.describe() {
|
||||
txt.add(Line(line));
|
||||
}
|
||||
menu.set_info(ctx, txt);
|
||||
|
||||
Some(Transition::Replace(Box::new(ABTestSetup { menu, ab_test })))
|
||||
Some(Transition::Replace(Box::new(ABTestSetup { ab_test })))
|
||||
}
|
||||
|
||||
// TODO I took out controls, obviously
|
||||
struct ABTestSetup {
|
||||
menu: ModalMenu,
|
||||
ab_test: ABTest,
|
||||
}
|
||||
|
||||
impl State for ABTestSetup {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
self.menu.event(ctx);
|
||||
ctx.canvas_movement();
|
||||
|
||||
if self.menu.action("quit") {
|
||||
if false {
|
||||
// quit
|
||||
return Transition::Pop;
|
||||
} else if self.menu.action("run A/B test") {
|
||||
} else if false {
|
||||
// run test
|
||||
return Transition::Replace(Box::new(launch_test(&self.ab_test, app, ctx)));
|
||||
} else if self.menu.action("load savestate") {
|
||||
} else if false {
|
||||
// load savestate
|
||||
return Transition::Push(make_load_savestate(self.ab_test.clone()));
|
||||
}
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.menu.draw(g);
|
||||
}
|
||||
fn draw(&self, _: &mut GfxCtx, _: &App) {}
|
||||
}
|
||||
|
||||
fn make_load_savestate(ab_test: ABTest) -> Box<dyn State> {
|
||||
|
@ -4,8 +4,9 @@ mod world;
|
||||
|
||||
use abstutil::{CmdArgs, Timer};
|
||||
use ezgui::{
|
||||
hotkey, Canvas, Choice, Color, Drawable, EventCtx, EventLoopMode, GeomBatch, GfxCtx, Key, Line,
|
||||
ModalMenu, ScreenPt, Text, Wizard, GUI,
|
||||
hotkey, Btn, Canvas, Choice, Color, Composite, Drawable, EventCtx, EventLoopMode, GeomBatch,
|
||||
GfxCtx, HorizontalAlignment, Key, Line, Outcome, ScreenPt, Text, VerticalAlignment, Widget,
|
||||
Wizard, GUI,
|
||||
};
|
||||
use geom::{Angle, Distance, Line, Polygon, Pt2D};
|
||||
use map_model::raw::{OriginalBuilding, OriginalIntersection, OriginalRoad, RestrictionType};
|
||||
@ -16,7 +17,7 @@ use std::collections::HashSet;
|
||||
struct UI {
|
||||
model: Model,
|
||||
state: State,
|
||||
menu: ModalMenu,
|
||||
composite: Composite,
|
||||
popup: Option<Drawable>,
|
||||
info_key_held: bool,
|
||||
|
||||
@ -78,8 +79,11 @@ impl UI {
|
||||
let mut ui = UI {
|
||||
model,
|
||||
state: State::viewing(),
|
||||
menu: ModalMenu::new(
|
||||
"Map Editor",
|
||||
composite: Composite::new(
|
||||
Widget::col(vec![
|
||||
Line("Map Editor").roboto_bold().draw(ctx),
|
||||
Text::new().draw(ctx).named("current info"),
|
||||
Widget::col(
|
||||
vec![
|
||||
(hotkey(Key::Escape), "quit"),
|
||||
(None, "save raw map"),
|
||||
@ -88,10 +92,18 @@ impl UI {
|
||||
(None, "produce OSM parking+sidewalk diff"),
|
||||
(hotkey(Key::G), "preview all intersections"),
|
||||
(None, "find overlapping intersections"),
|
||||
(hotkey(Key::Z), "find short roads"),
|
||||
],
|
||||
ctx,
|
||||
(hotkey(Key::Z), "find/clear short roads"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(key, action)| Btn::text_fg(action).build_def(ctx, key))
|
||||
.collect(),
|
||||
),
|
||||
])
|
||||
.padding(10)
|
||||
.bg(Color::grey(0.4)),
|
||||
)
|
||||
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
|
||||
.build(ctx),
|
||||
popup: None,
|
||||
info_key_held: false,
|
||||
|
||||
@ -101,7 +113,7 @@ impl UI {
|
||||
ui
|
||||
}
|
||||
|
||||
fn recount_parking_tags(&mut self, ctx: &EventCtx) {
|
||||
fn recount_parking_tags(&mut self, ctx: &mut EventCtx) {
|
||||
let mut ways_audited = HashSet::new();
|
||||
let mut ways_missing = HashSet::new();
|
||||
for r in self.model.map.roads.values() {
|
||||
@ -121,7 +133,8 @@ impl UI {
|
||||
abstutil::prettyprint_usize(ways_audited.len() + ways_missing.len())
|
||||
)));
|
||||
txt.add(Line("Hold right Control to show info about objects"));
|
||||
self.menu.set_info(ctx, txt);
|
||||
self.composite
|
||||
.replace(ctx, "current info", txt.draw(ctx).named("current info"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +149,6 @@ impl GUI for UI {
|
||||
}
|
||||
|
||||
ctx.canvas_movement();
|
||||
self.menu.event(ctx);
|
||||
if ctx.redo_mouseover() {
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
@ -284,25 +296,57 @@ impl GUI for UI {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if self.menu.action("quit") {
|
||||
match self.composite.event(ctx) {
|
||||
Some(Outcome::Clicked(x)) => match x.as_ref() {
|
||||
"quit" => {
|
||||
self.before_quit(ctx.canvas);
|
||||
std::process::exit(0);
|
||||
} else if self.menu.action("save raw map") {
|
||||
}
|
||||
"save raw map" => {
|
||||
// TODO Only do this for synthetic maps
|
||||
if self.model.map.name != "" {
|
||||
self.model.export();
|
||||
} else {
|
||||
self.state = State::SavingModel(Wizard::new());
|
||||
}
|
||||
} else if self.menu.action("save map fixes") {
|
||||
}
|
||||
"save map fixes" => {
|
||||
self.model.save_fixes();
|
||||
} else if ctx.input.key_pressed(Key::I, "create intersection") {
|
||||
}
|
||||
"warp to something" => {
|
||||
self.state = State::EnteringWarp(Wizard::new());
|
||||
}
|
||||
"produce OSM parking+sidewalk diff" => {
|
||||
upstream::find_diffs(&self.model.map);
|
||||
}
|
||||
"preview all intersections" => {
|
||||
if !self.model.intersection_geom {
|
||||
let draw = preview_all_intersections(&self.model, ctx);
|
||||
self.state = State::PreviewIntersection(draw, false);
|
||||
}
|
||||
}
|
||||
"find overlapping intersections" => {
|
||||
let draw = find_overlapping_intersections(&self.model, ctx);
|
||||
self.state = State::PreviewIntersection(draw, false);
|
||||
}
|
||||
"find/clear short roads" => {
|
||||
if short_roads.is_empty() {
|
||||
*short_roads = find_short_roads(&self.model);
|
||||
} else {
|
||||
short_roads.clear();
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
None => {
|
||||
if ctx.input.key_pressed(Key::I, "create intersection") {
|
||||
if let Some(pt) = cursor {
|
||||
self.model.create_i(pt, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
// TODO Silly bug: Mouseover doesn't actually work! I think the cursor being
|
||||
// dead-center messes up the precomputed triangles.
|
||||
// TODO Silly bug: Mouseover doesn't actually work! I think the
|
||||
// cursor being dead-center messes
|
||||
// up the precomputed triangles.
|
||||
} else if ctx.input.key_pressed(Key::B, "create building") {
|
||||
if let Some(pt) = cursor {
|
||||
let id = self.model.create_b(pt, ctx.prerender);
|
||||
@ -312,37 +356,8 @@ impl GUI for UI {
|
||||
if let Some(pt) = cursor {
|
||||
self.state = State::SelectingRectangle(pt, pt, true);
|
||||
}
|
||||
} else if self.menu.action("warp to something") {
|
||||
self.state = State::EnteringWarp(Wizard::new());
|
||||
} else if self.menu.action("produce OSM parking+sidewalk diff") {
|
||||
upstream::find_diffs(&self.model.map);
|
||||
} else if !self.model.intersection_geom
|
||||
&& self.menu.action("preview all intersections")
|
||||
{
|
||||
let draw = preview_all_intersections(&self.model, ctx);
|
||||
self.state = State::PreviewIntersection(draw, false);
|
||||
} else if self.menu.action("find overlapping intersections") {
|
||||
let draw = find_overlapping_intersections(&self.model, ctx);
|
||||
self.state = State::PreviewIntersection(draw, false);
|
||||
} else if short_roads.is_empty()
|
||||
&& self
|
||||
.menu
|
||||
.swap_action(ctx, "find short roads", "clear short roads")
|
||||
{
|
||||
*short_roads = find_short_roads(&self.model);
|
||||
if short_roads.is_empty() {
|
||||
self.menu.change_action(
|
||||
ctx,
|
||||
"clear short roads",
|
||||
"find short roads",
|
||||
);
|
||||
}
|
||||
} else if !short_roads.is_empty()
|
||||
&& self
|
||||
.menu
|
||||
.swap_action(ctx, "clear short roads", "find short roads")
|
||||
{
|
||||
short_roads.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -511,6 +526,7 @@ impl GUI for UI {
|
||||
*show_tooltip = true;
|
||||
}
|
||||
|
||||
// TODO Woops, not communicating this kind of thing anymore
|
||||
if ctx
|
||||
.input
|
||||
.key_pressed(Key::P, "stop previewing intersection")
|
||||
@ -659,7 +675,7 @@ impl GUI for UI {
|
||||
}
|
||||
};
|
||||
|
||||
self.menu.draw(g);
|
||||
self.composite.draw(g);
|
||||
if let Some(ref popup) = self.popup {
|
||||
g.redraw_at(ScreenPt::new(0.0, 0.0), popup);
|
||||
}
|
||||
|
@ -11,13 +11,6 @@ pub struct ABTest {
|
||||
}
|
||||
|
||||
impl ABTest {
|
||||
pub fn describe(&self) -> Vec<String> {
|
||||
abstutil::to_json(self)
|
||||
.split('\n')
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
abstutil::write_json(
|
||||
abstutil::path_ab_test(&self.map_name, &self.test_name),
|
||||
|
Loading…
Reference in New Issue
Block a user