mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 15:02:59 +03:00
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:
parent
0f62e650ea
commit
078265718a
@ -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 {
|
||||
|
@ -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")
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -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![
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user