diff --git a/game/src/edit/lanes.rs b/game/src/edit/lanes.rs index a14347b9ac..54c0f8aae3 100644 --- a/game/src/edit/lanes.rs +++ b/game/src/edit/lanes.rs @@ -3,6 +3,7 @@ use crate::common::Colorer; use crate::edit::apply_map_edits; use crate::game::{msg, State, Transition, WizardState}; use crate::helpers::ID; +use crate::managed::WrappedComposite; use crate::ui::UI; use ezgui::{ hotkey, Button, Choice, Color, Composite, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, @@ -14,135 +15,149 @@ use map_model::{ use std::collections::BTreeSet; pub struct LaneEditor { - pub brush: Brush, + l: LaneID, composite: Composite, } -#[derive(Clone, Copy, PartialEq)] -pub enum Brush { - Inactive, - Driving, - Bike, - Bus, - Parking, - Construction, - Reverse, +impl LaneEditor { + pub fn new(l: LaneID, ctx: &mut EventCtx, ui: &UI) -> LaneEditor { + let mut row = Vec::new(); + let lt = ui.primary.map.get_l(l).lane_type; + for (icon, label, key, active) in vec![ + ( + "driving", + "convert to a driving lane", + Key::D, + lt != LaneType::Driving, + ), + ( + "bike", + "convert to a protected bike lane", + Key::B, + lt != LaneType::Biking, + ), + ( + "bus", + "convert to a bus-only lane", + Key::T, + lt != LaneType::Bus, + ), + ( + "parking", + "convert to an on-street parking lane", + Key::P, + lt != LaneType::Parking, + ), + ( + "construction", + "close for construction", + Key::C, + lt != LaneType::Construction, + ), + ("contraflow", "reverse lane direction", Key::F, true), + ] { + row.push( + if active { + ManagedWidget::btn(Button::rectangle_svg( + &format!("assets/edit/{}.svg", icon), + label, + hotkey(key), + RewriteColor::ChangeAll(colors::HOVERING), + ctx, + )) + } else { + ManagedWidget::draw_svg_transform( + ctx, + &format!("assets/edit/{}.svg", icon), + RewriteColor::ChangeAll(Color::WHITE.alpha(0.5)), + ) + } + .padding(5), + ); + } + + let revert = if ui.primary.map.get_edits().original_lts.contains_key(&l) + || ui.primary.map.get_edits().reversed_lanes.contains(&l) + { + WrappedComposite::text_button(ctx, "Revert", hotkey(Key::R)) + } else { + Button::inactive_button("Revert", ctx) + }; + + let composite = Composite::new( + ManagedWidget::col(vec![ + ManagedWidget::draw_text(ctx, Text::from(Line("Modify lane"))).centered_horiz(), + ManagedWidget::row(row).centered(), + WrappedComposite::text_button(ctx, "Finish", hotkey(Key::Escape)), + WrappedComposite::text_button(ctx, "Edit entire road", hotkey(Key::U)), + revert, + ]) + .bg(colors::PANEL_BG) + .padding(10), + ) + .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) + .build(ctx); + + LaneEditor { l, composite } + } } -impl LaneEditor { - pub fn new(ctx: &mut EventCtx) -> LaneEditor { - LaneEditor { - brush: Brush::Inactive, - composite: make_brush_panel(ctx, Brush::Inactive), - } - } +impl State for LaneEditor { + fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { + ctx.canvas_movement(); - pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Option { - // Change brush match self.composite.event(ctx) { Some(Outcome::Clicked(x)) => { - let b = match x.as_ref() { - "convert to a driving lane" => Brush::Driving, - "convert to a protected bike lane" => Brush::Bike, - "convert to a bus-only lane" => Brush::Bus, - "convert to a on-street parking lane" => Brush::Parking, - "convert to a closed for construction" => Brush::Construction, - "convert to a reverse lane direction" => Brush::Reverse, + let map = &ui.primary.map; + let result = match x.as_ref() { + "convert to a driving lane" => { + try_change_lane_type(self.l, LaneType::Driving, map) + } + "convert to a protected bike lane" => { + try_change_lane_type(self.l, LaneType::Biking, map) + } + "convert to a bus-only lane" => { + try_change_lane_type(self.l, LaneType::Bus, map) + } + "convert to an on-street parking lane" => { + try_change_lane_type(self.l, LaneType::Parking, map) + } + "close for construction" => { + try_change_lane_type(self.l, LaneType::Construction, map) + } + "reverse lane direction" => try_reverse(self.l, map), + "Finish" => { + return Transition::Pop; + } + "Edit entire road" => { + return Transition::Replace(make_bulk_edit_lanes(map.get_l(self.l).parent)); + } + "Revert" => { + // TODO It's hard to revert both changes at once. + if let Some(lt) = map.get_edits().original_lts.get(&self.l) { + try_change_lane_type(self.l, *lt, map) + } else { + try_reverse(self.l, map) + } + } _ => unreachable!(), }; - if self.brush == b { - self.brush = Brush::Inactive; - } else { - self.brush = b; + match result { + Ok(cmd) => { + let mut edits = ui.primary.map.get_edits().clone(); + edits.commands.push(cmd); + apply_map_edits(ctx, ui, edits); + return Transition::Replace(Box::new(LaneEditor::new(self.l, ctx, ui))); + } + Err(err) => { + return Transition::Push(msg("Error", vec![err])); + } } - self.composite = make_brush_panel(ctx, self.brush); } None => {} } - if let Some(ID::Lane(l)) = ui.primary.current_selection { - // TODO Refactor all of these mappings! - if self.brush != Brush::Inactive { - let label = match self.brush { - Brush::Inactive => unreachable!(), - Brush::Driving => "driving lane", - Brush::Bike => "protected bike lane", - Brush::Bus => "bus-only lane", - Brush::Parking => "on-street parking lane", - Brush::Construction => "closed for construction", - Brush::Reverse => "reverse lane direction", - }; - if ui - .per_obj - .action(ctx, Key::Space, &format!("convert to a {}", label)) - { - // These errors are universal. - if ui.primary.map.get_l(l).is_sidewalk() { - return Some(Transition::Push(msg( - "Error", - vec!["Can't modify sidewalks"], - ))); - } - if ui.primary.map.get_l(l).lane_type == LaneType::SharedLeftTurn { - return Some(Transition::Push(msg( - "Error", - vec!["Can't modify shared-left turn lanes yet"], - ))); - } - - match apply_brush(self.brush, &ui.primary.map, l) { - Ok(Some(cmd)) => { - let mut edits = ui.primary.map.get_edits().clone(); - edits.commands.push(cmd); - apply_map_edits(ctx, ui, edits); - } - Ok(None) => {} - Err(err) => { - return Some(Transition::Push(msg("Error", vec![err]))); - } - } - } - } - - if ui - .per_obj - .action(ctx, Key::U, "bulk edit lanes on this road") - { - return Some(Transition::Push(make_bulk_edit_lanes( - ui.primary.map.get_l(l).parent, - ))); - } else if let Some(lt) = ui.primary.map.get_edits().original_lts.get(&l) { - if ui.per_obj.action(ctx, Key::R, "revert") { - if let Some(err) = can_change_lane_type(l, *lt, &ui.primary.map) { - return Some(Transition::Push(msg("Error", vec![err]))); - } - - let mut edits = ui.primary.map.get_edits().clone(); - edits.commands.push(EditCmd::ChangeLaneType { - id: l, - lt: *lt, - orig_lt: ui.primary.map.get_l(l).lane_type, - }); - apply_map_edits(ctx, ui, edits); - } - } else if ui.primary.map.get_edits().reversed_lanes.contains(&l) { - if ui.per_obj.action(ctx, Key::R, "revert") { - match apply_brush(Brush::Reverse, &ui.primary.map, l) { - Ok(Some(cmd)) => { - let mut edits = ui.primary.map.get_edits().clone(); - edits.commands.push(cmd); - apply_map_edits(ctx, ui, edits); - } - Ok(None) => {} - Err(err) => { - return Some(Transition::Push(msg("Error", vec![err]))); - } - } - } - } - } - - // Woo, a special case! The construction tool also applies to intersections. + /*// Woo, a special case! The construction tool also applies to intersections. if let Some(ID::Intersection(i)) = ui.primary.current_selection { if self.brush == Brush::Construction && ui @@ -181,64 +196,16 @@ impl LaneEditor { } } } - } + }*/ - None + Transition::Keep } - pub fn draw(&self, g: &mut GfxCtx) { + fn draw(&self, g: &mut GfxCtx, _: &UI) { self.composite.draw(g); } } -fn make_brush_panel(ctx: &mut EventCtx, brush: Brush) -> Composite { - let mut row = Vec::new(); - for (b, icon, label, key) in vec![ - (Brush::Driving, "driving", "driving lane", Key::D), - (Brush::Bike, "bike", "protected bike lane", Key::B), - (Brush::Bus, "bus", "bus-only lane", Key::T), - (Brush::Parking, "parking", "on-street parking lane", Key::P), - ( - Brush::Construction, - "construction", - "closed for construction", - Key::C, - ), - ( - Brush::Reverse, - "contraflow", - "reverse lane direction", - Key::F, - ), - ] { - row.push( - ManagedWidget::col(vec![ManagedWidget::btn(Button::rectangle_svg_rewrite( - &format!("assets/edit/{}.svg", icon), - &format!("convert to a {}", label), - hotkey(key), - if brush == b { - RewriteColor::ChangeAll(Color::RED) - } else { - RewriteColor::NoOp - }, - RewriteColor::ChangeAll(colors::HOVERING), - ctx, - ))]) - .padding(5), - ); - } - Composite::new( - ManagedWidget::col(vec![ - ManagedWidget::draw_text(ctx, Text::from(Line("Modify lanes"))).centered_horiz(), - ManagedWidget::row(row).centered(), - ]) - .bg(colors::PANEL_BG) - .padding(10), - ) - .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) - .build(ctx) -} - fn can_change_lane_type(l: LaneID, new_lt: LaneType, map: &Map) -> Option { let r = map.get_parent(l); let (fwds, idx) = r.dir_and_offset(l); @@ -292,18 +259,30 @@ fn can_change_lane_type(l: LaneID, new_lt: LaneType, map: &Map) -> Option Result, String> { +fn try_change_lane_type(l: LaneID, new_lt: LaneType, map: &Map) -> Result { if let Some(err) = can_change_lane_type(l, new_lt, map) { return Err(err); } - if map.get_l(l).lane_type == new_lt { - Ok(None) + Ok(EditCmd::ChangeLaneType { + id: l, + lt: new_lt, + orig_lt: map.get_l(l).lane_type, + }) +} + +fn try_reverse(l: LaneID, map: &Map) -> Result { + let lane = map.get_l(l); + if !lane.lane_type.is_for_moving_vehicles() { + Err(format!("You can't reverse a {:?} lane", lane.lane_type)) + } else if map.get_r(lane.parent).dir_and_offset(l).1 != 0 { + Err(format!( + "You can only reverse the lanes next to the road's yellow center line" + )) } else { - Ok(Some(EditCmd::ChangeLaneType { - id: l, - lt: new_lt, - orig_lt: map.get_l(l).lane_type, - })) + Ok(EditCmd::ReverseLane { + l, + dst_i: lane.src_i, + }) } } @@ -372,31 +351,3 @@ fn make_bulk_edit_lanes(road: RoadID) -> Box { ))) })) } - -// If this returns a string error message, the edit didn't work. If it returns Ok(None), then -// it's a no-op. -fn apply_brush(brush: Brush, map: &Map, l: LaneID) -> Result, String> { - match brush { - Brush::Inactive => unreachable!(), - Brush::Driving => try_change_lane_type(l, LaneType::Driving, map), - Brush::Bike => try_change_lane_type(l, LaneType::Biking, map), - Brush::Bus => try_change_lane_type(l, LaneType::Bus, map), - Brush::Parking => try_change_lane_type(l, LaneType::Parking, map), - Brush::Construction => try_change_lane_type(l, LaneType::Construction, map), - Brush::Reverse => { - let lane = map.get_l(l); - if !lane.lane_type.is_for_moving_vehicles() { - return Err(format!("You can't reverse a {:?} lane", lane.lane_type)); - } - if map.get_r(lane.parent).dir_and_offset(l).1 != 0 { - return Err(format!( - "You can only reverse the lanes next to the road's yellow center line" - )); - } - Ok(Some(EditCmd::ReverseLane { - l, - dst_i: lane.src_i, - })) - } - } -} diff --git a/game/src/edit/mod.rs b/game/src/edit/mod.rs index e11a6f1939..663597079b 100644 --- a/game/src/edit/mod.rs +++ b/game/src/edit/mod.rs @@ -2,7 +2,6 @@ mod lanes; mod stop_signs; mod traffic_signals; -use self::lanes::{Brush, LaneEditor}; pub use self::stop_signs::StopSignEditor; pub use self::traffic_signals::TrafficSignalEditor; use crate::common::{tool_panel, CommonState, Overlays, Warping}; @@ -15,7 +14,7 @@ use crate::sandbox::{GameplayMode, SandboxMode}; use crate::ui::UI; use abstutil::Timer; use ezgui::{hotkey, lctrl, Choice, EventCtx, GfxCtx, Key, Line, ModalMenu, Text, WrappedWizard}; -use map_model::{EditCmd, LaneID, MapEdits}; +use map_model::{EditCmd, LaneID, LaneType, MapEdits}; use sim::Sim; use std::collections::BTreeSet; @@ -27,8 +26,6 @@ pub struct EditMode { mode: GameplayMode, pub suspended_sim: Sim, - lane_editor: LaneEditor, - once: bool, } @@ -51,7 +48,6 @@ impl EditMode { ), mode, suspended_sim, - lane_editor: LaneEditor::new(ctx), once: true, } } @@ -88,22 +84,18 @@ impl State for EditMode { self.menu.event(ctx); - if self.mode.can_edit_lanes() { - if let Some(t) = self.lane_editor.event(ctx, ui) { - return t; - } - } ctx.canvas_movement(); // Restrict what can be selected. if ctx.redo_mouseover() { ui.recalculate_current_selection(ctx); - if let Some(ID::Lane(_)) = ui.primary.current_selection { - } else if let Some(ID::Intersection(_)) = ui.primary.current_selection { - if self.lane_editor.brush != Brush::Construction - && self.lane_editor.brush != Brush::Inactive + if let Some(ID::Lane(l)) = ui.primary.current_selection { + if !self.mode.can_edit_lanes() + || ui.primary.map.get_l(l).is_sidewalk() + || ui.primary.map.get_l(l).lane_type == LaneType::SharedLeftTurn { ui.primary.current_selection = None; } + } else if let Some(ID::Intersection(_)) = ui.primary.current_selection { } else { ui.primary.current_selection = None; } @@ -149,6 +141,11 @@ impl State for EditMode { apply_map_edits(ctx, ui, edits); } } + if let Some(ID::Lane(l)) = ui.primary.current_selection { + if ui.per_obj.left_click(ctx, "edit lane") { + return Transition::Push(Box::new(lanes::LaneEditor::new(l, ctx, ui))); + } + } if !ui.primary.map.get_edits().commands.is_empty() && self.menu.action("undo") { let mut edits = ui.primary.map.get_edits().clone(); @@ -188,9 +185,6 @@ impl State for EditMode { self.tool_panel.draw(g); self.menu.draw(g); - if self.mode.can_edit_lanes() { - self.lane_editor.draw(g); - } CommonState::draw_osd(g, ui, &ui.primary.current_selection); } } diff --git a/game/src/game.rs b/game/src/game.rs index 1a5117e65f..6d2ae7213d 100644 --- a/game/src/game.rs +++ b/game/src/game.rs @@ -125,6 +125,20 @@ impl GUI for Game { } DrawBaselayer::Custom => {} DrawBaselayer::PreviousState => { + match self.states[self.states.len() - 2].draw_baselayer() { + DrawBaselayer::DefaultMap => { + self.ui.draw( + g, + DrawOptions::new(), + &self.ui.primary.sim, + &ShowEverything::new(), + ); + } + DrawBaselayer::Custom => {} + // Nope, don't recurse + DrawBaselayer::PreviousState => {} + } + self.states[self.states.len() - 2].draw(g, &self.ui); } }