fix(glfw): route a drag by the row it began in, and drop stale hover state
`controller_at()` routed every pointer event by the row the pixel was in, ignoring the row a drag started in, so a press on one figure row followed by a motion and release in another delivered the motion and release to a controller whose `mode_ == None`. The owning controller kept `mode_`, `drag_region_`, `drag_cursor_` and `drag_fixed_x_` set for ever: `publish_hot()` kept publishing the grabbed handle, `grabbed_region()` reported a drag that had ended, and a later gesture re-entering the row resumed the stale edit from the old anchor. `feed_pointer_event()` now owns the routing: a press captures the row it landed in and every motion and the release until the button comes up go to that row (`pointer_target()`), cleared on release and when another figure is set. The GLFW callbacks build the event and call it, so a windowless host or a test can drive the routing too. Two neighbours in the same seam: * `sync_figure_crosshair()` called the four-argument overload unconditionally, so a documented vertical-only crosshair (`horizontal == false`) was promoted to a full cross at the stored y == 0, clamped to the axes edge and made permanent; the change detection also keyed on x alone, missing a y-only move to another row at the same x (two rows showing one horizontal line, spec 17). It now consults `crosshair().horizontal` and compares the hovered row's y. * Only the row under the pointer republished its hot handles, so a row the pointer left kept drawing a hovered marker or bound as if it were still under the pointer, and answered `hovered_region()`/`grabbed_cursor()` for it. The run loop clears every other row's handles, keeping the captured row while a drag owns the button, and reads the frame's one layout once instead of recomputing every row five times. Test: a press on row A's region, a motion into row B, a release in row B asserts no controller keeps a drag mode, nothing reports a grabbed handle, and a later press-and-motion that re-enters row A does not resume the old edit.
This commit is contained in:
1 parent
81792dcd05
commit
9aff06acee
3 files changed
+214
-13
No files matched your search
@@ -135,11 +135,25 @@ class Host {
|
||||
/// that leaves the data area still ends where it should.
|
||||
///
|
||||
/// It also hands that row its own viewport, which is what keeps the controller's
|
||||
/// hit-test geometry in step with the layout between two frames. The event callbacks
|
||||
/// route through it, and so can a host that wants to know where a synthetic gesture
|
||||
/// would land.
|
||||
/// hit-test geometry in step with the layout between two frames. It is the pure
|
||||
/// query -- what a gesture *would* land on -- and it does not move the capture
|
||||
/// below; the GLFW callbacks and `feed_pointer_event()` are the routing.
|
||||
input::Controller& controller_at(f32 x, f32 y) noexcept;
|
||||
|
||||
/// Feeds one pointer event, stated in framebuffer pixels, through the host's own
|
||||
/// routing and into the controller it belongs to. The GLFW callbacks fill the
|
||||
/// position and the button transitions out of GLFW and call exactly this, so a host
|
||||
/// or a test with no window can drive the routing directly.
|
||||
///
|
||||
/// The routing is the row the event lands in, with one exception that is the whole
|
||||
/// point of it: a press **captures** the row it landed in, and every motion and the
|
||||
/// release until that button comes up go to the captured row. A drag that crosses
|
||||
/// into a neighbouring figure row is therefore still the drag it began as -- the
|
||||
/// owning controller sees the motion and the release that end it -- rather than
|
||||
/// being handed to a controller whose `mode_ == None`, which would leave the owner's
|
||||
/// drag open for ever and let a later gesture resume it from the old anchor.
|
||||
void feed_pointer_event(const input::PointerEvent& e) noexcept;
|
||||
|
||||
/// The gesture controller the host feeds. Valid for the host's whole life.
|
||||
input::Controller& controller() noexcept { return controller_; }
|
||||
|
||||
@@ -183,6 +197,19 @@ class Host {
|
||||
/// that has just laid the figure out does not make it lay out again for this.
|
||||
static u32 row_in(const std::vector<Rect>& rows, f32 y) noexcept;
|
||||
|
||||
/// The controller a pointer event at (x, y) belongs to, applying the button capture:
|
||||
/// a press claims the row it landed in (`captured_row_`) and every motion and the
|
||||
/// release until the button comes up are routed to it; `controller_at()` is the pure
|
||||
/// query that never captures. Also hands the target its row's viewport, so the
|
||||
/// hit-test geometry is the last frame's.
|
||||
input::Controller& pointer_target(f32 x, f32 y, bool pressed, bool released) noexcept;
|
||||
|
||||
/// The row the pointer's events currently go to: the captured row while a drag owns
|
||||
/// the button, the row under the pointer otherwise. The run loop uses it to clear
|
||||
/// every *other* row's hot handles, so a row the pointer left cannot keep drawing a
|
||||
/// hover or a grab it last published.
|
||||
u32 pointer_row(const std::vector<Rect>& rows) noexcept;
|
||||
|
||||
/// Spreads the hover over a whole `Figure` (spec §15).
|
||||
///
|
||||
/// A figure's rows share one x, so the crosshair one row's controller placed is read back here and
|
||||
@@ -220,10 +247,19 @@ class Host {
|
||||
/// `sync_figure_crosshair()`): what a row is compared against to find the one a
|
||||
/// scripted run touched, since the figure writes one value to every row.
|
||||
/// `spread_active_` false means "spread nothing" -- the state before the first
|
||||
/// sync, and the one a clear leaves.
|
||||
/// sync, and the one a clear leaves. `spread_y_` is the horizontal line's own
|
||||
/// value, compared as well so a y-only change -- the same x in another row --
|
||||
/// is not mistaken for "nothing changed".
|
||||
f64 spread_x_ = 0.0;
|
||||
f64 spread_y_ = 0.0;
|
||||
bool spread_active_ = false;
|
||||
|
||||
/// The row a button-down drag has captured: the row its press landed in, which
|
||||
/// owns its motions and its release until the button comes up. `captured_` is
|
||||
/// false between gestures, and cleared when another figure is set.
|
||||
bool captured_ = false;
|
||||
u32 captured_row_ = 0u;
|
||||
|
||||
u32 fb_width_ = 0; ///< framebuffer size of the last refresh_sizes()
|
||||
u32 fb_height_ = 0;
|
||||
/// Framebuffer pixels per window pixel. Taken from the two sizes GLFW reports
|
||||
|
||||
Reference in new issue
Block a user