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.
This commit is contained in:
Dustin Carlino 2020-11-26 13:09:57 -08:00
parent b7f3f2113b
commit 65a4b578be
5 changed files with 448 additions and 421 deletions

View File

@ -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<Transition> {
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<dyn SimpleState>) -> Box<dyn State<App>> {
Box::new(SimpleStateWrapper { panel, inner })
}
}
pub struct SimpleStateWrapper {
panel: Panel,
inner: Box<dyn SimpleState>,
}
impl State<App> 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()
}
}

View File

@ -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<App> 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<Transition> {
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<App> 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<App> for LaneEditor {
.get_l(self.l)
.get_outline(&app.primary.map),
);
self.panel.draw(g);
CommonState::draw_osd(g, app);
}
}

View File

@ -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<App> 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<App> 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<App> 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![

View File

@ -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<dyn State<App>> {
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<App> 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::<TrafficSignalEditor>().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::<TrafficSignalEditor>().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 {

View File

@ -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<IntersectionID>,
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<App> 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<App> 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<App> 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<App> 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<Transition> {
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<App> for UberTurnViewer {
.extend(self.ic.members.clone());
app.draw(g, opts, &ShowEverything::new());
self.panel.draw(g);
g.redraw(&self.draw);
}
}