diff --git a/apps/ltn/src/connectivity.rs b/apps/ltn/src/connectivity.rs index 7a7e9700de..1d5f1d494f 100644 --- a/apps/ltn/src/connectivity.rs +++ b/apps/ltn/src/connectivity.rs @@ -1,13 +1,13 @@ use geom::{ArrowCap, Distance, PolyLine}; use map_gui::tools::ColorNetwork; -use widgetry::mapspace::{ToggleZoomed, World}; +use widgetry::mapspace::ToggleZoomed; use widgetry::tools::PopupMsg; use widgetry::{ DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, TextExt, Toggle, Widget, }; +use crate::edit::{EditNeighborhood, Tab}; use crate::filters::auto::Heuristic; -use crate::per_neighborhood::{FilterableObj, Tab}; use crate::shortcuts::find_shortcuts; use crate::{colors, App, Neighborhood, NeighborhoodID, Transition}; @@ -15,8 +15,8 @@ pub struct Viewer { top_panel: Panel, left_panel: Panel, neighborhood: Neighborhood, - world: World, draw_top_layer: ToggleZoomed, + edit: EditNeighborhood, } impl Viewer { @@ -27,8 +27,8 @@ impl Viewer { top_panel: crate::components::TopPanel::panel(ctx, app), left_panel: Panel::empty(ctx), neighborhood, - world: World::unbounded(), draw_top_layer: ToggleZoomed::empty(ctx), + edit: EditNeighborhood::temporary(), }; viewer.update(ctx, app); Box::new(viewer) @@ -47,10 +47,12 @@ impl Viewer { format!("{} cells are totally disconnected", disconnected_cells) }; - self.left_panel = Tab::Connectivity + self.left_panel = self + .edit .panel_builder( ctx, app, + Tab::Connectivity, &self.top_panel, Widget::col(vec![ format!( @@ -66,8 +68,8 @@ impl Viewer { ) .build(ctx); - let (world, draw_top_layer) = make_world(ctx, app, &self.neighborhood); - self.world = world; + let (edit, draw_top_layer) = setup_editing(ctx, app, &self.neighborhood); + self.edit = edit; self.draw_top_layer = draw_top_layer; } } @@ -98,7 +100,7 @@ impl State for Viewer { )); } } - } else if let Some(t) = crate::per_neighborhood::handle_action( + } else if let Some(t) = self.edit.handle_panel_action( ctx, app, x.as_ref(), @@ -131,16 +133,15 @@ impl State for Viewer { app.session.heuristic = self.left_panel.dropdown_value("heuristic"); if x != "heuristic" { - let (world, draw_top_layer) = make_world(ctx, app, &self.neighborhood); - self.world = world; + let (edit, draw_top_layer) = setup_editing(ctx, app, &self.neighborhood); + self.edit = edit; self.draw_top_layer = draw_top_layer; } } _ => {} } - let world_outcome = self.world.event(ctx); - if crate::per_neighborhood::handle_world_outcome(ctx, app, world_outcome) { + if self.edit.event(ctx, app) { self.neighborhood = Neighborhood::new(ctx, app, self.neighborhood.id); self.update(ctx, app); } @@ -153,7 +154,7 @@ impl State for Viewer { } fn draw(&self, g: &mut GfxCtx, app: &App) { - crate::draw_with_layering(g, app, |g| self.world.draw(g)); + crate::draw_with_layering(g, app, |g| self.edit.world.draw(g)); g.redraw(&self.neighborhood.fade_irrelevant); self.draw_top_layer.draw(g); @@ -173,16 +174,16 @@ impl State for Viewer { } } -fn make_world( +fn setup_editing( ctx: &mut EventCtx, app: &App, neighborhood: &Neighborhood, -) -> (World, ToggleZoomed) { +) -> (EditNeighborhood, ToggleZoomed) { let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| { find_shortcuts(app, neighborhood, timer) }); - let mut world = crate::per_neighborhood::make_world(ctx, app, neighborhood, &shortcuts); + let mut edit = EditNeighborhood::new(ctx, app, neighborhood, &shortcuts); let map = &app.map; // The world is drawn in between areas and roads, but some things need to be drawn on top of @@ -191,7 +192,7 @@ fn make_world( let render_cells = crate::draw_cells::RenderCells::new(map, neighborhood); if app.session.draw_cells_as_areas { - world.draw_master_batch(ctx, render_cells.draw()); + edit.world.draw_master_batch(ctx, render_cells.draw()); let mut colorer = ColorNetwork::no_fading(app); colorer.ranked_roads(shortcuts.count_per_road.clone(), &app.cs.good_to_bad_red); @@ -301,7 +302,7 @@ fn make_world( } } - (world, draw_top_layer.build(ctx)) + (edit, draw_top_layer.build(ctx)) } fn help() -> Vec<&'static str> { diff --git a/apps/ltn/src/edit/filters.rs b/apps/ltn/src/edit/filters.rs new file mode 100644 index 0000000000..1073287373 --- /dev/null +++ b/apps/ltn/src/edit/filters.rs @@ -0,0 +1,131 @@ +use geom::Distance; +use map_model::PathConstraints; +use widgetry::mapspace::{World, WorldOutcome}; +use widgetry::tools::open_browser; +use widgetry::{lctrl, EventCtx, Image, Key, Line, Text, TextExt, Widget}; + +use super::Obj; +use crate::shortcuts::Shortcuts; +use crate::{after_edit, colors, App, DiagonalFilter, Neighborhood}; + +pub fn widget(ctx: &mut EventCtx, app: &App) -> Widget { + Widget::col(vec![ + Widget::row(vec![ + Image::from_path("system/assets/tools/pencil.svg") + .into_widget(ctx) + .centered_vert(), + Text::from(Line( + "Click a road or intersection to add or remove a modal filter", + )) + .wrap_to_pct(ctx, 15) + .into_widget(ctx), + ]), + crate::components::FreehandFilters::button(ctx), + Widget::row(vec![ + format!( + "{} filters added", + app.session.modal_filters.roads.len() + + app.session.modal_filters.intersections.len() + ) + .text_widget(ctx) + .centered_vert(), + ctx.style() + .btn_plain + .icon("system/assets/tools/undo.svg") + .disabled(app.session.modal_filters.previous_version.is_none()) + .hotkey(lctrl(Key::Z)) + .build_widget(ctx, "undo"), + ]), + ]) +} + +/// Creates clickable objects for managing filters on roads and intersections. Everything is +/// invisible; the caller is responsible for drawing things. +pub fn make_world( + ctx: &mut EventCtx, + app: &App, + neighborhood: &Neighborhood, + shortcuts: &Shortcuts, +) -> World { + let map = &app.map; + let mut world = World::bounded(map.get_bounds()); + + for r in &neighborhood.orig_perimeter.interior { + let road = map.get_r(*r); + world + .add(Obj::InteriorRoad(*r)) + .hitbox(road.get_thick_polygon()) + .drawn_in_master_batch() + .hover_outline(colors::OUTLINE, Distance::meters(5.0)) + .tooltip(Text::from(format!( + "{} shortcuts cross {}", + shortcuts.count_per_road.get(*r), + road.get_name(app.opts.language.as_ref()), + ))) + .hotkey(lctrl(Key::D), "debug") + .clickable() + .build(ctx); + } + + for i in &neighborhood.interior_intersections { + world + .add(Obj::InteriorIntersection(*i)) + .hitbox(map.get_i(*i).polygon.clone()) + .drawn_in_master_batch() + .hover_outline(colors::OUTLINE, Distance::meters(5.0)) + .tooltip(Text::from(format!( + "{} shortcuts cross this intersection", + shortcuts.count_per_intersection.get(*i) + ))) + .clickable() + .hotkey(lctrl(Key::D), "debug") + .build(ctx); + } + + world.initialize_hover(ctx); + world +} + +pub fn handle_world_outcome(ctx: &mut EventCtx, app: &mut App, outcome: WorldOutcome) -> bool { + let map = &app.map; + match outcome { + WorldOutcome::ClickedObject(Obj::InteriorRoad(r)) => { + let road = map.get_r(r); + // Filtering a road that's already marked bike-only doesn't make sense. Likewise for + // one-ways. + if !PathConstraints::Car.can_use_road(road, map) || road.is_oneway() { + 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 + // map-space. And project_pt returns a point that's guaranteed to be on the + // polyline. + let cursor_pt = ctx.canvas.get_cursor_in_map_space().unwrap(); + let pt_on_line = road.center_pts.project_pt(cursor_pt); + let (distance, _) = road.center_pts.dist_along_of_point(pt_on_line).unwrap(); + + app.session.modal_filters.roads.insert(r, distance); + } + after_edit(ctx, app); + true + } + WorldOutcome::ClickedObject(Obj::InteriorIntersection(i)) => { + app.session.modal_filters.before_edit(); + DiagonalFilter::cycle_through_alternatives(app, i); + after_edit(ctx, app); + true + } + WorldOutcome::Keypress("debug", Obj::InteriorIntersection(i)) => { + open_browser(app.map.get_i(i).orig_id.to_string()); + false + } + WorldOutcome::Keypress("debug", Obj::InteriorRoad(r)) => { + open_browser(app.map.get_r(r).orig_id.osm_way_id.to_string()); + false + } + _ => false, + } +} diff --git a/apps/ltn/src/edit/mod.rs b/apps/ltn/src/edit/mod.rs new file mode 100644 index 0000000000..3d66826b95 --- /dev/null +++ b/apps/ltn/src/edit/mod.rs @@ -0,0 +1,181 @@ +mod filters; + +use map_model::{IntersectionID, RoadID}; +use widgetry::mapspace::{ObjectID, World}; +use widgetry::{EventCtx, Key, Line, Panel, PanelBuilder, Widget, DEFAULT_CORNER_RADIUS}; + +use crate::shortcuts::Shortcuts; +use crate::{after_edit, App, BrowseNeighborhoods, Neighborhood, Transition}; + +// TODO This is only used for styling now +#[derive(PartialEq)] +pub enum Tab { + Connectivity, + Shortcuts, +} + +impl Tab { + fn make_buttons(self, ctx: &mut EventCtx, app: &App) -> Widget { + let mut row = Vec::new(); + for (tab, label, key) in [ + (Tab::Connectivity, "Connectivity", Key::F1), + (Tab::Shortcuts, "Shortcuts", Key::F2), + ] { + // TODO Match the TabController styling + row.push( + ctx.style() + .btn_tab + .text(label) + .corner_rounding(geom::CornerRadii { + top_left: DEFAULT_CORNER_RADIUS, + top_right: DEFAULT_CORNER_RADIUS, + bottom_left: 0.0, + bottom_right: 0.0, + }) + .hotkey(key) + // We abuse "disabled" to denote "currently selected" + .disabled(self == tab) + .build_def(ctx), + ); + } + if app.session.consultation.is_none() { + // TODO The 3rd doesn't really act like a tab + row.push( + ctx.style() + .btn_tab + .text("Adjust boundary") + .corner_rounding(geom::CornerRadii { + top_left: DEFAULT_CORNER_RADIUS, + top_right: DEFAULT_CORNER_RADIUS, + bottom_left: 0.0, + bottom_right: 0.0, + }) + .hotkey(Key::B) + .build_def(ctx), + ); + } + + Widget::row(row) + } +} + +pub struct EditNeighborhood { + // Only pub for drawing + pub world: World, + // True if we're editing filters, false if we're editing one-ways. (An enum is overkill) + filters: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Obj { + InteriorRoad(RoadID), + InteriorIntersection(IntersectionID), +} +impl ObjectID for Obj {} + +impl EditNeighborhood { + pub fn temporary() -> Self { + Self { + world: World::unbounded(), + filters: true, + } + } + + // TODO filters! + pub fn new( + ctx: &mut EventCtx, + app: &App, + neighborhood: &Neighborhood, + shortcuts: &Shortcuts, + ) -> Self { + Self { + world: filters::make_world(ctx, app, neighborhood, shortcuts), + filters: true, + } + } + + pub fn panel_builder( + &self, + ctx: &mut EventCtx, + app: &App, + tab: Tab, + top_panel: &Panel, + per_tab_contents: Widget, + ) -> PanelBuilder { + let contents = Widget::col(vec![ + app.session.alt_proposals.to_widget(ctx, app), + BrowseNeighborhoods::button(ctx, app), + Line("Editing neighborhood") + .small_heading() + .into_widget(ctx), + if self.filters { + filters::widget(ctx, app) + } else { + todo!() + } + .section(ctx), + tab.make_buttons(ctx, app), + per_tab_contents, + crate::route_planner::RoutePlanner::button(ctx), + ]); + crate::components::LeftPanel::builder(ctx, top_panel, contents) + } + + /// If true, the neighborhood has changed and the caller should recalculate stuff, including + /// the panel + pub fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> bool { + let outcome = self.world.event(ctx); + if self.filters { + filters::handle_world_outcome(ctx, app, outcome) + } else { + todo!() + } + } + + pub fn handle_panel_action( + &self, + ctx: &mut EventCtx, + app: &mut App, + action: &str, + neighborhood: &Neighborhood, + panel: &Panel, + ) -> Option { + 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), + )), + // Overkill to force all mode-specific code into the module + "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, + } + } +} diff --git a/apps/ltn/src/lib.rs b/apps/ltn/src/lib.rs index a052500114..d7095b0b36 100644 --- a/apps/ltn/src/lib.rs +++ b/apps/ltn/src/lib.rs @@ -22,12 +22,12 @@ mod colors; mod components; mod connectivity; mod draw_cells; +mod edit; mod export; mod filters; mod impact; mod neighborhood; mod partition; -mod per_neighborhood; mod route_planner; mod save; mod select_boundary; diff --git a/apps/ltn/src/per_neighborhood.rs b/apps/ltn/src/per_neighborhood.rs deleted file mode 100644 index 21635856e3..0000000000 --- a/apps/ltn/src/per_neighborhood.rs +++ /dev/null @@ -1,263 +0,0 @@ -use geom::Distance; -use map_model::{IntersectionID, PathConstraints, RoadID}; -use widgetry::mapspace::{ObjectID, World, WorldOutcome}; -use widgetry::tools::open_browser; -use widgetry::{ - lctrl, EventCtx, Image, Key, Line, Panel, PanelBuilder, Text, TextExt, Widget, - DEFAULT_CORNER_RADIUS, -}; - -use crate::shortcuts::Shortcuts; -use crate::{ - after_edit, colors, App, BrowseNeighborhoods, DiagonalFilter, Neighborhood, Transition, -}; - -// TODO This is only used for styling now -#[derive(PartialEq)] -pub enum Tab { - Connectivity, - Shortcuts, -} - -impl Tab { - pub fn panel_builder( - self, - ctx: &mut EventCtx, - app: &App, - top_panel: &Panel, - per_tab_contents: Widget, - ) -> PanelBuilder { - let contents = Widget::col(vec![ - app.session.alt_proposals.to_widget(ctx, app), - BrowseNeighborhoods::button(ctx, app), - Line("Editing neighborhood") - .small_heading() - .into_widget(ctx), - Widget::col(vec![ - Widget::row(vec![ - Image::from_path("system/assets/tools/pencil.svg") - .into_widget(ctx) - .centered_vert(), - Text::from(Line( - "Click a road or intersection to add or remove a modal filter", - )) - .wrap_to_pct(ctx, 15) - .into_widget(ctx), - ]), - crate::components::FreehandFilters::button(ctx), - Widget::row(vec![ - format!( - "{} filters added", - app.session.modal_filters.roads.len() - + app.session.modal_filters.intersections.len() - ) - .text_widget(ctx) - .centered_vert(), - ctx.style() - .btn_plain - .icon("system/assets/tools/undo.svg") - .disabled(app.session.modal_filters.previous_version.is_none()) - .hotkey(lctrl(Key::Z)) - .build_widget(ctx, "undo"), - ]), - ]) - .section(ctx), - self.make_buttons(ctx, app), - per_tab_contents, - crate::route_planner::RoutePlanner::button(ctx), - ]); - crate::components::LeftPanel::builder(ctx, top_panel, contents) - } - - fn make_buttons(self, ctx: &mut EventCtx, app: &App) -> Widget { - let mut row = Vec::new(); - for (tab, label, key) in [ - (Tab::Connectivity, "Connectivity", Key::F1), - (Tab::Shortcuts, "Shortcuts", Key::F2), - ] { - // TODO Match the TabController styling - row.push( - ctx.style() - .btn_tab - .text(label) - .corner_rounding(geom::CornerRadii { - top_left: DEFAULT_CORNER_RADIUS, - top_right: DEFAULT_CORNER_RADIUS, - bottom_left: 0.0, - bottom_right: 0.0, - }) - .hotkey(key) - // We abuse "disabled" to denote "currently selected" - .disabled(self == tab) - .build_def(ctx), - ); - } - if app.session.consultation.is_none() { - // TODO The 3rd doesn't really act like a tab - row.push( - ctx.style() - .btn_tab - .text("Adjust boundary") - .corner_rounding(geom::CornerRadii { - top_left: DEFAULT_CORNER_RADIUS, - top_right: DEFAULT_CORNER_RADIUS, - bottom_left: 0.0, - bottom_right: 0.0, - }) - .hotkey(Key::B) - .build_def(ctx), - ); - } - - Widget::row(row) - } -} - -pub fn handle_action( - ctx: &mut EventCtx, - app: &mut App, - action: &str, - neighborhood: &Neighborhood, - panel: &Panel, -) -> Option { - 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), - InteriorIntersection(IntersectionID), -} -impl ObjectID for FilterableObj {} - -/// Creates clickable objects for managing filters on roads and intersections. Everything is -/// invisible; the caller is responsible for drawing things. -pub fn make_world( - ctx: &mut EventCtx, - app: &App, - neighborhood: &Neighborhood, - shortcuts: &Shortcuts, -) -> World { - let map = &app.map; - let mut world = World::bounded(map.get_bounds()); - - for r in &neighborhood.orig_perimeter.interior { - let road = map.get_r(*r); - world - .add(FilterableObj::InteriorRoad(*r)) - .hitbox(road.get_thick_polygon()) - .drawn_in_master_batch() - .hover_outline(colors::OUTLINE, Distance::meters(5.0)) - .tooltip(Text::from(format!( - "{} shortcuts cross {}", - shortcuts.count_per_road.get(*r), - road.get_name(app.opts.language.as_ref()), - ))) - .hotkey(lctrl(Key::D), "debug") - .clickable() - .build(ctx); - } - - for i in &neighborhood.interior_intersections { - world - .add(FilterableObj::InteriorIntersection(*i)) - .hitbox(map.get_i(*i).polygon.clone()) - .drawn_in_master_batch() - .hover_outline(colors::OUTLINE, Distance::meters(5.0)) - .tooltip(Text::from(format!( - "{} shortcuts cross this intersection", - shortcuts.count_per_intersection.get(*i) - ))) - .clickable() - .hotkey(lctrl(Key::D), "debug") - .build(ctx); - } - - world.initialize_hover(ctx); - world -} - -/// If true, the neighborhood has changed and the caller should recalculate stuff, including the -/// panel -pub fn handle_world_outcome( - ctx: &mut EventCtx, - app: &mut App, - outcome: WorldOutcome, -) -> bool { - let map = &app.map; - match outcome { - WorldOutcome::ClickedObject(FilterableObj::InteriorRoad(r)) => { - let road = map.get_r(r); - // Filtering a road that's already marked bike-only doesn't make sense. Likewise for - // one-ways. - if !PathConstraints::Car.can_use_road(road, map) || road.is_oneway() { - 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 - // map-space. And project_pt returns a point that's guaranteed to be on the - // polyline. - let cursor_pt = ctx.canvas.get_cursor_in_map_space().unwrap(); - let pt_on_line = road.center_pts.project_pt(cursor_pt); - let (distance, _) = road.center_pts.dist_along_of_point(pt_on_line).unwrap(); - - app.session.modal_filters.roads.insert(r, distance); - } - after_edit(ctx, app); - true - } - WorldOutcome::ClickedObject(FilterableObj::InteriorIntersection(i)) => { - app.session.modal_filters.before_edit(); - DiagonalFilter::cycle_through_alternatives(app, i); - after_edit(ctx, app); - true - } - WorldOutcome::Keypress("debug", FilterableObj::InteriorIntersection(i)) => { - open_browser(app.map.get_i(i).orig_id.to_string()); - false - } - WorldOutcome::Keypress("debug", FilterableObj::InteriorRoad(r)) => { - open_browser(app.map.get_r(r).orig_id.osm_way_id.to_string()); - false - } - _ => false, - } -} diff --git a/apps/ltn/src/shortcut_viewer.rs b/apps/ltn/src/shortcut_viewer.rs index 765c2b3688..ffa18e9c8e 100644 --- a/apps/ltn/src/shortcut_viewer.rs +++ b/apps/ltn/src/shortcut_viewer.rs @@ -1,9 +1,9 @@ use map_gui::tools::percentage_bar; use map_model::{PathRequest, NORMAL_LANE_THICKNESS}; -use widgetry::mapspace::{ToggleZoomed, World}; +use widgetry::mapspace::ToggleZoomed; use widgetry::{EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, Text, TextExt, Widget}; -use crate::per_neighborhood::{FilterableObj, Tab}; +use crate::edit::{EditNeighborhood, Tab}; use crate::shortcuts::{find_shortcuts, Shortcuts}; use crate::{colors, App, Neighborhood, NeighborhoodID, Transition}; @@ -14,7 +14,7 @@ pub struct BrowseShortcuts { current_idx: usize, draw_path: ToggleZoomed, - world: World, + edit: EditNeighborhood, neighborhood: Neighborhood, } @@ -30,7 +30,7 @@ impl BrowseShortcuts { let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| { find_shortcuts(app, &neighborhood, timer) }); - let world = crate::per_neighborhood::make_world(ctx, app, &neighborhood, &shortcuts); + let edit = EditNeighborhood::new(ctx, app, &neighborhood, &shortcuts); let mut state = BrowseShortcuts { top_panel: crate::components::TopPanel::panel(ctx, app), @@ -39,7 +39,7 @@ impl BrowseShortcuts { current_idx: 0, draw_path: ToggleZoomed::empty(ctx), neighborhood, - world, + edit, }; if let Some(req) = start_with_request { @@ -63,10 +63,12 @@ impl BrowseShortcuts { self.shortcuts.quiet_and_total_streets(&self.neighborhood); if self.shortcuts.paths.is_empty() { - self.left_panel = Tab::Shortcuts + self.left_panel = self + .edit .panel_builder( ctx, app, + Tab::Shortcuts, &self.top_panel, percentage_bar( ctx, @@ -86,10 +88,12 @@ impl BrowseShortcuts { let controls = self.prev_next_controls(ctx); self.left_panel.replace(ctx, "prev/next controls", controls); } else { - self.left_panel = Tab::Shortcuts + self.left_panel = self + .edit .panel_builder( ctx, app, + Tab::Shortcuts, &self.top_panel, Widget::col(vec![ percentage_bar( @@ -174,7 +178,7 @@ impl State for BrowseShortcuts { self.recalculate(ctx, app); } x => { - if let Some(t) = crate::per_neighborhood::handle_action( + if let Some(t) = self.edit.handle_panel_action( ctx, app, x, @@ -205,10 +209,7 @@ impl State for BrowseShortcuts { _ => {} } - // TODO Bit weird to allow this while showing individual paths, since we don't draw the - // world - let world_outcome = self.world.event(ctx); - if crate::per_neighborhood::handle_world_outcome(ctx, app, world_outcome) { + if self.edit.event(ctx, app) { // Reset state, but if possible, preserve the current individual shortcut. let current_request = self.shortcuts.paths[self.current_idx].get_req().clone(); return Transition::Replace(BrowseShortcuts::new_state( @@ -226,7 +227,7 @@ impl State for BrowseShortcuts { self.top_panel.draw(g); self.left_panel.draw(g); - self.world.draw(g); + self.edit.world.draw(g); self.draw_path.draw(g); g.redraw(&self.neighborhood.fade_irrelevant);