a-b-street / a-b-street/abstreet

Refactor map-space objects

未关闭
#763 1 条评论 1 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
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 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。