feat(widgets): circle lock_center — pin the centre, keep the radius draggable - #58
Merged
Conversation
…draggable A ring measured on a power spectrum is centred on the DC term, so its centre is not a free parameter: a draggable one is a control that can only ever be wrong, and one nudged off-centre silently corrupts every radius measured from it. Enforced in the HIT-TEST, not afterwards. Correcting the centre from Python when the drag settles cannot work -- _doDrag2d recomputes the position every frame from its own grab-time snapshot, so a pushed-back centre is overwritten on the next mousemove; the ring tracks the cursor for the whole drag and jumps back on release, which reads as a broken lock rather than a locked ring. Refusing the grab also leaves the hover cursor at 'default' and lets the drag fall through to the plot's own pan, so the ring simply stops being a handle. _doDrag2d keeps a guard for a drag already in flight when the flag is set. test_circle_lock_center.py drives real browser drags: the two lock assertions fail with the hit-test gate removed, and the unlocked circle + the radius handle keep working.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #58 +/- ##
=======================================
Coverage 90.74% 90.74%
=======================================
Files 40 40
Lines 4526 4526
=======================================
Hits 4107 4107
Misses 419 419 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A ring measured on a power spectrum is centred on the DC term. Its centre is not
a free parameter, so a draggable one is a control that can only ever be wrong —
and a ring nudged off-centre silently corrupts every radius measured from it.
The centre is pinned; the radius handle still drags.
Why the hit-test, and not a callback
The obvious implementation lives entirely in user code: watch the widget, and
put the centre back when the drag settles. It does not work, and the way it
fails is worth writing down because it looks like it is working when you read
the code.
_doDrag2drecomputes the position every frame from its own grab-timesnapshot (
w.cx = s.cx + dix). A centre pushed from Python is thereforeoverwritten on the very next mousemove. What the user sees is the ring tracking
the cursor for the whole drag and snapping home on release — which reads as a
broken lock rather than a locked ring. Moving the correction from
pointer_upto
pointer_moveonly shortens the wander; it cannot remove it, because thefight is one round trip against one animation frame.
So the constraint has to be applied where the grab is decided.
_ovHitTest2dnow
continues past a locked circle's body instead of returningmode:'move'.Two things fall out of that for free:
defaultrather than promising a move it will refuse;image does everywhere else — so the ring simply stops being a handle, rather
than becoming a dead zone.
_doDrag2dkeeps a matching guard for the one case the hit-test cannot cover:a drag already in flight when the flag is turned on.
Tests
test_circle_lock_center.py— 5 API tests (including that the flag survivesto_dict(), since JS reads the widget dict) and 4 real browser drags:The two lock assertions fail with the hit-test
continueremoved. 578 tests intest_interactive/pass.Downstream
This is the fix for a Ground Crew calibration bug: the FFT ring wandered under
exactly the Python-side snap-back described above. Its e2e drives a real drag
and measures the ring against the FFT's DC disc — an oracle chosen because the
ring's canvas position moves 1:1 with a pan, so locked, snap-back and unlocked
all reported an identical 119.6 px displacement until it was measured relative
to the image instead. Against this branch it passes; against a snap-back lock it
fails at ~125 px.