1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::collections::BTreeSet;

use map_gui::tools::CityPicker;
use map_gui::ID;
use widgetry::{
    Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, Outcome, Panel, State,
    TextExt, VerticalAlignment, Widget,
};

use super::{Neighborhood, Viewer};
use crate::app::{App, Transition};
use crate::common::intersections_from_roads;

pub struct BrowseNeighborhoods {
    panel: Panel,
    draw_neighborhoods: Drawable,
}

impl BrowseNeighborhoods {
    pub fn new_state(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
        let panel = Panel::new_builder(Widget::col(vec![
            Widget::row(vec![
                Line("LTN tool").small_heading().into_widget(ctx),
                map_gui::tools::change_map_btn(ctx, app)
                    .centered_vert()
                    .align_right(),
            ]),
            "Click a neighborhood".text_widget(ctx),
        ]))
        .aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
        .build(ctx);
        let draw_neighborhoods = calculate_neighborhoods(app).upload(ctx);
        Box::new(BrowseNeighborhoods {
            panel,
            draw_neighborhoods,
        })
    }
}

impl State<App> for BrowseNeighborhoods {
    fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
        ctx.canvas_movement();
        if ctx.redo_mouseover() {
            app.primary.current_selection =
                match app.mouseover_unzoomed_roads_and_intersections(ctx) {
                    x @ Some(ID::Road(_)) => x,
                    Some(ID::Lane(l)) => Some(ID::Road(l.road)),
                    _ => None,
                };
        }
        if let Some(ID::Road(r)) = app.primary.current_selection {
            if Neighborhood::is_interior_road(r, &app.primary.map) && ctx.normal_left_click() {
                return Transition::Replace(Viewer::start_from_road(ctx, app, r));
            }
        }

        if let Outcome::Clicked(x) = self.panel.event(ctx) {
            match x.as_ref() {
                "change map" => {
                    return Transition::Push(CityPicker::new_state(
                        ctx,
                        app,
                        Box::new(|ctx, app| {
                            Transition::Replace(BrowseNeighborhoods::new_state(ctx, app))
                        }),
                    ));
                }
                _ => unreachable!(),
            }
        }
        Transition::Keep
    }

    fn draw(&self, g: &mut GfxCtx, _: &App) {
        self.panel.draw(g);
        g.redraw(&self.draw_neighborhoods);
    }
}

fn calculate_neighborhoods(app: &App) -> GeomBatch {
    let map = &app.primary.map;
    let mut unvisited = BTreeSet::new();

    let mut batch = GeomBatch::new();
    let colors = [
        Color::BLUE,
        Color::ORANGE,
        Color::PURPLE,
        Color::RED,
        Color::GREEN,
        Color::CYAN,
    ];
    let mut num_neighborhoods = 0;

    for r in map.all_roads() {
        if Neighborhood::is_interior_road(r.id, map) {
            unvisited.insert(r.id);
        }
    }

    while !unvisited.is_empty() {
        let start = *unvisited.iter().next().unwrap();
        let neighborhood = Neighborhood::from_road(map, start);

        // TODO Either use that 4-color theorem and actually guarantee no adjacent same-color ones,
        // or change the style to have a clear outline around each
        let color = colors[num_neighborhoods % colors.len()];
        num_neighborhoods += 1;
        for i in intersections_from_roads(&neighborhood.interior, map) {
            batch.push(color, map.get_i(i).polygon.clone());
        }
        for r in neighborhood.interior {
            batch.push(color, map.get_r(r).get_thick_polygon());
            unvisited.remove(&r);
        }
    }

    batch
}