use abstio::MapName;
use abstutil::Timer;
use geom::{Duration, Pt2D, Time};
use map_gui::colors::ColorScheme;
use map_gui::load::MapLoader;
use map_gui::options::Options;
use map_gui::render::{DrawMap, DrawOptions};
use map_gui::tools::CameraState;
use map_gui::tools::DrawSimpleRoadLabels;
use map_gui::{AppLike, ID};
use map_model::{osm, CrossingType, FilterType, IntersectionID, Map, MapEdits, RoutingParams};
use widgetry::tools::URLManager;
use widgetry::{Canvas, Drawable, EventCtx, GfxCtx, SharedAppState, State, Warper};
use crate::logic::Partitioning;
use crate::{logic, pages, render, NeighbourhoodID};
pub type Transition = widgetry::Transition<App>;
pub struct App {
pub per_map: PerMap,
pub cs: ColorScheme,
pub opts: Options,
pub session: Session,
}
pub struct PerMap {
pub map: Map,
pub draw_map: DrawMap,
pub current_neighbourhood: Option<NeighbourhoodID>,
pub routing_params_before_changes: RoutingParams,
pub proposals: crate::save::Proposals,
pub impact: logic::Impact,
pub consultation: Option<NeighbourhoodID>,
pub consultation_id: Option<String>,
pub draw_all_filters: render::Toggle3Zoomed,
pub draw_major_road_labels: DrawSimpleRoadLabels,
pub draw_all_local_road_labels: Option<DrawSimpleRoadLabels>,
pub draw_poi_icons: Drawable,
pub draw_bus_routes: Drawable,
pub draw_turn_restrictions: Drawable,
pub current_trip_name: Option<String>,
}
impl PerMap {
fn new(
ctx: &mut EventCtx,
mut map: Map,
opts: &Options,
cs: &ColorScheme,
timer: &mut Timer,
) -> Self {
logic::transform_existing(&mut map, timer);
let proposals = crate::save::Proposals::new(&map, timer);
let routing_params_before_changes = map.routing_params_respecting_modal_filters();
let draw_all_filters = render::render_modal_filters(ctx, &map);
let draw_map = DrawMap::new(ctx, &map, opts, cs, timer);
let draw_poi_icons = render::render_poi_icons(ctx, &map);
let draw_bus_routes = render::render_bus_routes(ctx, &map);
let draw_turn_restrictions = render::render_turn_restrictions(ctx, &map);
let per_map = Self {
map,
draw_map,
current_neighbourhood: None,
routing_params_before_changes,
proposals,
impact: logic::Impact::empty(ctx),
consultation: None,
consultation_id: None,
draw_all_filters,
draw_major_road_labels: DrawSimpleRoadLabels::empty(ctx),
draw_all_local_road_labels: None,
draw_poi_icons,
draw_bus_routes,
draw_turn_restrictions,
current_trip_name: None,
};
if !CameraState::load(ctx, per_map.map.get_name()) {
ctx.canvas.cam_zoom = ctx.canvas.min_zoom();
ctx.canvas
.center_on_map_pt(per_map.map.get_boundary_polygon().center());
}
per_map
}
}
pub struct Session {
pub edit_mode: pages::EditMode,
pub filter_type: FilterType,
pub crossing_type: CrossingType,
pub draw_neighbourhood_style: pages::PickAreaStyle,
pub main_road_penalty: f64,
pub show_walking_cycling_routes: bool,
pub add_intermediate_blocks: bool,
pub layers: crate::components::Layers,
pub manage_proposals: bool,
}
impl AppLike for App {
#[inline]
fn map(&self) -> &Map {
&self.per_map.map
}
#[inline]
fn cs(&self) -> &ColorScheme {
&self.cs
}
#[inline]
fn mut_cs(&mut self) -> &mut ColorScheme {
&mut self.cs
}
#[inline]
fn draw_map(&self) -> &DrawMap {
&self.per_map.draw_map
}
#[inline]
fn mut_draw_map(&mut self) -> &mut DrawMap {
&mut self.per_map.draw_map
}
#[inline]
fn opts(&self) -> &Options {
&self.opts
}
#[inline]
fn mut_opts(&mut self) -> &mut Options {
&mut self.opts
}
fn map_switched(&mut self, ctx: &mut EventCtx, map: Map, timer: &mut Timer) {
CameraState::save(ctx.canvas, self.per_map.map.get_name());
self.per_map = PerMap::new(ctx, map, &self.opts, &self.cs, timer);
self.per_map.draw_major_road_labels =
DrawSimpleRoadLabels::only_major_roads(ctx, self, render::colors::MAIN_ROAD_LABEL);
self.opts.units.metric = self.per_map.map.get_name().city.uses_metric();
}
fn draw_with_opts(&self, g: &mut GfxCtx, _l: DrawOptions) {
self.draw_with_layering(g, |_| {});
}
fn make_warper(
&mut self,
ctx: &EventCtx,
pt: Pt2D,
target_cam_zoom: Option<f64>,
_: Option<ID>,
) -> Box<dyn State<App>> {
Box::new(SimpleWarper {
warper: Warper::new(ctx, pt, target_cam_zoom),
})
}
fn sim_time(&self) -> Time {
Time::START_OF_DAY
}
fn current_stage_and_remaining_time(&self, _: IntersectionID) -> (usize, Duration) {
(0, Duration::ZERO)
}
}
impl SharedAppState for App {
fn draw_default(&self, g: &mut GfxCtx) {
self.draw_with_opts(g, DrawOptions::new());
}
fn dump_before_abort(&self, canvas: &Canvas) {
CameraState::save(canvas, self.per_map.map.get_name());
}
fn before_quit(&self, canvas: &Canvas) {
CameraState::save(canvas, self.per_map.map.get_name());
}
fn free_memory(&mut self) {
self.per_map.draw_map.free_memory();
}
}
impl App {
pub fn new<F: 'static + Fn(&mut EventCtx, &mut App) -> Vec<Box<dyn State<App>>>>(
ctx: &mut EventCtx,
opts: Options,
map_name: MapName,
cam: Option<String>,
init_states: F,
) -> (App, Vec<Box<dyn State<App>>>) {
abstutil::logger::setup();
ctx.canvas.settings = opts.canvas_settings.clone();
let session = Session {
edit_mode: pages::EditMode::Filters,
filter_type: FilterType::WalkCycleOnly,
crossing_type: CrossingType::Unsignalized,
draw_neighbourhood_style: pages::PickAreaStyle::Simple,
main_road_penalty: 1.0,
show_walking_cycling_routes: false,
add_intermediate_blocks: true,
layers: crate::components::Layers::new(ctx),
manage_proposals: false,
};
let cs = ColorScheme::new(ctx, opts.color_scheme);
let app = App {
per_map: PerMap::new(
ctx,
Map::almost_blank(),
&opts,
&cs,
&mut Timer::throwaway(),
),
cs,
opts,
session,
};
let states = vec![MapLoader::new_state(
ctx,
&app,
map_name,
Box::new(move |ctx, app| {
URLManager::change_camera(ctx, cam.as_ref(), app.map().get_gps_bounds());
Transition::Clear(init_states(ctx, app))
}),
)];
(app, states)
}
pub fn draw_with_layering<F: Fn(&mut GfxCtx)>(&self, g: &mut GfxCtx, custom: F) {
g.clear(self.cs.void_background);
g.redraw(&self.per_map.draw_map.boundary_polygon);
g.redraw(&self.per_map.draw_map.draw_all_areas);
custom(g);
g.redraw(&self.per_map.draw_map.draw_all_unzoomed_parking_lots);
g.redraw(
&self
.per_map
.draw_map
.draw_all_unzoomed_roads_and_intersections,
);
g.redraw(&self.per_map.draw_map.draw_all_buildings);
g.redraw(&self.per_map.draw_map.draw_all_building_outlines);
}
pub fn partitioning(&self) -> &Partitioning {
&self.per_map.proposals.get_current().partitioning
}
pub fn calculate_draw_all_local_road_labels(&mut self, ctx: &mut EventCtx) {
if self.per_map.draw_all_local_road_labels.is_none() {
self.per_map.draw_all_local_road_labels = Some(DrawSimpleRoadLabels::new(
ctx,
self,
render::colors::LOCAL_ROAD_LABEL,
Box::new(|r| r.get_rank() == osm::RoadRank::Local && !r.is_light_rail()),
));
}
}
pub fn apply_edits(&mut self, edits: MapEdits) {
self.per_map.proposals.before_edit(edits.clone());
self.per_map
.map
.must_apply_edits(edits, &mut Timer::throwaway());
}
}
struct SimpleWarper {
warper: Warper,
}
impl State<App> for SimpleWarper {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
if self.warper.event(ctx) {
Transition::Keep
} else {
Transition::Pop
}
}
fn draw(&self, _: &mut GfxCtx, _: &App) {}
}