add turn icons to the new multi-signal editor

This commit is contained in:
Dustin Carlino 2020-08-11 16:36:41 -07:00
parent c08e244d23
commit 5803e5721e
2 changed files with 197 additions and 80 deletions

View File

@ -1,14 +1,14 @@
use crate::app::App;
use crate::edit::traffic_signals::make_top_panel;
use crate::game::{State, Transition};
use crate::app::{App, ShowEverything};
use crate::edit::traffic_signals::{draw_selected_group, make_top_panel};
use crate::game::{DrawBaselayer, State, Transition};
use crate::options::TrafficSignalStyle;
use crate::render::draw_signal_phase;
use crate::render::{draw_signal_phase, DrawOptions, DrawTurnGroup};
use ezgui::{
hotkey, Btn, Color, Composite, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
Outcome, VerticalAlignment, Widget,
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Outcome, VerticalAlignment, Widget,
};
use geom::{Bounds, Distance, Polygon};
use map_model::{IntersectionID, Phase};
use map_model::{IntersectionID, Phase, TurnGroupID, TurnPriority};
use std::collections::BTreeSet;
pub struct NewTrafficSignalEditor {
@ -17,6 +17,12 @@ pub struct NewTrafficSignalEditor {
members: BTreeSet<IntersectionID>,
current_phase: usize,
groups: Vec<DrawTurnGroup>,
// And the next priority to toggle to
group_selected: Option<(TurnGroupID, Option<TurnPriority>)>,
fade_irrelevant: Drawable,
}
impl NewTrafficSignalEditor {
@ -25,13 +31,38 @@ impl NewTrafficSignalEditor {
app: &mut App,
members: BTreeSet<IntersectionID>,
) -> Box<dyn State> {
let map = &app.primary.map;
app.primary.current_selection = None;
let fade_area = {
let mut holes = Vec::new();
for i in &members {
let i = map.get_i(*i);
holes.push(i.polygon.clone());
for r in &i.roads {
holes.push(map.get_r(*r).get_thick_polygon(map));
}
}
// The convex hull illuminates a bit more of the surrounding area, looks better
Polygon::with_holes(
map.get_boundary_polygon().clone().into_ring(),
vec![Polygon::convex_hull(holes).into_ring()],
)
};
let mut groups = Vec::new();
for i in &members {
groups.extend(DrawTurnGroup::for_i(*i, &app.primary.map));
}
Box::new(NewTrafficSignalEditor {
side_panel: make_side_panel(ctx, app, &members, 0),
top_panel: make_top_panel(ctx, app, false, false),
members,
current_phase: 0,
groups,
group_selected: None,
fade_irrelevant: GeomBatch::from(vec![(app.cs.fade_map_dark, fade_area)]).upload(ctx),
})
}
@ -78,26 +109,102 @@ impl State for NewTrafficSignalEditor {
_ => {}
}
if self.current_phase != 0 && ctx.input.key_pressed(Key::UpArrow) {
self.change_phase(ctx, app, self.current_phase - 1);
{
if self.current_phase != 0 && ctx.input.key_pressed(Key::UpArrow) {
self.change_phase(ctx, app, self.current_phase - 1);
}
// TODO When we enter this state, force all signals to have the same number of phases,
// so we can look up any of them.
let num_phases = self
.members
.iter()
.map(|i| app.primary.map.get_traffic_signal(*i).phases.len())
.max()
.unwrap();
if self.current_phase != num_phases - 1 && ctx.input.key_pressed(Key::DownArrow) {
self.change_phase(ctx, app, self.current_phase + 1);
}
}
// TODO When we enter this state, force all signals to have the same number of phases, so
// we can look up any of them.
let num_phases = self
.members
.iter()
.map(|i| app.primary.map.get_traffic_signal(*i).phases.len())
.max()
.unwrap();
if self.current_phase != num_phases - 1 && ctx.input.key_pressed(Key::DownArrow) {
self.change_phase(ctx, app, self.current_phase + 1);
if ctx.redo_mouseover() {
self.group_selected = None;
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
for g in &self.groups {
let signal = app.primary.map.get_traffic_signal(g.id.parent);
if g.block.contains_pt(pt) {
let phase = &signal.phases[self.current_phase];
let next_priority = match phase.get_priority_of_group(g.id) {
TurnPriority::Banned => {
if phase.could_be_protected(g.id, &signal.turn_groups) {
Some(TurnPriority::Protected)
} else if g.id.crosswalk {
None
} else {
Some(TurnPriority::Yield)
}
}
TurnPriority::Yield => Some(TurnPriority::Banned),
TurnPriority::Protected => {
if g.id.crosswalk {
Some(TurnPriority::Banned)
} else {
Some(TurnPriority::Yield)
}
}
};
self.group_selected = Some((g.id, next_priority));
break;
}
}
}
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, _: &App) {
fn draw_baselayer(&self) -> DrawBaselayer {
DrawBaselayer::Custom
}
fn draw(&self, g: &mut GfxCtx, app: &App) {
{
let mut opts = DrawOptions::new();
opts.suppress_traffic_signal_details
.extend(self.members.clone());
app.draw(g, opts, &app.primary.sim, &ShowEverything::new());
}
g.redraw(&self.fade_irrelevant);
let mut batch = GeomBatch::new();
for g in &self.groups {
let signal = app.primary.map.get_traffic_signal(g.id.parent);
if self
.group_selected
.as_ref()
.map(|(id, _)| *id == g.id)
.unwrap_or(false)
{
draw_selected_group(
app,
&mut batch,
g,
&signal.turn_groups[&g.id],
self.group_selected.unwrap().1,
);
} else {
batch.push(app.cs.signal_turn_block_bg, g.block.clone());
let phase = &signal.phases[self.current_phase];
let arrow_color = match phase.get_priority_of_group(g.id) {
TurnPriority::Protected => app.cs.signal_protected_turn,
TurnPriority::Yield => app.cs.signal_permitted_turn,
TurnPriority::Banned => app.cs.signal_banned_turn,
};
batch.push(arrow_color, g.arrow.clone());
}
}
batch.draw(g);
self.top_panel.draw(g);
self.side_panel.draw(g);
}

View File

@ -14,7 +14,7 @@ use ezgui::{
use geom::{ArrowCap, Distance, Duration, Polygon};
use map_model::{
ControlStopSign, ControlTrafficSignal, EditCmd, EditIntersection, IntersectionID, Phase,
PhaseType, TurnGroupID, TurnPriority,
PhaseType, TurnGroup, TurnGroupID, TurnPriority,
};
use std::collections::BTreeSet;
@ -336,65 +336,13 @@ impl State for TrafficSignalEditor {
.map(|(id, _)| *id == g.id)
.unwrap_or(false)
{
// TODO Refactor this mess. Maybe after things like "dashed with outline" can be
// expressed more composably like SVG, using lyon.
let block_color = match self.group_selected.unwrap().1 {
Some(TurnPriority::Protected) => {
let green = Color::hex("#72CE36");
let arrow = signal.turn_groups[&g.id]
.geom
.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
batch.push(green.alpha(0.5), arrow.clone());
if let Ok(p) = arrow.to_outline(Distance::meters(0.1)) {
batch.push(green, p);
}
green
}
Some(TurnPriority::Yield) => {
batch.extend(
// TODO Ideally the inner part would be the lower opacity blue, but
// can't yet express that it should cover up the thicker solid blue
// beneath it
Color::BLACK.alpha(0.8),
signal.turn_groups[&g.id].geom.dashed_arrow(
BIG_ARROW_THICKNESS,
Distance::meters(1.2),
Distance::meters(0.3),
ArrowCap::Triangle,
),
);
batch.extend(
app.cs.signal_permitted_turn.alpha(0.8),
signal.turn_groups[&g.id]
.geom
.exact_slice(
Distance::meters(0.1),
signal.turn_groups[&g.id].geom.length() - Distance::meters(0.1),
)
.dashed_arrow(
BIG_ARROW_THICKNESS / 2.0,
Distance::meters(1.0),
Distance::meters(0.5),
ArrowCap::Triangle,
),
);
app.cs.signal_permitted_turn
}
Some(TurnPriority::Banned) => {
let red = Color::hex("#EB3223");
let arrow = signal.turn_groups[&g.id]
.geom
.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
batch.push(red.alpha(0.5), arrow.clone());
if let Ok(p) = arrow.to_outline(Distance::meters(0.1)) {
batch.push(red, p);
}
red
}
None => app.cs.signal_turn_block_bg,
};
batch.push(block_color, g.block.clone());
batch.push(Color::WHITE, g.arrow.clone());
draw_selected_group(
app,
&mut batch,
g,
&signal.turn_groups[&g.id],
self.group_selected.unwrap().1,
);
} else {
batch.push(app.cs.signal_turn_block_bg, g.block.clone());
let arrow_color = match phase.get_priority_of_group(g.id) {
@ -1044,3 +992,65 @@ fn make_signal_diagram(
.exact_size_percent(30, 85)
.build(ctx)
}
pub fn draw_selected_group(
app: &App,
batch: &mut GeomBatch,
g: &DrawTurnGroup,
tg: &TurnGroup,
next_priority: Option<TurnPriority>,
) {
// TODO Refactor this mess. Maybe after things like "dashed with outline" can be expressed more
// composably like SVG, using lyon.
let block_color = match next_priority {
Some(TurnPriority::Protected) => {
let green = Color::hex("#72CE36");
let arrow = tg.geom.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
batch.push(green.alpha(0.5), arrow.clone());
if let Ok(p) = arrow.to_outline(Distance::meters(0.1)) {
batch.push(green, p);
}
green
}
Some(TurnPriority::Yield) => {
batch.extend(
// TODO Ideally the inner part would be the lower opacity blue, but can't yet
// express that it should cover up the thicker solid blue beneath it
Color::BLACK.alpha(0.8),
tg.geom.dashed_arrow(
BIG_ARROW_THICKNESS,
Distance::meters(1.2),
Distance::meters(0.3),
ArrowCap::Triangle,
),
);
batch.extend(
app.cs.signal_permitted_turn.alpha(0.8),
tg.geom
.exact_slice(
Distance::meters(0.1),
tg.geom.length() - Distance::meters(0.1),
)
.dashed_arrow(
BIG_ARROW_THICKNESS / 2.0,
Distance::meters(1.0),
Distance::meters(0.5),
ArrowCap::Triangle,
),
);
app.cs.signal_permitted_turn
}
Some(TurnPriority::Banned) => {
let red = Color::hex("#EB3223");
let arrow = tg.geom.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
batch.push(red.alpha(0.5), arrow.clone());
if let Ok(p) = arrow.to_outline(Distance::meters(0.1)) {
batch.push(red, p);
}
red
}
None => app.cs.signal_turn_block_bg,
};
batch.push(block_color, g.block.clone());
batch.push(Color::WHITE, g.arrow.clone());
}