diff --git a/game/src/debug/routes.rs b/game/src/debug/routes.rs index a487b93a22..2fbf6bdd2d 100644 --- a/game/src/debug/routes.rs +++ b/game/src/debug/routes.rs @@ -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 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 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) +} diff --git a/widgetry/src/widgets/button.rs b/widgetry/src/widgets/button.rs index a342bd534c..f433021be0 100644 --- a/widgetry/src/widgets/button.rs +++ b/widgetry/src/widgets/button.rs @@ -64,6 +64,10 @@ impl Button { dims, } } + + pub fn is_enabled(&self) -> bool { + !self.is_disabled + } } impl WidgetImpl for Button { diff --git a/widgetry/src/widgets/panel.rs b/widgetry/src/widgets/panel.rs index 3ef93b79c7..0253a8ee7a 100644 --- a/widgetry/src/widgets/panel.rs +++ b/widgetry/src/widgets/panel.rs @@ -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::>(name).final_value() } + pub fn is_button_enabled(&self, name: &str) -> bool { + self.find::