mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-01 02:33:54 +03:00
MVP of Yuwen's new road editor!
This commit is contained in:
parent
38f5da4f80
commit
c73ceebd35
84
game/src/edit/debug_roads.rs
Normal file
84
game/src/edit/debug_roads.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use map_gui::tools::PromptInput;
|
||||
use map_model::{Direction, LaneSpec, LaneType, Road};
|
||||
use widgetry::{EventCtx, State};
|
||||
|
||||
use crate::app::{App, Transition};
|
||||
use crate::edit::apply_map_edits;
|
||||
|
||||
/// Specify the lane types for a road using a text box. This is a temporary UI to start
|
||||
/// experimenting with widening roads. It'll be replaced by a real UI once the design is ready.
|
||||
pub fn prompt_for_lanes(ctx: &mut EventCtx, road: &Road) -> Box<dyn State<App>> {
|
||||
let r = road.id;
|
||||
PromptInput::new(
|
||||
ctx,
|
||||
"Define lanes_ltr",
|
||||
lanes_to_string(road),
|
||||
Box::new(move |string, ctx, app| {
|
||||
// We're selecting a lane before this, but the ID is probably about to be invalidated.
|
||||
app.primary.current_selection = None;
|
||||
|
||||
let mut edits = app.primary.map.get_edits().clone();
|
||||
edits.commands.push(app.primary.map.edit_road_cmd(r, |new| {
|
||||
new.lanes_ltr = string_to_lanes(string.clone());
|
||||
}));
|
||||
apply_map_edits(ctx, app, edits);
|
||||
Transition::Multi(vec![Transition::Pop, Transition::Pop])
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn lanes_to_string(road: &Road) -> String {
|
||||
// TODO Assuming driving on the right.
|
||||
let mut dir_change = false;
|
||||
let mut string = String::new();
|
||||
for (_, dir, lt) in road.lanes_ltr() {
|
||||
if !dir_change && dir == Direction::Fwd {
|
||||
string.push('/');
|
||||
dir_change = true;
|
||||
}
|
||||
string.push(
|
||||
lane_type_codes()
|
||||
.into_iter()
|
||||
.find(|(x, _)| *x == lt)
|
||||
.unwrap()
|
||||
.1,
|
||||
);
|
||||
}
|
||||
string
|
||||
}
|
||||
|
||||
fn string_to_lanes(string: String) -> Vec<LaneSpec> {
|
||||
let mut lanes = Vec::new();
|
||||
let mut dir = Direction::Back;
|
||||
for x in string.chars() {
|
||||
if x == '/' {
|
||||
dir = Direction::Fwd;
|
||||
continue;
|
||||
}
|
||||
let lt = lane_type_codes()
|
||||
.into_iter()
|
||||
.find(|(_, code)| *code == x)
|
||||
.unwrap()
|
||||
.0;
|
||||
lanes.push(LaneSpec {
|
||||
lt,
|
||||
dir,
|
||||
width: map_model::NORMAL_LANE_THICKNESS,
|
||||
});
|
||||
}
|
||||
lanes
|
||||
}
|
||||
|
||||
fn lane_type_codes() -> Vec<(LaneType, char)> {
|
||||
vec![
|
||||
(LaneType::Driving, 'd'),
|
||||
(LaneType::Parking, 'p'),
|
||||
(LaneType::Sidewalk, 's'),
|
||||
(LaneType::Shoulder, 'S'),
|
||||
(LaneType::Biking, 'b'),
|
||||
(LaneType::Bus, 't'), // transit
|
||||
(LaneType::SharedLeftTurn, 'l'),
|
||||
(LaneType::Construction, 'c'),
|
||||
(LaneType::LightRail, 'r'),
|
||||
]
|
||||
}
|
@ -113,6 +113,14 @@ impl LaneEditor {
|
||||
} else {
|
||||
Widget::nothing()
|
||||
},
|
||||
if app.opts.dev {
|
||||
ctx.style()
|
||||
.btn_plain_destructive
|
||||
.text("Modify entire road (debug)")
|
||||
.build_def(ctx)
|
||||
} else {
|
||||
Widget::nothing()
|
||||
},
|
||||
ctx.style()
|
||||
.btn_solid_primary
|
||||
.text("Finish")
|
||||
@ -140,10 +148,14 @@ impl SimpleState<App> for LaneEditor {
|
||||
app,
|
||||
app.primary.map.get_l(self.l).parent,
|
||||
)),
|
||||
"Modify entire road" => Transition::Push(crate::edit::roads::prompt_for_lanes(
|
||||
"Modify entire road" => Transition::Push(crate::edit::roads::RoadEditor::new(
|
||||
ctx,
|
||||
app.primary.map.get_parent(self.l),
|
||||
app,
|
||||
app.primary.map.get_l(self.l).parent,
|
||||
)),
|
||||
"Modify entire road (debug)" => Transition::Push(
|
||||
crate::edit::debug_roads::prompt_for_lanes(ctx, app.primary.map.get_parent(self.l)),
|
||||
),
|
||||
"Finish" => Transition::Pop,
|
||||
x => {
|
||||
let map = &mut app.primary.map;
|
||||
|
@ -23,6 +23,7 @@ use crate::debug::DebugMode;
|
||||
use crate::sandbox::{GameplayMode, SandboxMode, TimeWarpScreen};
|
||||
|
||||
mod bulk;
|
||||
mod debug_roads;
|
||||
mod lanes;
|
||||
mod roads;
|
||||
mod routes;
|
||||
|
@ -1,84 +1,388 @@
|
||||
use map_gui::tools::PromptInput;
|
||||
use map_model::{Direction, LaneSpec, LaneType, Road};
|
||||
use widgetry::{EventCtx, State};
|
||||
use geom::Distance;
|
||||
use map_gui::render::{Renderable, OUTLINE_THICKNESS};
|
||||
use map_model::{Direction, LaneID, LaneSpec, LaneType, Road, RoadID, NORMAL_LANE_THICKNESS};
|
||||
use widgetry::{
|
||||
Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
|
||||
Panel, State, Text, TextExt, VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use crate::app::{App, Transition};
|
||||
use crate::edit::apply_map_edits;
|
||||
use crate::edit::{apply_map_edits, speed_limit_choices};
|
||||
|
||||
/// Specify the lane types for a road using a text box. This is a temporary UI to start
|
||||
/// experimenting with widening roads. It'll be replaced by a real UI once the design is ready.
|
||||
pub fn prompt_for_lanes(ctx: &mut EventCtx, road: &Road) -> Box<dyn State<App>> {
|
||||
let r = road.id;
|
||||
PromptInput::new(
|
||||
ctx,
|
||||
"Define lanes_ltr",
|
||||
lanes_to_string(road),
|
||||
Box::new(move |string, ctx, app| {
|
||||
// We're selecting a lane before this, but the ID is probably about to be invalidated.
|
||||
app.primary.current_selection = None;
|
||||
|
||||
let mut edits = app.primary.map.get_edits().clone();
|
||||
edits.commands.push(app.primary.map.edit_road_cmd(r, |new| {
|
||||
new.lanes_ltr = string_to_lanes(string.clone());
|
||||
}));
|
||||
apply_map_edits(ctx, app, edits);
|
||||
Transition::Multi(vec![Transition::Pop, Transition::Pop])
|
||||
}),
|
||||
)
|
||||
pub struct RoadEditor {
|
||||
r: RoadID,
|
||||
current_lane: Option<LaneID>,
|
||||
top_panel: Panel,
|
||||
main_panel: Panel,
|
||||
highlight_selection: (Option<LaneID>, Drawable),
|
||||
}
|
||||
|
||||
fn lanes_to_string(road: &Road) -> String {
|
||||
// TODO Assuming driving on the right.
|
||||
let mut dir_change = false;
|
||||
let mut string = String::new();
|
||||
for (_, dir, lt) in road.lanes_ltr() {
|
||||
if !dir_change && dir == Direction::Fwd {
|
||||
string.push('/');
|
||||
dir_change = true;
|
||||
impl RoadEditor {
|
||||
pub fn new(ctx: &mut EventCtx, app: &mut App, r: RoadID) -> Box<dyn State<App>> {
|
||||
app.primary.current_selection = None;
|
||||
|
||||
let top_panel = Panel::new(Widget::row(vec![ctx
|
||||
.style()
|
||||
.btn_solid_primary
|
||||
.text("Finish")
|
||||
.hotkey(Key::Escape)
|
||||
.build_def(ctx)]))
|
||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||
.build(ctx);
|
||||
let current_lane = None;
|
||||
let main_panel = make_main_panel(ctx, app, app.primary.map.get_r(r), current_lane);
|
||||
let highlight_selection = highlight_current_selection(ctx, app, r, current_lane);
|
||||
Box::new(RoadEditor {
|
||||
r,
|
||||
current_lane,
|
||||
top_panel,
|
||||
main_panel,
|
||||
highlight_selection,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State<App> for RoadEditor {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
ctx.canvas_movement();
|
||||
|
||||
match self.top_panel.event(ctx) {
|
||||
Outcome::Clicked(x) => match x.as_ref() {
|
||||
"Finish" => {
|
||||
return Transition::Pop;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
string.push(
|
||||
lane_type_codes()
|
||||
.into_iter()
|
||||
.find(|(x, _)| *x == lt)
|
||||
.unwrap()
|
||||
.1,
|
||||
|
||||
match self.main_panel.event(ctx) {
|
||||
Outcome::Clicked(x) => {
|
||||
if x == "delete lane" {
|
||||
let idx = app
|
||||
.primary
|
||||
.map
|
||||
.get_r(self.r)
|
||||
.offset(self.current_lane.unwrap());
|
||||
|
||||
let mut edits = app.primary.map.get_edits().clone();
|
||||
edits
|
||||
.commands
|
||||
.push(app.primary.map.edit_road_cmd(self.r, |new| {
|
||||
new.lanes_ltr.remove(idx);
|
||||
}));
|
||||
apply_map_edits(ctx, app, edits);
|
||||
|
||||
self.current_lane = None;
|
||||
self.main_panel =
|
||||
make_main_panel(ctx, app, app.primary.map.get_r(self.r), self.current_lane);
|
||||
self.highlight_selection =
|
||||
highlight_current_selection(ctx, app, self.r, self.current_lane);
|
||||
} else if x == "flip direction" {
|
||||
let idx = app
|
||||
.primary
|
||||
.map
|
||||
.get_r(self.r)
|
||||
.offset(self.current_lane.unwrap());
|
||||
|
||||
let mut edits = app.primary.map.get_edits().clone();
|
||||
edits
|
||||
.commands
|
||||
.push(app.primary.map.edit_road_cmd(self.r, |new| {
|
||||
new.lanes_ltr[idx].dir = new.lanes_ltr[idx].dir.opposite();
|
||||
}));
|
||||
apply_map_edits(ctx, app, edits);
|
||||
|
||||
self.current_lane = Some(app.primary.map.get_r(self.r).lanes_ltr()[idx].0);
|
||||
self.main_panel =
|
||||
make_main_panel(ctx, app, app.primary.map.get_r(self.r), self.current_lane);
|
||||
self.highlight_selection =
|
||||
highlight_current_selection(ctx, app, self.r, self.current_lane);
|
||||
} else if let Some(idx) = x.strip_prefix("modify Lane #") {
|
||||
self.current_lane = Some(LaneID(idx.parse().unwrap()));
|
||||
self.main_panel =
|
||||
make_main_panel(ctx, app, app.primary.map.get_r(self.r), self.current_lane);
|
||||
self.highlight_selection =
|
||||
highlight_current_selection(ctx, app, self.r, self.current_lane);
|
||||
} else if let Some(lt) = x.strip_prefix("add ") {
|
||||
let lt = LaneType::from_short_name(lt).unwrap();
|
||||
|
||||
let mut edits = app.primary.map.get_edits().clone();
|
||||
edits
|
||||
.commands
|
||||
.push(app.primary.map.edit_road_cmd(self.r, |new| {
|
||||
new.lanes_ltr.push(LaneSpec {
|
||||
lt,
|
||||
dir: Direction::Fwd,
|
||||
width: NORMAL_LANE_THICKNESS,
|
||||
});
|
||||
}));
|
||||
apply_map_edits(ctx, app, edits);
|
||||
|
||||
assert!(self.current_lane.is_none());
|
||||
self.main_panel =
|
||||
make_main_panel(ctx, app, app.primary.map.get_r(self.r), self.current_lane);
|
||||
self.highlight_selection =
|
||||
highlight_current_selection(ctx, app, self.r, self.current_lane);
|
||||
} else if let Some(lt) = x.strip_prefix("change to ") {
|
||||
let lt = LaneType::from_short_name(lt).unwrap();
|
||||
let idx = app
|
||||
.primary
|
||||
.map
|
||||
.get_r(self.r)
|
||||
.offset(self.current_lane.unwrap());
|
||||
|
||||
let mut edits = app.primary.map.get_edits().clone();
|
||||
edits
|
||||
.commands
|
||||
.push(app.primary.map.edit_road_cmd(self.r, |new| {
|
||||
new.lanes_ltr[idx].lt = lt;
|
||||
}));
|
||||
apply_map_edits(ctx, app, edits);
|
||||
|
||||
self.current_lane = Some(app.primary.map.get_r(self.r).lanes_ltr()[idx].0);
|
||||
self.main_panel =
|
||||
make_main_panel(ctx, app, app.primary.map.get_r(self.r), self.current_lane);
|
||||
self.highlight_selection =
|
||||
highlight_current_selection(ctx, app, self.r, self.current_lane);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
Outcome::Changed => {
|
||||
let speed_limit = self.main_panel.dropdown_value("speed limit");
|
||||
if speed_limit != app.primary.map.get_r(self.r).speed_limit {
|
||||
let mut edits = app.primary.map.get_edits().clone();
|
||||
edits
|
||||
.commands
|
||||
.push(app.primary.map.edit_road_cmd(self.r, |new| {
|
||||
new.speed_limit = speed_limit;
|
||||
}));
|
||||
apply_map_edits(ctx, app, edits);
|
||||
|
||||
// Lane IDs don't change
|
||||
self.main_panel =
|
||||
make_main_panel(ctx, app, app.primary.map.get_r(self.r), self.current_lane);
|
||||
} else {
|
||||
let width = self.main_panel.dropdown_value("width");
|
||||
let idx = app
|
||||
.primary
|
||||
.map
|
||||
.get_r(self.r)
|
||||
.offset(self.current_lane.unwrap());
|
||||
|
||||
let mut edits = app.primary.map.get_edits().clone();
|
||||
edits
|
||||
.commands
|
||||
.push(app.primary.map.edit_road_cmd(self.r, |new| {
|
||||
new.lanes_ltr[idx].width = width;
|
||||
}));
|
||||
apply_map_edits(ctx, app, edits);
|
||||
|
||||
self.current_lane = Some(app.primary.map.get_r(self.r).lanes_ltr()[idx].0);
|
||||
self.main_panel =
|
||||
make_main_panel(ctx, app, app.primary.map.get_r(self.r), self.current_lane);
|
||||
self.highlight_selection =
|
||||
highlight_current_selection(ctx, app, self.r, self.current_lane);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut highlight = self.current_lane;
|
||||
if let Some(name) = self.main_panel.currently_hovering() {
|
||||
if let Some(idx) = name.strip_prefix("modify Lane #") {
|
||||
highlight = Some(LaneID(idx.parse().unwrap()));
|
||||
}
|
||||
}
|
||||
if highlight != self.highlight_selection.0 {
|
||||
self.highlight_selection = highlight_current_selection(ctx, app, self.r, highlight);
|
||||
}
|
||||
|
||||
if self.current_lane.is_some()
|
||||
&& ctx.canvas.get_cursor_in_screen_space().is_none()
|
||||
&& ctx.normal_left_click()
|
||||
{
|
||||
self.current_lane = None;
|
||||
self.main_panel =
|
||||
make_main_panel(ctx, app, app.primary.map.get_r(self.r), self.current_lane);
|
||||
self.highlight_selection =
|
||||
highlight_current_selection(ctx, app, self.r, self.current_lane);
|
||||
}
|
||||
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
g.redraw(&self.highlight_selection.1);
|
||||
self.top_panel.draw(g);
|
||||
self.main_panel.draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
fn make_main_panel(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
road: &Road,
|
||||
current_lane: Option<LaneID>,
|
||||
) -> Panel {
|
||||
let map = &app.primary.map;
|
||||
|
||||
let modify_lane = if let Some(l) = current_lane {
|
||||
let lane = map.get_l(l);
|
||||
Widget::row(vec![
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon("system/assets/tools/trash.svg")
|
||||
.disabled(road.lanes_ltr().len() == 1)
|
||||
.build_widget(ctx, "delete lane"),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.text("flip direction")
|
||||
.disabled(!can_reverse(lane.lane_type))
|
||||
.build_def(ctx),
|
||||
Line("Width").secondary().into_widget(ctx),
|
||||
Widget::dropdown(ctx, "width", lane.width, width_choices(app, l)),
|
||||
])
|
||||
} else {
|
||||
Widget::nothing()
|
||||
};
|
||||
let current_lt = current_lane.map(|l| map.get_l(l).lane_type);
|
||||
|
||||
let mut available_lane_types_row = vec![
|
||||
LaneType::Driving,
|
||||
LaneType::Biking,
|
||||
LaneType::Bus,
|
||||
LaneType::Parking,
|
||||
LaneType::Construction,
|
||||
LaneType::Sidewalk,
|
||||
]
|
||||
.into_iter()
|
||||
.map(|lt| {
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon(lane_type_to_icon(lt).unwrap())
|
||||
.disabled(Some(lt) == current_lt)
|
||||
.build_widget(
|
||||
ctx,
|
||||
format!(
|
||||
"{} {}",
|
||||
if current_lane.is_some() {
|
||||
"change to"
|
||||
} else {
|
||||
"add"
|
||||
},
|
||||
lt.short_name()
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<Widget>>();
|
||||
if current_lane.is_some() {
|
||||
available_lane_types_row.insert(0, "change to".text_widget(ctx));
|
||||
} else {
|
||||
available_lane_types_row.insert(0, "add new".text_widget(ctx));
|
||||
}
|
||||
let available_lane_types_row = Widget::row(available_lane_types_row);
|
||||
|
||||
let mut current_lanes_ltr = Vec::new();
|
||||
for (id, _, lt) in road.lanes_ltr() {
|
||||
// TODO Add direction arrow sometimes
|
||||
current_lanes_ltr.push(
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon(lane_type_to_icon(lt).unwrap())
|
||||
.disabled(Some(id) == current_lane)
|
||||
.build_widget(ctx, format!("modify {}", id)),
|
||||
);
|
||||
}
|
||||
string
|
||||
let current_lanes_ltr = Widget::row(current_lanes_ltr);
|
||||
|
||||
let road_settings = Widget::row(vec![
|
||||
Text::from_all(vec![
|
||||
Line("Total width").secondary(),
|
||||
Line((2.0 * road.get_half_width(map)).to_string(&app.opts.units)),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
Line("Speed limit").secondary().into_widget(ctx),
|
||||
{
|
||||
let mut choices = speed_limit_choices(app);
|
||||
if !choices.iter().any(|c| c.data == road.speed_limit) {
|
||||
choices.push(Choice::new(
|
||||
road.speed_limit.to_string(&app.opts.units),
|
||||
road.speed_limit,
|
||||
));
|
||||
}
|
||||
Widget::dropdown(ctx, "speed limit", road.speed_limit, choices)
|
||||
},
|
||||
]);
|
||||
|
||||
Panel::new(Widget::col(vec![
|
||||
modify_lane,
|
||||
available_lane_types_row,
|
||||
current_lanes_ltr,
|
||||
road_settings,
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Center)
|
||||
.build(ctx)
|
||||
}
|
||||
|
||||
fn string_to_lanes(string: String) -> Vec<LaneSpec> {
|
||||
let mut lanes = Vec::new();
|
||||
let mut dir = Direction::Back;
|
||||
for x in string.chars() {
|
||||
if x == '/' {
|
||||
dir = Direction::Fwd;
|
||||
continue;
|
||||
}
|
||||
let lt = lane_type_codes()
|
||||
.into_iter()
|
||||
.find(|(_, code)| *code == x)
|
||||
.unwrap()
|
||||
.0;
|
||||
lanes.push(LaneSpec {
|
||||
lt,
|
||||
dir,
|
||||
width: map_model::NORMAL_LANE_THICKNESS,
|
||||
});
|
||||
fn highlight_current_selection(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
r: RoadID,
|
||||
l: Option<LaneID>,
|
||||
) -> (Option<LaneID>, Drawable) {
|
||||
let mut batch = GeomBatch::new();
|
||||
let color = Color::hex("#DF8C3D");
|
||||
let map = &app.primary.map;
|
||||
|
||||
let road = map.get_r(r);
|
||||
batch.push(
|
||||
color,
|
||||
road.center_pts
|
||||
.to_thick_boundary(2.0 * road.get_half_width(map), OUTLINE_THICKNESS)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
if let Some(l) = l {
|
||||
batch.push(color, app.primary.draw_map.get_l(l).get_outline(map));
|
||||
}
|
||||
lanes
|
||||
(l, ctx.upload(batch))
|
||||
}
|
||||
|
||||
fn lane_type_codes() -> Vec<(LaneType, char)> {
|
||||
vec![
|
||||
(LaneType::Driving, 'd'),
|
||||
(LaneType::Parking, 'p'),
|
||||
(LaneType::Sidewalk, 's'),
|
||||
(LaneType::Shoulder, 'S'),
|
||||
(LaneType::Biking, 'b'),
|
||||
(LaneType::Bus, 't'), // transit
|
||||
(LaneType::SharedLeftTurn, 'l'),
|
||||
(LaneType::Construction, 'c'),
|
||||
(LaneType::LightRail, 'r'),
|
||||
]
|
||||
fn lane_type_to_icon(lt: LaneType) -> Option<&'static str> {
|
||||
match lt {
|
||||
LaneType::Driving => Some("system/assets/edit/driving.svg"),
|
||||
LaneType::Parking => Some("system/assets/edit/parking.svg"),
|
||||
LaneType::Sidewalk | LaneType::Shoulder => Some("system/assets/meters/pedestrian.svg"),
|
||||
LaneType::Biking => Some("system/assets/edit/bike.svg"),
|
||||
LaneType::Bus => Some("system/assets/edit/bus.svg"),
|
||||
// TODO Add an icon for this
|
||||
LaneType::SharedLeftTurn => None,
|
||||
LaneType::Construction => Some("system/assets/edit/construction.svg"),
|
||||
// Don't allow creating these yet
|
||||
LaneType::LightRail => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn width_choices(app: &App, l: LaneID) -> Vec<Choice<Distance>> {
|
||||
// TODO Use real standard widths for different types
|
||||
let mut choices = vec![
|
||||
Distance::meters(1.5),
|
||||
Distance::meters(2.0),
|
||||
Distance::meters(2.5),
|
||||
Distance::meters(3.0),
|
||||
];
|
||||
let current_width = app.primary.map.get_l(l).width;
|
||||
if choices.iter().any(|x| *x != current_width) {
|
||||
choices.push(current_width);
|
||||
}
|
||||
choices
|
||||
.into_iter()
|
||||
.map(|x| Choice::new(x.to_string(&app.opts.units), x))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn can_reverse(lt: LaneType) -> bool {
|
||||
lt == LaneType::Driving || lt == LaneType::Biking || lt == LaneType::Bus
|
||||
}
|
||||
|
@ -105,6 +105,21 @@ impl LaneType {
|
||||
LaneType::LightRail => "light rail track",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_short_name(x: &str) -> Option<LaneType> {
|
||||
match x {
|
||||
"driving lane" => Some(LaneType::Driving),
|
||||
"bike lane" => Some(LaneType::Biking),
|
||||
"bus lane" => Some(LaneType::Bus),
|
||||
"parking lane" => Some(LaneType::Parking),
|
||||
"sidewalk" => Some(LaneType::Sidewalk),
|
||||
"shoulder" => Some(LaneType::Shoulder),
|
||||
"left-turn lane" => Some(LaneType::SharedLeftTurn),
|
||||
"construction" => Some(LaneType::Construction),
|
||||
"light rail track" => Some(LaneType::LightRail),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A road segment is broken down into individual lanes, which have a LaneType.
|
||||
|
Loading…
Reference in New Issue
Block a user