1mod bike_network;
2mod explore;
3mod layers;
4mod predict;
5mod quick_sketch;
6mod trip;
7
8use geom::CornerRadii;
9use map_gui::tools::CityPicker;
10use widgetry::{
11 EventCtx, HorizontalAlignment, Key, Line, Panel, PanelDims, State, VerticalAlignment, Widget,
12 DEFAULT_CORNER_RADIUS,
13};
14
15pub use self::explore::ExploreMap;
16pub use self::layers::Layers;
17use crate::app::{App, Transition};
18pub use predict::ModeShiftData;
19pub use trip::RoutingPreferences;
20
21#[derive(PartialEq)]
23pub enum Tab {
24 Explore,
25 Trip,
26 AddLanes,
27 PredictImpact,
28}
29
30pub trait TakeLayers {
31 fn take_layers(self) -> Layers;
32}
33
34impl Tab {
35 pub fn make_left_panel(self, ctx: &mut EventCtx, app: &App, contents: Widget) -> Panel {
36 let mut contents = Some(contents.section(ctx));
40
41 let header = Widget::row(vec![
44 map_gui::tools::home_btn(ctx),
45 Line("Ungap the Map")
46 .small_heading()
47 .into_widget(ctx)
48 .centered_vert(),
49 map_gui::tools::change_map_btn(ctx, app)
50 .centered_vert()
51 .align_right(),
52 ]);
53
54 let mut build_tab = |(tab, image_path, tab_title, hotkey): (Tab, &str, &str, Key)| {
55 let mut btn = ctx
56 .style()
57 .btn_tab
58 .icon_text(image_path, tab_title)
59 .hotkey(hotkey);
60
61 if self == tab {
62 btn = btn
63 .corner_rounding(CornerRadii {
64 top_left: DEFAULT_CORNER_RADIUS,
65 top_right: DEFAULT_CORNER_RADIUS,
66 bottom_left: 0.0,
67 bottom_right: 0.0,
68 })
69 .disabled(true);
70 }
71
72 let btn_widget = btn.build_def(ctx).margin_left(1);
75 let mut tab_elements = vec![btn_widget];
76
77 if self == tab {
78 let mut contents = contents.take().unwrap();
79 contents = contents.corner_rounding(CornerRadii {
80 top_left: 0.0,
81 top_right: DEFAULT_CORNER_RADIUS,
82 bottom_left: DEFAULT_CORNER_RADIUS,
83 bottom_right: DEFAULT_CORNER_RADIUS,
84 });
85 tab_elements.push(contents);
86 }
87
88 Widget::custom_col(tab_elements)
89 };
90
91 let tabs = Widget::col(vec![
92 build_tab((
93 Tab::Explore,
94 "system/assets/tools/pan.svg",
95 "Explore",
96 Key::Num1,
97 )),
98 build_tab((
99 Tab::Trip,
100 "system/assets/tools/pin.svg",
101 "Your trip",
102 Key::Num2,
103 )),
104 build_tab((
105 Tab::AddLanes,
106 "system/assets/tools/pencil.svg",
107 "Propose new bike lanes",
108 Key::Num3,
109 )),
110 build_tab((
111 Tab::PredictImpact,
112 "system/assets/meters/trip_histogram.svg",
113 "Predict impact",
114 Key::Num4,
115 )),
116 ]);
117
118 let mut panel = Panel::new_builder(Widget::col(vec![header, tabs]))
119 .dims_width(PanelDims::ExactPixels(620.0))
122 .dims_height(PanelDims::ExactPercent(1.0))
123 .aligned(HorizontalAlignment::Left, VerticalAlignment::Top);
124 if self == Tab::Trip {
125 panel = panel.ignore_initial_events();
127 }
128 panel.build(ctx)
129 }
130
131 pub fn handle_action<T: TakeLayers + State<App>>(
132 self,
133 ctx: &mut EventCtx,
134 app: &mut App,
135 action: &str,
136 ) -> Option<Transition> {
137 match action {
138 "Home" => Some(Transition::Pop),
139 "change map" => {
140 Some(Transition::Push(CityPicker::new_state(
141 ctx,
142 app,
143 Box::new(move |ctx, app| {
144 let layers = Layers::new(ctx, app);
146 Transition::Multi(vec![
147 Transition::Pop,
148 Transition::Replace(match self {
149 Tab::Explore => ExploreMap::new_state(ctx, app, layers),
150 Tab::Trip => trip::TripPlanner::new_state(ctx, app, layers),
151 Tab::AddLanes => {
152 quick_sketch::QuickSketch::new_state(ctx, app, layers)
153 }
154 Tab::PredictImpact => {
155 predict::ShowGaps::new_state(ctx, app, layers)
156 }
157 }),
158 ])
159 }),
160 )))
161 }
162 "Explore" => Some(Transition::ConsumeState(Box::new(|state, ctx, app| {
163 let state = state.downcast::<T>().ok().unwrap();
164 vec![ExploreMap::new_state(ctx, app, state.take_layers())]
165 }))),
166 "Your trip" => Some(Transition::ConsumeState(Box::new(|state, ctx, app| {
167 let state = state.downcast::<T>().ok().unwrap();
168 vec![trip::TripPlanner::new_state(ctx, app, state.take_layers())]
169 }))),
170 "Propose new bike lanes" => {
171 app.primary.current_selection = None;
173 Some(Transition::ConsumeState(Box::new(|state, ctx, app| {
174 let state = state.downcast::<T>().ok().unwrap();
175 vec![quick_sketch::QuickSketch::new_state(
176 ctx,
177 app,
178 state.take_layers(),
179 )]
180 })))
181 }
182 "Predict impact" => Some(Transition::ConsumeState(Box::new(|state, ctx, app| {
183 let state = state.downcast::<T>().ok().unwrap();
184 vec![predict::ShowGaps::new_state(ctx, app, state.take_layers())]
185 }))),
186 _ => None,
187 }
188 }
189}