evaluateMove method
bool
evaluateMove(
- double y,
- double x,
- GlassSheetState currentState,
- GlassSheetState maxState,
- double threshold, {
- required bool canScrollListUp,
- required bool hasScrollClients,
- required bool atTopDetent,
Returns true if gesture should be claimed by sheet (not scroll).
The decision is not final. A sheet that reaches its topmost detent part-way through an upward drag hands the rest of that pointer to the content, so growing the sheet and scrolling its content are one continuous gesture.
atTopDetent reports measured travel, and the handovers use it rather than
currentState == maxState: currentState carries the drag's predicted
target mid-gesture, so keying off it would stand the sheet down while it
was still travelling, stranding it short of the detent it was predicting.
Implementation
bool evaluateMove(
double y,
double x,
GlassSheetState currentState,
GlassSheetState maxState,
double threshold, {
required bool canScrollListUp,
required bool hasScrollClients,
required bool atTopDetent,
}) {
final double previousY = lastMoveY;
lastMoveY = y;
final bool draggingUp = y < previousY;
final bool draggingDown = y > previousY;
if (phase == GesturePhase.contentDrag) {
// Re-evaluated on every move rather than only at the decision moment: a
// drag that began below the top detent stops being the sheet's the
// instant the sheet tops out. Standing down here lets the same finger
// carry on as a content scroll, instead of rubber-banding a sheet that
// cannot grow any further.
if (atTopDetent && draggingUp && hasScrollClients) {
phase = GesturePhase.scrolling;
isVerticalGesture = true;
return false;
}
return true;
}
if (phase == GesturePhase.scrolling) {
// The reverse handover. Content pulled back to its top gives the rest of
// a downward drag to the sheet, so returning to the top and collapsing
// are one gesture rather than two. Gated on a vertical gesture: a
// horizontal swipe also rests in this phase and must never wake the
// sheet.
if (isVerticalGesture &&
atTopDetent &&
draggingDown &&
!canScrollListUp) {
phase = GesturePhase.contentDrag;
return true;
}
return false;
}
if (phase == GesturePhase.handleDrag) return true;
final dy = (y - dragStartY).abs();
final dx = (x - dragStartX).abs();
// Decide the axis ONCE per touch, on the first movement past the
// threshold, and hold that decision for the rest of the gesture.
//
// The comparison is still cumulative from the touch origin, but it is now
// only ever read at the moment of decision, which fixes two things:
//
// * A gesture that started horizontal stays horizontal. Previously the
// test was re-run on every move, so a sideways swipe would grab the
// sheet the instant total vertical travel happened to exceed total
// horizontal — and conversely, after a long sideways scroll the sheet
// could not be dragged at all without out-travelling that distance
// vertically, which on a wide swipe is most of the screen.
// * The reverse latch is gone. `contentDrag` was only ever set from a
// vertical-looking move and then never re-evaluated, so a horizontal
// swipe that began with a slight vertical wobble dragged the sheet for
// its whole duration.
//
// Below the threshold on both axes the gesture is still undecided; return
// false and wait rather than guessing from a few pixels of noise.
if (dy <= threshold && dx <= threshold) return false;
if (dx >= dy) {
// Horizontal. An inner scrollable owns this touch; the sheet stays out
// of it until the finger lifts and `reset()` clears the phase.
phase = GesturePhase.scrolling;
return false;
}
if (dy > threshold) {
// At the topmost detent the sheet can't expand further, so an inner
// scroll view (if any) owns the gesture. maxState is `full` for a
// normal sheet but `half` for a half-only sheet (enableFull: false),
// which is why this compares against maxState rather than hardcoding
// full — otherwise a half-only sheet would never scroll its content.
if (currentState == maxState) {
if ((y - dragStartY) < 0) {
// Swiping UP
if (hasScrollClients) {
phase = GesturePhase.scrolling;
isVerticalGesture = true;
return false;
} else {
phase = GesturePhase.contentDrag;
isVerticalGesture = true;
return true;
}
} else {
// Swiping DOWN
if (canScrollListUp) {
phase = GesturePhase.scrolling;
isVerticalGesture = true;
return false;
} else {
phase = GesturePhase.contentDrag;
isVerticalGesture = true;
return true;
}
}
} else {
phase = GesturePhase.contentDrag;
isVerticalGesture = true;
return true;
}
}
return false;
}