From 0a03887eab9c05233a63b7a5d854c008e81196b0 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Thu, 26 Aug 2021 09:56:41 -0700 Subject: [PATCH] Refactor common event handlers for the 3 modes --- game/src/ungap/mod.rs | 184 +++++++++++++++++-------------- game/src/ungap/quick_sketch.rs | 49 ++------ game/src/ungap/route.rs | 46 ++------ map_gui/src/tools/city_picker.rs | 2 +- 4 files changed, 125 insertions(+), 156 deletions(-) diff --git a/game/src/ungap/mod.rs b/game/src/ungap/mod.rs index 4594663840..d639793ff4 100644 --- a/game/src/ungap/mod.rs +++ b/game/src/ungap/mod.rs @@ -109,44 +109,6 @@ impl State 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::().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::().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 for ExploreMap { "Share proposal" => { return Transition::Push(share::upload_proposal(ctx, app)); } - _ => unreachable!(), + x => { + return Tab::Explore.handle_action::(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> { + fn new_state(ctx: &mut EventCtx) -> Box> { let panel = Panel::new_builder(Widget::col(vec![ Widget::row(vec![ Line("About A/B Street").small_heading().into_widget(ctx), @@ -309,44 +273,102 @@ pub enum Tab { Route, } -pub fn make_header(ctx: &mut EventCtx, app: &App, current_tab: Tab) -> Widget { - Widget::col(vec![ - Widget::row(vec![ - ctx.style() - .btn_plain - .btn() - .image_path("system/assets/pregame/logo.svg") - .image_dims(50.0) - .build_widget(ctx, "about A/B Street"), - ctx.style() - .btn_popup_icon_text( - "system/assets/tools/map.svg", - nice_map_name(app.primary.map.get_name()), - ) - .hotkey(lctrl(Key::L)) - .build_widget(ctx, "change map") - .centered_vert() - .align_right(), - ]), - Widget::row(vec![ - ctx.style() - .btn_tab - .icon_text("system/assets/tools/pan.svg", "Explore") - .hotkey(Key::E) - .disabled(current_tab == 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) - .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) - .build_def(ctx), - ]), - ]) +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() + .btn_plain + .btn() + .image_path("system/assets/pregame/logo.svg") + .image_dims(50.0) + .build_widget(ctx, "about A/B Street"), + ctx.style() + .btn_popup_icon_text( + "system/assets/tools/map.svg", + nice_map_name(app.primary.map.get_name()), + ) + .hotkey(lctrl(Key::L)) + .build_widget(ctx, "change map") + .centered_vert() + .align_right(), + ]), + Widget::row(vec![ + ctx.style() + .btn_tab + .icon_text("system/assets/tools/pan.svg", "Explore") + .hotkey(Key::E) + .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(self == Tab::Create) + .build_def(ctx), + ctx.style() + .btn_tab + .icon_text("system/assets/tools/pin.svg", "Plan a route") + .hotkey(Key::R) + .disabled(self == Tab::Route) + .build_def(ctx), + ]), + ]) + } + + pub fn handle_action>( + 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::().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::().ok().unwrap(); + vec![route::RoutePlanner::new_state( + ctx, + app, + state.take_layers(), + )] + })), + _ => unreachable!(), + } + } } diff --git a/game/src/ungap/quick_sketch.rs b/game/src/ungap/quick_sketch.rs index 40d5306a90..c4de332ce0 100644 --- a/game/src/ungap/quick_sketch.rs +++ b/game/src/ungap/quick_sketch.rs @@ -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> { 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 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::().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::().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 for QuickSketch { ] })); } - _ => unreachable!(), + x => { + return Tab::Create.handle_action::(ctx, app, x); + } } } diff --git a/game/src/ungap/route.rs b/game/src/ungap/route.rs index 31d29d849f..395b975d79 100644 --- a/game/src/ungap/route.rs +++ b/game/src/ungap/route.rs @@ -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 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::().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::().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 for RoutePlanner { self.update_waypoints_drawable(ctx); self.update_route(ctx, app); } else { - unreachable!() + return Tab::Route.handle_action::(ctx, app, x); } } } diff --git a/map_gui/src/tools/city_picker.rs b/map_gui/src/tools/city_picker.rs index 9faa34d6c8..5dff2e7cad 100644 --- a/map_gui/src/tools/city_picker.rs +++ b/map_gui/src/tools/city_picker.rs @@ -24,7 +24,7 @@ pub struct CityPicker { impl CityPicker { pub fn new_state( ctx: &mut EventCtx, - app: &mut A, + app: &A, on_load: Box Transition>, ) -> Box> { let city = app.map().get_city_name().clone();