mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 15:33:44 +03:00
Create a new widgetry transition to simplify state preservation in the LTN tool. It's maybe a bit specialized, but hopefully this kind of pattern could come in handy elsewhere, or a slightly more general pattern could emerge.
This commit is contained in:
parent
36d3d85584
commit
7bb426f804
@ -5,17 +5,14 @@ use map_model::{IntersectionID, Perimeter};
|
||||
use widgetry::tools::PolyLineLasso;
|
||||
use widgetry::{DrawBaselayer, EventCtx, GfxCtx, Key, Line, ScreenPt, State, Text, Widget};
|
||||
|
||||
use crate::per_neighborhood::Tab;
|
||||
use crate::{after_edit, App, DiagonalFilter, Neighborhood, NeighborhoodID, Transition};
|
||||
use crate::{after_edit, App, DiagonalFilter, Neighborhood, Transition};
|
||||
|
||||
pub struct FreehandFilters {
|
||||
lasso: PolyLineLasso,
|
||||
id: NeighborhoodID,
|
||||
perimeter: Perimeter,
|
||||
interior_intersections: BTreeSet<IntersectionID>,
|
||||
instructions: Text,
|
||||
instructions_at: ScreenPt,
|
||||
tab: Tab,
|
||||
}
|
||||
|
||||
impl FreehandFilters {
|
||||
@ -23,11 +20,9 @@ impl FreehandFilters {
|
||||
ctx: &EventCtx,
|
||||
neighborhood: &Neighborhood,
|
||||
instructions_at: ScreenPt,
|
||||
tab: Tab,
|
||||
) -> Box<dyn State<App>> {
|
||||
Box::new(Self {
|
||||
lasso: PolyLineLasso::new(),
|
||||
id: neighborhood.id,
|
||||
perimeter: neighborhood.orig_perimeter.clone(),
|
||||
interior_intersections: neighborhood.interior_intersections.clone(),
|
||||
instructions_at,
|
||||
@ -35,7 +30,6 @@ impl FreehandFilters {
|
||||
Line("Click and drag").fg(ctx.style().text_hotkey_color),
|
||||
Line(" across the roads you want to filter"),
|
||||
]),
|
||||
tab,
|
||||
})
|
||||
}
|
||||
|
||||
@ -80,16 +74,7 @@ impl State<App> for FreehandFilters {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
if let Some(pl) = self.lasso.event(ctx) {
|
||||
self.make_filters_along_path(ctx, app, pl);
|
||||
return Transition::Multi(vec![
|
||||
Transition::Pop,
|
||||
Transition::Replace(match self.tab {
|
||||
Tab::Connectivity => crate::connectivity::Viewer::new_state(ctx, app, self.id),
|
||||
// TODO Preserve the current shortcut
|
||||
Tab::Shortcuts => {
|
||||
crate::shortcut_viewer::BrowseShortcuts::new_state(ctx, app, self.id, None)
|
||||
}
|
||||
}),
|
||||
]);
|
||||
return Transition::Multi(vec![Transition::Pop, Transition::Recreate]);
|
||||
}
|
||||
Transition::Keep
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ impl State<App> for Viewer {
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if let Some(t) = Tab::Connectivity.handle_action(
|
||||
} else if let Some(t) = crate::per_neighborhood::handle_action(
|
||||
ctx,
|
||||
app,
|
||||
x.as_ref(),
|
||||
@ -167,6 +167,10 @@ impl State<App> for Viewer {
|
||||
self.neighborhood.labels.draw(g, app);
|
||||
}
|
||||
}
|
||||
|
||||
fn recreate(&mut self, ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
|
||||
Self::new_state(ctx, app, self.neighborhood.id)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_world(
|
||||
|
@ -12,6 +12,7 @@ use crate::{
|
||||
after_edit, colors, App, BrowseNeighborhoods, DiagonalFilter, Neighborhood, Transition,
|
||||
};
|
||||
|
||||
// TODO This is only used for styling now
|
||||
#[derive(PartialEq)]
|
||||
pub enum Tab {
|
||||
Connectivity,
|
||||
@ -68,60 +69,6 @@ impl Tab {
|
||||
crate::components::LeftPanel::builder(ctx, top_panel, contents)
|
||||
}
|
||||
|
||||
pub fn handle_action(
|
||||
self,
|
||||
ctx: &mut EventCtx,
|
||||
app: &mut App,
|
||||
action: &str,
|
||||
neighborhood: &Neighborhood,
|
||||
panel: &Panel,
|
||||
) -> Option<Transition> {
|
||||
let id = neighborhood.id;
|
||||
match action {
|
||||
"Browse neighborhoods" => {
|
||||
// Recalculate the state to redraw any changed filters
|
||||
Some(Transition::Replace(BrowseNeighborhoods::new_state(
|
||||
ctx, app,
|
||||
)))
|
||||
}
|
||||
"Adjust boundary" => Some(Transition::Replace(
|
||||
crate::select_boundary::SelectBoundary::new_state(ctx, app, id),
|
||||
)),
|
||||
"Connectivity" => Some(Transition::Replace(crate::connectivity::Viewer::new_state(
|
||||
ctx, app, id,
|
||||
))),
|
||||
"Shortcuts" => Some(Transition::Replace(
|
||||
crate::shortcut_viewer::BrowseShortcuts::new_state(ctx, app, id, None),
|
||||
)),
|
||||
"Create filters along a shape" => Some(Transition::Push(
|
||||
crate::components::FreehandFilters::new_state(
|
||||
ctx,
|
||||
neighborhood,
|
||||
panel.center_of("Create filters along a shape"),
|
||||
self,
|
||||
),
|
||||
)),
|
||||
"undo" => {
|
||||
let prev = app.session.modal_filters.previous_version.take().unwrap();
|
||||
app.session.modal_filters = prev;
|
||||
after_edit(ctx, app);
|
||||
// Recreate the current state. This will reset any panel state (checkboxes and
|
||||
// dropdowns)
|
||||
Some(Transition::Replace(match self {
|
||||
Tab::Connectivity => crate::connectivity::Viewer::new_state(ctx, app, id),
|
||||
// TODO Preserve the current shortcut
|
||||
Tab::Shortcuts => {
|
||||
crate::shortcut_viewer::BrowseShortcuts::new_state(ctx, app, id, None)
|
||||
}
|
||||
}))
|
||||
}
|
||||
"Plan a route" => Some(Transition::Push(
|
||||
crate::route_planner::RoutePlanner::new_state(ctx, app),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_buttons(self, ctx: &mut EventCtx, app: &App) -> Widget {
|
||||
let mut row = Vec::new();
|
||||
for (tab, label, key) in [
|
||||
@ -166,6 +113,51 @@ impl Tab {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_action(
|
||||
ctx: &mut EventCtx,
|
||||
app: &mut App,
|
||||
action: &str,
|
||||
neighborhood: &Neighborhood,
|
||||
panel: &Panel,
|
||||
) -> Option<Transition> {
|
||||
let id = neighborhood.id;
|
||||
match action {
|
||||
"Browse neighborhoods" => {
|
||||
// Recalculate the state to redraw any changed filters
|
||||
Some(Transition::Replace(BrowseNeighborhoods::new_state(
|
||||
ctx, app,
|
||||
)))
|
||||
}
|
||||
"Adjust boundary" => Some(Transition::Replace(
|
||||
crate::select_boundary::SelectBoundary::new_state(ctx, app, id),
|
||||
)),
|
||||
"Connectivity" => Some(Transition::Replace(crate::connectivity::Viewer::new_state(
|
||||
ctx, app, id,
|
||||
))),
|
||||
"Shortcuts" => Some(Transition::Replace(
|
||||
crate::shortcut_viewer::BrowseShortcuts::new_state(ctx, app, id, None),
|
||||
)),
|
||||
"Create filters along a shape" => Some(Transition::Push(
|
||||
crate::components::FreehandFilters::new_state(
|
||||
ctx,
|
||||
neighborhood,
|
||||
panel.center_of("Create filters along a shape"),
|
||||
),
|
||||
)),
|
||||
"undo" => {
|
||||
let prev = app.session.modal_filters.previous_version.take().unwrap();
|
||||
app.session.modal_filters = prev;
|
||||
after_edit(ctx, app);
|
||||
// TODO Ideally, preserve panel state (checkboxes and dropdowns)
|
||||
Some(Transition::Recreate)
|
||||
}
|
||||
"Plan a route" => Some(Transition::Push(
|
||||
crate::route_planner::RoutePlanner::new_state(ctx, app),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum FilterableObj {
|
||||
InteriorRoad(RoadID),
|
||||
|
@ -174,7 +174,7 @@ impl State<App> for BrowseShortcuts {
|
||||
self.recalculate(ctx, app);
|
||||
}
|
||||
x => {
|
||||
if let Some(t) = Tab::Shortcuts.handle_action(
|
||||
if let Some(t) = crate::per_neighborhood::handle_action(
|
||||
ctx,
|
||||
app,
|
||||
x,
|
||||
@ -235,6 +235,15 @@ impl State<App> for BrowseShortcuts {
|
||||
self.neighborhood.labels.draw(g, app);
|
||||
}
|
||||
}
|
||||
|
||||
fn recreate(&mut self, ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
|
||||
let current_request = if self.shortcuts.paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.shortcuts.paths[self.current_idx].get_req().clone())
|
||||
};
|
||||
Self::new_state(ctx, app, self.neighborhood.id, current_request)
|
||||
}
|
||||
}
|
||||
|
||||
fn help() -> Vec<&'static str> {
|
||||
|
@ -144,6 +144,13 @@ impl<A: SharedAppState> App<A> {
|
||||
self.states.extend(states);
|
||||
true
|
||||
}
|
||||
Transition::Recreate => {
|
||||
// TODO Don't call on_destroy?
|
||||
let mut last = self.states.pop().unwrap();
|
||||
let replacement = last.recreate(ctx, &mut self.shared_app_state);
|
||||
self.states.push(replacement);
|
||||
true
|
||||
}
|
||||
Transition::Multi(list) => {
|
||||
// Always wake-up just the last state remaining after the sequence
|
||||
for t in list {
|
||||
@ -182,6 +189,12 @@ pub trait State<A>: downcast_rs::Downcast {
|
||||
/// Before this state is popped or replaced, call this.
|
||||
fn on_destroy(&mut self, _: &mut EventCtx, _: &mut A) {}
|
||||
// We don't need an on_enter -- the constructor for the state can just do it.
|
||||
|
||||
/// Respond to `Transition::Recreate` by assuming state in the app has changed, but preserving
|
||||
/// the `State`-specific state appropriately.
|
||||
fn recreate(&mut self, _: &mut EventCtx, _: &mut A) -> Box<dyn State<A>> {
|
||||
panic!("This state hasn't implemented support for Transition::Recreate");
|
||||
}
|
||||
}
|
||||
|
||||
downcast_rs::impl_downcast!(State<A>);
|
||||
@ -210,6 +223,8 @@ pub enum Transition<A> {
|
||||
Replace(Box<dyn State<A>>),
|
||||
/// Replace the entire stack of states with this stack.
|
||||
Clear(Vec<Box<dyn State<A>>>),
|
||||
/// Call `State::recreate` on the current top of the stack
|
||||
Recreate,
|
||||
/// Execute a sequence of transitions in order.
|
||||
Multi(Vec<Transition<A>>),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user