mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-30 18:24:04 +03:00
Edit modal filters from within the pathfinding view too.
Slow implementation, but at least it works
This commit is contained in:
parent
fbc847f48c
commit
f7f23465c1
@ -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 {
|
||||||
|
@ -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;
|
@ -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(),
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user