mirror of
https://github.com/a-b-street/abstreet.git
synced 2025-01-04 20:44:52 +03:00
add turn icons to the new multi-signal editor
This commit is contained in:
parent
c08e244d23
commit
5803e5721e
@ -1,14 +1,14 @@
|
|||||||
use crate::app::App;
|
use crate::app::{App, ShowEverything};
|
||||||
use crate::edit::traffic_signals::make_top_panel;
|
use crate::edit::traffic_signals::{draw_selected_group, make_top_panel};
|
||||||
use crate::game::{State, Transition};
|
use crate::game::{DrawBaselayer, State, Transition};
|
||||||
use crate::options::TrafficSignalStyle;
|
use crate::options::TrafficSignalStyle;
|
||||||
use crate::render::draw_signal_phase;
|
use crate::render::{draw_signal_phase, DrawOptions, DrawTurnGroup};
|
||||||
use ezgui::{
|
use ezgui::{
|
||||||
hotkey, Btn, Color, Composite, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
|
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
|
||||||
Outcome, VerticalAlignment, Widget,
|
Line, Outcome, VerticalAlignment, Widget,
|
||||||
};
|
};
|
||||||
use geom::{Bounds, Distance, Polygon};
|
use geom::{Bounds, Distance, Polygon};
|
||||||
use map_model::{IntersectionID, Phase};
|
use map_model::{IntersectionID, Phase, TurnGroupID, TurnPriority};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
pub struct NewTrafficSignalEditor {
|
pub struct NewTrafficSignalEditor {
|
||||||
@ -17,6 +17,12 @@ pub struct NewTrafficSignalEditor {
|
|||||||
|
|
||||||
members: BTreeSet<IntersectionID>,
|
members: BTreeSet<IntersectionID>,
|
||||||
current_phase: usize,
|
current_phase: usize,
|
||||||
|
|
||||||
|
groups: Vec<DrawTurnGroup>,
|
||||||
|
// And the next priority to toggle to
|
||||||
|
group_selected: Option<(TurnGroupID, Option<TurnPriority>)>,
|
||||||
|
|
||||||
|
fade_irrelevant: Drawable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NewTrafficSignalEditor {
|
impl NewTrafficSignalEditor {
|
||||||
@ -25,13 +31,38 @@ impl NewTrafficSignalEditor {
|
|||||||
app: &mut App,
|
app: &mut App,
|
||||||
members: BTreeSet<IntersectionID>,
|
members: BTreeSet<IntersectionID>,
|
||||||
) -> Box<dyn State> {
|
) -> Box<dyn State> {
|
||||||
|
let map = &app.primary.map;
|
||||||
app.primary.current_selection = None;
|
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 {
|
Box::new(NewTrafficSignalEditor {
|
||||||
side_panel: make_side_panel(ctx, app, &members, 0),
|
side_panel: make_side_panel(ctx, app, &members, 0),
|
||||||
top_panel: make_top_panel(ctx, app, false, false),
|
top_panel: make_top_panel(ctx, app, false, false),
|
||||||
members,
|
members,
|
||||||
current_phase: 0,
|
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
|
if ctx.redo_mouseover() {
|
||||||
// we can look up any of them.
|
self.group_selected = None;
|
||||||
let num_phases = self
|
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
|
||||||
.members
|
for g in &self.groups {
|
||||||
.iter()
|
let signal = app.primary.map.get_traffic_signal(g.id.parent);
|
||||||
.map(|i| app.primary.map.get_traffic_signal(*i).phases.len())
|
if g.block.contains_pt(pt) {
|
||||||
.max()
|
let phase = &signal.phases[self.current_phase];
|
||||||
.unwrap();
|
let next_priority = match phase.get_priority_of_group(g.id) {
|
||||||
if self.current_phase != num_phases - 1 && ctx.input.key_pressed(Key::DownArrow) {
|
TurnPriority::Banned => {
|
||||||
self.change_phase(ctx, app, self.current_phase + 1);
|
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
|
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.top_panel.draw(g);
|
||||||
self.side_panel.draw(g);
|
self.side_panel.draw(g);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ use ezgui::{
|
|||||||
use geom::{ArrowCap, Distance, Duration, Polygon};
|
use geom::{ArrowCap, Distance, Duration, Polygon};
|
||||||
use map_model::{
|
use map_model::{
|
||||||
ControlStopSign, ControlTrafficSignal, EditCmd, EditIntersection, IntersectionID, Phase,
|
ControlStopSign, ControlTrafficSignal, EditCmd, EditIntersection, IntersectionID, Phase,
|
||||||
PhaseType, TurnGroupID, TurnPriority,
|
PhaseType, TurnGroup, TurnGroupID, TurnPriority,
|
||||||
};
|
};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
@ -336,65 +336,13 @@ impl State for TrafficSignalEditor {
|
|||||||
.map(|(id, _)| *id == g.id)
|
.map(|(id, _)| *id == g.id)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
// TODO Refactor this mess. Maybe after things like "dashed with outline" can be
|
draw_selected_group(
|
||||||
// expressed more composably like SVG, using lyon.
|
app,
|
||||||
let block_color = match self.group_selected.unwrap().1 {
|
&mut batch,
|
||||||
Some(TurnPriority::Protected) => {
|
g,
|
||||||
let green = Color::hex("#72CE36");
|
&signal.turn_groups[&g.id],
|
||||||
let arrow = signal.turn_groups[&g.id]
|
self.group_selected.unwrap().1,
|
||||||
.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());
|
|
||||||
} else {
|
} else {
|
||||||
batch.push(app.cs.signal_turn_block_bg, g.block.clone());
|
batch.push(app.cs.signal_turn_block_bg, g.block.clone());
|
||||||
let arrow_color = match phase.get_priority_of_group(g.id) {
|
let arrow_color = match phase.get_priority_of_group(g.id) {
|
||||||
@ -1044,3 +992,65 @@ fn make_signal_diagram(
|
|||||||
.exact_size_percent(30, 85)
|
.exact_size_percent(30, 85)
|
||||||
.build(ctx)
|
.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());
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user