Prepare for editing one-ways by better hiding the API of editing filters.

This commit is contained in:
Dustin Carlino 2022-05-25 13:10:30 +01:00
parent df854db143
commit 2c7ebbcc13
6 changed files with 346 additions and 295 deletions

View File

@ -1,13 +1,13 @@
use geom::{ArrowCap, Distance, PolyLine}; use geom::{ArrowCap, Distance, PolyLine};
use map_gui::tools::ColorNetwork; use map_gui::tools::ColorNetwork;
use widgetry::mapspace::{ToggleZoomed, World}; use widgetry::mapspace::ToggleZoomed;
use widgetry::tools::PopupMsg; use widgetry::tools::PopupMsg;
use widgetry::{ use widgetry::{
DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, TextExt, Toggle, Widget, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, TextExt, Toggle, Widget,
}; };
use crate::edit::{EditNeighborhood, Tab};
use crate::filters::auto::Heuristic; use crate::filters::auto::Heuristic;
use crate::per_neighborhood::{FilterableObj, Tab};
use crate::shortcuts::find_shortcuts; use crate::shortcuts::find_shortcuts;
use crate::{colors, App, Neighborhood, NeighborhoodID, Transition}; use crate::{colors, App, Neighborhood, NeighborhoodID, Transition};
@ -15,8 +15,8 @@ pub struct Viewer {
top_panel: Panel, top_panel: Panel,
left_panel: Panel, left_panel: Panel,
neighborhood: Neighborhood, neighborhood: Neighborhood,
world: World<FilterableObj>,
draw_top_layer: ToggleZoomed, draw_top_layer: ToggleZoomed,
edit: EditNeighborhood,
} }
impl Viewer { impl Viewer {
@ -27,8 +27,8 @@ impl Viewer {
top_panel: crate::components::TopPanel::panel(ctx, app), top_panel: crate::components::TopPanel::panel(ctx, app),
left_panel: Panel::empty(ctx), left_panel: Panel::empty(ctx),
neighborhood, neighborhood,
world: World::unbounded(),
draw_top_layer: ToggleZoomed::empty(ctx), draw_top_layer: ToggleZoomed::empty(ctx),
edit: EditNeighborhood::temporary(),
}; };
viewer.update(ctx, app); viewer.update(ctx, app);
Box::new(viewer) Box::new(viewer)
@ -47,10 +47,12 @@ impl Viewer {
format!("{} cells are totally disconnected", disconnected_cells) format!("{} cells are totally disconnected", disconnected_cells)
}; };
self.left_panel = Tab::Connectivity self.left_panel = self
.edit
.panel_builder( .panel_builder(
ctx, ctx,
app, app,
Tab::Connectivity,
&self.top_panel, &self.top_panel,
Widget::col(vec![ Widget::col(vec![
format!( format!(
@ -66,8 +68,8 @@ impl Viewer {
) )
.build(ctx); .build(ctx);
let (world, draw_top_layer) = make_world(ctx, app, &self.neighborhood); let (edit, draw_top_layer) = setup_editing(ctx, app, &self.neighborhood);
self.world = world; self.edit = edit;
self.draw_top_layer = draw_top_layer; self.draw_top_layer = draw_top_layer;
} }
} }
@ -98,7 +100,7 @@ impl State<App> for Viewer {
)); ));
} }
} }
} else if let Some(t) = crate::per_neighborhood::handle_action( } else if let Some(t) = self.edit.handle_panel_action(
ctx, ctx,
app, app,
x.as_ref(), x.as_ref(),
@ -131,16 +133,15 @@ impl State<App> for Viewer {
app.session.heuristic = self.left_panel.dropdown_value("heuristic"); app.session.heuristic = self.left_panel.dropdown_value("heuristic");
if x != "heuristic" { if x != "heuristic" {
let (world, draw_top_layer) = make_world(ctx, app, &self.neighborhood); let (edit, draw_top_layer) = setup_editing(ctx, app, &self.neighborhood);
self.world = world; self.edit = edit;
self.draw_top_layer = draw_top_layer; self.draw_top_layer = draw_top_layer;
} }
} }
_ => {} _ => {}
} }
let world_outcome = self.world.event(ctx); if self.edit.event(ctx, app) {
if crate::per_neighborhood::handle_world_outcome(ctx, app, world_outcome) {
self.neighborhood = Neighborhood::new(ctx, app, self.neighborhood.id); self.neighborhood = Neighborhood::new(ctx, app, self.neighborhood.id);
self.update(ctx, app); self.update(ctx, app);
} }
@ -153,7 +154,7 @@ impl State<App> for Viewer {
} }
fn draw(&self, g: &mut GfxCtx, app: &App) { 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); g.redraw(&self.neighborhood.fade_irrelevant);
self.draw_top_layer.draw(g); self.draw_top_layer.draw(g);
@ -173,16 +174,16 @@ impl State<App> for Viewer {
} }
} }
fn make_world( fn setup_editing(
ctx: &mut EventCtx, ctx: &mut EventCtx,
app: &App, app: &App,
neighborhood: &Neighborhood, neighborhood: &Neighborhood,
) -> (World<FilterableObj>, ToggleZoomed) { ) -> (EditNeighborhood, ToggleZoomed) {
let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| { let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| {
find_shortcuts(app, neighborhood, 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; let map = &app.map;
// The world is drawn in between areas and roads, but some things need to be drawn on top of // 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); let render_cells = crate::draw_cells::RenderCells::new(map, neighborhood);
if app.session.draw_cells_as_areas { 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); let mut colorer = ColorNetwork::no_fading(app);
colorer.ranked_roads(shortcuts.count_per_road.clone(), &app.cs.good_to_bad_red); 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> { fn help() -> Vec<&'static str> {

View File

@ -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<Obj> {
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<Obj>) -> 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,
}
}

181
apps/ltn/src/edit/mod.rs Normal file
View File

@ -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<Obj>,
// 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<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),
)),
// 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,
}
}
}

View File

@ -22,12 +22,12 @@ mod colors;
mod components; mod components;
mod connectivity; mod connectivity;
mod draw_cells; mod draw_cells;
mod edit;
mod export; mod export;
mod filters; mod filters;
mod impact; mod impact;
mod neighborhood; mod neighborhood;
mod partition; mod partition;
mod per_neighborhood;
mod route_planner; mod route_planner;
mod save; mod save;
mod select_boundary; mod select_boundary;

View File

@ -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<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),
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<FilterableObj> {
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<FilterableObj>,
) -> 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,
}
}

View File

@ -1,9 +1,9 @@
use map_gui::tools::percentage_bar; use map_gui::tools::percentage_bar;
use map_model::{PathRequest, NORMAL_LANE_THICKNESS}; 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 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::shortcuts::{find_shortcuts, Shortcuts};
use crate::{colors, App, Neighborhood, NeighborhoodID, Transition}; use crate::{colors, App, Neighborhood, NeighborhoodID, Transition};
@ -14,7 +14,7 @@ pub struct BrowseShortcuts {
current_idx: usize, current_idx: usize,
draw_path: ToggleZoomed, draw_path: ToggleZoomed,
world: World<FilterableObj>, edit: EditNeighborhood,
neighborhood: Neighborhood, neighborhood: Neighborhood,
} }
@ -30,7 +30,7 @@ impl BrowseShortcuts {
let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| { let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| {
find_shortcuts(app, &neighborhood, 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 { let mut state = BrowseShortcuts {
top_panel: crate::components::TopPanel::panel(ctx, app), top_panel: crate::components::TopPanel::panel(ctx, app),
@ -39,7 +39,7 @@ impl BrowseShortcuts {
current_idx: 0, current_idx: 0,
draw_path: ToggleZoomed::empty(ctx), draw_path: ToggleZoomed::empty(ctx),
neighborhood, neighborhood,
world, edit,
}; };
if let Some(req) = start_with_request { if let Some(req) = start_with_request {
@ -63,10 +63,12 @@ impl BrowseShortcuts {
self.shortcuts.quiet_and_total_streets(&self.neighborhood); self.shortcuts.quiet_and_total_streets(&self.neighborhood);
if self.shortcuts.paths.is_empty() { if self.shortcuts.paths.is_empty() {
self.left_panel = Tab::Shortcuts self.left_panel = self
.edit
.panel_builder( .panel_builder(
ctx, ctx,
app, app,
Tab::Shortcuts,
&self.top_panel, &self.top_panel,
percentage_bar( percentage_bar(
ctx, ctx,
@ -86,10 +88,12 @@ impl BrowseShortcuts {
let controls = self.prev_next_controls(ctx); let controls = self.prev_next_controls(ctx);
self.left_panel.replace(ctx, "prev/next controls", controls); self.left_panel.replace(ctx, "prev/next controls", controls);
} else { } else {
self.left_panel = Tab::Shortcuts self.left_panel = self
.edit
.panel_builder( .panel_builder(
ctx, ctx,
app, app,
Tab::Shortcuts,
&self.top_panel, &self.top_panel,
Widget::col(vec![ Widget::col(vec![
percentage_bar( percentage_bar(
@ -174,7 +178,7 @@ impl State<App> for BrowseShortcuts {
self.recalculate(ctx, app); self.recalculate(ctx, app);
} }
x => { x => {
if let Some(t) = crate::per_neighborhood::handle_action( if let Some(t) = self.edit.handle_panel_action(
ctx, ctx,
app, app,
x, x,
@ -205,10 +209,7 @@ impl State<App> for BrowseShortcuts {
_ => {} _ => {}
} }
// TODO Bit weird to allow this while showing individual paths, since we don't draw the if self.edit.event(ctx, app) {
// world
let world_outcome = self.world.event(ctx);
if crate::per_neighborhood::handle_world_outcome(ctx, app, world_outcome) {
// Reset state, but if possible, preserve the current individual shortcut. // Reset state, but if possible, preserve the current individual shortcut.
let current_request = self.shortcuts.paths[self.current_idx].get_req().clone(); let current_request = self.shortcuts.paths[self.current_idx].get_req().clone();
return Transition::Replace(BrowseShortcuts::new_state( return Transition::Replace(BrowseShortcuts::new_state(
@ -226,7 +227,7 @@ impl State<App> for BrowseShortcuts {
self.top_panel.draw(g); self.top_panel.draw(g);
self.left_panel.draw(g); self.left_panel.draw(g);
self.world.draw(g); self.edit.world.draw(g);
self.draw_path.draw(g); self.draw_path.draw(g);
g.redraw(&self.neighborhood.fade_irrelevant); g.redraw(&self.neighborhood.fade_irrelevant);