remove last users of ModalMenu. so long, (some variant of) the original widget.

This commit is contained in:
Dustin Carlino 2020-03-22 20:59:44 -07:00
parent b70037b4e8
commit 4df64b4c83
11 changed files with 135 additions and 465 deletions

View File

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

View File

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

View File

@ -402,7 +402,6 @@ impl MultiKey {
}
}
// For easy ModalMenu construction
pub fn hotkey(key: Key) -> Option<MultiKey> {
Some(MultiKey::Normal(key))
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,20 +79,31 @@ impl UI {
let mut ui = UI {
model,
state: State::viewing(),
menu: ModalMenu::new(
"Map Editor",
vec![
(hotkey(Key::Escape), "quit"),
(None, "save raw map"),
(hotkey(Key::F), "save map fixes"),
(hotkey(Key::J), "warp to something"),
(None, "produce OSM parking+sidewalk diff"),
(hotkey(Key::G), "preview all intersections"),
(None, "find overlapping intersections"),
(hotkey(Key::Z), "find short roads"),
],
ctx,
),
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"),
(hotkey(Key::F), "save map fixes"),
(hotkey(Key::J), "warp to something"),
(None, "produce OSM parking+sidewalk diff"),
(hotkey(Key::G), "preview all intersections"),
(None, "find overlapping intersections"),
(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,65 +296,68 @@ impl GUI for UI {
}
}
None => {
if self.menu.action("quit") {
self.before_quit(ctx.canvas);
std::process::exit(0);
} else if self.menu.action("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());
match self.composite.event(ctx) {
Some(Outcome::Clicked(x)) => match x.as_ref() {
"quit" => {
self.before_quit(ctx.canvas);
std::process::exit(0);
}
"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());
}
}
"save map fixes" => {
self.model.save_fixes();
}
"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.
} else if ctx.input.key_pressed(Key::B, "create building") {
if let Some(pt) = cursor {
let id = self.model.create_b(pt, ctx.prerender);
self.model.world.force_set_selection(id);
}
} else if ctx.input.key_pressed(Key::LeftShift, "select area") {
if let Some(pt) = cursor {
self.state = State::SelectingRectangle(pt, pt, true);
}
}
}
} else if self.menu.action("save map fixes") {
self.model.save_fixes();
} else 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.
} else if ctx.input.key_pressed(Key::B, "create building") {
if let Some(pt) = cursor {
let id = self.model.create_b(pt, ctx.prerender);
self.model.world.force_set_selection(id);
}
} else if ctx.input.key_pressed(Key::LeftShift, "select area") {
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);
}

View File

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