Edit modal filters from within the pathfinding view too.

Slow implementation, but at least it works
This commit is contained in:
Dustin Carlino 2021-12-29 13:14:44 +00:00
parent fbc847f48c
commit f7f23465c1
4 changed files with 60 additions and 22 deletions

View File

@ -15,10 +15,10 @@ pub use browse::BrowseNeighborhoods;
mod browse; mod browse;
mod connectivity; mod connectivity;
mod draw_cells; mod draw_cells;
mod pathfinding;
mod per_neighborhood; mod per_neighborhood;
mod rat_run_viewer; mod rat_run_viewer;
mod rat_runs; mod rat_runs;
mod route;
mod select_boundary; mod select_boundary;
pub struct Neighborhood { pub struct Neighborhood {

View File

@ -6,7 +6,7 @@ use widgetry::{
Color, EventCtx, GfxCtx, Line, Outcome, Panel, RoundedF64, Spinner, State, Text, Widget, Color, EventCtx, GfxCtx, Line, Outcome, Panel, RoundedF64, Spinner, State, Text, Widget,
}; };
use super::per_neighborhood::{Tab, TakeNeighborhood}; use super::per_neighborhood::{FilterableObj, Tab, TakeNeighborhood};
use super::Neighborhood; use super::Neighborhood;
use crate::app::{App, Transition}; use crate::app::{App, Transition};
use crate::common::{cmp_dist, cmp_duration, InputWaypoints, WaypointID}; use crate::common::{cmp_dist, cmp_duration, InputWaypoints, WaypointID};
@ -14,7 +14,7 @@ use crate::common::{cmp_dist, cmp_duration, InputWaypoints, WaypointID};
pub struct RoutePlanner { pub struct RoutePlanner {
panel: Panel, panel: Panel,
waypoints: InputWaypoints, waypoints: InputWaypoints,
world: World<ID>, world: World<Obj>,
neighborhood: Neighborhood, neighborhood: Neighborhood,
} }
@ -26,12 +26,13 @@ impl TakeNeighborhood for RoutePlanner {
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum ID { enum Obj {
RouteAfterFilters, RouteAfterFilters,
RouteBeforeFilters, RouteBeforeFilters,
Waypoint(WaypointID), Waypoint(WaypointID),
Filterable(FilterableObj),
} }
impl ObjectID for ID {} impl ObjectID for Obj {}
impl RoutePlanner { impl RoutePlanner {
pub fn new_state( pub fn new_state(
@ -42,9 +43,10 @@ impl RoutePlanner {
let mut rp = RoutePlanner { let mut rp = RoutePlanner {
panel: Panel::empty(ctx), panel: Panel::empty(ctx),
waypoints: InputWaypoints::new(app), waypoints: InputWaypoints::new(app),
world: World::bounded(app.primary.map.get_bounds()), world: World::unbounded(),
neighborhood, neighborhood,
}; };
rp.update(ctx, app); rp.update(ctx, app);
Box::new(rp) Box::new(rp)
} }
@ -75,17 +77,30 @@ impl RoutePlanner {
let mut world = self.calculate_paths(ctx, app); let mut world = self.calculate_paths(ctx, app);
self.waypoints self.waypoints
.rebuild_world(ctx, &mut world, ID::Waypoint, 2); .rebuild_world(ctx, &mut world, Obj::Waypoint, 3);
world.initialize_hover(ctx); world.initialize_hover(ctx);
world.rebuilt_during_drag(&self.world); world.rebuilt_during_drag(&self.world);
self.world = world; self.world = world;
} }
/// Also has the side effect of changing a note in the panel /// Also has the side effect of changing a note in the panel
fn calculate_paths(&mut self, ctx: &mut EventCtx, app: &App) -> World<ID> { fn calculate_paths(&mut self, ctx: &mut EventCtx, app: &App) -> World<Obj> {
let map = &app.primary.map; let map = &app.primary.map;
let mut world = World::bounded(map.get_bounds()); let mut world = World::bounded(map.get_bounds());
// TODO It's expensive to do this as we constantly drag the route!
super::per_neighborhood::populate_world(
ctx,
app,
&self.neighborhood,
&mut world,
Obj::Filterable,
// TODO Put these on top of the routes, so we can click and filter roads part of the
// route. We lose the tooltip though; probably should put that in the panel instead
// anyway.
2,
);
// First the route respecting the filters // First the route respecting the filters
let (total_time_after, total_dist_after) = { let (total_time_after, total_dist_after) = {
let mut params = map.routing_params().clone(); let mut params = map.routing_params().clone();
@ -123,7 +138,7 @@ impl RoutePlanner {
txt.add_line(Line(format!("Distance: {}", total_dist))); txt.add_line(Line(format!("Distance: {}", total_dist)));
world world
.add(ID::RouteAfterFilters) .add(Obj::RouteAfterFilters)
.hitbox(Polygon::union_all(hitbox_pieces)) .hitbox(Polygon::union_all(hitbox_pieces))
.zorder(0) .zorder(0)
.draw(draw_route) .draw(draw_route)
@ -167,7 +182,7 @@ impl RoutePlanner {
let mut txt = Text::new(); let mut txt = Text::new();
// If these two stats are the same, assume the two paths are equivalent // If these two stats are the same, assume the two paths are equivalent
if total_time == total_time_after && total_dist == total_dist_after { if total_time == total_time_after && total_dist == total_dist_after {
world.delete(ID::RouteAfterFilters); world.delete(Obj::RouteAfterFilters);
txt.add_line(Line( txt.add_line(Line(
"The route is the same before/after the new modal filters", "The route is the same before/after the new modal filters",
)); ));
@ -206,7 +221,7 @@ impl RoutePlanner {
} }
world world
.add(ID::RouteBeforeFilters) .add(Obj::RouteBeforeFilters)
.hitbox(Polygon::union_all(hitbox_pieces)) .hitbox(Polygon::union_all(hitbox_pieces))
// If the two routes partly overlap, put the "before" on top, since it has // If the two routes partly overlap, put the "before" on top, since it has
// the comparison stats. // the comparison stats.
@ -224,8 +239,25 @@ impl RoutePlanner {
impl State<App> for RoutePlanner { impl State<App> for RoutePlanner {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
let world_outcome_for_waypoints = self.world.event(ctx).map_id(|id| match id { let world_outcome = self.world.event(ctx);
ID::Waypoint(id) => id, // TODO map_id can only extract one case. Do a bit of a hack to handle filter managament
// first.
if let Some(outcome) = world_outcome.clone().maybe_map_id(|id| match id {
Obj::Filterable(id) => Some(id),
_ => None,
}) {
if super::per_neighborhood::handle_world_outcome(ctx, app, outcome) {
// Recalculate the neighborhood
self.neighborhood =
Neighborhood::new(ctx, app, self.neighborhood.orig_perimeter.clone());
self.update(ctx, app);
return Transition::Keep;
}
// Fall through. Clicking free space and other ID-less outcomes will match here, but we
// don't want them to.
}
let world_outcome_for_waypoints = world_outcome.map_id(|id| match id {
Obj::Waypoint(id) => id,
_ => unreachable!(), _ => unreachable!(),
}); });
@ -239,7 +271,7 @@ impl State<App> for RoutePlanner {
// Recompute paths // Recompute paths
let mut world = self.calculate_paths(ctx, app); let mut world = self.calculate_paths(ctx, app);
self.waypoints self.waypoints
.rebuild_world(ctx, &mut world, ID::Waypoint, 2); .rebuild_world(ctx, &mut world, Obj::Waypoint, 2);
world.initialize_hover(ctx); world.initialize_hover(ctx);
world.rebuilt_during_drag(&self.world); world.rebuilt_during_drag(&self.world);
self.world = world; self.world = world;

View File

@ -89,7 +89,7 @@ impl Tab {
})), })),
"Pathfinding" => Transition::ConsumeState(Box::new(|state, ctx, app| { "Pathfinding" => Transition::ConsumeState(Box::new(|state, ctx, app| {
let state = state.downcast::<T>().ok().unwrap(); let state = state.downcast::<T>().ok().unwrap();
vec![super::route::RoutePlanner::new_state( vec![super::pathfinding::RoutePlanner::new_state(
ctx, ctx,
app, app,
state.take_neighborhood(), state.take_neighborhood(),

View File

@ -31,6 +31,7 @@ pub struct World<ID: ObjectID> {
} }
/// The result of a `World` handling an event /// The result of a `World` handling an event
#[derive(Clone)]
pub enum WorldOutcome<ID: ObjectID> { pub enum WorldOutcome<ID: ObjectID> {
/// A left click occurred while not hovering on any object /// A left click occurred while not hovering on any object
ClickedFreeSpace(Pt2D), ClickedFreeSpace(Pt2D),
@ -55,22 +56,27 @@ impl<I: ObjectID> WorldOutcome<I> {
/// component owns a World that contains a few different types of objects, some of which are /// component owns a World that contains a few different types of objects, some of which are
/// managed by another component that only cares about its IDs. /// managed by another component that only cares about its IDs.
pub fn map_id<O: ObjectID, F: Fn(I) -> O>(self, f: F) -> WorldOutcome<O> { pub fn map_id<O: ObjectID, F: Fn(I) -> O>(self, f: F) -> WorldOutcome<O> {
self.maybe_map_id(|id| Some(f(id))).unwrap()
}
/// Like `map_id`, but the transformation may fail.
pub fn maybe_map_id<O: ObjectID, F: Fn(I) -> Option<O>>(self, f: F) -> Option<WorldOutcome<O>> {
match self { match self {
WorldOutcome::ClickedFreeSpace(pt) => WorldOutcome::ClickedFreeSpace(pt), WorldOutcome::ClickedFreeSpace(pt) => Some(WorldOutcome::ClickedFreeSpace(pt)),
WorldOutcome::Dragging { WorldOutcome::Dragging {
obj, obj,
dx, dx,
dy, dy,
cursor, cursor,
} => WorldOutcome::Dragging { } => Some(WorldOutcome::Dragging {
obj: f(obj), obj: f(obj)?,
dx, dx,
dy, dy,
cursor, cursor,
}, }),
WorldOutcome::Keypress(action, id) => WorldOutcome::Keypress(action, f(id)), WorldOutcome::Keypress(action, id) => Some(WorldOutcome::Keypress(action, f(id)?)),
WorldOutcome::ClickedObject(id) => WorldOutcome::ClickedObject(f(id)), WorldOutcome::ClickedObject(id) => Some(WorldOutcome::ClickedObject(f(id)?)),
WorldOutcome::Nothing => WorldOutcome::Nothing, WorldOutcome::Nothing => Some(WorldOutcome::Nothing),
} }
} }
} }