Refactor common event handlers for the 3 modes

This commit is contained in:
Dustin Carlino 2021-08-26 09:56:41 -07:00
parent 57d1cba40c
commit 0a03887eab
4 changed files with 125 additions and 156 deletions

View File

@ -109,44 +109,6 @@ impl State<App> for ExploreMap {
if let Outcome::Clicked(x) = self.top_panel.event(ctx) {
match x.as_ref() {
"about A/B Street" => {
return Transition::Push(About::new_state(ctx));
}
"change map" => {
return Transition::Push(CityPicker::new_state(
ctx,
app,
Box::new(|ctx, app| {
Transition::Multi(vec![
Transition::Pop,
// Since we're totally changing maps, don't reuse the Layers
Transition::Replace(ExploreMap::launch(ctx, app)),
])
}),
));
}
"Create new bike lanes" => {
app.primary.current_selection = None;
return Transition::ConsumeState(Box::new(|state, ctx, app| {
let state = state.downcast::<ExploreMap>().ok().unwrap();
vec![crate::ungap::quick_sketch::QuickSketch::new_state(
ctx,
app,
state.layers,
)]
}));
}
"Plan a route" => {
app.primary.current_selection = None;
return Transition::ConsumeState(Box::new(|state, ctx, app| {
let state = state.downcast::<ExploreMap>().ok().unwrap();
vec![crate::ungap::route::RoutePlanner::new_state(
ctx,
app,
state.layers,
)]
}));
}
"Open a proposal" => {
// Dummy mode, just to allow all edits
// TODO Actually, should we make one to express that only road edits are
@ -171,7 +133,9 @@ impl State<App> for ExploreMap {
"Share proposal" => {
return Transition::Push(share::upload_proposal(ctx, app));
}
_ => unreachable!(),
x => {
return Tab::Explore.handle_action::<ExploreMap>(ctx, app, x);
}
}
}
@ -257,7 +221,7 @@ fn make_top_panel(ctx: &mut EventCtx, app: &App) -> Panel {
// TODO Should undo/redo, save, share functionality also live here?
Panel::new_builder(Widget::col(vec![
make_header(ctx, app, Tab::Explore),
Tab::Explore.make_header(ctx, app),
Widget::col(file_management).section(ctx),
]))
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
@ -267,7 +231,7 @@ fn make_top_panel(ctx: &mut EventCtx, app: &App) -> Panel {
struct About;
impl About {
pub fn new_state(ctx: &mut EventCtx) -> Box<dyn State<App>> {
fn new_state(ctx: &mut EventCtx) -> Box<dyn State<App>> {
let panel = Panel::new_builder(Widget::col(vec![
Widget::row(vec![
Line("About A/B Street").small_heading().into_widget(ctx),
@ -309,7 +273,18 @@ pub enum Tab {
Route,
}
pub fn make_header(ctx: &mut EventCtx, app: &App, current_tab: Tab) -> Widget {
pub trait TakeLayers {
fn take_layers(self) -> Layers;
}
impl TakeLayers for ExploreMap {
fn take_layers(self) -> Layers {
self.layers
}
}
impl Tab {
pub fn make_header(self, ctx: &mut EventCtx, app: &App) -> Widget {
Widget::col(vec![
Widget::row(vec![
ctx.style()
@ -333,20 +308,67 @@ pub fn make_header(ctx: &mut EventCtx, app: &App, current_tab: Tab) -> Widget {
.btn_tab
.icon_text("system/assets/tools/pan.svg", "Explore")
.hotkey(Key::E)
.disabled(current_tab == Tab::Explore)
.disabled(self == Tab::Explore)
.build_def(ctx),
ctx.style()
.btn_tab
.icon_text("system/assets/tools/pencil.svg", "Create new bike lanes")
.hotkey(Key::C)
.disabled(current_tab == Tab::Create)
.disabled(self == Tab::Create)
.build_def(ctx),
ctx.style()
.btn_tab
.icon_text("system/assets/tools/pin.svg", "Plan a route")
.hotkey(Key::R)
.disabled(current_tab == Tab::Route)
.disabled(self == Tab::Route)
.build_def(ctx),
]),
])
}
pub fn handle_action<T: TakeLayers + State<App>>(
self,
ctx: &mut EventCtx,
app: &mut App,
action: &str,
) -> Transition {
match action {
"about A/B Street" => Transition::Push(About::new_state(ctx)),
"change map" => {
Transition::Push(CityPicker::new_state(
ctx,
app,
Box::new(|ctx, app| {
Transition::Multi(vec![
Transition::Pop,
// Since we're totally changing maps, don't reuse the Layers
// TODO Keep current tab...
Transition::Replace(ExploreMap::launch(ctx, app)),
])
}),
))
}
"Create new bike lanes" => {
// This is only necessary to do coming from ExploreMap, but eh
app.primary.current_selection = None;
Transition::ConsumeState(Box::new(|state, ctx, app| {
let state = state.downcast::<T>().ok().unwrap();
vec![quick_sketch::QuickSketch::new_state(
ctx,
app,
state.take_layers(),
)]
}))
}
"Plan a route" => Transition::ConsumeState(Box::new(|state, ctx, app| {
let state = state.downcast::<T>().ok().unwrap();
vec![route::RoutePlanner::new_state(
ctx,
app,
state.take_layers(),
)]
})),
_ => unreachable!(),
}
}
}

View File

@ -1,5 +1,5 @@
use abstutil::Tags;
use map_gui::tools::{CityPicker, PopupMsg};
use map_gui::tools::PopupMsg;
use map_model::{BufferType, Direction, EditCmd, EditRoad, LaneSpec, LaneType, RoadID};
use widgetry::{
Choice, EventCtx, GfxCtx, HorizontalAlignment, Key, Outcome, Panel, State, TextExt,
@ -9,7 +9,7 @@ use widgetry::{
use crate::app::{App, Transition};
use crate::common::RouteSketcher;
use crate::edit::apply_map_edits;
use crate::ungap::{make_header, Layers, Tab};
use crate::ungap::{Layers, Tab, TakeLayers};
pub struct QuickSketch {
top_panel: Panel,
@ -17,6 +17,12 @@ pub struct QuickSketch {
route_sketcher: RouteSketcher,
}
impl TakeLayers for QuickSketch {
fn take_layers(self) -> Layers {
self.layers
}
}
impl QuickSketch {
pub fn new_state(ctx: &mut EventCtx, app: &mut App, layers: Layers) -> Box<dyn State<App>> {
let mut qs = QuickSketch {
@ -30,7 +36,7 @@ impl QuickSketch {
fn update_top_panel(&mut self, ctx: &mut EventCtx, app: &App) {
let mut col = vec![
make_header(ctx, app, Tab::Create),
Tab::Create.make_header(ctx, app),
self.route_sketcher.get_widget_to_describe(ctx),
];
@ -80,39 +86,6 @@ impl State<App> for QuickSketch {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
if let Outcome::Clicked(x) = self.top_panel.event(ctx) {
match x.as_ref() {
"about A/B Street" => {
return Transition::Push(crate::ungap::About::new_state(ctx));
}
"change map" => {
return Transition::Push(CityPicker::new_state(
ctx,
app,
Box::new(|ctx, app| {
Transition::Multi(vec![
Transition::Pop,
// Since we're totally changing maps, don't reuse the Layers
// TODO Should we keep the current tab or always reset here?
Transition::Replace(crate::ungap::ExploreMap::launch(ctx, app)),
])
}),
));
}
"Explore" => {
return Transition::ConsumeState(Box::new(|state, ctx, app| {
let state = state.downcast::<QuickSketch>().ok().unwrap();
vec![crate::ungap::ExploreMap::new_state(ctx, app, state.layers)]
}));
}
"Plan a route" => {
return Transition::ConsumeState(Box::new(|state, ctx, app| {
let state = state.downcast::<QuickSketch>().ok().unwrap();
vec![crate::ungap::route::RoutePlanner::new_state(
ctx,
app,
state.layers,
)]
}));
}
"Add bike lanes" => {
let messages = make_quick_changes(
ctx,
@ -128,7 +101,9 @@ impl State<App> for QuickSketch {
]
}));
}
_ => unreachable!(),
x => {
return Tab::Create.handle_action::<QuickSketch>(ctx, app, x);
}
}
}

View File

@ -5,7 +5,6 @@ use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
use geom::{Circle, Distance, Duration, FindClosest, Polygon};
use map_gui::tools::CityPicker;
use map_model::{PathStep, NORMAL_LANE_THICKNESS};
use sim::{TripEndpoint, TripMode};
use widgetry::{
@ -14,7 +13,7 @@ use widgetry::{
};
use crate::app::{App, Transition};
use crate::ungap::{make_header, Layers, Tab};
use crate::ungap::{Layers, Tab, TakeLayers};
pub struct RoutePlanner {
layers: Layers,
@ -36,6 +35,12 @@ pub struct RoutePlanner {
results_panel: Panel,
}
impl TakeLayers for RoutePlanner {
fn take_layers(self) -> Layers {
self.layers
}
}
// TODO Maybe it's been a while and I've forgotten some UI patterns, but this is painfully manual.
// I think we need a draggable map-space thing.
struct Waypoint {
@ -82,7 +87,7 @@ impl RoutePlanner {
}
fn update_input_panel(&mut self, ctx: &mut EventCtx, app: &App) {
let mut col = vec![make_header(ctx, app, Tab::Route)];
let mut col = vec![Tab::Route.make_header(ctx, app)];
for (idx, waypt) in self.waypoints.iter().enumerate() {
col.push(Widget::row(vec![
@ -346,39 +351,6 @@ impl State<App> for RoutePlanner {
if let Outcome::Clicked(x) = self.input_panel.event(ctx) {
match x.as_ref() {
"about A/B Street" => {
return Transition::Push(crate::ungap::About::new_state(ctx));
}
"change map" => {
return Transition::Push(CityPicker::new_state(
ctx,
app,
Box::new(|ctx, app| {
Transition::Multi(vec![
Transition::Pop,
// Since we're totally changing maps, don't reuse the Layers
// TODO Should we keep the current tab or always reset here?
Transition::Replace(crate::ungap::ExploreMap::launch(ctx, app)),
])
}),
));
}
"Explore" => {
return Transition::ConsumeState(Box::new(|state, ctx, app| {
let state = state.downcast::<RoutePlanner>().ok().unwrap();
vec![crate::ungap::ExploreMap::new_state(ctx, app, state.layers)]
}));
}
"Create new bike lanes" => {
return Transition::ConsumeState(Box::new(|state, ctx, app| {
let state = state.downcast::<RoutePlanner>().ok().unwrap();
vec![crate::ungap::quick_sketch::QuickSketch::new_state(
ctx,
app,
state.layers,
)]
}));
}
"Add waypoint" => {
self.waypoints.push(self.make_new_waypt(ctx, app));
self.update_input_panel(ctx, app);
@ -399,7 +371,7 @@ impl State<App> for RoutePlanner {
self.update_waypoints_drawable(ctx);
self.update_route(ctx, app);
} else {
unreachable!()
return Tab::Route.handle_action::<RoutePlanner>(ctx, app, x);
}
}
}

View File

@ -24,7 +24,7 @@ pub struct CityPicker<A: AppLike> {
impl<A: AppLike + 'static> CityPicker<A> {
pub fn new_state(
ctx: &mut EventCtx,
app: &mut A,
app: &A,
on_load: Box<dyn FnOnce(&mut EventCtx, &mut A) -> Transition<A>>,
) -> Box<dyn State<A>> {
let city = app.map().get_city_name().clone();