mirror of
https://github.com/a-b-street/abstreet.git
synced 2025-01-02 03:18:55 +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::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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user