fix(imgui): keep the plot inside the child's clip rect and reject unusable input
The widget drew a region derived from the child's content rect alone, so a child the hosting window cut off cleared and painted over the host's pixels; it also scaled ImGui's (-FLT_MAX, -FLT_MAX) no-pointer sentinel and differenced it against the last real position, turning a drag out of the window into a ~1e35 pan, and it forgot the host's button levels across a re-bind, handing the next plot a press that never happened. The region is now the content rect intersected with the child's clip rect (a no-op when nothing clips it), an invalid position is no position update, and the button levels survive a re-bind. The controller sanitises non-finite coordinates and deltas for any host, and the widget's input translation now has a committed test on the headless EGL context.
This commit is contained in:
1 parent
15931a2405
commit
b5b9b42be1
8 files changed
+697
-58
No files matched your search
@@ -66,6 +66,12 @@ namespace plot::imgui {
|
||||
/// The child's own background is forced transparent by the widget, whatever the
|
||||
/// host's `ImGuiCol_ChildBg`, so the widget itself never paints over the plot.
|
||||
///
|
||||
/// The converse also holds: what ImGui clips away, the widget does not paint. GL
|
||||
/// draws do not go through ImGui's clipping, so the region is intersected with the
|
||||
/// clip rect ImGui gives the child (its own rectangle when nothing cuts it off,
|
||||
/// the visible part of it when an ancestor does) -- a child the hosting window cuts
|
||||
/// off must not clear and paint over whatever the host drew beside it.
|
||||
///
|
||||
/// ## Input
|
||||
///
|
||||
/// Pointer, wheel and key state are translated into `plot::input` events and
|
||||
@@ -74,13 +80,21 @@ namespace plot::imgui {
|
||||
/// controller wants edges (`PointerEvent::pressed`/`released`), so the widget
|
||||
/// remembers the previous frame's buttons and synthesises the transitions.
|
||||
///
|
||||
/// Events are forwarded while an invisible item covering the child's region is
|
||||
/// hovered -- `IsItemHovered()` rather than `IsWindowHovered()`, because the
|
||||
/// item is the child's whole rectangle (the child has no padding) and its hover
|
||||
/// is false while the host is dragging some other widget, so dragging a slider
|
||||
/// next to the plot cannot pan it. A press that started over the child keeps
|
||||
/// being delivered until every button is up again, so a drag that leaves the
|
||||
/// region still ends instead of leaving the plot mid-pan.
|
||||
/// Events are forwarded while an invisible item covering the child's visible
|
||||
/// region is hovered -- `IsItemHovered()` rather than `IsWindowHovered()`, because
|
||||
/// the item is the child's whole rectangle (the child has no padding) and its hover
|
||||
/// is false while the host is dragging some other widget, so dragging a slider next
|
||||
/// to the plot cannot pan it. A press that started over the child keeps being
|
||||
/// delivered until every button is up again, so a drag that leaves the region still
|
||||
/// ends instead of leaving the plot mid-pan.
|
||||
///
|
||||
/// An *invalid* pointer position -- ImGui's `(-FLT_MAX, -FLT_MAX)` sentinel for "the
|
||||
/// pointer is not on this window", which the platform backends leave behind when the
|
||||
/// pointer leaves or the window loses focus -- is no position update: the last real
|
||||
/// position stands, the movement for that frame is zero (a sentinel scaled by the
|
||||
/// framebuffer scale and differenced against a real position would be a ~1e35 pan),
|
||||
/// and the button levels still flow, so a drag that leaves the window still ends on
|
||||
/// its release.
|
||||
///
|
||||
/// Wheel input goes to the plot while it is hovered, and the plot's zoom wins
|
||||
/// over ImGui's window scrolling: `SetItemKeyOwner(ImGuiKey_MouseWheelY)` on that
|
||||
@@ -109,15 +123,19 @@ class PlotWidget {
|
||||
/// window; the plot is drawn (GL calls issued) during the call, inside the
|
||||
/// child, so the host's later ImGui commands never have to restore anything.
|
||||
///
|
||||
/// The plot's viewport is the child's content rectangle, so it fills the child
|
||||
/// The plot's viewport is the child's visible rectangle, so it fills the child
|
||||
/// exactly; the widget sets it every frame through the controller, and the plot
|
||||
/// is re-laid out to match. A degenerate region (a collapsed or clipped child)
|
||||
/// draws nothing and forwards nothing.
|
||||
/// is re-laid out to match. A degenerate or entirely
|
||||
/// clipped region draws nothing and forwards nothing, and a partially clipped one
|
||||
/// draws and accepts gestures only where the host leaves the child visible.
|
||||
void draw(const char* id, Plot& plot, Vec2 size, Renderer& renderer);
|
||||
|
||||
/// The controller the widget feeds, or null before the first `draw()`. It is
|
||||
/// bound to whichever plot the last `draw()` was given; a `draw()` for a
|
||||
/// different plot re-binds it and drops any drag in progress.
|
||||
/// different plot re-binds it and drops any drag in progress. The remembered
|
||||
/// button levels survive the re-bind -- they are the host's state, not the
|
||||
/// plot's -- so a host drawing two plots through one widget does not hand the
|
||||
/// second one a fabricated press.
|
||||
input::Controller* controller() noexcept { return controller_ ? &*controller_ : nullptr; }
|
||||
const input::Controller* controller() const noexcept {
|
||||
return controller_ ? &*controller_ : nullptr;
|
||||
@@ -127,8 +145,9 @@ class PlotWidget {
|
||||
/// Synthesises this frame's `plot::input` events from ImGui's levels.
|
||||
void forward_input(bool hovered);
|
||||
|
||||
/// Forgets the previous frame's pointer state, so a re-bound plot cannot see an
|
||||
/// edge that belongs to the old one.
|
||||
/// Drops the remembered pointer position and any capture, keeping the remembered
|
||||
/// button levels: they are the host's state, and synthesising an edge out of the
|
||||
/// levels a re-bind started with would fabricate a press.
|
||||
void reset_pointer() noexcept;
|
||||
|
||||
Plot* plot_ = nullptr;
|
||||
|
||||
Reference in new issue
Block a user