Implement LTN undo/redo... and actually just handle undo, for

simplicity, and because repeating an undid (??) action is easy
This commit is contained in:
Dustin Carlino 2022-01-12 14:34:10 +00:00
parent e0b18d6d07
commit cb501933fd
6 changed files with 63 additions and 34 deletions

View File

@ -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();
}
}

View File

@ -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,

View File

@ -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());

View File

@ -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),

View File

@ -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();

View File

@ -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();
}