fix(regionSelector): freehand loop closed on its start pixel draws, not clicks

- circleDrawn predicate (Freehand && points.length > 1) excluded from the
  click branch, so zero-displacement closed loops reach the circle branch
- points cleared at press: stale samples from a cancelled grab could
  otherwise flip the new click/draw routing
- toolbar mode tabs ignore mid-drag swaps, matching the R/C key guards
This commit is contained in:
DJ committed 2026-07-05 09:22:16 -04:00
1 parent 304ee48a03
commit 2fa907c451
2 files changed
+14 -2

No files matched your search

+1
View File
@@ -22,6 +22,7 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
### Fixed
- **Edge-Straddling Window Targeting** - Clicking a window or layer that hangs off the monitor's left/top edge now selects it (matching its hover highlight) instead of dismissing the overlay; validity no longer sign-checks the region's origin. The capture is trimmed to the window's visible on-screen extent — screen clamping used to slide such regions fully on-screen at full size, which would have captured unrelated content beside the window
- **Freehand Closed Loops** - A freehand loop that ends precisely on its starting pixel is now captured as the drawn shape instead of being discarded as a click. Consequence: any Freehand gesture with 2+ motion samples counts as a draw even if it returns to its start pixel — a jittery click there now captures a tiny shape rather than selecting the content region under the cursor (matching how Freehand already treated jitter with any net drift)
- **Invisible Region Selection** - Clicks no longer select regions whose highlight type is disabled (`regionSelector.targetRegions.*: false`); hit-testing now follows the same gating as the visuals. Note this also changes Freehand mode: a stationary click on a window (never highlighted there) previously captured that window silently — it now dismisses the overlay unless the click lands inside a highlighted content region, which stays selectable in Freehand, consistent with what's on screen
---
@@ -416,14 +416,20 @@ PanelWindow {
// Controls
onPressed: (mouse) => {
// Every new gesture invalidates any freehand mask staged by a previous
// (e.g. Esc-cancelled) selection
// (e.g. Esc-cancelled) selection — and any recorded path (startDrag
// doesn't clear points; a cancelled grab can leave them populated, and
// stale points would now flip the click/draw routing below)
root.pendingSnipPoints = [];
dragState.points = [];
dragState.startDrag(mouse.x, mouse.y, mouse.button);
root.updateTargetedRegion(mouse.x, mouse.y);
}
onReleased: (mouse) => {
// A closed freehand loop can end on its exact start pixel, leaving
// draggedAway false — recorded points, not displacement, prove a draw
const circleDrawn = root.isCircleSelection && dragState.points.length > 1;
// Detect if it was a click -> Try to select targeted region
if (!dragState.draggedAway) {
if (!dragState.draggedAway && !circleDrawn) {
if (dragState.targetedRegionValid()) {
const padding = Config.options.regionSelector.targetRegions.selectionPadding;
if (root.isCropMode) {
@@ -618,11 +624,16 @@ PanelWindow {
selectionMode: root.selectionMode
captureMode: root.captureMode
onSelectionModeSelected: (mode) => {
// Ignored mid-drag, same as the R/C keys: a mode swap resets
// dragState and orphans the pending release (touch can reach
// the tabs while a mouse drag is live)
if (dragState.dragging) return;
// Crop implies rectangles (tab is disabled then too — belt and braces)
if (root.isCropMode && mode === RegionSelection.SelectionMode.Circle) return;
if (root.selectionMode !== mode) root.selectionMode = mode;
}
onCaptureModeSelected: (mode) => {
if (dragState.dragging) return;
if (root.captureMode !== mode) root.captureMode = mode;
}
Synchronizer on action {