a-b-street / a-b-street/abstreet
Refactor map-space objects
- 主要语言
- Rust
- 星标
- 8.2k
- 派生
- 380
- PR 合并指标
- 30 天内没有已合并 PR
描述
widgetry now has pretty good abstractions for handling most "screen-space" interactions, with nice APIs like the button builders. But the world of "map-space" objects is the wild west -- just ad-hoc code for figuring out what exists, hovering, selecting, dragging, tooltips, z-ordering. Can we do better?
# Use cases
All of these have some kind of hovering behavior.
- KML viewer (needs quadtree, can select multiple overlapping things)
- stop sign editor (underlying basemap renders enabled stop signs)
- traffic signal editor (both icons and movement arrows, complex hover state calculation)
- the devtools polygon editor (dragging, clicking to add new points. dragging the polygon itself would be nice!)
- map_editor (dragging, has a `World` abstraction)
- collision viewer (has `MapspaceTooltips`)
- story map editor (dragging and currently awkward way of placing new stuff)
- commuter patterns (has a hovering vs locked/selected state)
- road editor (also hovering vs locked/selected)
- traffic signal demand viewer (hierarchical -- first look for intersections. but if we trust the quadtree, is that necessary?)
- bike tool's quick sketch (dragging, snapping)
- bike tool's routing (both `InputWaypoints` and now alternate routes, snapping)
And I'm sure I've missed some.
An extreme take: Almost no app code should call `ctx.redo_mouseover()`, but there are... >50 callers right now!
# What makes this stuff hard?
Caching and maintaining state -- when you hover or select something different, we usually want to draw something, maybe add a tooltip. Ad-hoc right now.
When hovering or actively dragging a draggable object, we still want to allow zooming, but not panning.
Dragging is complicated and buggy. In the route tool, if you drag a waypoint close to an alternate path, stuff breaks. Z-ordering there is implicit in the code, has to match in `event` and `draw`. Conflicts between waypoints and alt paths.
# Ideas
Extend `map_editor`'s `World` abstraction. There'll be something for a `State` that knows about all of the map-space objects. Objects are:
- drawable -- always in a big batch?
- hoverable -- so they have a hitbox, and we can use a quadtree to prune
- "on hover" -- draw extra stuff and maybe a mouse tooltip
- sometimes clickable
- sometimes draggable
- z-orderable... waypoints and alternate routes are an example where it's hard to get it right ad-hoc
- sometimes different behavior in unzoomed and zoomed modes... for drawing, selecting, etc
## What's in a world?
In most of the use cases, the map-space stuff is "extra" objects on top of the basemap. The basemap stuff is mostly managed by `map_gui`, which has its own system for managing these kinds of concerns. Except there are also some places that "subset" the basemap objects:
- the picker for editing multiple traffic signals
- the LTN "pick a neighborhood" screen
- the LTN "edit a neighborhood" screen
- and everything calling things like `mouseover_unzoomed_buildings` or doing further filtering (like no editing service roads or light rail)
Hot take: `app.primary.current_selection` is a bad idea? I count around 30 places that reset it to `None` when entering/leaving the state. I think the world of selectable objects shouldn't be shared App-wide. Most states should control this more directly.
## How to drag "correctly"
1) Bail out if the cursor isn't in map-space
2) Go through all the objects, maybe start a drag
3) Only then allow normal canvas movement (not while hovering on something that's draggable)
## Where's the state live?
The `World` approach is to be generic over some ID type. Shoving in custom data for the objects is an option, or letting the `State` manage it and map things. (Hey, we're inventing parts of ECS...)
Similar to `Panel`s, could we make do with strings, or do we need IDs? Could the world container thing assign its own opaque IDs and make the caller do the mapping?
## Should we subsume chunks of `map_gui`?
Stepping back, `DrawMap` solves lots of these problems too. Should we try to subsume it?
```
pub trait Renderable {
fn get_id(&self) -> ID;
fn draw(&self, g: &mut GfxCtx, app: &dyn AppLike, opts: &DrawOptions);
fn get_zorder(&self) -> isize;
fn get_outline(&self, map: &Map) -> Polygon;
fn contains_pt(&self, pt: Pt2D, map: &Map) -> bool;
}
```
Things that're maybe different (but maybe not):
- the data ownership is a bit weird
- lazy rendering of zoomed-in stuff
- more complex batching for some stuff
## Delegation
We have a few places in the code where different logical components all shove their widgets in a single `Panel`, so we do scary dances to try to delegate outcomes. Will we have the same problems? Think about the route tool -- `InputWaypoints` is a common thing, living alongside the one-off alt route clickables.
## Prior art
Any good ideas from Leaflet, mapbox, other game engine APIs?
贡献指南
这个仓库没有索引到贡献指南
评估
这个 Issue 还没有评估数据。