mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 03:35:51 +03:00
Convert the new bike routing tool to use World. #763
This one is the most complicated, and it's still not done, but it's not any buggier than the ad-hoc implementation. I still need to figure out how to merge the two worlds of waypoints and routes. This one also required a large, but mechanical, refactor to lift ToggleZoomed and the concept of unzoomed/zoomed from map_gui to widgetry.
This commit is contained in:
parent
43f8a6d1e7
commit
41465c341b
@ -372,11 +372,7 @@ impl HoverOnBuilding {
|
||||
pub fn key(ctx: &EventCtx, app: &App) -> Option<HoverKey> {
|
||||
match app.mouseover_unzoomed_buildings(ctx) {
|
||||
Some(ID::Building(b)) => {
|
||||
let scale_factor = if ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail {
|
||||
1.0
|
||||
} else {
|
||||
10.0
|
||||
};
|
||||
let scale_factor = if ctx.canvas.is_zoomed() { 1.0 } else { 10.0 };
|
||||
Some((b, scale_factor))
|
||||
}
|
||||
_ => None,
|
||||
|
@ -11,11 +11,12 @@ use geom::{Bounds, Circle, Distance, Duration, FindClosest, Polygon, Pt2D, Time}
|
||||
use map_gui::colors::ColorScheme;
|
||||
use map_gui::options::Options;
|
||||
use map_gui::render::{unzoomed_agent_radius, AgentCache, DrawMap, DrawOptions, Renderable};
|
||||
use map_gui::tools::{CameraState, ToggleZoomed};
|
||||
use map_gui::tools::CameraState;
|
||||
use map_gui::ID;
|
||||
use map_model::AreaType;
|
||||
use map_model::{BufferType, IntersectionID, LaneType, Map, Traversable};
|
||||
use sim::{AgentID, Analytics, Scenario, Sim, SimCallback, SimFlags, VehicleType};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{Cached, Canvas, EventCtx, GfxCtx, Prerender, SharedAppState, State};
|
||||
|
||||
use crate::challenges::HighScore;
|
||||
@ -122,8 +123,7 @@ impl App {
|
||||
g.clear(self.cs.void_background);
|
||||
g.redraw(&draw_map.boundary_polygon);
|
||||
|
||||
if g.canvas.cam_zoom < self.opts.min_zoom_for_detail {
|
||||
// Unzoomed mode
|
||||
if g.canvas.is_unzoomed() {
|
||||
let layers = show_objs.layers();
|
||||
if layers.show_areas {
|
||||
g.redraw(&draw_map.draw_all_areas);
|
||||
@ -255,7 +255,7 @@ impl App {
|
||||
unzoomed_roads_and_intersections: bool,
|
||||
unzoomed_buildings: bool,
|
||||
) -> Option<ID> {
|
||||
let unzoomed = ctx.canvas.cam_zoom < self.opts.min_zoom_for_detail;
|
||||
let unzoomed = ctx.canvas.is_unzoomed();
|
||||
|
||||
// Unzoomed mode. Ignore when debugging areas.
|
||||
if unzoomed && !(debug_mode || unzoomed_roads_and_intersections || unzoomed_buildings) {
|
||||
|
@ -1,8 +1,9 @@
|
||||
use geom::{Circle, Distance, FindClosest, Polygon};
|
||||
use geom::{Circle, Distance, FindClosest, Pt2D};
|
||||
use sim::TripEndpoint;
|
||||
use widgetry::mapspace::{ObjectID, World, WorldOutcome};
|
||||
use widgetry::{
|
||||
Color, ControlState, CornerRounding, DragDrop, Drawable, EventCtx, GeomBatch, GfxCtx, Image,
|
||||
Line, Outcome, StackAxis, Text, Widget,
|
||||
Color, ControlState, CornerRounding, DragDrop, EventCtx, GeomBatch, GfxCtx, Image, Key, Line,
|
||||
Outcome, RewriteColor, StackAxis, Text, Widget,
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
@ -11,29 +12,22 @@ use crate::app::App;
|
||||
/// Panel, since there's probably more stuff there too.
|
||||
pub struct InputWaypoints {
|
||||
waypoints: Vec<Waypoint>,
|
||||
draw_waypoints: Drawable,
|
||||
hovering_on_waypt: Option<usize>,
|
||||
draw_hover: Drawable,
|
||||
// TODO Invariant not captured by these separate fields: when dragging is true,
|
||||
// hovering_on_waypt is fixed.
|
||||
dragging: bool,
|
||||
world: World<WaypointID>,
|
||||
snap_to_endpts: FindClosest<TripEndpoint>,
|
||||
}
|
||||
|
||||
// TODO Maybe it's been a while and I've forgotten some UI patterns, but this is painfully manual.
|
||||
// I think we need a draggable map-space thing.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
struct WaypointID(usize);
|
||||
impl ObjectID for WaypointID {}
|
||||
|
||||
struct Waypoint {
|
||||
at: TripEndpoint,
|
||||
label: String,
|
||||
hitbox: Polygon,
|
||||
}
|
||||
|
||||
fn get_waypoint_text(idx: usize) -> char {
|
||||
char::from_u32('A' as u32 + idx as u32).unwrap()
|
||||
center: Pt2D,
|
||||
}
|
||||
|
||||
impl InputWaypoints {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App) -> InputWaypoints {
|
||||
pub fn new(app: &App) -> InputWaypoints {
|
||||
let map = &app.primary.map;
|
||||
let mut snap_to_endpts = FindClosest::new(map.get_bounds());
|
||||
for i in map.all_intersections() {
|
||||
@ -47,10 +41,7 @@ impl InputWaypoints {
|
||||
|
||||
InputWaypoints {
|
||||
waypoints: Vec::new(),
|
||||
draw_waypoints: Drawable::empty(ctx),
|
||||
hovering_on_waypt: None,
|
||||
draw_hover: Drawable::empty(ctx),
|
||||
dragging: false,
|
||||
world: World::bounded(map.get_bounds()),
|
||||
snap_to_endpts,
|
||||
}
|
||||
}
|
||||
@ -60,8 +51,7 @@ impl InputWaypoints {
|
||||
for at in waypoints {
|
||||
self.waypoints.push(Waypoint::new(app, at));
|
||||
}
|
||||
self.update_waypoints_drawable(ctx);
|
||||
self.update_hover(ctx);
|
||||
self.rebuild_world(ctx, app);
|
||||
}
|
||||
|
||||
pub fn get_panel_widget(&self, ctx: &mut EventCtx) -> Widget {
|
||||
@ -142,47 +132,41 @@ impl InputWaypoints {
|
||||
self.waypoints.iter().map(|w| w.at).collect()
|
||||
}
|
||||
|
||||
/// If the outcome from the panel isn't used by the caller, pass it along here. This handles
|
||||
/// calling `ctx.canvas_movement` when appropriate. When this returns true, something has
|
||||
/// changed, so the caller may want to update their view of the route and call
|
||||
/// `get_panel_widget` again.
|
||||
/// If the outcome from the panel isn't used by the caller, pass it along here. When this
|
||||
/// returns true, something has changed, so the caller may want to update their view of the
|
||||
/// route and call `get_panel_widget` again.
|
||||
pub fn event(&mut self, ctx: &mut EventCtx, app: &mut App, outcome: Outcome) -> bool {
|
||||
if self.dragging {
|
||||
if ctx.redo_mouseover() && self.update_dragging(ctx, app) == Some(true) {
|
||||
return true;
|
||||
match self.world.event(ctx) {
|
||||
WorldOutcome::ClickedFreeSpace(pt) => {
|
||||
if let Some((at, _)) = self.snap_to_endpts.closest_pt(pt, Distance::meters(30.0)) {
|
||||
self.waypoints.push(Waypoint::new(app, at));
|
||||
self.rebuild_world(ctx, app);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ctx.input.left_mouse_button_released() {
|
||||
self.dragging = false;
|
||||
self.update_hover(ctx);
|
||||
}
|
||||
} else {
|
||||
if ctx.redo_mouseover() {
|
||||
self.update_hover(ctx);
|
||||
}
|
||||
|
||||
if self.hovering_on_waypt.is_none() {
|
||||
ctx.canvas_movement();
|
||||
} else if let Some((_, dy)) = ctx.input.get_mouse_scroll() {
|
||||
// Zooming is OK, but can't start click and drag
|
||||
ctx.canvas.zoom(dy, ctx.canvas.get_cursor());
|
||||
}
|
||||
|
||||
if self.hovering_on_waypt.is_some() && ctx.input.left_mouse_button_pressed() {
|
||||
self.dragging = true;
|
||||
}
|
||||
|
||||
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
|
||||
if self.hovering_on_waypt.is_none() && ctx.normal_left_click() {
|
||||
if let Some((at, _)) =
|
||||
self.snap_to_endpts.closest_pt(pt, Distance::meters(30.0))
|
||||
{
|
||||
self.waypoints.push(Waypoint::new(app, at));
|
||||
self.update_waypoints_drawable(ctx);
|
||||
self.update_hover(ctx);
|
||||
WorldOutcome::Dragging {
|
||||
obj: WaypointID(idx),
|
||||
cursor,
|
||||
..
|
||||
} => {
|
||||
if let Some((at, _)) = self
|
||||
.snap_to_endpts
|
||||
.closest_pt(cursor, Distance::meters(30.0))
|
||||
{
|
||||
if self.waypoints[idx].at != at {
|
||||
self.waypoints[idx] = Waypoint::new(app, at);
|
||||
self.rebuild_world(ctx, app);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
WorldOutcome::Keypress("delete", WaypointID(idx)) => {
|
||||
self.waypoints.remove(idx);
|
||||
self.rebuild_world(ctx, app);
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match outcome {
|
||||
@ -190,7 +174,7 @@ impl InputWaypoints {
|
||||
if let Some(x) = x.strip_prefix("delete waypoint ") {
|
||||
let idx = x.parse::<usize>().unwrap();
|
||||
self.waypoints.remove(idx);
|
||||
self.update_waypoints_drawable(ctx);
|
||||
self.rebuild_world(ctx, app);
|
||||
return true;
|
||||
} else {
|
||||
panic!("Unknown InputWaypoints click {}", x);
|
||||
@ -210,8 +194,7 @@ impl InputWaypoints {
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw_waypoints);
|
||||
g.redraw(&self.draw_hover);
|
||||
self.world.draw(g);
|
||||
}
|
||||
|
||||
fn get_waypoint_color(&self, idx: usize) -> Color {
|
||||
@ -219,66 +202,38 @@ impl InputWaypoints {
|
||||
match idx {
|
||||
0 => Color::GREEN,
|
||||
idx if idx == total_waypoints - 1 => Color::RED,
|
||||
// technically this includes the case where idx >= total_waypoints which should hopefully never happen
|
||||
_ => [Color::BLUE, Color::ORANGE, Color::PURPLE][idx % 3],
|
||||
}
|
||||
}
|
||||
|
||||
fn update_waypoints_drawable(&mut self, ctx: &mut EventCtx) {
|
||||
let mut batch = GeomBatch::new();
|
||||
for (idx, waypt) in self.waypoints.iter().enumerate() {
|
||||
let color = self.get_waypoint_color(idx);
|
||||
let text = get_waypoint_text(idx);
|
||||
fn rebuild_world(&mut self, ctx: &mut EventCtx, app: &App) {
|
||||
let mut world = World::bounded(app.primary.map.get_bounds());
|
||||
|
||||
let mut geom = GeomBatch::new();
|
||||
geom.push(color, waypt.hitbox.clone());
|
||||
geom.append(
|
||||
Text::from(Line(format!("{}", text)).fg(Color::WHITE))
|
||||
for (idx, waypoint) in self.waypoints.iter().enumerate() {
|
||||
let hitbox = Circle::new(waypoint.center, Distance::meters(30.0)).to_polygon();
|
||||
let color = self.get_waypoint_color(idx);
|
||||
|
||||
let mut draw_normal = GeomBatch::new();
|
||||
draw_normal.push(color, hitbox.clone());
|
||||
draw_normal.append(
|
||||
Text::from(Line(get_waypoint_text(idx).to_string()).fg(Color::WHITE))
|
||||
.render(ctx)
|
||||
.centered_on(waypt.hitbox.center()),
|
||||
.centered_on(waypoint.center),
|
||||
);
|
||||
|
||||
batch.append(geom);
|
||||
}
|
||||
self.draw_waypoints = ctx.upload(batch);
|
||||
}
|
||||
|
||||
fn update_hover(&mut self, ctx: &EventCtx) {
|
||||
self.hovering_on_waypt = None;
|
||||
|
||||
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
|
||||
self.hovering_on_waypt = self
|
||||
.waypoints
|
||||
.iter()
|
||||
.position(|waypt| waypt.hitbox.contains_pt(pt));
|
||||
world
|
||||
.add(WaypointID(idx))
|
||||
.hitbox(hitbox.clone())
|
||||
.draw(draw_normal)
|
||||
.draw_hover_rewrite(RewriteColor::Change(color, Color::BLUE.alpha(0.5)))
|
||||
.hotkey(Key::Backspace, "delete")
|
||||
.draggable()
|
||||
.build(ctx);
|
||||
}
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
if let Some(idx) = self.hovering_on_waypt {
|
||||
batch.push(Color::BLUE.alpha(0.5), self.waypoints[idx].hitbox.clone());
|
||||
}
|
||||
self.draw_hover = ctx.upload(batch);
|
||||
}
|
||||
|
||||
// `Some(true)` means to update.
|
||||
fn update_dragging(&mut self, ctx: &mut EventCtx, app: &App) -> Option<bool> {
|
||||
let pt = ctx.canvas.get_cursor_in_map_space()?;
|
||||
let (at, _) = self.snap_to_endpts.closest_pt(pt, Distance::meters(30.0))?;
|
||||
|
||||
let mut changed = false;
|
||||
let idx = self.hovering_on_waypt.unwrap();
|
||||
if self.waypoints[idx].at != at {
|
||||
self.waypoints[idx] = Waypoint::new(app, at);
|
||||
self.update_waypoints_drawable(ctx);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
// Show where we're currently snapped
|
||||
batch.push(Color::BLUE.alpha(0.5), self.waypoints[idx].hitbox.clone());
|
||||
self.draw_hover = ctx.upload(batch);
|
||||
|
||||
Some(changed)
|
||||
world.initialize_hover(ctx);
|
||||
world.rebuilt_during_drag(&self.world);
|
||||
self.world = world;
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,8 +251,10 @@ impl Waypoint {
|
||||
}
|
||||
TripEndpoint::SuddenlyAppear(pos) => (pos.pt(map), pos.to_string()),
|
||||
};
|
||||
|
||||
let hitbox = Circle::new(center, Distance::meters(30.0)).to_polygon();
|
||||
Waypoint { at, label, hitbox }
|
||||
Waypoint { at, label, center }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_waypoint_text(idx: usize) -> char {
|
||||
char::from_u32('A' as u32 + idx as u32).unwrap()
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use map_gui::tools::{ColorDiscrete, ToggleZoomed};
|
||||
use map_gui::tools::ColorDiscrete;
|
||||
use map_model::{connectivity, LaneID, Map, PathConstraints};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Choice, Color, EventCtx, GfxCtx, HorizontalAlignment, Line, Outcome, Panel, State, TextExt,
|
||||
VerticalAlignment, Widget,
|
||||
@ -104,8 +105,8 @@ impl State<App> for Floodfiller {
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.draw.draw(g, app);
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.draw.draw(g);
|
||||
self.panel.draw(g);
|
||||
}
|
||||
}
|
||||
|
@ -944,7 +944,7 @@ impl ScreenshotTest {
|
||||
// Taking screenshots messes with options and doesn't restore them after. It's expected
|
||||
// whoever's taking screenshots (just Dustin so far) will just quit after taking them.
|
||||
app.change_color_scheme(ctx, ColorSchemeChoice::DayMode);
|
||||
app.opts.min_zoom_for_detail = 0.0;
|
||||
ctx.canvas.settings.min_zoom_for_detail = 0.0;
|
||||
MapLoader::new_state(
|
||||
ctx,
|
||||
app,
|
||||
|
@ -1,7 +1,8 @@
|
||||
use abstutil::Counter;
|
||||
use map_gui::tools::{ColorLegend, ColorNetwork, ToggleZoomed};
|
||||
use map_gui::tools::{ColorLegend, ColorNetwork};
|
||||
use map_gui::ID;
|
||||
use map_model::{IntersectionID, PathStep, RoadID, Traversable};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Color, EventCtx, GfxCtx, HorizontalAlignment, Line, Outcome, Panel, State, Text,
|
||||
VerticalAlignment, Widget,
|
||||
@ -115,7 +116,7 @@ impl State<App> for PathCounter {
|
||||
self.panel.draw(g);
|
||||
CommonState::draw_osd(g, app);
|
||||
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
|
||||
if let Some(ref txt) = self.tooltip {
|
||||
g.draw_mouse_tooltip(txt.clone());
|
||||
|
@ -3,13 +3,14 @@ use std::collections::HashMap;
|
||||
use abstutil::{prettyprint_usize, Counter, Timer};
|
||||
use geom::{Duration, Polygon};
|
||||
use map_gui::colors::ColorSchemeChoice;
|
||||
use map_gui::tools::{ColorNetwork, ToggleZoomed};
|
||||
use map_gui::tools::ColorNetwork;
|
||||
use map_gui::{AppLike, ID};
|
||||
use map_model::{
|
||||
DirectedRoadID, Direction, PathRequest, RoadID, RoutingParams, Traversable,
|
||||
NORMAL_LANE_THICKNESS,
|
||||
};
|
||||
use sim::{TripEndpoint, TripMode};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
|
||||
RoundedF64, Spinner, State, Text, TextExt, TextSpan, VerticalAlignment, Widget,
|
||||
@ -466,7 +467,7 @@ impl State<App> for AllRoutesExplorer {
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.panel.draw(g);
|
||||
CommonState::draw_osd(g, app);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
if let Some(ref txt) = self.tooltip {
|
||||
g.draw_mouse_tooltip(txt.clone());
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use abstutil::{prettyprint_usize, Counter};
|
||||
use collisions::{CollisionDataset, Severity};
|
||||
use geom::{Circle, Distance, Duration, FindClosest, Polygon, Time};
|
||||
use map_gui::tools::{ColorNetwork, ToggleZoomed};
|
||||
use map_gui::tools::ColorNetwork;
|
||||
use map_gui::ID;
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, Outcome,
|
||||
Panel, Slider, State, Text, TextExt, Toggle, VerticalAlignment, Widget,
|
||||
@ -296,10 +297,10 @@ impl State<App> for CollisionsViewer {
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
match self.dataviz {
|
||||
Dataviz::Aggregated { ref draw } => {
|
||||
draw.draw(g, app);
|
||||
draw.draw(g);
|
||||
}
|
||||
Dataviz::Individual {
|
||||
ref draw_all_circles,
|
||||
|
@ -118,6 +118,7 @@ impl State<App> for PolygonEditor {
|
||||
obj: Obj::Point(idx),
|
||||
dx,
|
||||
dy,
|
||||
..
|
||||
} => {
|
||||
self.points[idx] = self.points[idx].offset(dx, dy);
|
||||
self.rebuild_world(ctx, app);
|
||||
@ -126,6 +127,7 @@ impl State<App> for PolygonEditor {
|
||||
obj: Obj::Polygon,
|
||||
dx,
|
||||
dy,
|
||||
..
|
||||
} => {
|
||||
for pt in &mut self.points {
|
||||
*pt = pt.offset(dx, dy);
|
||||
|
@ -1,6 +1,7 @@
|
||||
use abstutil::prettyprint_usize;
|
||||
use map_gui::tools::{ColorDiscrete, ToggleZoomed};
|
||||
use map_gui::tools::ColorDiscrete;
|
||||
use sim::Scenario;
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
|
||||
VerticalAlignment, Widget,
|
||||
@ -112,7 +113,7 @@ impl State<App> for ScenarioManager {
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
self.panel.draw(g);
|
||||
CommonState::draw_osd(g, app);
|
||||
}
|
||||
|
@ -150,6 +150,7 @@ impl State<App> for StoryMapEditor {
|
||||
obj: MarkerID(idx),
|
||||
dx,
|
||||
dy,
|
||||
..
|
||||
} => {
|
||||
for pt in &mut self.story.markers[idx].pts {
|
||||
*pt = pt.offset(dx, dy);
|
||||
|
@ -4,9 +4,10 @@ use abstutil::{prettyprint_usize, Timer};
|
||||
use geom::Speed;
|
||||
use map_gui::options::OptionsPanel;
|
||||
use map_gui::render::DrawMap;
|
||||
use map_gui::tools::{grey_out_map, ChooseSomething, ColorLegend, PopupMsg, ToggleZoomed};
|
||||
use map_gui::tools::{grey_out_map, ChooseSomething, ColorLegend, PopupMsg};
|
||||
use map_gui::ID;
|
||||
use map_model::{EditCmd, IntersectionID, LaneID, MapEdits};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
lctrl, Choice, Color, ControlState, EventCtx, GfxCtx, HorizontalAlignment, Image, Key, Line,
|
||||
Menu, Outcome, Panel, State, Text, TextBox, TextExt, VerticalAlignment, Widget,
|
||||
@ -330,7 +331,7 @@ impl State<App> for EditMode {
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
if ctx.canvas.is_unzoomed() {
|
||||
if let Some(id) = app.primary.current_selection.clone() {
|
||||
if app.per_obj.left_click(ctx, "edit this") {
|
||||
return Transition::Push(Warping::new_state(
|
||||
@ -369,7 +370,7 @@ impl State<App> for EditMode {
|
||||
self.tool_panel.draw(g);
|
||||
self.top_center.draw(g);
|
||||
self.changelist.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
CommonState::draw_osd(g, app);
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,10 @@ use std::collections::BTreeSet;
|
||||
use enumset::EnumSet;
|
||||
use maplit::btreeset;
|
||||
|
||||
use map_gui::tools::{ColorDiscrete, ToggleZoomed};
|
||||
use map_gui::tools::ColorDiscrete;
|
||||
use map_model::{AccessRestrictions, PathConstraints, RoadID};
|
||||
use sim::TripMode;
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
|
||||
VerticalAlignment, Widget,
|
||||
@ -157,7 +158,7 @@ impl State<App> for ZoneEditor {
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
// TODO The currently selected road is covered up pretty badly
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
self.panel.draw(g);
|
||||
self.selector.draw(g, app, false);
|
||||
CommonState::draw_osd(g, app);
|
||||
|
@ -3,13 +3,14 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
pub use trip::OpenTrip;
|
||||
|
||||
use geom::{Circle, Distance, Polygon, Time};
|
||||
use map_gui::tools::{open_browser, ToggleZoomed, ToggleZoomedBuilder};
|
||||
use map_gui::tools::open_browser;
|
||||
use map_gui::ID;
|
||||
use map_model::{AreaID, BuildingID, BusRouteID, BusStopID, IntersectionID, LaneID, ParkingLotID};
|
||||
use sim::{
|
||||
AgentID, AgentType, Analytics, CarID, ParkingSpot, PedestrianID, PersonID, PersonState, TripID,
|
||||
VehicleType,
|
||||
};
|
||||
use widgetry::mapspace::{ToggleZoomed, ToggleZoomedBuilder};
|
||||
use widgetry::{
|
||||
EventCtx, GfxCtx, Key, Line, LinePlot, Outcome, Panel, PlotOptions, Series, Text, TextExt,
|
||||
Toggle, Widget,
|
||||
@ -631,9 +632,9 @@ impl InfoPanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
pub fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw_extra.draw(g, app);
|
||||
self.draw_extra.draw(g);
|
||||
if let Some(pt) = g.canvas.get_cursor_in_map_space() {
|
||||
for (poly, txt) in &self.tooltips {
|
||||
if poly.contains_pt(pt) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
use geom::{Angle, Distance, FindClosest, PolyLine, Polygon, Pt2D};
|
||||
use map_gui::tools::{ColorDiscrete, ColorScale, Grid, ToggleZoomed};
|
||||
use map_gui::tools::{ColorDiscrete, ColorScale, Grid};
|
||||
use map_gui::ID;
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{Color, EventCtx, GeomBatch, GfxCtx, Panel, Text, TextExt, Widget};
|
||||
|
||||
use crate::app::App;
|
||||
@ -29,9 +30,9 @@ impl Layer for SteepStreets {
|
||||
|
||||
<dyn Layer>::simple_event(ctx, &mut self.panel)
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
if let Some(ref txt) = self.tooltip {
|
||||
g.draw_mouse_tooltip(txt.clone());
|
||||
}
|
||||
@ -168,7 +169,7 @@ impl Layer for ElevationContours {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Option<LayerOutcome> {
|
||||
if ctx.redo_mouseover() {
|
||||
self.tooltip = None;
|
||||
if ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
if ctx.canvas.is_unzoomed() {
|
||||
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
|
||||
if let Some((elevation, _)) = self
|
||||
.closest_elevation
|
||||
@ -185,9 +186,9 @@ impl Layer for ElevationContours {
|
||||
|
||||
<dyn Layer>::simple_event(ctx, &mut self.panel)
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
if let Some(ref txt) = self.tooltip {
|
||||
g.draw_mouse_tooltip(txt.clone());
|
||||
}
|
||||
|
@ -2,10 +2,11 @@ use maplit::btreeset;
|
||||
|
||||
use abstutil::{prettyprint_usize, Counter};
|
||||
use geom::{Distance, Time};
|
||||
use map_gui::tools::{ColorDiscrete, ColorLegend, ColorNetwork, ToggleZoomed};
|
||||
use map_gui::tools::{ColorDiscrete, ColorLegend, ColorNetwork};
|
||||
use map_gui::ID;
|
||||
use map_model::{AmenityType, LaneType};
|
||||
use sim::AgentType;
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{Color, EventCtx, GfxCtx, Line, Panel, Text, Widget};
|
||||
|
||||
use crate::app::App;
|
||||
@ -30,7 +31,7 @@ impl Layer for BikeActivity {
|
||||
}
|
||||
|
||||
// Show a tooltip with count, only when unzoomed
|
||||
if ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
if ctx.canvas.is_unzoomed() {
|
||||
if ctx.redo_mouseover() || recalc_tooltip {
|
||||
self.tooltip = None;
|
||||
if let Some(ID::Road(r)) = app.mouseover_unzoomed_roads_and_intersections(ctx) {
|
||||
@ -51,9 +52,9 @@ impl Layer for BikeActivity {
|
||||
|
||||
<dyn Layer>::simple_event(ctx, &mut self.panel)
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
if let Some(ref txt) = self.tooltip {
|
||||
g.draw_mouse_tooltip(txt.clone());
|
||||
}
|
||||
@ -174,9 +175,9 @@ impl Layer for Static {
|
||||
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Option<LayerOutcome> {
|
||||
<dyn Layer>::simple_event(ctx, &mut self.panel)
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
|
@ -2,8 +2,9 @@ use std::collections::HashSet;
|
||||
|
||||
use abstutil::prettyprint_usize;
|
||||
use geom::{Circle, Distance, Pt2D, Time};
|
||||
use map_gui::tools::{make_heatmap, HeatmapOptions, ToggleZoomed};
|
||||
use map_gui::tools::{make_heatmap, HeatmapOptions};
|
||||
use sim::PersonState;
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Choice, Color, EventCtx, GfxCtx, Line, Outcome, Panel, Text, TextExt, Toggle, Widget,
|
||||
};
|
||||
@ -47,9 +48,9 @@ impl Layer for Pandemic {
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
|
@ -3,9 +3,10 @@ use std::collections::BTreeSet;
|
||||
use abstutil::{prettyprint_usize, Counter};
|
||||
use geom::{Circle, Distance, Duration, Pt2D, Time};
|
||||
use map_gui::render::unzoomed_agent_radius;
|
||||
use map_gui::tools::{ColorLegend, ColorNetwork, ToggleZoomed};
|
||||
use map_gui::tools::{ColorLegend, ColorNetwork};
|
||||
use map_model::{BuildingID, OffstreetParking, ParkingLotID, PathRequest, RoadID};
|
||||
use sim::{ParkingSpot, VehicleType};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{EventCtx, GfxCtx, Line, Outcome, Panel, Text, Toggle, Widget};
|
||||
|
||||
use crate::app::App;
|
||||
@ -61,9 +62,9 @@ impl Layer for Occupancy {
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
@ -315,9 +316,9 @@ impl Layer for Efficiency {
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
|
@ -2,8 +2,9 @@ use std::collections::HashSet;
|
||||
|
||||
use abstutil::prettyprint_usize;
|
||||
use geom::{Circle, Distance, Pt2D, Time};
|
||||
use map_gui::tools::{make_heatmap, HeatmapOptions, ToggleZoomed};
|
||||
use map_gui::tools::{make_heatmap, HeatmapOptions};
|
||||
use sim::PersonState;
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{Color, EventCtx, GfxCtx, Image, Line, Outcome, Panel, Toggle, Widget};
|
||||
|
||||
use crate::app::App;
|
||||
@ -45,9 +46,9 @@ impl Layer for PopulationMap {
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
|
@ -2,8 +2,9 @@ use std::collections::BTreeSet;
|
||||
|
||||
use abstutil::prettyprint_usize;
|
||||
use geom::{Circle, Distance, Pt2D, Time};
|
||||
use map_gui::tools::{make_heatmap, HeatmapOptions, ToggleZoomed};
|
||||
use map_gui::tools::{make_heatmap, HeatmapOptions};
|
||||
use sim::{Problem, TripInfo, TripMode};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Color, EventCtx, GfxCtx, Line, Outcome, Panel, Slider, Text, TextExt, Toggle, Widget,
|
||||
};
|
||||
@ -46,9 +47,9 @@ impl Layer for ProblemMap {
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
|
@ -5,10 +5,11 @@ use maplit::btreeset;
|
||||
use abstutil::{prettyprint_usize, Counter};
|
||||
use geom::{Circle, Distance, Duration, Percent, Polygon, Pt2D, Time};
|
||||
use map_gui::render::unzoomed_agent_radius;
|
||||
use map_gui::tools::{ColorLegend, ColorNetwork, DivergingScale, ToggleZoomed};
|
||||
use map_gui::tools::{ColorLegend, ColorNetwork, DivergingScale};
|
||||
use map_gui::ID;
|
||||
use map_model::{IntersectionID, Map, Traversable};
|
||||
use sim::{AgentType, VehicleType};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{Color, EventCtx, GfxCtx, Line, Outcome, Panel, Text, TextExt, Toggle, Widget};
|
||||
|
||||
use crate::app::App;
|
||||
@ -31,9 +32,9 @@ impl Layer for Backpressure {
|
||||
|
||||
<dyn Layer>::simple_event(ctx, &mut self.panel)
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
@ -106,7 +107,7 @@ impl Layer for Throughput {
|
||||
}
|
||||
|
||||
// Show a tooltip with count, only when unzoomed
|
||||
if ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
if ctx.canvas.is_unzoomed() {
|
||||
if ctx.redo_mouseover() || recalc_tooltip {
|
||||
self.tooltip = None;
|
||||
match app.mouseover_unzoomed_roads_and_intersections(ctx) {
|
||||
@ -174,9 +175,9 @@ impl Layer for Throughput {
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
if let Some(ref txt) = self.tooltip {
|
||||
g.draw_mouse_tooltip(txt.clone());
|
||||
}
|
||||
@ -253,7 +254,7 @@ impl Layer for CompareThroughput {
|
||||
}
|
||||
|
||||
// Show a tooltip with count, only when unzoomed
|
||||
if ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
if ctx.canvas.is_unzoomed() {
|
||||
if ctx.redo_mouseover() || recalc_tooltip {
|
||||
self.tooltip = None;
|
||||
match app.mouseover_unzoomed_roads_and_intersections(ctx) {
|
||||
@ -315,9 +316,9 @@ impl Layer for CompareThroughput {
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
if let Some(ref txt) = self.tooltip {
|
||||
g.draw_mouse_tooltip(txt.clone());
|
||||
}
|
||||
@ -411,9 +412,9 @@ impl Layer for TrafficJams {
|
||||
|
||||
<dyn Layer>::simple_event(ctx, &mut self.panel)
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
@ -546,9 +547,9 @@ impl Layer for Delay {
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
|
@ -1,5 +1,6 @@
|
||||
use map_gui::tools::{ColorDiscrete, ToggleZoomed};
|
||||
use map_gui::tools::ColorDiscrete;
|
||||
use map_model::{PathConstraints, PathStep};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{EventCtx, GfxCtx, Outcome, Panel, Toggle, Widget};
|
||||
|
||||
use crate::app::App;
|
||||
@ -35,9 +36,9 @@ impl Layer for TransitNetwork {
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g, app);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
|
@ -85,9 +85,9 @@ fn run(mut settings: Settings) {
|
||||
ltn: args.enabled("--ltn"),
|
||||
};
|
||||
|
||||
settings = settings.canvas_settings(setup.opts.canvas_settings.clone());
|
||||
setup.opts.toggle_day_night_colors = true;
|
||||
setup.opts.update_from_args(&mut args);
|
||||
settings = settings.canvas_settings(setup.opts.canvas_settings.clone());
|
||||
|
||||
if args.enabled("--dump_raw_events") {
|
||||
settings = settings.dump_raw_events();
|
||||
|
@ -1,9 +1,10 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use geom::{Distance, Line};
|
||||
use map_gui::tools::{CityPicker, ColorDiscrete, ToggleZoomed};
|
||||
use map_gui::tools::{CityPicker, ColorDiscrete};
|
||||
use map_gui::ID;
|
||||
use map_model::{IntersectionID, Map, Road, RoadID};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
|
||||
State, Text, TextExt, VerticalAlignment, Widget,
|
||||
@ -216,7 +217,7 @@ impl State<App> for Viewer {
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw_neighborhood.draw(g, app);
|
||||
self.draw_neighborhood.draw(g);
|
||||
g.redraw(&self.draw_dynamic_stuff);
|
||||
|
||||
if let Some(ID::Road(r)) = app.primary.current_selection {
|
||||
|
@ -101,7 +101,7 @@ impl TrafficSignalDemand {
|
||||
}
|
||||
draw_all.extend(Color::WHITE, outlines);
|
||||
}
|
||||
world.draw_master_batch(ctx.upload(draw_all));
|
||||
world.draw_master_batch(ctx, draw_all);
|
||||
|
||||
world.initialize_hover(ctx);
|
||||
self.world = world;
|
||||
|
@ -411,11 +411,7 @@ impl GameplayState for Tutorial {
|
||||
// Special things
|
||||
if tut.interaction() == Task::Camera {
|
||||
let fire = GeomBatch::load_svg(g, "system/assets/tools/fire.svg")
|
||||
.scale(if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
0.2
|
||||
} else {
|
||||
0.1
|
||||
})
|
||||
.scale(if g.canvas.is_unzoomed() { 0.2 } else { 0.1 })
|
||||
.autocrop()
|
||||
.centered_on(app.primary.map.get_b(tut.fire_station).polygon.polylabel());
|
||||
let offset = -fire.get_dims().height / 2.0;
|
||||
|
@ -308,7 +308,7 @@ fn make_tool_panel(ctx: &mut EventCtx, app: &App) -> Widget {
|
||||
.padding(8);
|
||||
|
||||
Widget::col(vec![
|
||||
(if ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail {
|
||||
(if ctx.canvas.is_zoomed() {
|
||||
buttons
|
||||
.clone()
|
||||
.image_path("system/assets/minimap/zoom_out_fully.svg")
|
||||
|
@ -33,7 +33,7 @@ impl RoutePreview {
|
||||
.and_then(|id| id.agent_id())
|
||||
{
|
||||
let now = app.primary.sim.time();
|
||||
let zoomed = ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail;
|
||||
let zoomed = ctx.canvas.is_zoomed();
|
||||
if self
|
||||
.preview
|
||||
.as_ref()
|
||||
|
@ -161,7 +161,7 @@ impl State<App> for SandboxMode {
|
||||
// We need to recalculate unzoomed agent mouseover when the mouse is still and time passes
|
||||
// (since something could move beneath the cursor), or when the mouse moves.
|
||||
if app.primary.current_selection.is_none()
|
||||
&& ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail
|
||||
&& ctx.canvas.is_unzoomed()
|
||||
&& (ctx.redo_mouseover()
|
||||
|| self
|
||||
.recalc_unzoomed_agent
|
||||
|
@ -66,7 +66,7 @@ impl State<App> for ExploreMap {
|
||||
}
|
||||
|
||||
// Only when zoomed in, click to edit a road in detail
|
||||
if ctx.canvas.cam_zoom >= app.opts.min_zoom_for_detail {
|
||||
if ctx.canvas.is_zoomed() {
|
||||
if ctx.redo_mouseover() {
|
||||
app.primary.current_selection =
|
||||
match app.mouseover_unzoomed_roads_and_intersections(ctx) {
|
||||
|
@ -67,7 +67,7 @@ impl Layers {
|
||||
if ctx.redo_mouseover() && self.elevation && !self.minimized {
|
||||
let mut label = Text::new().into_widget(ctx);
|
||||
|
||||
if ctx.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
if ctx.canvas.is_unzoomed() {
|
||||
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
|
||||
if let Some((elevation, _)) = app
|
||||
.session
|
||||
@ -193,7 +193,7 @@ impl Layers {
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.panel.draw(g);
|
||||
if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
if g.canvas.is_unzoomed() {
|
||||
g.redraw(&self.fade_map);
|
||||
|
||||
let mut draw_bike_layer = true;
|
||||
@ -222,7 +222,7 @@ impl Layers {
|
||||
|
||||
if self.elevation {
|
||||
if let Some((_, ref draw)) = app.session.elevation_contours.value() {
|
||||
draw.draw(g, app);
|
||||
draw.draw(g);
|
||||
}
|
||||
}
|
||||
if let Some(ref draw) = self.steep_streets {
|
||||
|
@ -50,7 +50,7 @@ impl MagnifyingGlass {
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
if g.canvas.cam_zoom >= app.opts.min_zoom_for_detail {
|
||||
if g.canvas.is_zoomed() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,11 @@ use std::collections::HashSet;
|
||||
use abstutil::{prettyprint_usize, Counter, Timer};
|
||||
use geom::{Distance, Duration, Polygon};
|
||||
use map_gui::load::FileLoader;
|
||||
use map_gui::tools::{ColorNetwork, ToggleZoomed};
|
||||
use map_gui::tools::ColorNetwork;
|
||||
use map_gui::ID;
|
||||
use map_model::{PathRequest, PathStepV2, RoadID};
|
||||
use sim::{Scenario, TripEndpoint, TripMode};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
Color, EventCtx, GeomBatch, GfxCtx, Line, Outcome, Panel, Spinner, State, Text, TextExt, Widget,
|
||||
};
|
||||
@ -117,7 +118,7 @@ impl State<App> for ShowGaps {
|
||||
self.layers.draw(g, app);
|
||||
|
||||
let data = app.session.mode_shift.value().unwrap();
|
||||
data.gaps.draw.draw(g, app);
|
||||
data.gaps.draw.draw(g);
|
||||
if let Some(ref txt) = self.tooltip {
|
||||
g.draw_mouse_tooltip(txt.clone());
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
use map_model::RoutingParams;
|
||||
use widgetry::mapspace::{ObjectID, World, WorldOutcome};
|
||||
use widgetry::{Choice, EventCtx, GfxCtx, Outcome, Panel, State, TextExt, Widget};
|
||||
|
||||
use self::results::{AltRouteResults, RouteResults};
|
||||
use self::results::RouteDetails;
|
||||
use crate::app::{App, Transition};
|
||||
use crate::common::InputWaypoints;
|
||||
use crate::ungap::{Layers, Tab, TakeLayers};
|
||||
@ -15,10 +16,11 @@ pub struct RoutePlanner {
|
||||
|
||||
input_panel: Panel,
|
||||
waypoints: InputWaypoints,
|
||||
main_route: RouteResults,
|
||||
main_route: RouteDetails,
|
||||
files: files::RouteManagement,
|
||||
|
||||
alt_routes: Vec<AltRouteResults>,
|
||||
// TODO We really only need to store preferences and stats, but...
|
||||
alt_routes: Vec<RouteDetails>,
|
||||
world: World<RouteID>,
|
||||
}
|
||||
|
||||
impl TakeLayers for RoutePlanner {
|
||||
@ -27,6 +29,13 @@ impl TakeLayers for RoutePlanner {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
enum RouteID {
|
||||
Main,
|
||||
Alt(usize),
|
||||
}
|
||||
impl ObjectID for RouteID {}
|
||||
|
||||
impl RoutePlanner {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App, layers: Layers) -> Box<dyn State<App>> {
|
||||
let mut rp = RoutePlanner {
|
||||
@ -34,19 +43,29 @@ impl RoutePlanner {
|
||||
once: true,
|
||||
|
||||
input_panel: Panel::empty(ctx),
|
||||
waypoints: InputWaypoints::new(ctx, app),
|
||||
main_route: RouteResults::main_route(ctx, app, Vec::new()),
|
||||
waypoints: InputWaypoints::new(app),
|
||||
main_route: RouteDetails::main_route(ctx, app, Vec::new()).details,
|
||||
files: files::RouteManagement::new(app),
|
||||
|
||||
alt_routes: Vec::new(),
|
||||
world: World::bounded(app.primary.map.get_bounds()),
|
||||
};
|
||||
rp.update_input_panel(ctx, app);
|
||||
rp.recalculate_routes(ctx, app);
|
||||
Box::new(rp)
|
||||
}
|
||||
|
||||
// Use the current session settings to determine "main" and alts
|
||||
fn recalculate_routes(&mut self, ctx: &mut EventCtx, app: &App) {
|
||||
// Use the current session settings to determine "main" and alts
|
||||
self.main_route = RouteResults::main_route(ctx, app, self.waypoints.get_waypoints());
|
||||
let mut world = World::bounded(app.primary.map.get_bounds());
|
||||
|
||||
let main_route = RouteDetails::main_route(ctx, app, self.waypoints.get_waypoints());
|
||||
self.main_route = main_route.details;
|
||||
world
|
||||
.add(RouteID::Main)
|
||||
.hitbox(main_route.hitbox)
|
||||
.draw(main_route.draw)
|
||||
.build(ctx);
|
||||
// This doesn't depend on the alt routes, so just do it here
|
||||
self.update_input_panel(ctx, app, main_route.details_widget);
|
||||
|
||||
self.alt_routes.clear();
|
||||
// Just a few fixed variations... all 9 combos seems overwhelming
|
||||
@ -68,7 +87,7 @@ impl RoutePlanner {
|
||||
if app.session.routing_preferences == preferences {
|
||||
continue;
|
||||
}
|
||||
let alt = AltRouteResults::new(
|
||||
let mut alt = RouteDetails::alt_route(
|
||||
ctx,
|
||||
app,
|
||||
self.waypoints.get_waypoints(),
|
||||
@ -76,18 +95,26 @@ impl RoutePlanner {
|
||||
preferences,
|
||||
);
|
||||
// Dedupe equivalent routes based on their stats, which is usually detailed enough
|
||||
if alt.results.stats != self.main_route.stats
|
||||
&& self
|
||||
.alt_routes
|
||||
.iter()
|
||||
.all(|x| alt.results.stats != x.results.stats)
|
||||
if alt.details.stats != self.main_route.stats
|
||||
&& self.alt_routes.iter().all(|x| alt.details.stats != x.stats)
|
||||
{
|
||||
self.alt_routes.push(alt);
|
||||
self.alt_routes.push(alt.details);
|
||||
world
|
||||
.add(RouteID::Alt(self.alt_routes.len() - 1))
|
||||
.hitbox(alt.hitbox)
|
||||
.draw(alt.draw)
|
||||
.hover_alpha(0.8)
|
||||
.tooltip(alt.tooltip_for_alt.take().unwrap())
|
||||
.clickable()
|
||||
.build(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
world.initialize_hover(ctx);
|
||||
self.world = world;
|
||||
}
|
||||
|
||||
fn update_input_panel(&mut self, ctx: &mut EventCtx, app: &App) {
|
||||
fn update_input_panel(&mut self, ctx: &mut EventCtx, app: &App, main_route: Widget) {
|
||||
let col = Widget::col(vec![
|
||||
self.files.get_panel_widget(ctx),
|
||||
Widget::col(vec![Widget::row(vec![
|
||||
@ -118,7 +145,7 @@ impl RoutePlanner {
|
||||
])])
|
||||
.section(ctx),
|
||||
self.waypoints.get_panel_widget(ctx).section(ctx),
|
||||
self.main_route.to_widget(ctx, app).section(ctx),
|
||||
main_route.section(ctx),
|
||||
]);
|
||||
|
||||
let mut new_panel = Tab::Route.make_left_panel(ctx, app, col);
|
||||
@ -133,7 +160,6 @@ impl RoutePlanner {
|
||||
self.waypoints
|
||||
.overwrite(ctx, app, self.files.current.waypoints.clone());
|
||||
self.recalculate_routes(ctx, app);
|
||||
self.update_input_panel(ctx, app);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,17 +174,14 @@ impl State<App> for RoutePlanner {
|
||||
});
|
||||
}
|
||||
|
||||
let mut focused_on_alt_route = false;
|
||||
for r in &mut self.alt_routes {
|
||||
r.event(ctx);
|
||||
focused_on_alt_route |= r.has_focus();
|
||||
if r.has_focus() && ctx.normal_left_click() {
|
||||
match self.world.event(ctx) {
|
||||
WorldOutcome::ClickedObject(RouteID::Alt(idx)) => {
|
||||
// Switch routes
|
||||
app.session.routing_preferences = r.results.preferences;
|
||||
app.session.routing_preferences = self.alt_routes[idx].preferences;
|
||||
self.recalculate_routes(ctx, app);
|
||||
self.update_input_panel(ctx, app);
|
||||
return Transition::Keep;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let outcome = self.input_panel.event(ctx);
|
||||
@ -181,7 +204,6 @@ impl State<App> for RoutePlanner {
|
||||
stressful_roads: self.input_panel.dropdown_value("stressful roads"),
|
||||
};
|
||||
self.recalculate_routes(ctx, app);
|
||||
self.update_input_panel(ctx, app);
|
||||
return Transition::Keep;
|
||||
}
|
||||
}
|
||||
@ -193,21 +215,12 @@ impl State<App> for RoutePlanner {
|
||||
{
|
||||
return t;
|
||||
}
|
||||
// Dragging behavior inside here only works if we're not hovering on an alternate route
|
||||
// TODO But then that prevents dragging some waypoints! Can we give waypoints precedence
|
||||
// instead?
|
||||
if !focused_on_alt_route && self.waypoints.event(ctx, app, outcome) {
|
||||
|
||||
if self.waypoints.event(ctx, app, outcome) {
|
||||
// Sync from waypoints to file management
|
||||
// TODO Maaaybe this directly live in the InputWaypoints system?
|
||||
self.files.current.waypoints = self.waypoints.get_waypoints();
|
||||
self.recalculate_routes(ctx, app);
|
||||
self.update_input_panel(ctx, app);
|
||||
}
|
||||
if focused_on_alt_route {
|
||||
// Still allow zooming
|
||||
if let Some((_, dy)) = ctx.input.get_mouse_scroll() {
|
||||
ctx.canvas.zoom(dy, ctx.canvas.get_cursor());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(t) = self.layers.event(ctx, app) {
|
||||
@ -221,10 +234,8 @@ impl State<App> for RoutePlanner {
|
||||
self.layers.draw(g, app);
|
||||
self.input_panel.draw(g);
|
||||
self.waypoints.draw(g);
|
||||
self.main_route.draw(g, app, &self.input_panel);
|
||||
for r in &self.alt_routes {
|
||||
r.draw(g, app);
|
||||
}
|
||||
self.main_route.draw(g, &self.input_panel);
|
||||
self.world.draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use geom::{Circle, Distance, Duration, FindClosest, PolyLine};
|
||||
use map_gui::tools::{PopupMsg, ToggleZoomed};
|
||||
use geom::{Circle, Distance, Duration, FindClosest, PolyLine, Polygon};
|
||||
use map_gui::tools::PopupMsg;
|
||||
use map_model::{Path, PathStep, NORMAL_LANE_THICKNESS};
|
||||
use sim::{TripEndpoint, TripMode};
|
||||
use widgetry::mapspace::{ToggleZoomed, ToggleZoomedBuilder};
|
||||
use widgetry::{
|
||||
Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, LinePlot, Outcome, Panel, PlotOptions,
|
||||
Series, Text, Widget,
|
||||
@ -12,26 +13,31 @@ use widgetry::{
|
||||
use super::RoutingPreferences;
|
||||
use crate::app::{App, Transition};
|
||||
|
||||
pub struct RouteResults {
|
||||
/// A temporary structure that the caller should unpack and use as needed.
|
||||
pub struct BuiltRoute {
|
||||
pub details: RouteDetails,
|
||||
pub details_widget: Widget,
|
||||
pub draw: ToggleZoomedBuilder,
|
||||
pub hitbox: Polygon,
|
||||
pub tooltip_for_alt: Option<Text>,
|
||||
}
|
||||
|
||||
pub struct RouteDetails {
|
||||
pub preferences: RoutingPreferences,
|
||||
pub stats: RouteStats,
|
||||
|
||||
// It's tempting to glue together all of the paths. But since some waypoints might force the
|
||||
// path to double back on itself, rendering the path as a single PolyLine would break.
|
||||
paths: Vec<(Path, Option<PolyLine>)>,
|
||||
// Match each polyline to the index in paths
|
||||
closest_path_segment: FindClosest<usize>,
|
||||
pub stats: RouteStats,
|
||||
|
||||
hover_on_line_plot: Option<(Distance, Drawable)>,
|
||||
hover_on_route_tooltip: Option<Text>,
|
||||
draw_route: ToggleZoomed,
|
||||
|
||||
draw_high_stress: Drawable,
|
||||
draw_traffic_signals: Drawable,
|
||||
draw_unprotected_turns: Drawable,
|
||||
|
||||
// Possibly a bit large to stash
|
||||
elevation_pts: Vec<(Distance, Distance)>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@ -45,10 +51,10 @@ pub struct RouteStats {
|
||||
total_down: Distance,
|
||||
}
|
||||
|
||||
impl RouteResults {
|
||||
impl RouteDetails {
|
||||
/// "main" is determined by `app.session.routing_preferences`
|
||||
pub fn main_route(ctx: &mut EventCtx, app: &App, waypoints: Vec<TripEndpoint>) -> RouteResults {
|
||||
RouteResults::new(
|
||||
pub fn main_route(ctx: &mut EventCtx, app: &App, waypoints: Vec<TripEndpoint>) -> BuiltRoute {
|
||||
RouteDetails::new(
|
||||
ctx,
|
||||
app,
|
||||
waypoints,
|
||||
@ -58,15 +64,41 @@ impl RouteResults {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn alt_route(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
waypoints: Vec<TripEndpoint>,
|
||||
main: &RouteDetails,
|
||||
preferences: RoutingPreferences,
|
||||
) -> BuiltRoute {
|
||||
let mut built = RouteDetails::new(
|
||||
ctx,
|
||||
app,
|
||||
waypoints,
|
||||
Color::grey(0.3),
|
||||
Some(Color::CYAN),
|
||||
preferences,
|
||||
);
|
||||
built.tooltip_for_alt = Some(compare_routes(
|
||||
app,
|
||||
&main.stats,
|
||||
&built.details.stats,
|
||||
preferences,
|
||||
));
|
||||
built
|
||||
}
|
||||
|
||||
fn new(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
waypoints: Vec<TripEndpoint>,
|
||||
route_color: Color,
|
||||
// Only used for alts
|
||||
outline_color: Option<Color>,
|
||||
preferences: RoutingPreferences,
|
||||
) -> RouteResults {
|
||||
) -> BuiltRoute {
|
||||
let mut draw_route = ToggleZoomed::builder();
|
||||
let mut hitbox_pieces = Vec::new();
|
||||
let mut draw_high_stress = GeomBatch::new();
|
||||
let mut draw_traffic_signals = GeomBatch::new();
|
||||
let mut draw_unprotected_turns = GeomBatch::new();
|
||||
@ -135,7 +167,11 @@ impl RouteResults {
|
||||
draw_route
|
||||
.unzoomed
|
||||
.push(route_color.alpha(0.8), shape.clone());
|
||||
draw_route.zoomed.push(route_color.alpha(0.5), shape);
|
||||
draw_route
|
||||
.zoomed
|
||||
.push(route_color.alpha(0.5), shape.clone());
|
||||
|
||||
hitbox_pieces.push(shape);
|
||||
|
||||
if let Some(color) = outline_color {
|
||||
if let Some(outline) =
|
||||
@ -162,27 +198,39 @@ impl RouteResults {
|
||||
total_up += dy;
|
||||
}
|
||||
}
|
||||
let stats = RouteStats {
|
||||
total_distance,
|
||||
dist_along_high_stress_roads,
|
||||
total_time,
|
||||
num_traffic_signals,
|
||||
num_unprotected_turns,
|
||||
total_up,
|
||||
total_down,
|
||||
};
|
||||
|
||||
RouteResults {
|
||||
preferences,
|
||||
draw_route: draw_route.build(ctx),
|
||||
draw_high_stress: ctx.upload(draw_high_stress),
|
||||
draw_traffic_signals: ctx.upload(draw_traffic_signals),
|
||||
draw_unprotected_turns: ctx.upload(draw_unprotected_turns),
|
||||
paths,
|
||||
closest_path_segment,
|
||||
hover_on_line_plot: None,
|
||||
hover_on_route_tooltip: None,
|
||||
elevation_pts,
|
||||
stats: RouteStats {
|
||||
total_distance,
|
||||
dist_along_high_stress_roads,
|
||||
total_time,
|
||||
num_traffic_signals,
|
||||
num_unprotected_turns,
|
||||
total_up,
|
||||
total_down,
|
||||
let details_widget = make_detail_widget(ctx, app, &stats, elevation_pts);
|
||||
|
||||
BuiltRoute {
|
||||
details: RouteDetails {
|
||||
preferences,
|
||||
draw_high_stress: ctx.upload(draw_high_stress),
|
||||
draw_traffic_signals: ctx.upload(draw_traffic_signals),
|
||||
draw_unprotected_turns: ctx.upload(draw_unprotected_turns),
|
||||
paths,
|
||||
closest_path_segment,
|
||||
hover_on_line_plot: None,
|
||||
hover_on_route_tooltip: None,
|
||||
stats,
|
||||
},
|
||||
details_widget,
|
||||
draw: draw_route,
|
||||
hitbox: if hitbox_pieces.is_empty() {
|
||||
// Dummy tiny hitbox
|
||||
Polygon::rectangle(0.0001, 0.0001)
|
||||
} else {
|
||||
Polygon::union_all(hitbox_pieces)
|
||||
},
|
||||
tooltip_for_alt: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,8 +341,7 @@ impl RouteResults {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx, app: &App, panel: &Panel) {
|
||||
self.draw_route.draw(g, app);
|
||||
pub fn draw(&self, g: &mut GfxCtx, panel: &Panel) {
|
||||
if let Some((_, ref draw)) = self.hover_on_line_plot {
|
||||
g.redraw(draw);
|
||||
}
|
||||
@ -311,155 +358,99 @@ impl RouteResults {
|
||||
g.redraw(&self.draw_unprotected_turns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_widget(&self, ctx: &mut EventCtx, app: &App) -> Widget {
|
||||
let pct_stressful = if self.stats.total_distance == Distance::ZERO {
|
||||
0.0
|
||||
} else {
|
||||
((self.stats.dist_along_high_stress_roads / self.stats.total_distance) * 100.0).round()
|
||||
};
|
||||
fn make_detail_widget(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
stats: &RouteStats,
|
||||
elevation_pts: Vec<(Distance, Distance)>,
|
||||
) -> Widget {
|
||||
let pct_stressful = if stats.total_distance == Distance::ZERO {
|
||||
0.0
|
||||
} else {
|
||||
((stats.dist_along_high_stress_roads / stats.total_distance) * 100.0).round()
|
||||
};
|
||||
|
||||
let elevation_plot = LinePlot::new_widget(
|
||||
Widget::col(vec![
|
||||
Line("Route details").small_heading().into_widget(ctx),
|
||||
Text::from_all(vec![
|
||||
Line("Distance: ").secondary(),
|
||||
Line(stats.total_distance.to_string(&app.opts.units)),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
Widget::row(vec![
|
||||
Text::from_all(vec![
|
||||
Line(format!(
|
||||
" {} or {}%",
|
||||
stats
|
||||
.dist_along_high_stress_roads
|
||||
.to_string(&app.opts.units),
|
||||
pct_stressful
|
||||
)),
|
||||
Line(" along ").secondary(),
|
||||
])
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.btn()
|
||||
.label_underlined_text("high-stress roads")
|
||||
.build_def(ctx),
|
||||
]),
|
||||
Text::from_all(vec![
|
||||
Line("Estimated time: ").secondary(),
|
||||
Line(stats.total_time.to_string(&app.opts.units)),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
Widget::row(vec![
|
||||
Line("Traffic signals crossed: ")
|
||||
.secondary()
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.btn()
|
||||
.label_underlined_text(stats.num_traffic_signals.to_string())
|
||||
.build_widget(ctx, "traffic signals"),
|
||||
]),
|
||||
Widget::row(vec![
|
||||
Line("Unprotected left turns onto busy roads: ")
|
||||
.secondary()
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.btn()
|
||||
.label_underlined_text(stats.num_unprotected_turns.to_string())
|
||||
.build_widget(ctx, "unprotected turns"),
|
||||
]),
|
||||
Text::from_all(vec![
|
||||
Line("Elevation change: ").secondary(),
|
||||
Line(format!(
|
||||
"{}↑, {}↓",
|
||||
stats.total_up.to_string(&app.opts.units),
|
||||
stats.total_down.to_string(&app.opts.units)
|
||||
)),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
LinePlot::new_widget(
|
||||
ctx,
|
||||
"elevation",
|
||||
vec![Series {
|
||||
label: "Elevation".to_string(),
|
||||
color: Color::RED,
|
||||
pts: self.elevation_pts.clone(),
|
||||
pts: elevation_pts,
|
||||
}],
|
||||
PlotOptions {
|
||||
filterable: false,
|
||||
max_x: Some(self.stats.total_distance.round_up_for_axis()),
|
||||
max_x: Some(stats.total_distance.round_up_for_axis()),
|
||||
max_y: Some(app.primary.map.max_elevation().round_up_for_axis()),
|
||||
disabled: HashSet::new(),
|
||||
},
|
||||
app.opts.units,
|
||||
);
|
||||
|
||||
Widget::col(vec![
|
||||
Line("Route details").small_heading().into_widget(ctx),
|
||||
Text::from_all(vec![
|
||||
Line("Distance: ").secondary(),
|
||||
Line(self.stats.total_distance.to_string(&app.opts.units)),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
Widget::row(vec![
|
||||
Text::from_all(vec![
|
||||
Line(format!(
|
||||
" {} or {}%",
|
||||
self.stats
|
||||
.dist_along_high_stress_roads
|
||||
.to_string(&app.opts.units),
|
||||
pct_stressful
|
||||
)),
|
||||
Line(" along ").secondary(),
|
||||
])
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.btn()
|
||||
.label_underlined_text("high-stress roads")
|
||||
.build_def(ctx),
|
||||
]),
|
||||
Text::from_all(vec![
|
||||
Line("Estimated time: ").secondary(),
|
||||
Line(self.stats.total_time.to_string(&app.opts.units)),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
Widget::row(vec![
|
||||
Line("Traffic signals crossed: ")
|
||||
.secondary()
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.btn()
|
||||
.label_underlined_text(self.stats.num_traffic_signals.to_string())
|
||||
.build_widget(ctx, "traffic signals"),
|
||||
]),
|
||||
Widget::row(vec![
|
||||
Line("Unprotected left turns onto busy roads: ")
|
||||
.secondary()
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.btn()
|
||||
.label_underlined_text(self.stats.num_unprotected_turns.to_string())
|
||||
.build_widget(ctx, "unprotected turns"),
|
||||
]),
|
||||
Text::from_all(vec![
|
||||
Line("Elevation change: ").secondary(),
|
||||
Line(format!(
|
||||
"{}↑, {}↓",
|
||||
self.stats.total_up.to_string(&app.opts.units),
|
||||
self.stats.total_down.to_string(&app.opts.units)
|
||||
)),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
elevation_plot,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AltRouteResults {
|
||||
pub results: RouteResults,
|
||||
hovering: bool,
|
||||
tooltip: Text,
|
||||
}
|
||||
|
||||
impl AltRouteResults {
|
||||
pub fn new(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
waypoints: Vec<TripEndpoint>,
|
||||
main: &RouteResults,
|
||||
preferences: RoutingPreferences,
|
||||
) -> AltRouteResults {
|
||||
let results = RouteResults::new(
|
||||
ctx,
|
||||
app,
|
||||
waypoints,
|
||||
Color::grey(0.3),
|
||||
Some(Color::CYAN),
|
||||
preferences,
|
||||
);
|
||||
let tooltip = compare_routes(app, &main.stats, &results.stats, preferences);
|
||||
AltRouteResults {
|
||||
results,
|
||||
hovering: false,
|
||||
tooltip,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_focus(&self) -> bool {
|
||||
self.hovering
|
||||
}
|
||||
|
||||
pub fn event(&mut self, ctx: &mut EventCtx) {
|
||||
if ctx.redo_mouseover() {
|
||||
self.hovering = false;
|
||||
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
|
||||
if self
|
||||
.results
|
||||
.closest_path_segment
|
||||
.closest_pt(pt, 10.0 * NORMAL_LANE_THICKNESS)
|
||||
.is_some()
|
||||
{
|
||||
self.hovering = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.results.draw_route.draw(g, app);
|
||||
|
||||
if self.hovering {
|
||||
g.draw_mouse_tooltip(self.tooltip.clone());
|
||||
}
|
||||
}
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
fn compare_routes(
|
||||
|
@ -30,9 +30,6 @@ pub struct Options {
|
||||
pub color_scheme: ColorSchemeChoice,
|
||||
/// Automatically change color_scheme based on simulation time to reflect day/night
|
||||
pub toggle_day_night_colors: bool,
|
||||
/// Map elements are drawn differently when unzoomed and zoomed. This specifies the canvas zoom
|
||||
/// level where they switch.
|
||||
pub min_zoom_for_detail: f64,
|
||||
/// Draw buildings in different perspectives
|
||||
pub camera_angle: CameraAngle,
|
||||
/// Draw building driveways.
|
||||
@ -80,7 +77,6 @@ impl Options {
|
||||
traffic_signal_style: TrafficSignalStyle::Brian,
|
||||
color_scheme: ColorSchemeChoice::DayMode,
|
||||
toggle_day_night_colors: false,
|
||||
min_zoom_for_detail: 4.0,
|
||||
camera_angle: CameraAngle::TopDown,
|
||||
show_building_driveways: true,
|
||||
|
||||
@ -103,7 +99,7 @@ impl Options {
|
||||
pub fn update_from_args(&mut self, args: &mut CmdArgs) {
|
||||
self.dev = args.enabled("--dev");
|
||||
if args.enabled("--lowzoom") {
|
||||
self.min_zoom_for_detail = 1.0;
|
||||
self.canvas_settings.min_zoom_for_detail = 1.0;
|
||||
}
|
||||
if let Some(x) = args.optional("--color_scheme") {
|
||||
let mut ok = false;
|
||||
@ -257,7 +253,7 @@ impl OptionsPanel {
|
||||
Widget::dropdown(
|
||||
ctx,
|
||||
"min zoom",
|
||||
app.opts().min_zoom_for_detail,
|
||||
ctx.canvas.settings.min_zoom_for_detail,
|
||||
vec![
|
||||
Choice::new("1.0", 1.0),
|
||||
Choice::new("2.0", 2.0),
|
||||
@ -347,6 +343,7 @@ impl<A: AppLike> State<A> for OptionsPanel {
|
||||
ctx.canvas.settings.gui_scroll_speed = self.panel.spinner("gui_scroll_speed");
|
||||
ctx.canvas.settings.canvas_scroll_speed =
|
||||
self.panel.spinner("canvas_scroll_speed");
|
||||
ctx.canvas.settings.min_zoom_for_detail = self.panel.dropdown_value("min zoom");
|
||||
// Copy the settings into the Options struct, so they're saved.
|
||||
opts.canvas_settings = ctx.canvas.settings.clone();
|
||||
|
||||
@ -398,7 +395,6 @@ impl<A: AppLike> State<A> for OptionsPanel {
|
||||
opts.toggle_day_night_colors = false;
|
||||
}
|
||||
|
||||
opts.min_zoom_for_detail = self.panel.dropdown_value("min zoom");
|
||||
opts.units.metric = self.panel.is_checked("metric / imperial units");
|
||||
|
||||
let language = self.panel.dropdown_value("language");
|
||||
|
@ -37,6 +37,7 @@ impl<T: 'static> SimpleApp<T> {
|
||||
) -> (SimpleApp<T>, Vec<Box<dyn State<SimpleApp<T>>>>) {
|
||||
let mut args = CmdArgs::new();
|
||||
opts.update_from_args(&mut args);
|
||||
ctx.canvas.settings = opts.canvas_settings.clone();
|
||||
let map_name = args
|
||||
.optional_free()
|
||||
.map(|path| {
|
||||
@ -169,9 +170,7 @@ impl<T: 'static> SimpleApp<T> {
|
||||
unzoomed_buildings: bool,
|
||||
) -> Option<ID> {
|
||||
// Unzoomed mode. Ignore when debugging areas.
|
||||
if ctx.canvas.cam_zoom < self.opts.min_zoom_for_detail
|
||||
&& !(unzoomed_roads_and_intersections || unzoomed_buildings)
|
||||
{
|
||||
if ctx.canvas.is_unzoomed() && !(unzoomed_roads_and_intersections || unzoomed_buildings) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -186,26 +185,22 @@ impl<T: 'static> SimpleApp<T> {
|
||||
for obj in objects {
|
||||
match obj.get_id() {
|
||||
ID::Road(_) => {
|
||||
if !unzoomed_roads_and_intersections
|
||||
|| ctx.canvas.cam_zoom >= self.opts.min_zoom_for_detail
|
||||
{
|
||||
if !unzoomed_roads_and_intersections || ctx.canvas.is_zoomed() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ID::Intersection(_) => {
|
||||
if ctx.canvas.cam_zoom < self.opts.min_zoom_for_detail
|
||||
&& !unzoomed_roads_and_intersections
|
||||
{
|
||||
if ctx.canvas.is_unzoomed() && !unzoomed_roads_and_intersections {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ID::Building(_) => {
|
||||
if ctx.canvas.cam_zoom < self.opts.min_zoom_for_detail && !unzoomed_buildings {
|
||||
if ctx.canvas.is_unzoomed() && !unzoomed_buildings {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if ctx.canvas.cam_zoom < self.opts.min_zoom_for_detail {
|
||||
if ctx.canvas.is_unzoomed() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -260,7 +255,7 @@ impl<T: 'static> AppLike for SimpleApp<T> {
|
||||
}
|
||||
|
||||
fn draw_with_opts(&self, g: &mut GfxCtx, opts: DrawOptions) {
|
||||
if g.canvas.cam_zoom < self.opts.min_zoom_for_detail {
|
||||
if g.canvas.is_unzoomed() {
|
||||
self.draw_unzoomed(g);
|
||||
} else {
|
||||
self.draw_zoomed(g, opts);
|
||||
|
@ -3,9 +3,9 @@ use std::collections::HashMap;
|
||||
use abstutil::Counter;
|
||||
use geom::{Circle, Distance, Line, Polygon, Pt2D};
|
||||
use map_model::{BuildingID, BusStopID, IntersectionID, LaneID, Map, ParkingLotID, RoadID};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{Color, EventCtx, Fill, GeomBatch, Line, LinearGradient, Text, Widget};
|
||||
|
||||
use crate::tools::ToggleZoomed;
|
||||
use crate::AppLike;
|
||||
|
||||
pub struct ColorDiscrete<'a> {
|
||||
|
@ -1,52 +0,0 @@
|
||||
use widgetry::{Drawable, EventCtx, GeomBatch, GfxCtx};
|
||||
|
||||
use crate::AppLike;
|
||||
|
||||
/// Draws one of two versions of something, based on whether the app is zoomed in past a threshold.
|
||||
pub struct ToggleZoomed {
|
||||
// Some callers access directly for minimaps
|
||||
pub unzoomed: Drawable,
|
||||
pub zoomed: Drawable,
|
||||
}
|
||||
|
||||
impl ToggleZoomed {
|
||||
pub fn new(ctx: &EventCtx, unzoomed: GeomBatch, zoomed: GeomBatch) -> ToggleZoomed {
|
||||
ToggleZoomed {
|
||||
unzoomed: ctx.upload(unzoomed),
|
||||
zoomed: ctx.upload(zoomed),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty(ctx: &EventCtx) -> ToggleZoomed {
|
||||
ToggleZoomed {
|
||||
unzoomed: Drawable::empty(ctx),
|
||||
zoomed: Drawable::empty(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builder() -> ToggleZoomedBuilder {
|
||||
ToggleZoomedBuilder {
|
||||
unzoomed: GeomBatch::new(),
|
||||
zoomed: GeomBatch::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx, app: &dyn AppLike) {
|
||||
if g.canvas.cam_zoom < app.opts().min_zoom_for_detail {
|
||||
g.redraw(&self.unzoomed);
|
||||
} else {
|
||||
g.redraw(&self.zoomed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToggleZoomedBuilder {
|
||||
pub unzoomed: GeomBatch,
|
||||
pub zoomed: GeomBatch,
|
||||
}
|
||||
|
||||
impl ToggleZoomedBuilder {
|
||||
pub fn build(self, ctx: &EventCtx) -> ToggleZoomed {
|
||||
ToggleZoomed::new(ctx, self.unzoomed, self.zoomed)
|
||||
}
|
||||
}
|
@ -81,7 +81,7 @@ impl<A: AppLike + 'static, T: MinimapControls<A>> Minimap<A, T> {
|
||||
|
||||
dragging: false,
|
||||
panel: Panel::empty(ctx),
|
||||
zoomed: ctx.canvas.cam_zoom >= app.opts().min_zoom_for_detail,
|
||||
zoomed: ctx.canvas.is_zoomed(),
|
||||
layer,
|
||||
|
||||
zoom_lvl: 0,
|
||||
@ -98,7 +98,7 @@ impl<A: AppLike + 'static, T: MinimapControls<A>> Minimap<A, T> {
|
||||
}
|
||||
|
||||
pub fn recreate_panel(&mut self, ctx: &mut EventCtx, app: &A) {
|
||||
if ctx.canvas.cam_zoom < app.opts().min_zoom_for_detail {
|
||||
if ctx.canvas.is_unzoomed() {
|
||||
self.panel = self.controls.make_unzoomed_panel(ctx, app);
|
||||
return;
|
||||
}
|
||||
@ -271,7 +271,7 @@ impl<A: AppLike + 'static, T: MinimapControls<A>> Minimap<A, T> {
|
||||
self.recreate_panel(ctx, app);
|
||||
}
|
||||
|
||||
let zoomed = ctx.canvas.cam_zoom >= app.opts().min_zoom_for_detail;
|
||||
let zoomed = ctx.canvas.is_zoomed();
|
||||
let layer = self.controls.has_layer(app);
|
||||
if zoomed != self.zoomed || layer != self.layer {
|
||||
let just_zoomed_in = zoomed && !self.zoomed;
|
||||
|
@ -7,7 +7,6 @@ use widgetry::{lctrl, EventCtx, GfxCtx, Key, Line, Text, Widget};
|
||||
pub use self::camera::{CameraState, DefaultMap};
|
||||
pub use self::city_picker::CityPicker;
|
||||
pub use self::colors::{ColorDiscrete, ColorLegend, ColorNetwork, ColorScale, DivergingScale};
|
||||
pub use self::draw::{ToggleZoomed, ToggleZoomedBuilder};
|
||||
pub use self::heatmap::{draw_isochrone, make_heatmap, Grid, HeatmapOptions};
|
||||
pub use self::icons::{goal_marker, start_marker};
|
||||
pub use self::minimap::{Minimap, MinimapControls};
|
||||
@ -27,7 +26,6 @@ mod city_picker;
|
||||
mod colors;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod command;
|
||||
mod draw;
|
||||
mod heatmap;
|
||||
mod icons;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
@ -148,7 +148,7 @@ impl<A: AppLike + 'static> State<A> for CrossStreet {
|
||||
return Transition::Replace(app.make_warper(
|
||||
ctx,
|
||||
pt,
|
||||
Some(app.opts().min_zoom_for_detail),
|
||||
Some(ctx.canvas.settings.min_zoom_for_detail),
|
||||
None,
|
||||
));
|
||||
}
|
||||
@ -172,7 +172,7 @@ impl<A: AppLike + 'static> State<A> for CrossStreet {
|
||||
return Transition::Replace(app.make_warper(
|
||||
ctx,
|
||||
pt,
|
||||
Some(app.opts().min_zoom_for_detail),
|
||||
Some(ctx.canvas.settings.min_zoom_for_detail),
|
||||
Some(ID::Intersection(i)),
|
||||
));
|
||||
} else {
|
||||
@ -273,7 +273,7 @@ impl<A: AppLike + 'static> State<A> for SearchBuildings {
|
||||
return Transition::Replace(app.make_warper(
|
||||
ctx,
|
||||
pt,
|
||||
Some(app.opts().min_zoom_for_detail),
|
||||
Some(ctx.canvas.settings.min_zoom_for_detail),
|
||||
Some(ID::Building(bldgs[0])),
|
||||
));
|
||||
}
|
||||
|
@ -353,7 +353,7 @@ impl State<App> for Viewer {
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
if g.canvas.is_unzoomed() {
|
||||
app.draw_unzoomed(g);
|
||||
} else {
|
||||
app.draw_zoomed(g, DrawOptions::new());
|
||||
|
@ -5,7 +5,7 @@ mod mapper;
|
||||
|
||||
fn main() {
|
||||
let mut options = map_gui::options::Options::load_or_default();
|
||||
options.min_zoom_for_detail = 2.0;
|
||||
options.canvas_settings.min_zoom_for_detail = 2.0;
|
||||
let settings = widgetry::Settings::new("OSM parking mapper")
|
||||
.read_svg(Box::new(abstio::slurp_bytes))
|
||||
.canvas_settings(options.canvas_settings.clone());
|
||||
|
@ -477,7 +477,7 @@ impl State<App> for Game {
|
||||
if let Some((_, dy)) = ctx.input.get_mouse_scroll() {
|
||||
ctx.canvas.cam_zoom = 1.1_f64
|
||||
.powf(ctx.canvas.cam_zoom.log(1.1) + dy)
|
||||
.max(app.opts.min_zoom_for_detail)
|
||||
.max(ctx.canvas.settings.min_zoom_for_detail)
|
||||
.min(50.0);
|
||||
ctx.canvas.center_on_map_pt(self.player.get_pos());
|
||||
}
|
||||
|
@ -54,6 +54,10 @@ pub struct CanvasSettings {
|
||||
// TODO Ideally this would be an f64, but elsewhere we use it in a Spinner. Until we override
|
||||
// the Display trait to do some rounding, floating point increments render pretty horribly.
|
||||
pub canvas_scroll_speed: usize,
|
||||
/// Some map-space elements are drawn differently when unzoomed and zoomed. This specifies the canvas
|
||||
/// zoom level where they switch. The concept of "unzoomed" and "zoomed" is used by
|
||||
/// `ToggleZoomed`.
|
||||
pub min_zoom_for_detail: f64,
|
||||
}
|
||||
|
||||
impl CanvasSettings {
|
||||
@ -65,6 +69,7 @@ impl CanvasSettings {
|
||||
keys_to_pan: false,
|
||||
gui_scroll_speed: 5,
|
||||
canvas_scroll_speed: 10,
|
||||
min_zoom_for_detail: 4.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -353,6 +358,14 @@ impl Canvas {
|
||||
};
|
||||
ScreenPt::new(x1, y1)
|
||||
}
|
||||
|
||||
pub fn is_unzoomed(&self) -> bool {
|
||||
self.cam_zoom < self.settings.min_zoom_for_detail
|
||||
}
|
||||
|
||||
pub fn is_zoomed(&self) -> bool {
|
||||
self.cam_zoom >= self.settings.min_zoom_for_detail
|
||||
}
|
||||
}
|
||||
|
||||
const INSET: f64 = 16.0;
|
||||
|
85
widgetry/src/mapspace/mod.rs
Normal file
85
widgetry/src/mapspace/mod.rs
Normal file
@ -0,0 +1,85 @@
|
||||
mod world;
|
||||
|
||||
use crate::{Drawable, EventCtx, GeomBatch, GfxCtx, RewriteColor};
|
||||
pub use world::{DummyID, ObjectID, World, WorldOutcome};
|
||||
|
||||
/// Draws one of two versions of something, based on whether the canvas is zoomed in past a threshold.
|
||||
pub struct ToggleZoomed {
|
||||
// Some callers access directly for minimaps
|
||||
pub unzoomed: Drawable,
|
||||
pub zoomed: Drawable,
|
||||
// Draw the same thing whether zoomed or unzoomed
|
||||
just_unzoomed: bool,
|
||||
}
|
||||
|
||||
impl ToggleZoomed {
|
||||
pub fn new(ctx: &EventCtx, unzoomed: GeomBatch, zoomed: GeomBatch) -> ToggleZoomed {
|
||||
ToggleZoomed {
|
||||
unzoomed: ctx.upload(unzoomed),
|
||||
zoomed: ctx.upload(zoomed),
|
||||
just_unzoomed: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty(ctx: &EventCtx) -> ToggleZoomed {
|
||||
ToggleZoomed {
|
||||
unzoomed: Drawable::empty(ctx),
|
||||
zoomed: Drawable::empty(ctx),
|
||||
just_unzoomed: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builder() -> ToggleZoomedBuilder {
|
||||
ToggleZoomedBuilder {
|
||||
unzoomed: GeomBatch::new(),
|
||||
zoomed: GeomBatch::new(),
|
||||
just_unzoomed: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
if self.just_unzoomed || g.canvas.cam_zoom < g.canvas.settings.min_zoom_for_detail {
|
||||
g.redraw(&self.unzoomed);
|
||||
} else {
|
||||
g.redraw(&self.zoomed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToggleZoomedBuilder {
|
||||
pub unzoomed: GeomBatch,
|
||||
pub zoomed: GeomBatch,
|
||||
just_unzoomed: bool,
|
||||
}
|
||||
|
||||
impl ToggleZoomedBuilder {
|
||||
/// Transforms all colors in both batches.
|
||||
pub fn color(mut self, transformation: RewriteColor) -> Self {
|
||||
self.unzoomed = self.unzoomed.color(transformation);
|
||||
self.zoomed = self.zoomed.color(transformation);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self, ctx: &EventCtx) -> ToggleZoomed {
|
||||
if self.just_unzoomed {
|
||||
assert!(self.zoomed.is_empty());
|
||||
}
|
||||
ToggleZoomed {
|
||||
unzoomed: ctx.upload(self.unzoomed),
|
||||
zoomed: ctx.upload(self.zoomed),
|
||||
just_unzoomed: self.just_unzoomed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drawing just one batch means the same thing will appear whether zoomed or unzoomed
|
||||
impl std::convert::From<GeomBatch> for ToggleZoomedBuilder {
|
||||
fn from(unzoomed: GeomBatch) -> Self {
|
||||
Self {
|
||||
unzoomed,
|
||||
zoomed: GeomBatch::new(),
|
||||
just_unzoomed: true,
|
||||
}
|
||||
}
|
||||
}
|
@ -6,12 +6,14 @@ use aabb_quadtree::{ItemId, QuadTree};
|
||||
|
||||
use geom::{Bounds, Circle, Distance, Polygon, Pt2D};
|
||||
|
||||
use crate::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, MultiKey, RewriteColor, Text};
|
||||
use crate::mapspace::{ToggleZoomed, ToggleZoomedBuilder};
|
||||
use crate::{Color, EventCtx, GeomBatch, GfxCtx, MultiKey, RewriteColor, Text};
|
||||
|
||||
// TODO Tests...
|
||||
// - start drag in screenspace, release in map
|
||||
// - start drag in mapspace, release in screen
|
||||
// - reset hovering when we go out of screenspace
|
||||
// - start dragging one object, and while dragging, hover on top of other objects
|
||||
|
||||
/// A `World` manages objects that exist in "map-space", the zoomable and pannable canvas. These
|
||||
/// objects can be drawn, hovered on, clicked, dragged, etc.
|
||||
@ -20,7 +22,7 @@ pub struct World<ID: ObjectID> {
|
||||
objects: HashMap<ID, Object<ID>>,
|
||||
quadtree: QuadTree<ID>,
|
||||
|
||||
draw_master_batches: Vec<Drawable>,
|
||||
draw_master_batches: Vec<ToggleZoomed>,
|
||||
|
||||
hovering: Option<ID>,
|
||||
// If we're currently dragging, where was the cursor during the last movement, and has the
|
||||
@ -33,7 +35,13 @@ pub enum WorldOutcome<ID: ObjectID> {
|
||||
/// A left click occurred while not hovering on any object
|
||||
ClickedFreeSpace(Pt2D),
|
||||
/// An object is being dragged. The given offsets are relative to the previous dragging event.
|
||||
Dragging { obj: ID, dx: f64, dy: f64 },
|
||||
/// The current position of the cursor is included.
|
||||
Dragging {
|
||||
obj: ID,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
cursor: Pt2D,
|
||||
},
|
||||
/// While hovering on an object with a defined hotkey, that key was pressed.
|
||||
Keypress(&'static str, ID),
|
||||
/// A hoverable object was clicked
|
||||
@ -52,8 +60,8 @@ pub struct ObjectBuilder<'a, ID: ObjectID> {
|
||||
id: ID,
|
||||
hitbox: Option<Polygon>,
|
||||
zorder: usize,
|
||||
draw_normal: Option<GeomBatch>,
|
||||
draw_hover: Option<GeomBatch>,
|
||||
draw_normal: Option<ToggleZoomedBuilder>,
|
||||
draw_hover: Option<ToggleZoomedBuilder>,
|
||||
tooltip: Option<Text>,
|
||||
clickable: bool,
|
||||
draggable: bool,
|
||||
@ -76,12 +84,12 @@ impl<'a, ID: ObjectID> ObjectBuilder<'a, ID> {
|
||||
}
|
||||
|
||||
/// Specifies how to draw this object normally (while not hovering on it)
|
||||
pub fn draw(mut self, batch: GeomBatch) -> Self {
|
||||
pub fn draw<I: Into<ToggleZoomedBuilder>>(mut self, normal: I) -> Self {
|
||||
assert!(
|
||||
self.draw_normal.is_none(),
|
||||
"already specified how to draw normally"
|
||||
);
|
||||
self.draw_normal = Some(batch);
|
||||
self.draw_normal = Some(normal.into());
|
||||
self
|
||||
}
|
||||
|
||||
@ -102,23 +110,28 @@ impl<'a, ID: ObjectID> ObjectBuilder<'a, ID> {
|
||||
|
||||
/// Specifies how to draw the object while the cursor is hovering on it. Note that an object
|
||||
/// isn't considered hoverable unless this is specified!
|
||||
pub fn draw_hovered(mut self, batch: GeomBatch) -> Self {
|
||||
pub fn draw_hovered<I: Into<ToggleZoomedBuilder>>(mut self, hovered: I) -> Self {
|
||||
assert!(
|
||||
self.draw_hover.is_none(),
|
||||
"already specified how to draw hovered"
|
||||
);
|
||||
self.draw_hover = Some(batch);
|
||||
self.draw_hover = Some(hovered.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Draw the object in a hovered state by transforming the normal drawing.
|
||||
pub fn draw_hover_rewrite(self, rewrite: RewriteColor) -> Self {
|
||||
let hovered = self
|
||||
.draw_normal
|
||||
.clone()
|
||||
.expect("first specify how to draw normally")
|
||||
.color(rewrite);
|
||||
self.draw_hovered(hovered)
|
||||
}
|
||||
|
||||
/// Draw the object in a hovered state by changing the alpha value of the normal drawing.
|
||||
pub fn hover_alpha(self, alpha: f32) -> Self {
|
||||
let batch = self
|
||||
.draw_normal
|
||||
.clone()
|
||||
.expect("first specify how to draw normally")
|
||||
.color(RewriteColor::ChangeAlpha(alpha));
|
||||
self.draw_hovered(batch)
|
||||
self.draw_hover_rewrite(RewriteColor::ChangeAlpha(alpha))
|
||||
}
|
||||
|
||||
/// Draw a tooltip while hovering over this object.
|
||||
@ -176,11 +189,11 @@ impl<'a, ID: ObjectID> ObjectBuilder<'a, ID> {
|
||||
_quadtree_id: quadtree_id,
|
||||
hitbox,
|
||||
zorder: self.zorder,
|
||||
draw_normal: ctx.upload(
|
||||
self.draw_normal
|
||||
.expect("didn't specify how to draw normally"),
|
||||
),
|
||||
draw_hover: self.draw_hover.take().map(|batch| ctx.upload(batch)),
|
||||
draw_normal: self
|
||||
.draw_normal
|
||||
.expect("didn't specify how to draw normally")
|
||||
.build(ctx),
|
||||
draw_hover: self.draw_hover.take().map(|draw| draw.build(ctx)),
|
||||
tooltip: self.tooltip,
|
||||
clickable: self.clickable,
|
||||
draggable: self.draggable,
|
||||
@ -195,8 +208,8 @@ struct Object<ID: ObjectID> {
|
||||
_quadtree_id: ItemId,
|
||||
hitbox: Polygon,
|
||||
zorder: usize,
|
||||
draw_normal: Drawable,
|
||||
draw_hover: Option<Drawable>,
|
||||
draw_normal: ToggleZoomed,
|
||||
draw_hover: Option<ToggleZoomed>,
|
||||
tooltip: Option<Text>,
|
||||
clickable: bool,
|
||||
draggable: bool,
|
||||
@ -265,14 +278,23 @@ impl<ID: ObjectID> World<ID> {
|
||||
|
||||
/// If a drag event causes the world to be totally rebuilt, call this with the previous world
|
||||
/// to preserve the ongoing drag.
|
||||
///
|
||||
/// This should be called after `initialize_hover`.
|
||||
///
|
||||
/// Important: the rebuilt world must include the same object ID that's currently being dragged
|
||||
/// from the previous world.
|
||||
pub fn rebuilt_during_drag(&mut self, prev_world: &World<ID>) {
|
||||
self.dragging_from = prev_world.dragging_from;
|
||||
if prev_world.dragging_from.is_some() {
|
||||
self.dragging_from = prev_world.dragging_from;
|
||||
self.hovering = prev_world.hovering;
|
||||
assert!(self.objects.contains_key(self.hovering.as_ref().unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw something underneath all objects. This is useful for performance, when a large number
|
||||
/// of objects never change appearance.
|
||||
pub fn draw_master_batch(&mut self, draw: Drawable) {
|
||||
self.draw_master_batches.push(draw);
|
||||
pub fn draw_master_batch<I: Into<ToggleZoomedBuilder>>(&mut self, ctx: &EventCtx, draw: I) {
|
||||
self.draw_master_batches.push(draw.into().build(ctx));
|
||||
}
|
||||
|
||||
/// Let objects in the world respond to something happening.
|
||||
@ -306,6 +328,7 @@ impl<ID: ObjectID> World<ID> {
|
||||
obj: self.hovering.unwrap(),
|
||||
dx,
|
||||
dy,
|
||||
cursor,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -393,7 +416,7 @@ impl<ID: ObjectID> World<ID> {
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
// Always draw master batches first
|
||||
for draw in &self.draw_master_batches {
|
||||
g.redraw(draw);
|
||||
draw.draw(g);
|
||||
}
|
||||
|
||||
let mut objects = Vec::new();
|
||||
@ -407,7 +430,7 @@ impl<ID: ObjectID> World<ID> {
|
||||
let obj = &self.objects[&id];
|
||||
if Some(id) == self.hovering {
|
||||
if let Some(ref draw) = obj.draw_hover {
|
||||
g.redraw(draw);
|
||||
draw.draw(g);
|
||||
drawn = true;
|
||||
}
|
||||
if let Some(ref txt) = obj.tooltip {
|
||||
@ -415,7 +438,7 @@ impl<ID: ObjectID> World<ID> {
|
||||
}
|
||||
}
|
||||
if !drawn {
|
||||
g.redraw(&obj.draw_normal);
|
||||
obj.draw_normal.draw(g);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user