diff --git a/apps/ltn/src/browse.rs b/apps/ltn/src/browse.rs index 8aba305de4..763c9a1e2f 100644 --- a/apps/ltn/src/browse.rs +++ b/apps/ltn/src/browse.rs @@ -44,11 +44,7 @@ impl BrowseNeighborhoods { &top_panel, Widget::col(vec![ app.session.alt_proposals.to_widget(ctx, app), - ctx.style() - .btn_outline - .text("Plan a route") - .hotkey(Key::R) - .build_def(ctx), + crate::route_planner::RoutePlanner::button(ctx), Toggle::checkbox(ctx, "Advanced features", None, app.opts.dev), advanced_panel(ctx, app), ]), @@ -63,6 +59,14 @@ impl BrowseNeighborhoods { draw_boundary_roads: draw_boundary_roads(ctx, app), }) } + + pub fn button(ctx: &EventCtx, app: &App) -> Widget { + ctx.style() + .btn_back("Browse neighborhoods") + .hotkey(Key::Escape) + .build_def(ctx) + .hide(app.session.consultation.is_some()) + } } impl State for BrowseNeighborhoods { diff --git a/apps/ltn/src/components/about.rs b/apps/ltn/src/components/about.rs index 71b2205f64..f1a9109442 100644 --- a/apps/ltn/src/components/about.rs +++ b/apps/ltn/src/components/about.rs @@ -1,6 +1,6 @@ use map_gui::tools::grey_out_map; use widgetry::tools::open_browser; -use widgetry::{EventCtx, GfxCtx, Line, Panel, SimpleState, State, TextExt, Widget}; +use widgetry::{EventCtx, GfxCtx, Line, Panel, SimpleState, State, Text, Widget}; use crate::{App, Transition}; @@ -13,9 +13,13 @@ impl About { Line("About the LTN tool").small_heading().into_widget(ctx), ctx.style().btn_close_widget(ctx), ]), - "Created by Dustin Carlino & Cindy Huang".text_widget(ctx), - "Data from OpenStreetMap".text_widget(ctx), - "See below for full credits and more info".text_widget(ctx), + Text::from_multiline(vec![ + Line("Created by Dustin Carlino, Cindy Huang, and Jennifer Ding"), + Line("Developed at the Alan Turing Institute"), + Line("Data from OpenStreetMap"), + Line("See below for full credits and more info"), + ]) + .into_widget(ctx), ctx.style() .btn_outline .text("ltn.abstreet.org") diff --git a/apps/ltn/src/components/top_panel.rs b/apps/ltn/src/components/top_panel.rs index 5735f52506..a9e5dfd908 100644 --- a/apps/ltn/src/components/top_panel.rs +++ b/apps/ltn/src/components/top_panel.rs @@ -11,25 +11,35 @@ pub struct TopPanel; impl TopPanel { pub fn panel(ctx: &mut EventCtx, app: &App) -> Panel { + let consultation = app.session.consultation.is_some(); + Panel::new_builder( Widget::row(vec![ map_gui::tools::home_btn(ctx), - Line("Low traffic neighborhoods") - .small_heading() - .into_widget(ctx) - .centered_vert(), + Line(if consultation { + "East Bristol Liveable Neighbourhood" + } else { + "Low traffic neighborhoods" + }) + .small_heading() + .into_widget(ctx) + .centered_vert(), ctx.style() .btn_plain .icon("system/assets/tools/info.svg") .build_widget(ctx, "about this tool") - .centered_vert(), - map_gui::tools::change_map_btn(ctx, app).centered_vert(), + .centered_vert() + .hide(consultation), + map_gui::tools::change_map_btn(ctx, app) + .centered_vert() + .hide(consultation), Widget::row(vec![ ctx.style() .btn_plain .text("Export to GeoJSON") .build_def(ctx) - .centered_vert(), + .centered_vert() + .hide(consultation), ctx.style() .btn_plain .icon("system/assets/tools/search.svg") @@ -64,14 +74,20 @@ impl TopPanel { ) -> Option { if let Outcome::Clicked(x) = panel.event(ctx) { match x.as_ref() { - "Home" => Some(Transition::Clear(vec![ - map_gui::tools::TitleScreen::new_state( - ctx, - app, - map_gui::tools::Executable::LTN, - Box::new(|ctx, app, _| BrowseNeighborhoods::new_state(ctx, app)), - ), - ])), + "Home" => { + if app.session.consultation.is_none() { + Some(Transition::Clear(vec![ + map_gui::tools::TitleScreen::new_state( + ctx, + app, + map_gui::tools::Executable::LTN, + Box::new(|ctx, app, _| BrowseNeighborhoods::new_state(ctx, app)), + ), + ])) + } else { + Some(Transition::Push(super::about::About::new_state(ctx))) + } + } "change map" => Some(Transition::Push(map_gui::tools::CityPicker::new_state( ctx, app, diff --git a/apps/ltn/src/connectivity.rs b/apps/ltn/src/connectivity.rs index 202c0bc122..40b07b15ac 100644 --- a/apps/ltn/src/connectivity.rs +++ b/apps/ltn/src/connectivity.rs @@ -61,7 +61,6 @@ impl Viewer { ) .text_widget(ctx), warning.text_widget(ctx), - Toggle::checkbox(ctx, "Advanced features", None, app.opts.dev), advanced_panel(ctx, app), ]), ) @@ -304,7 +303,6 @@ fn make_world( fn help() -> Vec<&'static str> { vec![ "The colored cells show where it's possible to drive without leaving the neighborhood.", - "Green cells don't allow car-traffic.", "", "The darker red roads have more predicted shortcutting traffic.", "", @@ -314,10 +312,14 @@ fn help() -> Vec<&'static str> { } fn advanced_panel(ctx: &EventCtx, app: &App) -> Widget { - if !app.opts.dev { + if app.session.consultation.is_some() { return Widget::nothing(); } + if !app.opts.dev { + return Toggle::checkbox(ctx, "Advanced features", None, app.opts.dev); + } Widget::col(vec![ + Toggle::checkbox(ctx, "Advanced features", None, app.opts.dev), Line("Advanced features").small_heading().into_widget(ctx), Widget::row(vec![ "Draw traffic cells as".text_widget(ctx).centered_vert(), diff --git a/apps/ltn/src/impact/ui.rs b/apps/ltn/src/impact/ui.rs index d8919a606a..b19a53fec6 100644 --- a/apps/ltn/src/impact/ui.rs +++ b/apps/ltn/src/impact/ui.rs @@ -47,10 +47,7 @@ impl ShowResults { } let contents = Widget::col(vec![ - ctx.style() - .btn_back("Browse neighborhoods") - .hotkey(Key::Escape) - .build_def(ctx), + BrowseNeighborhoods::button(ctx, app), Line("Impact prediction").small_heading().into_widget(ctx), Text::from(Line("This tool starts with a travel demand model, calculates the route every trip takes before and after changes, and displays volumes along roads")).wrap_to_pct(ctx, 20).into_widget(ctx), Text::from_all(vec![ diff --git a/apps/ltn/src/lib.rs b/apps/ltn/src/lib.rs index c73a7d681c..105bcbfba9 100644 --- a/apps/ltn/src/lib.rs +++ b/apps/ltn/src/lib.rs @@ -2,6 +2,7 @@ use structopt::StructOpt; +use abstio::MapName; use abstutil::Timer; use widgetry::{EventCtx, GfxCtx, Settings}; @@ -46,6 +47,9 @@ struct Args { /// Load a previously saved proposal with this name. Note this takes a name, not a full path. #[structopt(long)] proposal: Option, + /// Lock the user into one fixed neighborhood, and remove many controls + #[structopt(long)] + consultation: bool, #[structopt(flatten)] app_args: map_gui::SimpleAppArgs, } @@ -77,6 +81,8 @@ fn run(mut settings: Settings) { main_road_penalty: 1.0, current_trip_name: None, + + consultation: None, }; map_gui::SimpleApp::new( ctx, @@ -91,15 +97,32 @@ fn run(mut settings: Settings) { .as_ref() .and_then(|name| crate::save::Proposal::load(ctx, app, name)); - let mut states = vec![ - map_gui::tools::TitleScreen::new_state( + let mut states = Vec::new(); + if args.consultation { + if app.map.get_name() != &MapName::new("gb", "bristol", "east") { + panic!("Consultation mode not supported on this map"); + } + // TODO Don't hardcode + app.session.consultation = Some(NeighborhoodID(33)); + + app.session.alt_proposals = crate::save::AltProposals::new(); + ctx.loading_screen("initialize", |ctx, timer| { + crate::clear_current_proposal(ctx, app, timer); + }); + states.push(connectivity::Viewer::new_state( + ctx, + app, + app.session.consultation.unwrap(), + )); + } else { + states.push(map_gui::tools::TitleScreen::new_state( ctx, app, map_gui::tools::Executable::LTN, Box::new(|ctx, app, _| BrowseNeighborhoods::new_state(ctx, app)), - ), - BrowseNeighborhoods::new_state(ctx, app), - ]; + )); + states.push(BrowseNeighborhoods::new_state(ctx, app)); + } if let Some(state) = popup_state { states.push(state); } @@ -146,6 +169,8 @@ pub struct Session { pub main_road_penalty: f64, current_trip_name: Option, + + consultation: Option, } /// Do the equivalent of `SimpleApp::draw_unzoomed` or `draw_zoomed`, but after the water/park diff --git a/apps/ltn/src/partition.rs b/apps/ltn/src/partition.rs index 399949acac..86c58216bc 100644 --- a/apps/ltn/src/partition.rs +++ b/apps/ltn/src/partition.rs @@ -13,7 +13,7 @@ use crate::{colors, App}; /// An opaque ID, won't be contiguous as we adjust boundaries #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct NeighborhoodID(usize); +pub struct NeighborhoodID(pub usize); /// Identifies a single / unmerged block, which never changes #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] diff --git a/apps/ltn/src/per_neighborhood.rs b/apps/ltn/src/per_neighborhood.rs index 1582b0919c..2d4f01f0d3 100644 --- a/apps/ltn/src/per_neighborhood.rs +++ b/apps/ltn/src/per_neighborhood.rs @@ -28,10 +28,7 @@ impl Tab { ) -> PanelBuilder { let contents = Widget::col(vec![ app.session.alt_proposals.to_widget(ctx, app), - ctx.style() - .btn_back("Browse neighborhoods") - .hotkey(Key::Escape) - .build_def(ctx), + BrowseNeighborhoods::button(ctx, app), Line("Editing neighborhood") .small_heading() .into_widget(ctx), @@ -64,8 +61,9 @@ impl Tab { ]), ]) .section(ctx), - self.make_buttons(ctx), + self.make_buttons(ctx, app), per_tab_contents, + crate::route_planner::RoutePlanner::button(ctx), ]); crate::components::LeftPanel::builder(ctx, top_panel, contents) } @@ -117,11 +115,14 @@ impl Tab { } })) } + "Plan a route" => Some(Transition::Push( + crate::route_planner::RoutePlanner::new_state(ctx, app), + )), _ => None, } } - fn make_buttons(self, ctx: &mut EventCtx) -> Widget { + 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), @@ -144,20 +145,22 @@ impl Tab { .build_def(ctx), ); } - // 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), - ); + 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) } diff --git a/apps/ltn/src/route_planner.rs b/apps/ltn/src/route_planner.rs index 39e1407ca5..58b6028f9c 100644 --- a/apps/ltn/src/route_planner.rs +++ b/apps/ltn/src/route_planner.rs @@ -61,6 +61,14 @@ impl RoutePlanner { Box::new(rp) } + pub fn button(ctx: &EventCtx) -> Widget { + ctx.style() + .btn_outline + .text("Plan a route") + .hotkey(Key::R) + .build_def(ctx) + } + // Updates the panel and draw_routes fn update_everything(&mut self, ctx: &mut EventCtx, app: &mut App) { self.files.autosave(app); @@ -68,10 +76,12 @@ impl RoutePlanner { let contents = Widget::col(vec![ app.session.alt_proposals.to_widget(ctx, app), + BrowseNeighborhoods::button(ctx, app), ctx.style() - .btn_back("Browse neighborhoods") + .btn_back("Analyze neighbourhood") .hotkey(Key::Escape) - .build_def(ctx), + .build_def(ctx) + .hide(app.session.consultation.is_none()), Line("Plan a route").small_heading().into_widget(ctx), Widget::col(vec![ self.files.get_panel_widget(ctx), @@ -287,6 +297,9 @@ impl State for RoutePlanner { if x == "Browse neighborhoods" { return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app)); } + if x == "Analyze neighbourhood" { + return Transition::Pop; + } if let Some(t) = self.files.on_click(ctx, app, x) { // Bit hacky... if matches!(t, Transition::Keep) { diff --git a/widgetry/src/widgets/mod.rs b/widgetry/src/widgets/mod.rs index 19bfb9842c..adb25fca38 100644 --- a/widgetry/src/widgets/mod.rs +++ b/widgetry/src/widgets/mod.rs @@ -398,6 +398,16 @@ impl Widget { self.id = Some(id.into()); self } + + /// If the argument is true, don't actually create this widget. May be more readable than an + /// if/else block. + pub fn hide(self, x: bool) -> Widget { + if x { + Widget::nothing() + } else { + self + } + } } // Convenient?? constructors