mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
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:
parent
b7f3f2113b
commit
65a4b578be
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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![
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user