#553 viewer: prevent magnifier recognizer base to accept gestures it cannot handle
This commit is contained in:
parent
3306bc6340
commit
d4e7ca4b3d
2 changed files with 57 additions and 44 deletions
|
@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file.
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Video: switching to PiP when going home with gesture navigation
|
- Video: switching to PiP when going home with gesture navigation
|
||||||
|
- Viewer: swiping gestures not being handled in some cases
|
||||||
- Viewer: multi-page context update when removing burst entries
|
- Viewer: multi-page context update when removing burst entries
|
||||||
- Info: editing tags with placeholders
|
- Info: editing tags with placeholders
|
||||||
- prevent editing item when Exif editing changes mime type
|
- prevent editing item when Exif editing changes mime type
|
||||||
|
|
|
@ -47,15 +47,16 @@ class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
||||||
void handleEvent(PointerEvent event) {
|
void handleEvent(PointerEvent event) {
|
||||||
if (scope.axis.isNotEmpty) {
|
if (scope.axis.isNotEmpty) {
|
||||||
var didChangeConfiguration = false;
|
var didChangeConfiguration = false;
|
||||||
|
final pointer = event.pointer;
|
||||||
if (event is PointerMoveEvent) {
|
if (event is PointerMoveEvent) {
|
||||||
if (!event.synthesized) {
|
if (!event.synthesized) {
|
||||||
_pointerLocations[event.pointer] = event.position;
|
_pointerLocations[pointer] = event.position;
|
||||||
}
|
}
|
||||||
} else if (event is PointerDownEvent) {
|
} else if (event is PointerDownEvent) {
|
||||||
_pointerLocations[event.pointer] = event.position;
|
_pointerLocations[pointer] = event.position;
|
||||||
didChangeConfiguration = true;
|
didChangeConfiguration = true;
|
||||||
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
|
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
|
||||||
_pointerLocations.remove(event.pointer);
|
_pointerLocations.remove(pointer);
|
||||||
didChangeConfiguration = true;
|
didChangeConfiguration = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +68,9 @@ class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
||||||
_initialSpan = _currentSpan;
|
_initialSpan = _currentSpan;
|
||||||
}
|
}
|
||||||
|
|
||||||
_decideIfWeAcceptEvent(event);
|
if (event is PointerMoveEvent && (_areMultiPointers() || (_shouldMove() && _isOverSlop(event.kind)))) {
|
||||||
|
acceptGesture(pointer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
super.handleEvent(event);
|
super.handleEvent(event);
|
||||||
}
|
}
|
||||||
|
@ -93,54 +96,63 @@ class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
||||||
_currentSpan = count > 0 ? totalDeviation / count : 0.0;
|
_currentSpan = count > 0 ? totalDeviation / count : 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _decideIfWeAcceptEvent(PointerEvent event) {
|
// when there are multiple pointers, we always accept the gesture to scale
|
||||||
if (event is! PointerMoveEvent) return;
|
// as this is not competing with single taps or other drag gestures
|
||||||
|
bool _areMultiPointers() => _pointerLocations.keys.length >= 2;
|
||||||
|
|
||||||
if (_pointerLocations.keys.length >= 2) {
|
bool _shouldMove() {
|
||||||
// when there are multiple pointers, we always accept the gesture to scale
|
final move = _initialFocalPoint! - _currentFocalPoint!;
|
||||||
// as this is not competing with single taps or other drag gestures
|
|
||||||
acceptGesture(event.pointer);
|
// e.g. vertical drag to adjust brightness instead of panning
|
||||||
return;
|
if (scope.acceptPointerEvent?.call(move) ?? false) return true;
|
||||||
}
|
|
||||||
|
// e.g. double tap & drag for one finger zoom
|
||||||
|
if (doubleTapDetails.value != null) return true;
|
||||||
|
|
||||||
final validateAxis = scope.axis;
|
final validateAxis = scope.axis;
|
||||||
final canFling = scope.escapeByFling;
|
final canFling = scope.escapeByFling;
|
||||||
final move = _initialFocalPoint! - _currentFocalPoint!;
|
if (validateAxis.length == 2) {
|
||||||
bool shouldMove = scope.acceptPointerEvent?.call(move) ?? false;
|
// the image is the descendant of gesture detector(s) handling drag in both directions
|
||||||
|
final shouldMoveX = validateAxis.contains(Axis.horizontal) && hitDetector.shouldMoveX(move, canFling);
|
||||||
if (!shouldMove) {
|
final shouldMoveY = validateAxis.contains(Axis.vertical) && hitDetector.shouldMoveY(move, canFling);
|
||||||
if (validateAxis.length == 2) {
|
if (shouldMoveX == shouldMoveY) {
|
||||||
// the image is the descendant of gesture detector(s) handling drag in both directions
|
// consistently can/cannot pan the image in both direction the same way
|
||||||
final shouldMoveX = validateAxis.contains(Axis.horizontal) && hitDetector.shouldMoveX(move, canFling);
|
return shouldMoveX;
|
||||||
final shouldMoveY = validateAxis.contains(Axis.vertical) && hitDetector.shouldMoveY(move, canFling);
|
|
||||||
if (shouldMoveX == shouldMoveY) {
|
|
||||||
// consistently can/cannot pan the image in both direction the same way
|
|
||||||
shouldMove = shouldMoveX;
|
|
||||||
} else {
|
|
||||||
// can pan the image in one direction, but should yield to an ascendant gesture detector in the other one
|
|
||||||
// the gesture direction angle is in ]-pi, pi], cf `Offset` doc for details
|
|
||||||
shouldMove = (isXPan(move) && shouldMoveX) || (isYPan(move) && shouldMoveY);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// the image is the descendant of a gesture detector handling drag in one direction
|
// can pan the image in one direction, but should yield to an ascendant gesture detector in the other one
|
||||||
shouldMove = validateAxis.contains(Axis.vertical) ? hitDetector.shouldMoveY(move, canFling) : hitDetector.shouldMoveX(move, canFling);
|
// the gesture direction angle is in ]-pi, pi], cf `Offset` doc for details
|
||||||
|
return (isXPan(move) && shouldMoveX) || (isYPan(move) && shouldMoveY);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// the image is the descendant of a gesture detector handling drag in one direction
|
||||||
|
return validateAxis.contains(Axis.vertical) ? hitDetector.shouldMoveY(move, canFling) : hitDetector.shouldMoveX(move, canFling);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final doubleTap = doubleTapDetails.value != null;
|
bool _isOverSlop(PointerDeviceKind kind) {
|
||||||
if (shouldMove || doubleTap) {
|
final spanDelta = (_currentSpan! - _initialSpan!).abs();
|
||||||
final pointerDeviceKind = event.kind;
|
final focalPointDelta = (_currentFocalPoint! - _initialFocalPoint!).distance;
|
||||||
final spanDelta = (_currentSpan! - _initialSpan!).abs();
|
// warning: do not compare `focalPointDelta` to `kPanSlop`
|
||||||
final focalPointDelta = (_currentFocalPoint! - _initialFocalPoint!).distance;
|
// `ScaleGestureRecognizer` uses `kPanSlop` (or platform settings, cf gestures/events.dart `computePanSlop`),
|
||||||
// warning: do not compare `focalPointDelta` to `kPanSlop`
|
// but `HorizontalDragGestureRecognizer` uses `kTouchSlop` (or platform settings, cf gestures/events.dart `computeHitSlop`)
|
||||||
// `ScaleGestureRecognizer` uses `kPanSlop` (or platform settings, cf gestures/events.dart `computePanSlop`),
|
// and the magnifier recognizer may compete with the `HorizontalDragGestureRecognizer` from a containing `PageView`
|
||||||
// but `HorizontalDragGestureRecognizer` uses `kTouchSlop` (or platform settings, cf gestures/events.dart `computeHitSlop`)
|
// setting `touchSlopFactor` to 2 restores default `ScaleGestureRecognizer` behaviour as `kPanSlop = kTouchSlop * 2.0`
|
||||||
// and the magnifier recognizer may compete with the `HorizontalDragGestureRecognizer` from a containing `PageView`
|
// setting `touchSlopFactor` in [0, 1] will allow this recognizer to accept the gesture before the one from `PageView`
|
||||||
// setting `touchSlopFactor` to 2 restores default `ScaleGestureRecognizer` behaviour as `kPanSlop = kTouchSlop * 2.0`
|
return spanDelta > computeScaleSlop(kind) || focalPointDelta > computeHitSlop(kind, gestureSettings) * scope.touchSlopFactor;
|
||||||
// setting `touchSlopFactor` in [0, 1] will allow this recognizer to accept the gesture before the one from `PageView`
|
}
|
||||||
if (spanDelta > computeScaleSlop(pointerDeviceKind) || focalPointDelta > computeHitSlop(pointerDeviceKind, gestureSettings) * scope.touchSlopFactor) {
|
|
||||||
acceptGesture(event.pointer);
|
@override
|
||||||
}
|
void resolve(GestureDisposition disposition) {
|
||||||
|
switch (disposition) {
|
||||||
|
case GestureDisposition.accepted:
|
||||||
|
// do not let super `ScaleGestureRecognizer` accept gestures
|
||||||
|
// when it should yield to other recognizers
|
||||||
|
final canAccept = _areMultiPointers() || _shouldMove();
|
||||||
|
super.resolve(canAccept ? GestureDisposition.accepted : GestureDisposition.rejected);
|
||||||
|
break;
|
||||||
|
case GestureDisposition.rejected:
|
||||||
|
super.resolve(disposition);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue