1 use crate::app::App; 2 use crate::common::CommonState; 3 use crate::edit::{apply_map_edits, check_sidewalk_connectivity, TrafficSignalEditor}; 4 use crate::game::{State, Transition}; 5 use crate::render::DrawIntersection; 6 use crate::sandbox::GameplayMode; 7 use abstutil::Timer; 8 use geom::Polygon; 9 use map_model::{ 10 ControlStopSign, ControlTrafficSignal, EditCmd, EditIntersection, IntersectionID, RoadID, 11 }; 12 use maplit::btreeset; 13 use std::collections::HashMap; 14 use widgetry::{ 15 hotkey, Btn, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Text, 16 VerticalAlignment, Widget, 17 }; 18 19 // TODO For now, individual turns can't be manipulated. Banning turns could be useful, but I'm not 20 // sure what to do about the player orphaning a section of the map. 21 pub struct StopSignEditor { 22 panel: Panel, 23 id: IntersectionID, 24 mode: GameplayMode, 25 // (octagon, pole) 26 geom: HashMap<RoadID, (Polygon, Polygon)>, 27 selected_sign: Option<RoadID>, 28 } 29 30 impl StopSignEditor { new( ctx: &mut EventCtx, app: &mut App, id: IntersectionID, mode: GameplayMode, ) -> Box<dyn State>31 pub fn new( 32 ctx: &mut EventCtx, 33 app: &mut App, 34 id: IntersectionID, 35 mode: GameplayMode, 36 ) -> Box<dyn State> { 37 app.primary.current_selection = None; 38 let geom = app 39 .primary 40 .map 41 .get_stop_sign(id) 42 .roads 43 .iter() 44 .map(|(r, ss)| { 45 let (octagon, pole) = 46 DrawIntersection::stop_sign_geom(ss, &app.primary.map).unwrap(); 47 (*r, (octagon, pole)) 48 }) 49 .collect(); 50 51 let panel = Panel::new(Widget::col(vec![ 52 Line("Stop sign editor").small_heading().draw(ctx), 53 if ControlStopSign::new(&app.primary.map, id) 54 != app.primary.map.get_stop_sign(id).clone() 55 { 56 Btn::text_fg("reset to default").build_def(ctx, hotkey(Key::R)) 57 } else { 58 Btn::text_fg("reset to default").inactive(ctx) 59 }, 60 Btn::text_fg("close intersection for construction").build_def(ctx, hotkey(Key::C)), 61 Btn::text_fg("convert to traffic signal").build_def(ctx, None), 62 Btn::text_fg("Finish").build_def(ctx, hotkey(Key::Escape)), 63 ])) 64 .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) 65 .build(ctx); 66 67 Box::new(StopSignEditor { 68 panel, 69 id, 70 mode, 71 geom, 72 selected_sign: None, 73 }) 74 } 75 } 76 77 impl State for StopSignEditor { event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition78 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { 79 ctx.canvas_movement(); 80 81 if ctx.redo_mouseover() { 82 self.selected_sign = None; 83 if let Some(pt) = ctx.canvas.get_cursor_in_map_space() { 84 for (r, (octagon, _)) in &self.geom { 85 if octagon.contains_pt(pt) { 86 self.selected_sign = Some(*r); 87 break; 88 } 89 } 90 } 91 } 92 93 if let Some(r) = self.selected_sign { 94 let mut sign = app.primary.map.get_stop_sign(self.id).clone(); 95 let label = if sign.roads[&r].must_stop { 96 "remove stop sign" 97 } else { 98 "add stop sign" 99 }; 100 if app.per_obj.left_click(ctx, label) { 101 sign.flip_sign(r); 102 103 let mut edits = app.primary.map.get_edits().clone(); 104 edits.commands.push(EditCmd::ChangeIntersection { 105 i: self.id, 106 old: app.primary.map.get_i_edit(self.id), 107 new: EditIntersection::StopSign(sign), 108 }); 109 apply_map_edits(ctx, app, edits); 110 return Transition::Replace(StopSignEditor::new( 111 ctx, 112 app, 113 self.id, 114 self.mode.clone(), 115 )); 116 } 117 } 118 119 match self.panel.event(ctx) { 120 Outcome::Clicked(x) => match x.as_ref() { 121 "Finish" => { 122 return Transition::Pop; 123 } 124 "reset to default" => { 125 let mut edits = app.primary.map.get_edits().clone(); 126 edits.commands.push(EditCmd::ChangeIntersection { 127 i: self.id, 128 old: app.primary.map.get_i_edit(self.id), 129 new: EditIntersection::StopSign(ControlStopSign::new( 130 &app.primary.map, 131 self.id, 132 )), 133 }); 134 apply_map_edits(ctx, app, edits); 135 return Transition::Replace(StopSignEditor::new( 136 ctx, 137 app, 138 self.id, 139 self.mode.clone(), 140 )); 141 } 142 "close intersection for construction" => { 143 let cmd = EditCmd::ChangeIntersection { 144 i: self.id, 145 old: app.primary.map.get_i_edit(self.id), 146 new: EditIntersection::Closed, 147 }; 148 if let Some(err) = check_sidewalk_connectivity(ctx, app, cmd.clone()) { 149 return Transition::Push(err); 150 } else { 151 let mut edits = app.primary.map.get_edits().clone(); 152 edits.commands.push(cmd); 153 apply_map_edits(ctx, app, edits); 154 155 return Transition::Pop; 156 } 157 } 158 "convert to traffic signal" => { 159 let mut edits = app.primary.map.get_edits().clone(); 160 edits.commands.push(EditCmd::ChangeIntersection { 161 i: self.id, 162 old: app.primary.map.get_i_edit(self.id), 163 new: EditIntersection::TrafficSignal( 164 ControlTrafficSignal::new( 165 &app.primary.map, 166 self.id, 167 &mut Timer::throwaway(), 168 ) 169 .export(&app.primary.map), 170 ), 171 }); 172 apply_map_edits(ctx, app, edits); 173 return Transition::Replace(TrafficSignalEditor::new( 174 ctx, 175 app, 176 btreeset! {self.id}, 177 self.mode.clone(), 178 )); 179 } 180 _ => unreachable!(), 181 }, 182 _ => {} 183 } 184 Transition::Keep 185 } 186 draw(&self, g: &mut GfxCtx, app: &App)187 fn draw(&self, g: &mut GfxCtx, app: &App) { 188 let map = &app.primary.map; 189 let sign = map.get_stop_sign(self.id); 190 191 let mut batch = GeomBatch::new(); 192 193 for (r, (octagon, pole)) in &self.geom { 194 // The intersection will already draw enabled stop signs 195 if Some(*r) == self.selected_sign { 196 batch.push(app.cs.perma_selected_object, octagon.clone()); 197 if !sign.roads[r].must_stop { 198 batch.push(app.cs.stop_sign_pole.alpha(0.6), pole.clone()); 199 } 200 } else if !sign.roads[r].must_stop { 201 batch.push(app.cs.stop_sign.alpha(0.6), octagon.clone()); 202 batch.push(app.cs.stop_sign_pole.alpha(0.6), pole.clone()); 203 } 204 } 205 206 batch.draw(g); 207 208 self.panel.draw(g); 209 if let Some(r) = self.selected_sign { 210 let mut osd = Text::new(); 211 osd.add_appended(vec![ 212 Line("Stop sign for "), 213 Line( 214 app.primary 215 .map 216 .get_r(r) 217 .get_name(app.opts.language.as_ref()), 218 ) 219 .fg(app.cs.bottom_bar_name), 220 ]); 221 CommonState::draw_custom_osd(g, app, osd); 222 } else { 223 CommonState::draw_osd(g, app); 224 } 225 } 226 } 227