From 65a4b578be0f36da31c2c0a28d676cf2d27c1957 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Thu, 26 Nov 2020 13:09:57 -0800 Subject: [PATCH] Experiment with separating concerns in some simple States. This is closer to a callback style, which might be more familiar to people coming from other frameworks. It's a pretty mechanical refactor now, but the hope is that looking at a bunch of on_mouseover() functions will reveal further refactors more easily than a bunch of monolithic event() functions. I don't think every State fits this pattern; things like SandboxMode need to carefully orchestrate the order of things. Make simple things easy and hard things possible. --- game/src/common/mod.rs | 70 +++++- game/src/edit/lanes.rs | 185 ++++++++-------- game/src/edit/stop_signs.rs | 171 +++++++-------- game/src/edit/traffic_signals/edits.rs | 151 ++++++------- game/src/sandbox/uber_turns.rs | 292 ++++++++++++------------- 5 files changed, 448 insertions(+), 421 deletions(-) diff --git a/game/src/common/mod.rs b/game/src/common/mod.rs index 70fcf08e00..82bd75bbc2 100644 --- a/game/src/common/mod.rs +++ b/game/src/common/mod.rs @@ -5,8 +5,9 @@ use map_gui::ID; use map_model::{IntersectionID, Map, RoadID}; use sim::{AgentType, TripMode, TripPhaseType}; use widgetry::{ - lctrl, Btn, Checkbox, Color, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, - Panel, ScreenDims, ScreenPt, ScreenRectangle, Text, TextSpan, VerticalAlignment, Widget, + lctrl, Btn, Checkbox, Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, + Key, Line, Outcome, Panel, ScreenDims, ScreenPt, ScreenRectangle, State, Text, TextSpan, + VerticalAlignment, Widget, }; pub use self::minimap::Minimap; @@ -410,3 +411,68 @@ pub fn checkbox_per_mode( } Widget::custom_row(filters) } + +/// Many game states fit a pattern of managing a single panel, handling mouseover events, and other +/// interactions on the map. Implementing this instead of `State` reduces some boilerplate. +pub trait SimpleState { + /// Called when something on the panel has been clicked. Since the action is just a string, + /// the fallback case can just use `unreachable!()`. + fn on_click( + &mut self, + ctx: &mut EventCtx, + app: &mut App, + action: &str, + panel: &Panel, + ) -> Transition; + /// Called when something on the panel has changed. If a transition is returned, stop handling + /// the event and immediately apply the transition. + fn panel_changed(&mut self, _: &mut EventCtx, _: &mut App, _: &Panel) -> Option { + None + } + /// Called when the mouse has moved. + fn on_mouseover(&mut self, _: &mut EventCtx, _: &mut App) {} + /// If a panel `on_click` event didn't occur and `panel_changed` didn't return transition, then + /// call this to handle all other events. + fn other_event(&mut self, _: &mut EventCtx, _: &mut App) -> Transition { + Transition::Keep + } + fn draw(&self, _: &mut GfxCtx, _: &App) {} + fn draw_baselayer(&self) -> DrawBaselayer { + DrawBaselayer::DefaultDraw + } +} + +impl dyn SimpleState { + pub fn new(panel: Panel, inner: Box) -> Box> { + Box::new(SimpleStateWrapper { panel, inner }) + } +} + +pub struct SimpleStateWrapper { + panel: Panel, + inner: Box, +} + +impl State for SimpleStateWrapper { + fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { + if ctx.redo_mouseover() { + self.inner.on_mouseover(ctx, app); + } + match self.panel.event(ctx) { + Outcome::Clicked(action) => self.inner.on_click(ctx, app, &action, &self.panel), + Outcome::Changed => self + .inner + .panel_changed(ctx, app, &self.panel) + .unwrap_or_else(|| self.inner.other_event(ctx, app)), + Outcome::Nothing => self.inner.other_event(ctx, app), + } + } + + fn draw(&self, g: &mut GfxCtx, app: &App) { + self.panel.draw(g); + self.inner.draw(g, app); + } + fn draw_baselayer(&self) -> DrawBaselayer { + self.inner.draw_baselayer() + } +} diff --git a/game/src/edit/lanes.rs b/game/src/edit/lanes.rs index 7431fa0018..50b20ebfd6 100644 --- a/game/src/edit/lanes.rs +++ b/game/src/edit/lanes.rs @@ -2,13 +2,13 @@ use map_gui::render::Renderable; use map_gui::ID; use map_model::{EditCmd, LaneID, LaneType, Map}; use widgetry::{ - Btn, Choice, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, - Text, TextExt, VerticalAlignment, Widget, + Btn, Choice, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Panel, State, Text, + TextExt, VerticalAlignment, Widget, }; use crate::app::App; use crate::app::Transition; -use crate::common::CommonState; +use crate::common::{CommonState, SimpleState}; use crate::edit::zones::ZoneEditor; use crate::edit::{ apply_map_edits, can_edit_lane, maybe_edit_intersection, speed_limit_choices, try_change_lt, @@ -18,7 +18,6 @@ use crate::sandbox::GameplayMode; pub struct LaneEditor { l: LaneID, mode: GameplayMode, - panel: Panel, } impl LaneEditor { @@ -101,35 +100,101 @@ impl LaneEditor { Btn::text_fg("Change access restrictions").build_def(ctx, Key::A), Btn::text_bg2("Finish").build_def(ctx, Key::Escape), ]; - let panel = Panel::new(Widget::col(col)) .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) .build(ctx); - Box::new(LaneEditor { l, mode, panel }) + SimpleState::new(panel, Box::new(LaneEditor { l, mode })) } } -impl State for LaneEditor { - fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { - ctx.canvas_movement(); - // Restrict what can be selected. - if ctx.redo_mouseover() { - app.recalculate_current_selection(ctx); - if let Some(ID::Lane(l)) = app.primary.current_selection { - if !can_edit_lane(&self.mode, l, app) { - app.primary.current_selection = None; +impl SimpleState for LaneEditor { + fn on_click(&mut self, ctx: &mut EventCtx, app: &mut App, x: &str, _: &Panel) -> Transition { + match x { + "Edit multiple lanes" => Transition::Replace(crate::edit::bulk::BulkSelect::new( + ctx, + app, + app.primary.map.get_l(self.l).parent, + )), + "Change access restrictions" => Transition::Push(ZoneEditor::new( + ctx, + app, + app.primary.map.get_l(self.l).parent, + )), + "Finish" => Transition::Pop, + x => { + let map = &mut app.primary.map; + let result = match x { + "reverse direction" => Ok(reverse_lane(map, self.l)), + "convert to a driving lane" => { + try_change_lt(ctx, map, self.l, LaneType::Driving) + } + "convert to a protected bike lane" => { + try_change_lt(ctx, map, self.l, LaneType::Biking) + } + "convert to a bus-only lane" => try_change_lt(ctx, map, self.l, LaneType::Bus), + "convert to an on-street parking lane" => { + try_change_lt(ctx, map, self.l, LaneType::Parking) + } + "close for construction" => { + try_change_lt(ctx, map, self.l, LaneType::Construction) + } + _ => unreachable!(), + }; + match result { + Ok(cmd) => { + let mut edits = map.get_edits().clone(); + edits.commands.push(cmd); + apply_map_edits(ctx, app, edits); + + Transition::Replace(LaneEditor::new(ctx, app, self.l, self.mode.clone())) + } + Err(err) => Transition::Push(err), } - } else if let Some(ID::Intersection(i)) = app.primary.current_selection { - if app.primary.map.maybe_get_stop_sign(i).is_some() - && !self.mode.can_edit_stop_signs() - { - app.primary.current_selection = None; - } - } else { - app.primary.current_selection = None; } } + } + + fn panel_changed( + &mut self, + ctx: &mut EventCtx, + app: &mut App, + panel: &Panel, + ) -> Option { + let mut edits = app.primary.map.get_edits().clone(); + edits.commands.push(app.primary.map.edit_road_cmd( + app.primary.map.get_l(self.l).parent, + |new| { + new.speed_limit = panel.dropdown_value("speed limit"); + }, + )); + apply_map_edits(ctx, app, edits); + Some(Transition::Replace(LaneEditor::new( + ctx, + app, + self.l, + self.mode.clone(), + ))) + } + + fn on_mouseover(&mut self, ctx: &mut EventCtx, app: &mut App) { + app.recalculate_current_selection(ctx); + if let Some(ID::Lane(l)) = app.primary.current_selection { + if !can_edit_lane(&self.mode, l, app) { + app.primary.current_selection = None; + } + } else if let Some(ID::Intersection(i)) = app.primary.current_selection { + if app.primary.map.maybe_get_stop_sign(i).is_some() && !self.mode.can_edit_stop_signs() + { + app.primary.current_selection = None; + } + } else { + app.primary.current_selection = None; + } + } + + fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { + ctx.canvas_movement(); if let Some(ID::Lane(l)) = app.primary.current_selection { if app.per_obj.left_click(ctx, "edit this lane") { return Transition::Replace(LaneEditor::new(ctx, app, l, self.mode.clone())); @@ -141,79 +206,6 @@ impl State for LaneEditor { } } - match self.panel.event(ctx) { - Outcome::Clicked(x) => match x.as_ref() { - "Edit multiple lanes" => { - return Transition::Replace(crate::edit::bulk::BulkSelect::new( - ctx, - app, - app.primary.map.get_l(self.l).parent, - )); - } - "Change access restrictions" => { - return Transition::Push(ZoneEditor::new( - ctx, - app, - app.primary.map.get_l(self.l).parent, - )); - } - "Finish" => { - return Transition::Pop; - } - x => { - let map = &mut app.primary.map; - let result = match x { - "reverse direction" => Ok(reverse_lane(map, self.l)), - "convert to a driving lane" => { - try_change_lt(ctx, map, self.l, LaneType::Driving) - } - "convert to a protected bike lane" => { - try_change_lt(ctx, map, self.l, LaneType::Biking) - } - "convert to a bus-only lane" => { - try_change_lt(ctx, map, self.l, LaneType::Bus) - } - "convert to an on-street parking lane" => { - try_change_lt(ctx, map, self.l, LaneType::Parking) - } - "close for construction" => { - try_change_lt(ctx, map, self.l, LaneType::Construction) - } - _ => unreachable!(), - }; - match result { - Ok(cmd) => { - let mut edits = map.get_edits().clone(); - edits.commands.push(cmd); - apply_map_edits(ctx, app, edits); - - return Transition::Replace(LaneEditor::new( - ctx, - app, - self.l, - self.mode.clone(), - )); - } - Err(err) => { - return Transition::Push(err); - } - } - } - }, - Outcome::Changed => { - let mut edits = app.primary.map.get_edits().clone(); - edits.commands.push(app.primary.map.edit_road_cmd( - app.primary.map.get_l(self.l).parent, - |new| { - new.speed_limit = self.panel.dropdown_value("speed limit"); - }, - )); - apply_map_edits(ctx, app, edits); - return Transition::Replace(LaneEditor::new(ctx, app, self.l, self.mode.clone())); - } - _ => {} - } - Transition::Keep } @@ -225,7 +217,6 @@ impl State for LaneEditor { .get_l(self.l) .get_outline(&app.primary.map), ); - self.panel.draw(g); CommonState::draw_osd(g, app); } } diff --git a/game/src/edit/stop_signs.rs b/game/src/edit/stop_signs.rs index bcbc3801b0..2add77a9e0 100644 --- a/game/src/edit/stop_signs.rs +++ b/game/src/edit/stop_signs.rs @@ -9,20 +9,19 @@ use map_model::{ ControlStopSign, ControlTrafficSignal, EditCmd, EditIntersection, IntersectionID, RoadID, }; use widgetry::{ - Btn, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text, + Btn, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Panel, State, Text, VerticalAlignment, Widget, }; use crate::app::App; use crate::app::Transition; -use crate::common::CommonState; +use crate::common::{CommonState, SimpleState}; use crate::edit::{apply_map_edits, check_sidewalk_connectivity, TrafficSignalEditor}; use crate::sandbox::GameplayMode; // TODO For now, individual turns can't be manipulated. Banning turns could be useful, but I'm not // sure what to do about the player orphaning a section of the map. pub struct StopSignEditor { - panel: Panel, id: IntersectionID, mode: GameplayMode, // (octagon, pole) @@ -67,31 +66,94 @@ impl StopSignEditor { .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) .build(ctx); - Box::new(StopSignEditor { + SimpleState::new( panel, - id, - mode, - geom, - selected_sign: None, - }) + Box::new(StopSignEditor { + id, + mode, + geom, + selected_sign: None, + }), + ) } } -impl State for StopSignEditor { - fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { - ctx.canvas_movement(); +impl SimpleState for StopSignEditor { + fn on_click(&mut self, ctx: &mut EventCtx, app: &mut App, x: &str, _: &Panel) -> Transition { + match x { + "Finish" => Transition::Pop, + "reset to default" => { + let mut edits = app.primary.map.get_edits().clone(); + edits.commands.push(EditCmd::ChangeIntersection { + i: self.id, + old: app.primary.map.get_i_edit(self.id), + new: EditIntersection::StopSign(ControlStopSign::new( + &app.primary.map, + self.id, + )), + }); + apply_map_edits(ctx, app, edits); + Transition::Replace(StopSignEditor::new(ctx, app, self.id, self.mode.clone())) + } + "close intersection for construction" => { + let cmd = EditCmd::ChangeIntersection { + i: self.id, + old: app.primary.map.get_i_edit(self.id), + new: EditIntersection::Closed, + }; + if let Some(err) = check_sidewalk_connectivity(ctx, app, cmd.clone()) { + Transition::Push(err) + } else { + let mut edits = app.primary.map.get_edits().clone(); + edits.commands.push(cmd); + apply_map_edits(ctx, app, edits); - if ctx.redo_mouseover() { - self.selected_sign = None; - if let Some(pt) = ctx.canvas.get_cursor_in_map_space() { - for (r, (octagon, _)) in &self.geom { - if octagon.contains_pt(pt) { - self.selected_sign = Some(*r); - break; - } + Transition::Pop + } + } + "convert to traffic signal" => { + let mut edits = app.primary.map.get_edits().clone(); + edits.commands.push(EditCmd::ChangeIntersection { + i: self.id, + old: app.primary.map.get_i_edit(self.id), + new: EditIntersection::TrafficSignal( + ControlTrafficSignal::new( + &app.primary.map, + self.id, + &mut Timer::throwaway(), + ) + .export(&app.primary.map), + ), + }); + apply_map_edits(ctx, app, edits); + app.primary + .sim + .handle_live_edited_traffic_signals(&app.primary.map); + Transition::Replace(TrafficSignalEditor::new( + ctx, + app, + btreeset! {self.id}, + self.mode.clone(), + )) + } + _ => unreachable!(), + } + } + + fn on_mouseover(&mut self, ctx: &mut EventCtx, _: &mut App) { + self.selected_sign = None; + if let Some(pt) = ctx.canvas.get_cursor_in_map_space() { + for (r, (octagon, _)) in &self.geom { + if octagon.contains_pt(pt) { + self.selected_sign = Some(*r); + break; } } } + } + + fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { + ctx.canvas_movement(); if let Some(r) = self.selected_sign { let mut sign = app.primary.map.get_stop_sign(self.id).clone(); @@ -119,74 +181,6 @@ impl State for StopSignEditor { } } - match self.panel.event(ctx) { - Outcome::Clicked(x) => match x.as_ref() { - "Finish" => { - return Transition::Pop; - } - "reset to default" => { - let mut edits = app.primary.map.get_edits().clone(); - edits.commands.push(EditCmd::ChangeIntersection { - i: self.id, - old: app.primary.map.get_i_edit(self.id), - new: EditIntersection::StopSign(ControlStopSign::new( - &app.primary.map, - self.id, - )), - }); - apply_map_edits(ctx, app, edits); - return Transition::Replace(StopSignEditor::new( - ctx, - app, - self.id, - self.mode.clone(), - )); - } - "close intersection for construction" => { - let cmd = EditCmd::ChangeIntersection { - i: self.id, - old: app.primary.map.get_i_edit(self.id), - new: EditIntersection::Closed, - }; - if let Some(err) = check_sidewalk_connectivity(ctx, app, cmd.clone()) { - return Transition::Push(err); - } else { - let mut edits = app.primary.map.get_edits().clone(); - edits.commands.push(cmd); - apply_map_edits(ctx, app, edits); - - return Transition::Pop; - } - } - "convert to traffic signal" => { - let mut edits = app.primary.map.get_edits().clone(); - edits.commands.push(EditCmd::ChangeIntersection { - i: self.id, - old: app.primary.map.get_i_edit(self.id), - new: EditIntersection::TrafficSignal( - ControlTrafficSignal::new( - &app.primary.map, - self.id, - &mut Timer::throwaway(), - ) - .export(&app.primary.map), - ), - }); - apply_map_edits(ctx, app, edits); - app.primary - .sim - .handle_live_edited_traffic_signals(&app.primary.map); - return Transition::Replace(TrafficSignalEditor::new( - ctx, - app, - btreeset! {self.id}, - self.mode.clone(), - )); - } - _ => unreachable!(), - }, - _ => {} - } Transition::Keep } @@ -211,7 +205,6 @@ impl State for StopSignEditor { batch.draw(g); - self.panel.draw(g); if let Some(r) = self.selected_sign { let mut osd = Text::new(); osd.add_appended(vec![ diff --git a/game/src/edit/traffic_signals/edits.rs b/game/src/edit/traffic_signals/edits.rs index 1e1a93cbfb..650c8ab7b3 100644 --- a/game/src/edit/traffic_signals/edits.rs +++ b/game/src/edit/traffic_signals/edits.rs @@ -5,17 +5,17 @@ use map_model::{ ControlStopSign, ControlTrafficSignal, EditCmd, EditIntersection, IntersectionID, PhaseType, }; use widgetry::{ - Btn, Checkbox, Choice, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, Spinner, - State, TextExt, Widget, + Btn, Checkbox, Choice, DrawBaselayer, EventCtx, Key, Line, Panel, Spinner, State, TextExt, + Widget, }; use crate::app::{App, Transition}; +use crate::common::SimpleState; use crate::edit::traffic_signals::{BundleEdits, TrafficSignalEditor}; use crate::edit::{apply_map_edits, check_sidewalk_connectivity, StopSignEditor}; use crate::sandbox::GameplayMode; pub struct ChangeDuration { - panel: Panel, idx: usize, } @@ -25,90 +25,83 @@ impl ChangeDuration { signal: &ControlTrafficSignal, idx: usize, ) -> Box> { - Box::new(ChangeDuration { - panel: Panel::new(Widget::col(vec![ - Widget::row(vec![ - Line("How long should this stage last?") - .small_heading() - .draw(ctx), - Btn::close(ctx), - ]), - Widget::row(vec![ - "Seconds:".draw_text(ctx), - Spinner::new( - ctx, - ( - signal.get_min_crossing_time(idx).inner_seconds() as isize, - 300, - ), - signal.stages[idx] - .phase_type - .simple_duration() - .inner_seconds() as isize, - ) - .named("duration"), - ]), - Widget::row(vec![ - "Type:".draw_text(ctx), - Checkbox::toggle( - ctx, - "phase type", - "fixed", - "adaptive", - None, - match signal.stages[idx].phase_type { - PhaseType::Fixed(_) => true, - PhaseType::Adaptive(_) => false, - }, - ), - ]), - Line("Minimum time is set by the time required for crosswalk") - .secondary() + let panel = Panel::new(Widget::col(vec![ + Widget::row(vec![ + Line("How long should this stage last?") + .small_heading() .draw(ctx), - Btn::text_bg2("Apply").build_def(ctx, Key::Enter), - ])) - .build(ctx), - idx, - }) + Btn::close(ctx), + ]), + Widget::row(vec![ + "Seconds:".draw_text(ctx), + Spinner::new( + ctx, + ( + signal.get_min_crossing_time(idx).inner_seconds() as isize, + 300, + ), + signal.stages[idx] + .phase_type + .simple_duration() + .inner_seconds() as isize, + ) + .named("duration"), + ]), + Widget::row(vec![ + "Type:".draw_text(ctx), + Checkbox::toggle( + ctx, + "phase type", + "fixed", + "adaptive", + None, + match signal.stages[idx].phase_type { + PhaseType::Fixed(_) => true, + PhaseType::Adaptive(_) => false, + }, + ), + ]), + Line("Minimum time is set by the time required for crosswalk") + .secondary() + .draw(ctx), + Btn::text_bg2("Apply").build_def(ctx, Key::Enter), + ])) + .build(ctx); + SimpleState::new(panel, Box::new(ChangeDuration { idx })) } } -impl State for ChangeDuration { - fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition { - match self.panel.event(ctx) { - Outcome::Clicked(x) => match x.as_ref() { - "close" => Transition::Pop, - "Apply" => { - let dt = Duration::seconds(self.panel.spinner("duration") as f64); - let new_type = if self.panel.is_checked("phase type") { - PhaseType::Fixed(dt) - } else { - PhaseType::Adaptive(dt) - }; - let idx = self.idx; - return Transition::Multi(vec![ - Transition::Pop, - Transition::ModifyState(Box::new(move |state, ctx, app| { - let editor = state.downcast_mut::().unwrap(); - editor.add_new_edit(ctx, app, idx, |ts| { - ts.stages[idx].phase_type = new_type.clone(); - }); - })), - ]); - } - _ => unreachable!(), - }, - _ => { - if ctx.normal_left_click() && ctx.canvas.get_cursor_in_screen_space().is_none() { - return Transition::Pop; - } - Transition::Keep +impl SimpleState for ChangeDuration { + fn on_click(&mut self, _: &mut EventCtx, _: &mut App, x: &str, panel: &Panel) -> Transition { + match x { + "close" => Transition::Pop, + "Apply" => { + let dt = Duration::seconds(panel.spinner("duration") as f64); + let new_type = if panel.is_checked("phase type") { + PhaseType::Fixed(dt) + } else { + PhaseType::Adaptive(dt) + }; + let idx = self.idx; + Transition::Multi(vec![ + Transition::Pop, + Transition::ModifyState(Box::new(move |state, ctx, app| { + let editor = state.downcast_mut::().unwrap(); + editor.add_new_edit(ctx, app, idx, |ts| { + ts.stages[idx].phase_type = new_type.clone(); + }); + })), + ]) } + _ => unreachable!(), } } - fn draw(&self, g: &mut GfxCtx, _: &App) { - self.panel.draw(g); + fn other_event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition { + if ctx.normal_left_click() && ctx.canvas.get_cursor_in_screen_space().is_none() { + return Transition::Pop; + } + Transition::Keep } fn draw_baselayer(&self) -> DrawBaselayer { diff --git a/game/src/sandbox/uber_turns.rs b/game/src/sandbox/uber_turns.rs index d9897f9908..d295979d94 100644 --- a/game/src/sandbox/uber_turns.rs +++ b/game/src/sandbox/uber_turns.rs @@ -7,17 +7,15 @@ use map_gui::ID; use map_model::{IntersectionCluster, IntersectionID, PathConstraints}; use widgetry::{ Btn, Checkbox, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, - HorizontalAlignment, Key, Line, Outcome, Panel, State, Text, TextExt, VerticalAlignment, - Widget, + HorizontalAlignment, Key, Line, Panel, State, Text, TextExt, VerticalAlignment, Widget, }; use crate::app::{App, ShowEverything, Transition}; -use crate::common::CommonState; +use crate::common::{CommonState, SimpleState}; use crate::edit::ClusterTrafficSignalEditor; pub struct UberTurnPicker { members: BTreeSet, - panel: Panel, } impl UberTurnPicker { @@ -29,31 +27,67 @@ impl UberTurnPicker { members.insert(i); } - Box::new(UberTurnPicker { - members, - panel: Panel::new(Widget::col(vec![ - Widget::row(vec![ - Line("Select multiple intersections") - .small_heading() - .draw(ctx), - Btn::close(ctx), - ]), - Btn::text_fg("View uber-turns").build_def(ctx, Key::Enter), - Btn::text_fg("Edit").build_def(ctx, Key::E), - Btn::text_fg("Detect all clusters").build_def(ctx, Key::D), - ])) - .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) - .build(ctx), - }) + let panel = Panel::new(Widget::col(vec![ + Widget::row(vec![ + Line("Select multiple intersections") + .small_heading() + .draw(ctx), + Btn::close(ctx), + ]), + Btn::text_fg("View uber-turns").build_def(ctx, Key::Enter), + Btn::text_fg("Edit").build_def(ctx, Key::E), + Btn::text_fg("Detect all clusters").build_def(ctx, Key::D), + ])) + .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) + .build(ctx); + SimpleState::new(panel, Box::new(UberTurnPicker { members })) } } -impl State for UberTurnPicker { - fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { - ctx.canvas_movement(); - if ctx.redo_mouseover() { - app.recalculate_current_selection(ctx); +impl SimpleState for UberTurnPicker { + fn on_click(&mut self, ctx: &mut EventCtx, app: &mut App, x: &str, _: &Panel) -> Transition { + match x { + "close" => Transition::Pop, + "View uber-turns" => { + if self.members.len() < 2 { + return Transition::Push(PopupMsg::new( + ctx, + "Error", + vec!["Select at least two intersections"], + )); + } + Transition::Replace(UberTurnViewer::new(ctx, app, self.members.clone(), 0, true)) + } + "Edit" => { + if self.members.len() < 2 { + return Transition::Push(PopupMsg::new( + ctx, + "Error", + vec!["Select at least two intersections"], + )); + } + Transition::Replace(ClusterTrafficSignalEditor::new( + ctx, + app, + &IntersectionCluster::new(self.members.clone(), &app.primary.map).0, + )) + } + "Detect all clusters" => { + self.members.clear(); + for ic in IntersectionCluster::find_all(&app.primary.map) { + self.members.extend(ic.members); + } + Transition::Keep + } + _ => unreachable!(), } + } + + fn on_mouseover(&mut self, ctx: &mut EventCtx, app: &mut App) { + app.recalculate_current_selection(ctx); + } + fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { + ctx.canvas_movement(); if let Some(ID::Intersection(i)) = app.primary.current_selection { if !self.members.contains(&i) && app.per_obj.left_click(ctx, "add this intersection") { self.members.insert(i); @@ -63,58 +97,10 @@ impl State for UberTurnPicker { self.members.remove(&i); } } - - match self.panel.event(ctx) { - Outcome::Clicked(x) => match x.as_ref() { - "close" => { - return Transition::Pop; - } - "View uber-turns" => { - if self.members.len() < 2 { - return Transition::Push(PopupMsg::new( - ctx, - "Error", - vec!["Select at least two intersections"], - )); - } - return Transition::Replace(UberTurnViewer::new( - ctx, - app, - self.members.clone(), - 0, - true, - )); - } - "Edit" => { - if self.members.len() < 2 { - return Transition::Push(PopupMsg::new( - ctx, - "Error", - vec!["Select at least two intersections"], - )); - } - return Transition::Replace(ClusterTrafficSignalEditor::new( - ctx, - app, - &IntersectionCluster::new(self.members.clone(), &app.primary.map).0, - )); - } - "Detect all clusters" => { - self.members.clear(); - for ic in IntersectionCluster::find_all(&app.primary.map) { - self.members.extend(ic.members); - } - } - _ => unreachable!(), - }, - _ => {} - } - Transition::Keep } fn draw(&self, g: &mut GfxCtx, app: &App) { - self.panel.draw(g); CommonState::draw_osd(g, app); let mut batch = GeomBatch::new(); @@ -130,7 +116,6 @@ impl State for UberTurnPicker { } struct UberTurnViewer { - panel: Panel, draw: Drawable, ic: IntersectionCluster, idx: usize, @@ -174,90 +159,90 @@ impl UberTurnViewer { } } - Box::new(UberTurnViewer { - draw: ctx.upload(batch), - panel: Panel::new(Widget::col(vec![ - Widget::row(vec![ - Line("Uber-turn viewer").small_heading().draw(ctx), - Widget::vert_separator(ctx, 50.0), - if idx == 0 { - Btn::text_fg("<").inactive(ctx) - } else { - Btn::text_fg("<").build(ctx, "previous uber-turn", Key::LeftArrow) - }, - Text::from(Line(format!("{}/{}", idx + 1, ic.uber_turns.len())).secondary()) - .draw(ctx) - .centered_vert(), - if ic.uber_turns.is_empty() || idx == ic.uber_turns.len() - 1 { - Btn::text_fg(">").inactive(ctx) - } else { - Btn::text_fg(">").build(ctx, "next uber-turn", Key::RightArrow) - }, - Btn::close(ctx), - ]), - format!("driving_cost for a Car: {}", sum_cost).draw_text(ctx), - Widget::row(vec![ - Checkbox::toggle( - ctx, - "legal / illegal movements", - "legal", - "illegal", - None, - legal_turns, - ), - "movements".draw_text(ctx), - ]), - ])) - .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) - .build(ctx), - ic, - idx, - legal_turns, - }) + let panel = Panel::new(Widget::col(vec![ + Widget::row(vec![ + Line("Uber-turn viewer").small_heading().draw(ctx), + Widget::vert_separator(ctx, 50.0), + if idx == 0 { + Btn::text_fg("<").inactive(ctx) + } else { + Btn::text_fg("<").build(ctx, "previous uber-turn", Key::LeftArrow) + }, + Text::from(Line(format!("{}/{}", idx + 1, ic.uber_turns.len())).secondary()) + .draw(ctx) + .centered_vert(), + if ic.uber_turns.is_empty() || idx == ic.uber_turns.len() - 1 { + Btn::text_fg(">").inactive(ctx) + } else { + Btn::text_fg(">").build(ctx, "next uber-turn", Key::RightArrow) + }, + Btn::close(ctx), + ]), + format!("driving_cost for a Car: {}", sum_cost).draw_text(ctx), + Widget::row(vec![ + Checkbox::toggle( + ctx, + "legal / illegal movements", + "legal", + "illegal", + None, + legal_turns, + ), + "movements".draw_text(ctx), + ]), + ])) + .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) + .build(ctx); + SimpleState::new( + panel, + Box::new(UberTurnViewer { + draw: ctx.upload(batch), + ic, + idx, + legal_turns, + }), + ) } } -impl State for UberTurnViewer { - fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { - ctx.canvas_movement(); - - match self.panel.event(ctx) { - Outcome::Clicked(x) => match x.as_ref() { - "close" => { - return Transition::Pop; - } - "previous uber-turn" => { - return Transition::Replace(UberTurnViewer::new( - ctx, - app, - self.ic.members.clone(), - self.idx - 1, - self.legal_turns, - )); - } - "next uber-turn" => { - return Transition::Replace(UberTurnViewer::new( - ctx, - app, - self.ic.members.clone(), - self.idx + 1, - self.legal_turns, - )); - } - _ => unreachable!(), - }, - Outcome::Changed => { - return Transition::Replace(UberTurnViewer::new( - ctx, - app, - self.ic.members.clone(), - 0, - self.panel.is_checked("legal / illegal movements"), - )); - } - _ => {} +impl SimpleState for UberTurnViewer { + fn on_click(&mut self, ctx: &mut EventCtx, app: &mut App, x: &str, _: &Panel) -> Transition { + match x { + "close" => Transition::Pop, + "previous uber-turn" => Transition::Replace(UberTurnViewer::new( + ctx, + app, + self.ic.members.clone(), + self.idx - 1, + self.legal_turns, + )), + "next uber-turn" => Transition::Replace(UberTurnViewer::new( + ctx, + app, + self.ic.members.clone(), + self.idx + 1, + self.legal_turns, + )), + _ => unreachable!(), } + } + fn panel_changed( + &mut self, + ctx: &mut EventCtx, + app: &mut App, + panel: &Panel, + ) -> Option { + Some(Transition::Replace(UberTurnViewer::new( + ctx, + app, + self.ic.members.clone(), + 0, + panel.is_checked("legal / illegal movements"), + ))) + } + fn other_event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition { + ctx.canvas_movement(); Transition::Keep } @@ -271,7 +256,6 @@ impl State for UberTurnViewer { .extend(self.ic.members.clone()); app.draw(g, opts, &ShowEverything::new()); - self.panel.draw(g); g.redraw(&self.draw); } }