Start a UI with some sliders for tuning existing params of route cost functions. No effect yet. #494

This commit is contained in:
Dustin Carlino 2021-02-02 14:39:39 -08:00
parent 35f2beaa76
commit eebe7a098a
3 changed files with 118 additions and 26 deletions

View File

@ -2,8 +2,8 @@ use map_gui::ID;
use map_model::NORMAL_LANE_THICKNESS;
use sim::{TripEndpoint, TripMode};
use widgetry::{
Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, Outcome,
Panel, State, StyledButtons, TextExt, VerticalAlignment, Widget,
Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, Outcome, Panel,
Spinner, State, StyledButtons, TextExt, VerticalAlignment, Widget,
};
use crate::app::{App, Transition};
@ -26,36 +26,41 @@ impl RouteExplorer {
Line("Route explorer").small_heading().draw(ctx),
ctx.style().btn_close_widget(ctx),
]),
Widget::row(vec![
"Type of trip:".draw_text(ctx),
Widget::dropdown(
ctx,
"mode",
TripMode::Drive,
TripMode::all()
.into_iter()
.map(|m| Choice::new(m.ongoing_verb(), m))
.collect(),
),
]),
profile_to_controls(ctx, &RoutingProfile::default_biking()).named("profile"),
]))
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
.build(ctx),
})
}
fn controls_to_profile(&self) -> RoutingProfile {
if !self.panel.is_button_enabled("cars") {
return RoutingProfile::Driving;
}
if !self.panel.is_button_enabled("pedestrians") {
return RoutingProfile::Walking;
}
RoutingProfile::Biking {
bike_lane_penalty: self.panel.spinner("bike lane penalty") as f64 / 10.0,
bus_lane_penalty: self.panel.spinner("bus lane penalty") as f64 / 10.0,
driving_lane_penalty: self.panel.spinner("driving lane penalty") as f64 / 10.0,
}
}
fn recalc_paths(&mut self, ctx: &mut EventCtx, app: &App) {
let mode = match self.controls_to_profile() {
RoutingProfile::Driving => TripMode::Drive,
RoutingProfile::Walking => TripMode::Walk,
RoutingProfile::Biking { .. } => TripMode::Bike,
};
if let Some((ref goal, _, ref mut preview)) = self.goal {
*preview = Drawable::empty(ctx);
if let Some(polygon) = TripEndpoint::path_req(
self.start.clone(),
goal.clone(),
self.panel.dropdown_value("mode"),
&app.primary.map,
)
.and_then(|req| app.primary.map.pathfind(req).ok())
.and_then(|path| path.trace(&app.primary.map))
.map(|pl| pl.make_polygons(NORMAL_LANE_THICKNESS))
if let Some(polygon) =
TripEndpoint::path_req(self.start.clone(), goal.clone(), mode, &app.primary.map)
.and_then(|req| app.primary.map.pathfind(req).ok())
.and_then(|path| path.trace(&app.primary.map))
.map(|pl| pl.make_polygons(NORMAL_LANE_THICKNESS))
{
*preview = GeomBatch::from(vec![(Color::PURPLE, polygon)]).upload(ctx);
}
@ -72,10 +77,24 @@ impl State<App> for RouteExplorer {
"close" => {
return Transition::Pop;
}
"bikes" => {
let controls = profile_to_controls(ctx, &RoutingProfile::default_biking());
self.panel.replace(ctx, "profile", controls);
self.recalc_paths(ctx, app);
}
"cars" => {
let controls = profile_to_controls(ctx, &RoutingProfile::Driving);
self.panel.replace(ctx, "profile", controls);
self.recalc_paths(ctx, app);
}
"pedestrians" => {
let controls = profile_to_controls(ctx, &RoutingProfile::Walking);
self.panel.replace(ctx, "profile", controls);
self.recalc_paths(ctx, app);
}
_ => unreachable!(),
},
Outcome::Changed => {
// Mode choice changed
self.recalc_paths(ctx, app);
}
_ => {}
@ -158,3 +177,68 @@ impl State<App> for RouteExplorer {
}
}
}
// TODO Move to map_model
// TODO Not sure an enum makes sense, based on how we're still going to be toggling based on
// PathConstraints.
enum RoutingProfile {
Driving,
Biking {
bike_lane_penalty: f64,
bus_lane_penalty: f64,
driving_lane_penalty: f64,
},
Walking,
}
impl RoutingProfile {
fn default_biking() -> RoutingProfile {
RoutingProfile::Biking {
bike_lane_penalty: 1.0,
bus_lane_penalty: 1.1,
driving_lane_penalty: 1.5,
}
}
}
fn profile_to_controls(ctx: &mut EventCtx, profile: &RoutingProfile) -> Widget {
let mut rows = vec![Widget::custom_row(vec![
ctx.style()
.btn_plain_light_icon("system/assets/meters/bike.svg")
.disabled(matches!(profile, RoutingProfile::Biking { .. }))
.build_widget(ctx, "bikes"),
ctx.style()
.btn_plain_light_icon("system/assets/meters/car.svg")
.disabled(matches!(profile, RoutingProfile::Driving))
.build_widget(ctx, "cars"),
ctx.style()
.btn_plain_light_icon("system/assets/meters/pedestrian.svg")
.disabled(matches!(profile, RoutingProfile::Walking))
.build_widget(ctx, "pedestrians"),
])
.evenly_spaced()];
if let RoutingProfile::Biking {
bike_lane_penalty,
bus_lane_penalty,
driving_lane_penalty,
} = profile
{
// TODO Spinners that natively understand a floating point range with a given precision
rows.push(Widget::row(vec![
"Bike lane penalty:".draw_text(ctx).margin_right(20),
Spinner::new(ctx, (0, 20), (*bike_lane_penalty * 10.0) as isize)
.named("bike lane penalty"),
]));
rows.push(Widget::row(vec![
"Bus lane penalty:".draw_text(ctx).margin_right(20),
Spinner::new(ctx, (0, 20), (*bus_lane_penalty * 10.0) as isize)
.named("bus lane penalty"),
]));
rows.push(Widget::row(vec![
"Driving lane penalty:".draw_text(ctx).margin_right(20),
Spinner::new(ctx, (0, 20), (*driving_lane_penalty * 10.0) as isize)
.named("driving lane penalty"),
]));
}
Widget::col(rows)
}

View File

@ -64,6 +64,10 @@ impl Button {
dims,
}
}
pub fn is_enabled(&self) -> bool {
!self.is_disabled
}
}
impl WidgetImpl for Button {

View File

@ -10,8 +10,8 @@ use geom::{Percent, Polygon};
use crate::widgets::slider;
use crate::widgets::Container;
use crate::{
Autocomplete, Checkbox, Color, Dropdown, EventCtx, GfxCtx, HorizontalAlignment, Menu, Outcome,
PersistentSplit, ScreenDims, ScreenPt, ScreenRectangle, Slider, Spinner, TextBox,
Autocomplete, Button, Checkbox, Color, Dropdown, EventCtx, GfxCtx, HorizontalAlignment, Menu,
Outcome, PersistentSplit, ScreenDims, ScreenPt, ScreenRectangle, Slider, Spinner, TextBox,
VerticalAlignment, Widget, WidgetImpl, WidgetOutput,
};
@ -403,6 +403,10 @@ impl Panel {
self.find::<Autocomplete<T>>(name).final_value()
}
pub fn is_button_enabled(&self, name: &str) -> bool {
self.find::<Button>(name).is_enabled()
}
pub fn maybe_find(&self, name: &str) -> Option<&Widget> {
self.top_level.find(name)
}