mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 07:25:47 +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,
|
&top_panel,
|
||||||
Widget::col(vec![
|
Widget::col(vec![
|
||||||
app.session.alt_proposals.to_widget(ctx, app),
|
app.session.alt_proposals.to_widget(ctx, app),
|
||||||
ctx.style()
|
crate::route_planner::RoutePlanner::button(ctx),
|
||||||
.btn_outline
|
|
||||||
.text("Plan a route")
|
|
||||||
.hotkey(Key::R)
|
|
||||||
.build_def(ctx),
|
|
||||||
Toggle::checkbox(ctx, "Advanced features", None, app.opts.dev),
|
Toggle::checkbox(ctx, "Advanced features", None, app.opts.dev),
|
||||||
advanced_panel(ctx, app),
|
advanced_panel(ctx, app),
|
||||||
]),
|
]),
|
||||||
@ -63,6 +59,14 @@ impl BrowseNeighborhoods {
|
|||||||
draw_boundary_roads: draw_boundary_roads(ctx, app),
|
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 {
|
impl State<App> for BrowseNeighborhoods {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use map_gui::tools::grey_out_map;
|
use map_gui::tools::grey_out_map;
|
||||||
use widgetry::tools::open_browser;
|
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};
|
use crate::{App, Transition};
|
||||||
|
|
||||||
@ -13,9 +13,13 @@ impl About {
|
|||||||
Line("About the LTN tool").small_heading().into_widget(ctx),
|
Line("About the LTN tool").small_heading().into_widget(ctx),
|
||||||
ctx.style().btn_close_widget(ctx),
|
ctx.style().btn_close_widget(ctx),
|
||||||
]),
|
]),
|
||||||
"Created by Dustin Carlino & Cindy Huang".text_widget(ctx),
|
Text::from_multiline(vec![
|
||||||
"Data from OpenStreetMap".text_widget(ctx),
|
Line("Created by Dustin Carlino, Cindy Huang, and Jennifer Ding"),
|
||||||
"See below for full credits and more info".text_widget(ctx),
|
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()
|
ctx.style()
|
||||||
.btn_outline
|
.btn_outline
|
||||||
.text("ltn.abstreet.org")
|
.text("ltn.abstreet.org")
|
||||||
|
@ -11,25 +11,35 @@ pub struct TopPanel;
|
|||||||
|
|
||||||
impl TopPanel {
|
impl TopPanel {
|
||||||
pub fn panel(ctx: &mut EventCtx, app: &App) -> Panel {
|
pub fn panel(ctx: &mut EventCtx, app: &App) -> Panel {
|
||||||
|
let consultation = app.session.consultation.is_some();
|
||||||
|
|
||||||
Panel::new_builder(
|
Panel::new_builder(
|
||||||
Widget::row(vec![
|
Widget::row(vec![
|
||||||
map_gui::tools::home_btn(ctx),
|
map_gui::tools::home_btn(ctx),
|
||||||
Line("Low traffic neighborhoods")
|
Line(if consultation {
|
||||||
.small_heading()
|
"East Bristol Liveable Neighbourhood"
|
||||||
.into_widget(ctx)
|
} else {
|
||||||
.centered_vert(),
|
"Low traffic neighborhoods"
|
||||||
|
})
|
||||||
|
.small_heading()
|
||||||
|
.into_widget(ctx)
|
||||||
|
.centered_vert(),
|
||||||
ctx.style()
|
ctx.style()
|
||||||
.btn_plain
|
.btn_plain
|
||||||
.icon("system/assets/tools/info.svg")
|
.icon("system/assets/tools/info.svg")
|
||||||
.build_widget(ctx, "about this tool")
|
.build_widget(ctx, "about this tool")
|
||||||
.centered_vert(),
|
.centered_vert()
|
||||||
map_gui::tools::change_map_btn(ctx, app).centered_vert(),
|
.hide(consultation),
|
||||||
|
map_gui::tools::change_map_btn(ctx, app)
|
||||||
|
.centered_vert()
|
||||||
|
.hide(consultation),
|
||||||
Widget::row(vec![
|
Widget::row(vec![
|
||||||
ctx.style()
|
ctx.style()
|
||||||
.btn_plain
|
.btn_plain
|
||||||
.text("Export to GeoJSON")
|
.text("Export to GeoJSON")
|
||||||
.build_def(ctx)
|
.build_def(ctx)
|
||||||
.centered_vert(),
|
.centered_vert()
|
||||||
|
.hide(consultation),
|
||||||
ctx.style()
|
ctx.style()
|
||||||
.btn_plain
|
.btn_plain
|
||||||
.icon("system/assets/tools/search.svg")
|
.icon("system/assets/tools/search.svg")
|
||||||
@ -64,14 +74,20 @@ impl TopPanel {
|
|||||||
) -> Option<Transition> {
|
) -> Option<Transition> {
|
||||||
if let Outcome::Clicked(x) = panel.event(ctx) {
|
if let Outcome::Clicked(x) = panel.event(ctx) {
|
||||||
match x.as_ref() {
|
match x.as_ref() {
|
||||||
"Home" => Some(Transition::Clear(vec![
|
"Home" => {
|
||||||
map_gui::tools::TitleScreen::new_state(
|
if app.session.consultation.is_none() {
|
||||||
ctx,
|
Some(Transition::Clear(vec![
|
||||||
app,
|
map_gui::tools::TitleScreen::new_state(
|
||||||
map_gui::tools::Executable::LTN,
|
ctx,
|
||||||
Box::new(|ctx, app, _| BrowseNeighborhoods::new_state(ctx, app)),
|
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(
|
"change map" => Some(Transition::Push(map_gui::tools::CityPicker::new_state(
|
||||||
ctx,
|
ctx,
|
||||||
app,
|
app,
|
||||||
|
@ -61,7 +61,6 @@ impl Viewer {
|
|||||||
)
|
)
|
||||||
.text_widget(ctx),
|
.text_widget(ctx),
|
||||||
warning.text_widget(ctx),
|
warning.text_widget(ctx),
|
||||||
Toggle::checkbox(ctx, "Advanced features", None, app.opts.dev),
|
|
||||||
advanced_panel(ctx, app),
|
advanced_panel(ctx, app),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
@ -304,7 +303,6 @@ fn make_world(
|
|||||||
fn help() -> Vec<&'static str> {
|
fn help() -> Vec<&'static str> {
|
||||||
vec![
|
vec![
|
||||||
"The colored cells show where it's possible to drive without leaving the neighborhood.",
|
"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.",
|
"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 {
|
fn advanced_panel(ctx: &EventCtx, app: &App) -> Widget {
|
||||||
if !app.opts.dev {
|
if app.session.consultation.is_some() {
|
||||||
return Widget::nothing();
|
return Widget::nothing();
|
||||||
}
|
}
|
||||||
|
if !app.opts.dev {
|
||||||
|
return Toggle::checkbox(ctx, "Advanced features", None, app.opts.dev);
|
||||||
|
}
|
||||||
Widget::col(vec![
|
Widget::col(vec![
|
||||||
|
Toggle::checkbox(ctx, "Advanced features", None, app.opts.dev),
|
||||||
Line("Advanced features").small_heading().into_widget(ctx),
|
Line("Advanced features").small_heading().into_widget(ctx),
|
||||||
Widget::row(vec![
|
Widget::row(vec![
|
||||||
"Draw traffic cells as".text_widget(ctx).centered_vert(),
|
"Draw traffic cells as".text_widget(ctx).centered_vert(),
|
||||||
|
@ -47,10 +47,7 @@ impl ShowResults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let contents = Widget::col(vec![
|
let contents = Widget::col(vec![
|
||||||
ctx.style()
|
BrowseNeighborhoods::button(ctx, app),
|
||||||
.btn_back("Browse neighborhoods")
|
|
||||||
.hotkey(Key::Escape)
|
|
||||||
.build_def(ctx),
|
|
||||||
Line("Impact prediction").small_heading().into_widget(ctx),
|
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(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![
|
Text::from_all(vec![
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
use abstio::MapName;
|
||||||
use abstutil::Timer;
|
use abstutil::Timer;
|
||||||
use widgetry::{EventCtx, GfxCtx, Settings};
|
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.
|
/// Load a previously saved proposal with this name. Note this takes a name, not a full path.
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
proposal: Option<String>,
|
proposal: Option<String>,
|
||||||
|
/// Lock the user into one fixed neighborhood, and remove many controls
|
||||||
|
#[structopt(long)]
|
||||||
|
consultation: bool,
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
app_args: map_gui::SimpleAppArgs,
|
app_args: map_gui::SimpleAppArgs,
|
||||||
}
|
}
|
||||||
@ -77,6 +81,8 @@ fn run(mut settings: Settings) {
|
|||||||
main_road_penalty: 1.0,
|
main_road_penalty: 1.0,
|
||||||
|
|
||||||
current_trip_name: None,
|
current_trip_name: None,
|
||||||
|
|
||||||
|
consultation: None,
|
||||||
};
|
};
|
||||||
map_gui::SimpleApp::new(
|
map_gui::SimpleApp::new(
|
||||||
ctx,
|
ctx,
|
||||||
@ -91,15 +97,32 @@ fn run(mut settings: Settings) {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|name| crate::save::Proposal::load(ctx, app, name));
|
.and_then(|name| crate::save::Proposal::load(ctx, app, name));
|
||||||
|
|
||||||
let mut states = vec![
|
let mut states = Vec::new();
|
||||||
map_gui::tools::TitleScreen::new_state(
|
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,
|
ctx,
|
||||||
app,
|
app,
|
||||||
map_gui::tools::Executable::LTN,
|
map_gui::tools::Executable::LTN,
|
||||||
Box::new(|ctx, app, _| BrowseNeighborhoods::new_state(ctx, app)),
|
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 {
|
if let Some(state) = popup_state {
|
||||||
states.push(state);
|
states.push(state);
|
||||||
}
|
}
|
||||||
@ -146,6 +169,8 @@ pub struct Session {
|
|||||||
pub main_road_penalty: f64,
|
pub main_road_penalty: f64,
|
||||||
|
|
||||||
current_trip_name: Option<String>,
|
current_trip_name: Option<String>,
|
||||||
|
|
||||||
|
consultation: Option<NeighborhoodID>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do the equivalent of `SimpleApp::draw_unzoomed` or `draw_zoomed`, but after the water/park
|
/// 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
|
/// An opaque ID, won't be contiguous as we adjust boundaries
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[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
|
/// Identifies a single / unmerged block, which never changes
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
@ -28,10 +28,7 @@ impl Tab {
|
|||||||
) -> PanelBuilder {
|
) -> PanelBuilder {
|
||||||
let contents = Widget::col(vec![
|
let contents = Widget::col(vec![
|
||||||
app.session.alt_proposals.to_widget(ctx, app),
|
app.session.alt_proposals.to_widget(ctx, app),
|
||||||
ctx.style()
|
BrowseNeighborhoods::button(ctx, app),
|
||||||
.btn_back("Browse neighborhoods")
|
|
||||||
.hotkey(Key::Escape)
|
|
||||||
.build_def(ctx),
|
|
||||||
Line("Editing neighborhood")
|
Line("Editing neighborhood")
|
||||||
.small_heading()
|
.small_heading()
|
||||||
.into_widget(ctx),
|
.into_widget(ctx),
|
||||||
@ -64,8 +61,9 @@ impl Tab {
|
|||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
.section(ctx),
|
.section(ctx),
|
||||||
self.make_buttons(ctx),
|
self.make_buttons(ctx, app),
|
||||||
per_tab_contents,
|
per_tab_contents,
|
||||||
|
crate::route_planner::RoutePlanner::button(ctx),
|
||||||
]);
|
]);
|
||||||
crate::components::LeftPanel::builder(ctx, top_panel, contents)
|
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,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_buttons(self, ctx: &mut EventCtx) -> Widget {
|
fn make_buttons(self, ctx: &mut EventCtx, app: &App) -> Widget {
|
||||||
let mut row = Vec::new();
|
let mut row = Vec::new();
|
||||||
for (tab, label, key) in [
|
for (tab, label, key) in [
|
||||||
(Tab::Connectivity, "Connectivity", Key::F1),
|
(Tab::Connectivity, "Connectivity", Key::F1),
|
||||||
@ -144,20 +145,22 @@ impl Tab {
|
|||||||
.build_def(ctx),
|
.build_def(ctx),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TODO The 3rd doesn't really act like a tab
|
if app.session.consultation.is_none() {
|
||||||
row.push(
|
// TODO The 3rd doesn't really act like a tab
|
||||||
ctx.style()
|
row.push(
|
||||||
.btn_tab
|
ctx.style()
|
||||||
.text("Adjust boundary")
|
.btn_tab
|
||||||
.corner_rounding(geom::CornerRadii {
|
.text("Adjust boundary")
|
||||||
top_left: DEFAULT_CORNER_RADIUS,
|
.corner_rounding(geom::CornerRadii {
|
||||||
top_right: DEFAULT_CORNER_RADIUS,
|
top_left: DEFAULT_CORNER_RADIUS,
|
||||||
bottom_left: 0.0,
|
top_right: DEFAULT_CORNER_RADIUS,
|
||||||
bottom_right: 0.0,
|
bottom_left: 0.0,
|
||||||
})
|
bottom_right: 0.0,
|
||||||
.hotkey(Key::B)
|
})
|
||||||
.build_def(ctx),
|
.hotkey(Key::B)
|
||||||
);
|
.build_def(ctx),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget::row(row)
|
Widget::row(row)
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,14 @@ impl RoutePlanner {
|
|||||||
Box::new(rp)
|
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
|
// Updates the panel and draw_routes
|
||||||
fn update_everything(&mut self, ctx: &mut EventCtx, app: &mut App) {
|
fn update_everything(&mut self, ctx: &mut EventCtx, app: &mut App) {
|
||||||
self.files.autosave(app);
|
self.files.autosave(app);
|
||||||
@ -68,10 +76,12 @@ impl RoutePlanner {
|
|||||||
|
|
||||||
let contents = Widget::col(vec![
|
let contents = Widget::col(vec![
|
||||||
app.session.alt_proposals.to_widget(ctx, app),
|
app.session.alt_proposals.to_widget(ctx, app),
|
||||||
|
BrowseNeighborhoods::button(ctx, app),
|
||||||
ctx.style()
|
ctx.style()
|
||||||
.btn_back("Browse neighborhoods")
|
.btn_back("Analyze neighbourhood")
|
||||||
.hotkey(Key::Escape)
|
.hotkey(Key::Escape)
|
||||||
.build_def(ctx),
|
.build_def(ctx)
|
||||||
|
.hide(app.session.consultation.is_none()),
|
||||||
Line("Plan a route").small_heading().into_widget(ctx),
|
Line("Plan a route").small_heading().into_widget(ctx),
|
||||||
Widget::col(vec![
|
Widget::col(vec![
|
||||||
self.files.get_panel_widget(ctx),
|
self.files.get_panel_widget(ctx),
|
||||||
@ -287,6 +297,9 @@ impl State<App> for RoutePlanner {
|
|||||||
if x == "Browse neighborhoods" {
|
if x == "Browse neighborhoods" {
|
||||||
return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app));
|
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) {
|
if let Some(t) = self.files.on_click(ctx, app, x) {
|
||||||
// Bit hacky...
|
// Bit hacky...
|
||||||
if matches!(t, Transition::Keep) {
|
if matches!(t, Transition::Keep) {
|
||||||
|
@ -398,6 +398,16 @@ impl Widget {
|
|||||||
self.id = Some(id.into());
|
self.id = Some(id.into());
|
||||||
self
|
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
|
// Convenient?? constructors
|
||||||
|
Loading…
Reference in New Issue
Block a user