Start a simplified consultation mode for the LTN tool

- starts in a fixed neighborhood
- hides many controls
- lets users jump to route planning from the per-neighborhood screens
This commit is contained in:
Dustin Carlino 2022-05-17 16:08:01 +01:00
parent 0f62e650ea
commit 078265718a
10 changed files with 133 additions and 59 deletions

View File

@ -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<App> for BrowseNeighborhoods {

View File

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

View File

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

View File

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

View File

@ -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![

View File

@ -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<String>,
/// 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<String>,
consultation: Option<NeighborhoodID>,
}
/// Do the equivalent of `SimpleApp::draw_unzoomed` or `draw_zoomed`, but after the water/park

View File

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

View File

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

View File

@ -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<App> 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) {

View File

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