ltn/pages/
pick_area.rs

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        // Make sure we clear this state if we ever switch neighbourhoods
20        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        // Just force the layers panel to align above the bottom panel
46        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}