use geom::Distance;
use map_model::{EditRoad, Map, RoadID};
use osm2streets::RestrictionType;
use widgetry::mapspace::{World, WorldOutcome};
use widgetry::Color;
use widgetry::{EventCtx, GeomBatch, Text, TextExt, Widget};
use super::{road_name, EditMode, EditOutcome, Obj};
use crate::logic::turn_restrictions::{
possible_destination_roads, restricted_destination_roads, FocusedTurns,
};
use crate::render::colors;
use crate::{App, Neighbourhood};
pub fn widget(ctx: &mut EventCtx, app: &App, focus: Option<&FocusedTurns>) -> Widget {
match focus {
Some(focus) => {
let road = app.per_map.map.get_r(focus.from_r);
let restricted = focus.restricted_t.len();
let permitted = focus.possible_t.len() - restricted;
Widget::col(vec![
format!("{} permitted and {} restricted", permitted, restricted,).text_widget(ctx),
format!("turns from {} ", road.get_name(app.opts.language.as_ref()))
.text_widget(ctx),
format!("at selected intersection").text_widget(ctx),
])
}
None => Widget::nothing(),
}
}
pub fn make_world(
ctx: &mut EventCtx,
app: &App,
neighbourhood: &Neighbourhood,
focus: &Option<FocusedTurns>,
) -> World<Obj> {
let mut world = World::new();
if focus.is_none() {
for r in [
&neighbourhood.perimeter_roads,
&neighbourhood.interior_roads,
&neighbourhood.connected_exterior_roads,
]
.into_iter()
.flatten()
{
build_turn_restriction_hover_geom(*r, ctx, &mut world, app);
}
} else {
let focused_t = focus.as_ref().unwrap();
build_focused_turns_geom(focused_t, ctx, &mut world, app);
build_turn_options_geom(focused_t, ctx, &mut world, app);
}
world.initialize_hover(ctx);
world
}
fn build_turn_restriction_hover_geom(
r: RoadID,
ctx: &mut EventCtx,
world: &mut World<Obj>,
app: &App,
) {
let map = &app.per_map.map;
let road = map.get_r(r);
let restricted_destinations = restricted_destination_roads(map, r, None);
let possible_destinations = possible_destination_roads(map, road.id, None);
let mut hover_batch = GeomBatch::new();
hover_batch.push(colors::HOVER, road.get_thick_polygon());
for possible_r in possible_destinations.clone() {
let possible_road = map.get_r(possible_r);
hover_batch.push(
colors::TURN_PERMITTED_DESTINATION,
possible_road.get_thick_polygon(),
);
}
for restricted_r in restricted_destinations.clone() {
let restricted_road = map.get_r(restricted_r);
hover_batch.push(
colors::TURN_RESTRICTED_DESTINATION,
restricted_road.get_thick_polygon(),
);
}
world
.add(Obj::Road(r))
.hitbox(road.get_thick_polygon())
.drawn_in_master_batch()
.draw_hovered(hover_batch)
.tooltip(Text::from(format!(
"Click to edit turn restrictions from {}",
road_name(app, road)
)))
.clickable()
.build(ctx);
}
fn build_focused_turns_geom(
focused_t: &FocusedTurns,
ctx: &mut EventCtx,
world: &mut World<Obj>,
app: &App,
) {
let map = &app.per_map.map;
let mut batch = GeomBatch::new();
let from_road = map.get_r(focused_t.from_r);
batch.push(Color::grey(0.4).alpha(0.8), focused_t.hull.clone());
batch.push(
Color::grey(0.2),
focused_t.hull.to_outline(Distance::meters(2.0)),
);
batch.push(
colors::HOVER.alpha(1.0),
map.get_i(focused_t.i).polygon.clone(),
);
batch.push(
colors::HOVER.alpha(1.0),
map.get_i(focused_t.i)
.polygon
.to_outline(Distance::meters(3.0)),
);
world
.add(Obj::Intersection(focused_t.i))
.hitbox(focused_t.hull.clone())
.draw(batch.clone())
.draw_hovered(batch)
.zorder(1)
.tooltip(Text::from(format!(
"Edit restricted turn from {}",
road_name(app, from_road)
)))
.build(ctx);
}
fn build_turn_options_geom(
focused_t: &FocusedTurns,
ctx: &mut EventCtx,
world: &mut World<Obj>,
app: &App,
) {
let map = &app.per_map.map;
let from_road = map.get_r(focused_t.from_r);
let from_road_name = road_name(app, from_road);
let mut batch = GeomBatch::new();
batch.push(
colors::HOVER.alpha(1.0),
from_road
.get_thick_polygon()
.to_outline(Distance::meters(3.0)),
);
world
.add(Obj::Road(focused_t.from_r))
.hitbox(from_road.get_thick_polygon())
.draw(batch.clone())
.draw_hovered(batch)
.zorder(2)
.tooltip(Text::from(format!(
"Edit restricted turn from {}",
road_name(app, from_road)
)))
.clickable()
.build(ctx);
for target_r in &focused_t.possible_t {
if !&focused_t.restricted_t.contains(target_r) && target_r != &focused_t.from_r {
build_individual_target_road_geom(
*target_r,
colors::TURN_PERMITTED_DESTINATION.alpha(1.0),
colors::TURN_RESTRICTED_DESTINATION.alpha(1.0),
3,
format!(
"Add new turn restriction from '{}' to '{}'",
from_road_name,
road_name(app, map.get_r(*target_r))
),
focused_t,
ctx,
world,
map,
);
}
}
for target_r in &focused_t.restricted_t {
build_individual_target_road_geom(
*target_r,
colors::TURN_RESTRICTED_DESTINATION.alpha(1.0),
colors::TURN_PERMITTED_DESTINATION.alpha(1.0),
4,
format!(
"Remove turn restriction from '{}' to '{}'",
from_road_name,
road_name(app, map.get_r(*target_r))
),
focused_t,
ctx,
world,
map,
);
}
}
fn build_individual_target_road_geom(
target_r: RoadID,
before_edit_color: Color,
post_edit_color: Color,
z_order: usize,
tooltip: String,
focused_t: &FocusedTurns,
ctx: &mut EventCtx,
world: &mut World<Obj>,
map: &Map,
) {
if target_r == focused_t.from_r {
return;
}
let mut norm_batch = GeomBatch::new();
let mut hover_batch = GeomBatch::new();
let target_road = map.get_r(target_r);
norm_batch.push(
before_edit_color,
target_road
.get_thick_polygon()
.to_outline(Distance::meters(3.0)),
);
hover_batch.push(post_edit_color, target_road.get_thick_polygon());
world
.add(Obj::Road(target_r))
.hitbox(target_road.get_thick_polygon())
.draw(norm_batch)
.draw_hovered(hover_batch)
.zorder(z_order)
.tooltip(Text::from(tooltip))
.clickable()
.build(ctx);
}
pub fn handle_world_outcome(
ctx: &mut EventCtx,
app: &mut App,
outcome: WorldOutcome<Obj>,
) -> EditOutcome {
match outcome {
WorldOutcome::ClickedObject(Obj::Road(r)) => {
let cursor_pt = ctx.canvas.get_cursor_in_map_space().unwrap();
debug!("click point {:?}", cursor_pt);
if let EditMode::TurnRestrictions(ref prev_selection) = app.session.edit_mode {
if prev_selection.is_some() {
let prev = prev_selection.as_ref().unwrap();
if r == prev.from_r {
debug!("The same road has been clicked on twice {:?}", r);
} else if prev.restricted_t.contains(&r) || prev.possible_t.contains(&r) {
let mut edits = app.per_map.map.get_edits().clone();
let erc = app.per_map.map.edit_road_cmd(prev.from_r, |new| {
handle_edited_turn_restrictions(new, prev, r)
});
debug!("erc={:?}", erc);
edits.commands.push(erc);
app.apply_edits(edits);
crate::redraw_all_icons(ctx, app);
app.session.edit_mode = EditMode::TurnRestrictions(None);
return EditOutcome::UpdateAll;
} else {
debug!(
"Two difference roads have been clicked on prev={:?}, new {:?}",
prev.from_r, r
);
}
} else {
debug!("No previous road selected. New selection {:?}", r);
}
}
app.session.edit_mode =
EditMode::TurnRestrictions(Some(FocusedTurns::new(r, cursor_pt, &app.per_map.map)));
debug!(
"TURN RESTRICTIONS: handle_world_outcome - Clicked on Road {:?}",
r
);
EditOutcome::UpdatePanelAndWorld
}
WorldOutcome::ClickedFreeSpace(_) => {
app.session.edit_mode = EditMode::TurnRestrictions(None);
debug!("TURN RESTRICTIONS: handle_world_outcome - Clicked on FreeSpace");
EditOutcome::UpdatePanelAndWorld
}
_ => EditOutcome::Nothing,
}
}
pub fn handle_edited_turn_restrictions(new: &mut EditRoad, ft: &FocusedTurns, target_r: RoadID) {
if ft.restricted_t.contains(&target_r) {
debug!(
"Remove existing banned turn from src={:?}, to dst {:?}",
ft.from_r, target_r
);
new.turn_restrictions.retain(|(_, r)| *r != target_r);
new.complicated_turn_restrictions
.retain(|(_, r)| *r != target_r);
} else if ft.possible_t.contains(&target_r) {
debug!(
"Create new banned turn from src={:?}, to dst {:?}",
ft.from_r, target_r
);
new.turn_restrictions
.push((RestrictionType::BanTurns, target_r));
} else {
debug!(
"Nothing to change src={:?}, to dst {:?}",
ft.from_r, target_r
);
}
}