Gently highlight the cell area and its borders when hovering on it. This

complements the previous change.
This commit is contained in:
Dustin Carlino 2022-07-11 09:18:52 +01:00
parent 624a7b0d58
commit 9645e43133
2 changed files with 104 additions and 58 deletions

View File

@ -1,7 +1,7 @@
use geom::{ArrowCap, Distance, PolyLine}; use geom::{ArrowCap, Distance, PolyLine, Polygon};
use map_gui::tools::ColorNetwork; use map_gui::tools::ColorNetwork;
use raw_map::Direction; use raw_map::Direction;
use widgetry::mapspace::ToggleZoomed; use widgetry::mapspace::{DummyID, ToggleZoomed, World};
use widgetry::tools::PopupMsg; use widgetry::tools::PopupMsg;
use widgetry::{ use widgetry::{
Color, ControlState, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Color, ControlState, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome,
@ -19,6 +19,7 @@ pub struct Viewer {
left_panel: Panel, left_panel: Panel,
neighbourhood: Neighbourhood, neighbourhood: Neighbourhood,
draw_top_layer: ToggleZoomed, draw_top_layer: ToggleZoomed,
highlight_cell: World<DummyID>,
edit: EditNeighbourhood, edit: EditNeighbourhood,
show_error: Drawable, show_error: Drawable,
@ -33,6 +34,7 @@ impl Viewer {
left_panel: Panel::empty(ctx), left_panel: Panel::empty(ctx),
neighbourhood, neighbourhood,
draw_top_layer: ToggleZoomed::empty(ctx), draw_top_layer: ToggleZoomed::empty(ctx),
highlight_cell: World::unbounded(),
edit: EditNeighbourhood::temporary(), edit: EditNeighbourhood::temporary(),
show_error: Drawable::empty(ctx), show_error: Drawable::empty(ctx),
}; };
@ -41,9 +43,11 @@ impl Viewer {
} }
fn update(&mut self, ctx: &mut EventCtx, app: &App) { fn update(&mut self, ctx: &mut EventCtx, app: &App) {
let (edit, draw_top_layer, render_cells) = setup_editing(ctx, app, &self.neighbourhood); let (edit, draw_top_layer, render_cells, highlight_cell) =
setup_editing(ctx, app, &self.neighbourhood);
self.edit = edit; self.edit = edit;
self.draw_top_layer = draw_top_layer; self.draw_top_layer = draw_top_layer;
self.highlight_cell = highlight_cell;
let mut show_error = GeomBatch::new(); let mut show_error = GeomBatch::new();
let mut disconnected_cells = 0; let mut disconnected_cells = 0;
@ -171,9 +175,11 @@ impl State<App> for Viewer {
app.session.heuristic = self.left_panel.dropdown_value("heuristic"); app.session.heuristic = self.left_panel.dropdown_value("heuristic");
if x != "heuristic" { if x != "heuristic" {
let (edit, draw_top_layer, _) = setup_editing(ctx, app, &self.neighbourhood); let (edit, draw_top_layer, _, highlight_cell) =
setup_editing(ctx, app, &self.neighbourhood);
self.edit = edit; self.edit = edit;
self.draw_top_layer = draw_top_layer; self.draw_top_layer = draw_top_layer;
self.highlight_cell = highlight_cell;
} }
} }
_ => {} _ => {}
@ -190,6 +196,8 @@ impl State<App> for Viewer {
} }
} }
self.highlight_cell.event(ctx);
Transition::Keep Transition::Keep
} }
@ -201,6 +209,7 @@ impl State<App> for Viewer {
crate::draw_with_layering(g, app, |g| self.edit.world.draw(g)); crate::draw_with_layering(g, app, |g| self.edit.world.draw(g));
g.redraw(&self.neighbourhood.fade_irrelevant); g.redraw(&self.neighbourhood.fade_irrelevant);
self.draw_top_layer.draw(g); self.draw_top_layer.draw(g);
self.highlight_cell.draw(g);
self.top_panel.draw(g); self.top_panel.draw(g);
self.left_panel.draw(g); self.left_panel.draw(g);
@ -226,7 +235,7 @@ fn setup_editing(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &App, app: &App,
neighbourhood: &Neighbourhood, neighbourhood: &Neighbourhood,
) -> (EditNeighbourhood, ToggleZoomed, RenderCells) { ) -> (EditNeighbourhood, ToggleZoomed, RenderCells, World<DummyID>) {
let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| { let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| {
find_shortcuts(app, neighbourhood, timer) find_shortcuts(app, neighbourhood, timer)
}); });
@ -237,6 +246,10 @@ fn setup_editing(
// The world is drawn in between areas and roads, but some things need to be drawn on top of // The world is drawn in between areas and roads, but some things need to be drawn on top of
// roads // roads
let mut draw_top_layer = ToggleZoomed::builder(); let mut draw_top_layer = ToggleZoomed::builder();
// Use a separate world to highlight cells when hovering on them. This is separate from
// edit.world so it can be drawn at the right layer and also so that we draw it even while
// hovering on roads/intersections in a cell
let mut highlight_cell = World::bounded(app.map.get_bounds());
let render_cells = RenderCells::new(map, neighbourhood); let render_cells = RenderCells::new(map, neighbourhood);
if app.session.draw_cells_as_areas { if app.session.draw_cells_as_areas {
@ -246,6 +259,23 @@ fn setup_editing(
draw_top_layer draw_top_layer
.unzoomed .unzoomed
.append(render_cells.draw_island_outlines()); .append(render_cells.draw_island_outlines());
// Highlight cell areas and their border areas when hovered
for (idx, polygons) in render_cells.polygons_per_cell.iter().enumerate() {
let mut batch = GeomBatch::new();
batch.extend(Color::YELLOW.alpha(0.1), polygons.clone());
for arrow in neighbourhood.cells[idx].border_arrows(app) {
batch.push(Color::YELLOW, arrow);
}
highlight_cell
.add_unnamed()
.hitbox(Polygon::union_all(polygons.clone()))
// Don't draw cells by default
.drawn_in_master_batch()
.draw_hovered(batch)
.build(ctx);
}
} }
let mut colorer = ColorNetwork::no_fading(app); let mut colorer = ColorNetwork::no_fading(app);
@ -260,56 +290,13 @@ fn setup_editing(
// Draw the borders of each cell // Draw the borders of each cell
for (idx, cell) in neighbourhood.cells.iter().enumerate() { for (idx, cell) in neighbourhood.cells.iter().enumerate() {
let color = render_cells.colors[idx]; let color = if app.session.draw_cells_as_areas {
for i in &cell.borders { render_cells.colors[idx].alpha(1.0)
// Most borders only have one road in the interior of the neighbourhood. Draw an arrow
// for each of those. If there happen to be multiple interior roads for one border, the
// arrows will overlap each other -- but that happens anyway with borders close
// together at certain angles.
for r in cell.roads.keys() {
let road = map.get_r(*r);
// Design choice: when we have a filter right at the entrance of a neighbourhood, it
// creates its own little cell allowing access to just the very beginning of the
// road. Let's not draw anything for that.
if app.session.modal_filters.roads.contains_key(r) {
continue;
}
// Find the angle pointing into the neighbourhood
let angle_in = if road.src_i == *i {
road.center_pts.first_line().angle()
} else if road.dst_i == *i {
road.center_pts.last_line().angle().opposite()
} else { } else {
// This interior road isn't connected to this border Color::BLACK
continue;
}; };
for arrow in cell.border_arrows(app) {
let center = map.get_i(*i).polygon.center(); draw_top_layer = draw_top_layer.push(color, arrow);
let pt_farther = center.project_away(Distance::meters(40.0), angle_in.opposite());
let pt_closer = center.project_away(Distance::meters(10.0), angle_in.opposite());
// The arrow direction depends on if the road is one-way
let thickness = Distance::meters(6.0);
let arrow = if let Some(dir) = road.oneway_for_driving() {
let pl = if road.src_i == *i {
PolyLine::must_new(vec![pt_farther, pt_closer])
} else {
PolyLine::must_new(vec![pt_closer, pt_farther])
};
pl.maybe_reverse(dir == Direction::Back)
.make_arrow(thickness, ArrowCap::Triangle)
} else {
// Order doesn't matter
PolyLine::must_new(vec![pt_closer, pt_farther])
.make_double_arrow(thickness, ArrowCap::Triangle)
};
if app.session.draw_cells_as_areas {
draw_top_layer = draw_top_layer.push(color.alpha(1.0), arrow);
} else {
draw_top_layer = draw_top_layer.push(Color::BLACK, arrow);
}
}
} }
} }
@ -346,7 +333,12 @@ fn setup_editing(
} }
} }
(edit, draw_top_layer.build(ctx), render_cells) (
edit,
draw_top_layer.build(ctx),
render_cells,
highlight_cell,
)
} }
fn help() -> Vec<&'static str> { fn help() -> Vec<&'static str> {

View File

@ -2,9 +2,9 @@ use std::collections::{BTreeMap, BTreeSet};
use maplit::btreeset; use maplit::btreeset;
use geom::{Distance, Polygon}; use geom::{ArrowCap, Distance, PolyLine, Polygon};
use map_gui::tools::DrawRoadLabels; use map_gui::tools::DrawRoadLabels;
use map_model::{IntersectionID, Map, PathConstraints, Perimeter, RoadID}; use map_model::{Direction, IntersectionID, Map, PathConstraints, Perimeter, RoadID};
use widgetry::{Drawable, EventCtx, GeomBatch}; use widgetry::{Drawable, EventCtx, GeomBatch};
use crate::{App, ModalFilters, NeighbourhoodID}; use crate::{App, ModalFilters, NeighbourhoodID};
@ -41,6 +41,60 @@ impl Cell {
pub fn is_disconnected(&self) -> bool { pub fn is_disconnected(&self) -> bool {
self.borders.is_empty() self.borders.is_empty()
} }
pub fn border_arrows(&self, app: &App) -> Vec<Polygon> {
let mut arrows = Vec::new();
for i in &self.borders {
// Most borders only have one road in the interior of the neighbourhood. Draw an arrow
// for each of those. If there happen to be multiple interior roads for one border, the
// arrows will overlap each other -- but that happens anyway with borders close
// together at certain angles.
for r in self.roads.keys() {
let road = app.map.get_r(*r);
// Design choice: when we have a filter right at the entrance of a neighbourhood, it
// creates its own little cell allowing access to just the very beginning of the
// road. Let's not draw anything for that.
if app.session.modal_filters.roads.contains_key(r) {
continue;
}
// Find the angle pointing into the neighbourhood
let angle_in = if road.src_i == *i {
road.center_pts.first_line().angle()
} else if road.dst_i == *i {
road.center_pts.last_line().angle().opposite()
} else {
// This interior road isn't connected to this border
continue;
};
let center = app.map.get_i(*i).polygon.center();
let pt_farther = center.project_away(Distance::meters(40.0), angle_in.opposite());
let pt_closer = center.project_away(Distance::meters(10.0), angle_in.opposite());
// The arrow direction depends on if the road is one-way
let thickness = Distance::meters(6.0);
if let Some(dir) = road.oneway_for_driving() {
let pl = if road.src_i == *i {
PolyLine::must_new(vec![pt_farther, pt_closer])
} else {
PolyLine::must_new(vec![pt_closer, pt_farther])
};
arrows.push(
pl.maybe_reverse(dir == Direction::Back)
.make_arrow(thickness, ArrowCap::Triangle),
);
} else {
// Order doesn't matter
arrows.push(
PolyLine::must_new(vec![pt_closer, pt_farther])
.make_double_arrow(thickness, ArrowCap::Triangle),
);
}
}
}
arrows
}
} }
/// An interval along a road's length, with start < end. /// An interval along a road's length, with start < end.