1use widgetry::mapspace::{World, WorldOutcome};
2use widgetry::tools::{ChooseSomething, PromptInput};
3use widgetry::{Choice, Color, DrawBaselayer, EventCtx, GfxCtx, Outcome, Panel, State, Widget};
4
5use crate::components::{AppwidePanel, BottomPanel, Mode};
6use crate::render::colors;
7use crate::{pages, render, App, Neighbourhood, NeighbourhoodID, Transition};
8
9pub struct PickArea {
10 appwide_panel: AppwidePanel,
11 bottom_panel: Panel,
12 world: World<NeighbourhoodID>,
13}
14
15impl PickArea {
16 pub fn new_state(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
17 map_gui::tools::update_url_map_name(app);
18
19 if let pages::EditMode::Shortcuts(ref mut maybe_focus) = app.session.edit_mode {
21 *maybe_focus = None;
22 }
23 if let pages::EditMode::FreehandFilters(_) = app.session.edit_mode {
24 app.session.edit_mode = pages::EditMode::Filters;
25 }
26
27 let world = make_world(ctx, app);
28
29 let appwide_panel = AppwidePanel::new(ctx, app, Mode::PickArea);
30 let bottom_panel = BottomPanel::new(
31 ctx,
32 &appwide_panel,
33 Widget::row(vec![
34 ctx.style()
35 .btn_outline
36 .text("Change draw style")
37 .build_def(ctx),
38 ctx.style()
39 .btn_outline
40 .text("Manage custom boundaries")
41 .build_def(ctx),
42 ]),
43 );
44
45 app.session
47 .layers
48 .event(ctx, &app.cs, Mode::PickArea, Some(&bottom_panel));
49
50 Box::new(Self {
51 appwide_panel,
52 bottom_panel,
53 world,
54 })
55 }
56}
57
58impl State<App> for PickArea {
59 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
60 if let Some(t) =
61 self.appwide_panel
62 .event(ctx, app, &crate::save::PreserveState::PickArea, help)
63 {
64 return t;
65 }
66 if let Some(t) =
67 app.session
68 .layers
69 .event(ctx, &app.cs, Mode::PickArea, Some(&self.bottom_panel))
70 {
71 return t;
72 }
73
74 if let Outcome::Clicked(x) = self.bottom_panel.event(ctx) {
75 if x == "Change draw style" {
76 return change_draw_style(ctx);
77 } else if x == "Manage custom boundaries" {
78 return manage_custom_boundary(ctx, app);
79 } else {
80 unreachable!()
81 }
82 }
83
84 if let WorldOutcome::ClickedObject(id) = self.world.event(ctx) {
85 return Transition::Push(pages::DesignLTN::new_state(ctx, app, id));
86 }
87
88 Transition::Keep
89 }
90
91 fn draw_baselayer(&self) -> DrawBaselayer {
92 DrawBaselayer::Custom
93 }
94
95 fn draw(&self, g: &mut GfxCtx, app: &App) {
96 app.draw_with_layering(g, |g| self.world.draw(g));
97
98 self.appwide_panel.draw(g);
99 self.bottom_panel.draw(g);
100 app.per_map.draw_major_road_labels.draw(g);
101 app.session.layers.draw(g, app);
102 app.per_map.draw_all_filters.draw(g);
103 app.per_map.draw_poi_icons.draw(g);
104 }
105
106 fn recreate(&mut self, ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
107 Self::new_state(ctx, app)
108 }
109}
110
111fn make_world(ctx: &mut EventCtx, app: &App) -> World<NeighbourhoodID> {
112 let mut world = World::new();
113 let map = &app.per_map.map;
114 ctx.loading_screen("render neighbourhoods", |ctx, timer| {
115 timer.start_iter(
116 "render neighbourhoods",
117 app.partitioning().all_neighbourhoods().len(),
118 );
119 for (id, info) in app.partitioning().all_neighbourhoods() {
120 timer.next();
121 match app.session.draw_neighbourhood_style {
122 PickAreaStyle::Simple => {
123 world
124 .add(*id)
125 .hitbox(info.block.polygon.clone())
126 .draw_color(Color::YELLOW.alpha(0.2))
127 .hover_alpha(0.5)
128 .clickable()
129 .build(ctx);
130 }
131 PickAreaStyle::Cells => {
132 let neighbourhood = Neighbourhood::new(app, *id);
133 let render_cells = render::RenderCells::new(map, &neighbourhood);
134 let hovered_batch = render_cells.draw_colored_areas();
135 world
136 .add(*id)
137 .hitbox(info.block.polygon.clone())
138 .drawn_in_master_batch()
139 .draw_hovered(hovered_batch)
140 .clickable()
141 .build(ctx);
142 }
143 PickAreaStyle::Quietness => {
144 let neighbourhood = Neighbourhood::new(app, *id);
145 let (quiet_streets, total_streets) = neighbourhood
146 .shortcuts
147 .quiet_and_total_streets(&neighbourhood);
148 let pct = if total_streets == 0 {
149 0.0
150 } else {
151 1.0 - (quiet_streets as f64 / total_streets as f64)
152 };
153 let color = app.cs.good_to_bad_red.eval(pct);
154 world
155 .add(*id)
156 .hitbox(info.block.polygon.clone())
157 .draw_color(color.alpha(0.5))
158 .hover_color(colors::HOVER)
159 .clickable()
160 .build(ctx);
161 }
162 }
163 }
164 });
165 world
166}
167
168#[derive(Clone, Copy, Debug, PartialEq)]
169pub enum PickAreaStyle {
170 Simple,
171 Cells,
172 Quietness,
173}
174
175fn help() -> Vec<&'static str> {
176 vec![
177 "Basic map navigation: click and drag to pan, swipe or scroll to zoom",
178 "",
179 "Click a neighbourhood to analyze it. You can adjust boundaries there.",
180 ]
181}
182
183fn change_draw_style(ctx: &mut EventCtx) -> Transition {
184 Transition::Push(ChooseSomething::new_state(
185 ctx,
186 "Change draw style",
187 vec![
188 Choice::new("default", PickAreaStyle::Simple),
189 Choice::new("show cells when you hover on an area", PickAreaStyle::Cells),
190 Choice::new(
191 "color areas by how much shortcutting they have",
192 PickAreaStyle::Quietness,
193 ),
194 ],
195 Box::new(move |choice, _, app| {
196 app.session.draw_neighbourhood_style = choice;
197 Transition::Multi(vec![Transition::Pop, Transition::Recreate])
198 }),
199 ))
200}
201
202fn manage_custom_boundary(ctx: &mut EventCtx, app: &App) -> Transition {
203 let mut choices = vec![Choice::new("Create new", None)];
204 for (id, custom) in &app.partitioning().custom_boundaries {
205 choices.push(Choice::new(&custom.name, Some(*id)));
206 }
207
208 Transition::Push(ChooseSomething::new_state(
209 ctx,
210 "Manage custom boundaries",
211 choices,
212 Box::new(move |choice, ctx, app| {
213 if let Some(id) = choice {
214 Transition::Clear(vec![pages::DesignLTN::new_state(ctx, app, id)])
215 } else {
216 Transition::Replace(PromptInput::new_state(
217 ctx,
218 "Name the custom boundary",
219 String::new(),
220 Box::new(|name, ctx, app| {
221 Transition::Clear(vec![pages::FreehandBoundary::blank(ctx, app, name)])
222 }),
223 ))
224 }
225 }),
226 ))
227}