mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-23 17:07:12 +03:00
Implement LTN undo/redo... and actually just handle undo, for
simplicity, and because repeating an undid (??) action is easy
This commit is contained in:
parent
e0b18d6d07
commit
cb501933fd
@ -55,11 +55,15 @@ impl Heuristic {
|
||||
|
||||
// TODO If we already have no rat-runs, stop
|
||||
|
||||
app.session.modal_filters.before_edit();
|
||||
|
||||
match self {
|
||||
Heuristic::Greedy => greedy(ctx, app, neighborhood, timer),
|
||||
Heuristic::BruteForce => brute_force(ctx, app, neighborhood, timer),
|
||||
Heuristic::OnlyOneBorder => only_one_border(app, neighborhood),
|
||||
}
|
||||
|
||||
app.session.modal_filters.cancel_empty_edit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,9 @@ impl Viewer {
|
||||
app: &App,
|
||||
neighborhood: Neighborhood,
|
||||
) -> Box<dyn State<App>> {
|
||||
// TODO To handle undo. Going to switch to taking a NeighborhoodID instead!
|
||||
let neighborhood = Neighborhood::new(ctx, app, neighborhood.orig_perimeter);
|
||||
|
||||
let mut viewer = Viewer {
|
||||
panel: Panel::empty(ctx),
|
||||
neighborhood,
|
||||
|
@ -8,12 +8,15 @@ use widgetry::{Color, EventCtx, GeomBatch};
|
||||
use super::Neighborhood;
|
||||
use crate::app::App;
|
||||
|
||||
/// Stored in App session state
|
||||
#[derive(Default)]
|
||||
/// Stored in App session state. Before making any changes, call `before_edit`.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ModalFilters {
|
||||
/// For filters placed along a road, where is the filter located?
|
||||
pub roads: BTreeMap<RoadID, Distance>,
|
||||
pub intersections: BTreeMap<IntersectionID, DiagonalFilter>,
|
||||
|
||||
/// Edit history is preserved recursively
|
||||
pub previous_version: Box<Option<ModalFilters>>,
|
||||
}
|
||||
|
||||
/// A diagonal filter exists in an intersection. It's defined by two roads (the order is
|
||||
@ -34,6 +37,25 @@ pub struct DiagonalFilter {
|
||||
}
|
||||
|
||||
impl ModalFilters {
|
||||
/// Call before making any changes to preserve edit history
|
||||
pub fn before_edit(&mut self) {
|
||||
let copy = self.clone();
|
||||
self.previous_version = Box::new(Some(copy));
|
||||
}
|
||||
|
||||
/// If it's possible no edits were made, undo the previous call to `before_edit` and collapse
|
||||
/// the redundant piece of history.
|
||||
pub fn cancel_empty_edit(&mut self) {
|
||||
if let Some(prev) = self.previous_version.take() {
|
||||
if self.roads == prev.roads && self.intersections == prev.intersections {
|
||||
self.previous_version = prev.previous_version;
|
||||
} else {
|
||||
// There was a real difference, keep
|
||||
self.previous_version = Box::new(Some(prev));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Modify RoutingParams to respect these modal filters
|
||||
pub fn update_routing_params(&self, params: &mut RoutingParams) {
|
||||
params.avoid_roads.extend(self.roads.keys().cloned());
|
||||
|
@ -39,6 +39,9 @@ impl RoutePlanner {
|
||||
app: &mut App,
|
||||
neighborhood: Neighborhood,
|
||||
) -> Box<dyn State<App>> {
|
||||
// TODO To handle undo. Going to switch to taking a NeighborhoodID instead!
|
||||
let neighborhood = Neighborhood::new(ctx, app, neighborhood.orig_perimeter);
|
||||
|
||||
let mut rp = RoutePlanner {
|
||||
panel: Panel::empty(ctx),
|
||||
waypoints: InputWaypoints::new(app),
|
||||
|
@ -62,15 +62,9 @@ impl Tab {
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon("system/assets/tools/undo.svg")
|
||||
.disabled(true)
|
||||
.disabled(app.session.modal_filters.previous_version.is_none())
|
||||
.hotkey(lctrl(Key::Z))
|
||||
.build_widget(ctx, "undo"),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon("system/assets/tools/redo.svg")
|
||||
.disabled(true)
|
||||
.hotkey(lctrl(Key::Y))
|
||||
.build_widget(ctx, "redo"),
|
||||
]),
|
||||
])
|
||||
.section(ctx),
|
||||
@ -107,36 +101,34 @@ impl Tab {
|
||||
state.take_neighborhood().orig_perimeter,
|
||||
)]
|
||||
})),
|
||||
"Connectivity" => Transition::ConsumeState(Box::new(|state, ctx, app| {
|
||||
let state = state.downcast::<T>().ok().unwrap();
|
||||
vec![super::connectivity::Viewer::new_state(
|
||||
ctx,
|
||||
app,
|
||||
state.take_neighborhood(),
|
||||
)]
|
||||
})),
|
||||
"Rat runs" => Transition::ConsumeState(Box::new(|state, ctx, app| {
|
||||
let state = state.downcast::<T>().ok().unwrap();
|
||||
vec![super::rat_run_viewer::BrowseRatRuns::new_state(
|
||||
ctx,
|
||||
app,
|
||||
state.take_neighborhood(),
|
||||
)]
|
||||
})),
|
||||
"Pathfinding" => Transition::ConsumeState(Box::new(|state, ctx, app| {
|
||||
let state = state.downcast::<T>().ok().unwrap();
|
||||
vec![super::pathfinding::RoutePlanner::new_state(
|
||||
ctx,
|
||||
app,
|
||||
state.take_neighborhood(),
|
||||
)]
|
||||
})),
|
||||
"Connectivity" => Tab::Connectivity.switch_to_state::<T>(),
|
||||
"Rat runs" => Tab::RatRuns.switch_to_state::<T>(),
|
||||
"Pathfinding" => Tab::Pathfinding.switch_to_state::<T>(),
|
||||
"undo" => {
|
||||
let prev = app.session.modal_filters.previous_version.take().unwrap();
|
||||
app.session.modal_filters = prev;
|
||||
// Recreate the current state. This will reset any panel state (checkboxes and
|
||||
// dropdowns)
|
||||
self.switch_to_state::<T>()
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn switch_to_state<T: TakeNeighborhood + State<App>>(self) -> Transition {
|
||||
Transition::ConsumeState(Box::new(move |state, ctx, app| {
|
||||
let state = state.downcast::<T>().ok().unwrap();
|
||||
let n = state.take_neighborhood();
|
||||
vec![match self {
|
||||
Tab::Connectivity => super::connectivity::Viewer::new_state(ctx, app, n),
|
||||
Tab::RatRuns => super::rat_run_viewer::BrowseRatRuns::new_state(ctx, app, n),
|
||||
Tab::Pathfinding => super::pathfinding::RoutePlanner::new_state(ctx, app, n),
|
||||
}]
|
||||
}))
|
||||
}
|
||||
|
||||
fn make_buttons(self, ctx: &mut EventCtx) -> Widget {
|
||||
let mut row = Vec::new();
|
||||
for (tab, label, key) in [
|
||||
@ -223,6 +215,7 @@ pub fn handle_world_outcome(
|
||||
return true;
|
||||
}
|
||||
|
||||
app.session.modal_filters.before_edit();
|
||||
if app.session.modal_filters.roads.remove(&r).is_none() {
|
||||
// Place the filter on the part of the road that was clicked
|
||||
// These calls shouldn't fail -- since we clicked a road, the cursor must be in
|
||||
@ -244,6 +237,7 @@ pub fn handle_world_outcome(
|
||||
}
|
||||
|
||||
// Toggle through all possible filters
|
||||
app.session.modal_filters.before_edit();
|
||||
let mut all = DiagonalFilter::filters_for(app, i);
|
||||
if let Some(current) = app.session.modal_filters.intersections.get(&i) {
|
||||
let idx = all.iter().position(|x| x == current).unwrap();
|
||||
|
@ -34,6 +34,9 @@ impl BrowseRatRuns {
|
||||
app: &App,
|
||||
neighborhood: Neighborhood,
|
||||
) -> Box<dyn State<App>> {
|
||||
// TODO To handle undo. Going to switch to taking a NeighborhoodID instead!
|
||||
let neighborhood = Neighborhood::new(ctx, app, neighborhood.orig_perimeter);
|
||||
|
||||
let rat_runs = ctx.loading_screen("find rat runs", |_, timer| {
|
||||
find_rat_runs(
|
||||
&app.primary.map,
|
||||
@ -158,7 +161,7 @@ impl State<App> for BrowseRatRuns {
|
||||
self.recalculate(ctx, app);
|
||||
}
|
||||
x => {
|
||||
return Tab::Connectivity
|
||||
return Tab::RatRuns
|
||||
.handle_action::<BrowseRatRuns>(ctx, app, x)
|
||||
.unwrap();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user