walkers version: 0.56.0
egui version: 0.35
co-author: Antropic Fabel 5
Summary
Map reads pointer-gesture input from the global InputState and never
consumes it:
handle_gestures reads ui.input(|i| i.smooth_scroll_delta) for
wheel/two-finger panning (map.rs, the panning_enabled block), and
zoom_delta reads ui.input(|input| input.zoom_delta()).
Both reads are gated only by ui_contains_pointer(). Nothing marks the
scroll as used, so when the map is nested inside anything that also reacts
to smooth_scroll_delta — most commonly an egui::ScrollArea whose content
overflows — one wheel event is applied twice: the parent scrolls the panel
and the map pans. During zoom sessions (trackpad pinch with scroll
residue, or wheel ticks with ctrl released between notches) the map widget
bounces inside the scrolling parent while the map content moves under the
pointer. Visually this reads as violent flicker.
Note the ordering trap: the map runs inside the ScrollArea's content
closure, i.e. before the ScrollArea finishes its own scroll handling for
the frame — so even a parent that consumed the delta at the end of show
could not prevent the inner map from having already acted on it. Only the
inner widget (the map) can resolve the conflict, by consuming what it uses.
This is easy to hit in practice: egui_dock wraps every tab body in
ScrollArea::new([true, true]) by default, so a map inside a dock tab
whose body overflows gets the double-handling out of the box. (Same nesting
family as #516, which is about the projector vs. clipped-tiles anchor
offset in a partially scrolled ScrollArea.)
Reproduction
-
Put a Map (with HttpTiles) inside a ScrollArea together with
enough other content that the scroll area overflows:
egui::ScrollArea::vertical().show(ui, |ui| {
ui.label("some controls above the map");
ui.add_sized(
egui::vec2(960.0, 560.0),
walkers::Map::new(Some(&mut tiles), &mut map_memory, my_position),
);
ui.label("content below, tall enough to overflow");
});
-
Hover the map and scroll with the wheel (no ctrl).
Expected: one consumer — either the map pans (it acted on the input) or
the scroll area scrolls, not both.
Observed: the scroll area scrolls the panel and the map pans by the
same delta in the same frame.
With zoom_with_ctrl (default true) the wheel-pan path is always active
(panning_enabled = panning && (any_touches || zoom_with_ctrl)), so plain
wheel over the map is always double-handled in a scrollable parent.
Suggested direction
When the map decides to act on scroll/zoom input (pointer over the map,
gesture enabled), consume the input it used, e.g.:
let scroll_delta = ui.input_mut(|i| std::mem::take(&mut i.smooth_scroll_delta));
(and analogously zero the zoom component it applies), or gate the reads
through the Response-based helpers so egui's usual containment rules
apply. If unconditional consumption is considered too opinionated, an
opt-in builder flag (.consume_scroll(true) or similar) would let nested
users fix the conflict.
Downstream workaround (for reference)
We disable the wrapping ScrollArea for dock tabs that host a map
(egui_dock::TabViewer::scroll_bars → [false, false]), so the map is the
only scroll consumer under the pointer. That works, but costs the tab its
scrollability for overflow, which the map didn't need but sibling controls
might.
walkers version: 0.56.0
egui version: 0.35
co-author: Antropic Fabel 5
Summary
Mapreads pointer-gesture input from the globalInputStateand neverconsumes it:
handle_gesturesreadsui.input(|i| i.smooth_scroll_delta)forwheel/two-finger panning (
map.rs, thepanning_enabledblock), andzoom_deltareadsui.input(|input| input.zoom_delta()).Both reads are gated only by
ui_contains_pointer(). Nothing marks thescroll as used, so when the map is nested inside anything that also reacts
to
smooth_scroll_delta— most commonly anegui::ScrollAreawhose contentoverflows — one wheel event is applied twice: the parent scrolls the panel
and the map pans. During zoom sessions (trackpad pinch with scroll
residue, or wheel ticks with ctrl released between notches) the map widget
bounces inside the scrolling parent while the map content moves under the
pointer. Visually this reads as violent flicker.
Note the ordering trap: the map runs inside the
ScrollArea's contentclosure, i.e. before the
ScrollAreafinishes its own scroll handling forthe frame — so even a parent that consumed the delta at the end of
showcould not prevent the inner map from having already acted on it. Only the
inner widget (the map) can resolve the conflict, by consuming what it uses.
This is easy to hit in practice:
egui_dockwraps every tab body inScrollArea::new([true, true])by default, so a map inside a dock tabwhose body overflows gets the double-handling out of the box. (Same nesting
family as #516, which is about the projector vs. clipped-tiles anchor
offset in a partially scrolled
ScrollArea.)Reproduction
Put a
Map(withHttpTiles) inside aScrollAreatogether withenough other content that the scroll area overflows:
Hover the map and scroll with the wheel (no ctrl).
Expected: one consumer — either the map pans (it acted on the input) or
the scroll area scrolls, not both.
Observed: the scroll area scrolls the panel and the map pans by the
same delta in the same frame.
With
zoom_with_ctrl(defaulttrue) the wheel-pan path is always active(
panning_enabled = panning && (any_touches || zoom_with_ctrl)), so plainwheel over the map is always double-handled in a scrollable parent.
Suggested direction
When the map decides to act on scroll/zoom input (pointer over the map,
gesture enabled), consume the input it used, e.g.:
(and analogously zero the zoom component it applies), or gate the reads
through the
Response-based helpers so egui's usual containment rulesapply. If unconditional consumption is considered too opinionated, an
opt-in builder flag (
.consume_scroll(true)or similar) would let nestedusers fix the conflict.
Downstream workaround (for reference)
We disable the wrapping
ScrollAreafor dock tabs that host a map(
egui_dock::TabViewer::scroll_bars→[false, false]), so the map is theonly scroll consumer under the pointer. That works, but costs the tab its
scrollability for overflow, which the map didn't need but sibling controls
might.