Fix edge cases of PointerEventConverter (#29998)

* Fix: ui.PointerChange.remove might contain position change, but we used to expand it into a Cancel and Remove, neither of which allows position change. A Hover event is added, and a test is updated accordingly.
* Fixed the issue where a PointerMoveEvent and a PointerCancelEvent do not receive the correct pressure.
* Refactor the calculation of delta into deltaTo.
diff --git a/packages/flutter/lib/src/gestures/converter.dart b/packages/flutter/lib/src/gestures/converter.dart
index 0364c59..8c0d209 100644
--- a/packages/flutter/lib/src/gestures/converter.dart
+++ b/packages/flutter/lib/src/gestures/converter.dart
@@ -32,6 +32,8 @@
 
   Offset lastPosition;
 
+  Offset deltaTo(Offset to) => to - lastPosition;
+
   @override
   String toString() {
     return '_PointerState(pointer: $pointer, down: $down, lastPosition: $lastPosition)';
@@ -126,14 +128,12 @@
                 tilt: datum.tilt,
               );
             }
-            final Offset offset = position - state.lastPosition;
-            state.lastPosition = position;
             yield PointerHoverEvent(
               timeStamp: timeStamp,
               kind: kind,
               device: datum.device,
               position: position,
-              delta: offset,
+              delta: state.deltaTo(position),
               buttons: datum.buttons,
               obscured: datum.obscured,
               pressureMin: datum.pressureMin,
@@ -176,14 +176,12 @@
               // Not all sources of pointer packets respect the invariant that
               // they hover the pointer to the down location before sending the
               // down event. We restore the invariant here for our clients.
-              final Offset offset = position - state.lastPosition;
-              state.lastPosition = position;
               yield PointerHoverEvent(
                 timeStamp: timeStamp,
                 kind: kind,
                 device: datum.device,
                 position: position,
-                delta: offset,
+                delta: state.deltaTo(position),
                 buttons: datum.buttons,
                 obscured: datum.obscured,
                 pressureMin: datum.pressureMin,
@@ -231,15 +229,13 @@
             assert(_pointers.containsKey(datum.device));
             final _PointerState state = _pointers[datum.device];
             assert(state.down);
-            final Offset offset = position - state.lastPosition;
-            state.lastPosition = position;
             yield PointerMoveEvent(
               timeStamp: timeStamp,
               pointer: state.pointer,
               kind: kind,
               device: datum.device,
               position: position,
-              delta: offset,
+              delta: state.deltaTo(position),
               buttons: datum.buttons,
               obscured: datum.obscured,
               pressure: datum.pressure,
@@ -255,6 +251,7 @@
               tilt: datum.tilt,
               platformData: datum.platformData,
             );
+            state.lastPosition = position;
             break;
           case ui.PointerChange.up:
           case ui.PointerChange.cancel:
@@ -267,15 +264,13 @@
               // event. For example, in the iOS simulator, of you drag outside the
               // window, you'll get a stream of pointers that violates that
               // invariant. We restore the invariant here for our clients.
-              final Offset offset = position - state.lastPosition;
-              state.lastPosition = position;
               yield PointerMoveEvent(
                 timeStamp: timeStamp,
                 pointer: state.pointer,
                 kind: kind,
                 device: datum.device,
                 position: position,
-                delta: offset,
+                delta: state.deltaTo(position),
                 buttons: datum.buttons,
                 obscured: datum.obscured,
                 pressure: datum.pressure,
@@ -326,6 +321,7 @@
                 position: position,
                 buttons: datum.buttons,
                 obscured: datum.obscured,
+                pressure: datum.pressure,
                 pressureMin: datum.pressureMin,
                 pressureMax: datum.pressureMax,
                 distance: datum.distance,
@@ -349,8 +345,31 @@
                 pointer: state.pointer,
                 kind: kind,
                 device: datum.device,
+                position: state.lastPosition, // Change position in Hover
+                buttons: datum.buttons,
+                obscured: datum.obscured,
+                pressure: datum.pressure,
+                pressureMin: datum.pressureMin,
+                pressureMax: datum.pressureMax,
+                distance: datum.distance,
+                distanceMax: datum.distanceMax,
+                size: datum.size,
+                radiusMajor: radiusMajor,
+                radiusMinor: radiusMinor,
+                radiusMin: radiusMin,
+                radiusMax: radiusMax,
+                orientation: datum.orientation,
+                tilt: datum.tilt,
+              );
+            }
+            if (position != state.lastPosition) {
+              yield PointerHoverEvent(
+                timeStamp: timeStamp,
+                kind: kind,
+                device: datum.device,
                 position: position,
                 buttons: datum.buttons,
+                delta: state.deltaTo(position),
                 obscured: datum.obscured,
                 pressureMin: datum.pressureMin,
                 pressureMax: datum.pressureMax,
@@ -363,6 +382,7 @@
                 radiusMax: radiusMax,
                 orientation: datum.orientation,
                 tilt: datum.tilt,
+                synthesized: true,
               );
             }
             _pointers.remove(datum.device);
@@ -390,8 +410,6 @@
               // before sending the scroll event, if necessary, so that clients
               // don't have to worry about native ordering of hover and scroll
               // events.
-              final Offset offset = position - state.lastPosition;
-              state.lastPosition = position;
               if (state.down) {
                 yield PointerMoveEvent(
                   timeStamp: timeStamp,
@@ -399,9 +417,10 @@
                   kind: kind,
                   device: datum.device,
                   position: position,
-                  delta: offset,
+                  delta: state.deltaTo(position),
                   buttons: datum.buttons,
                   obscured: datum.obscured,
+                  pressure: datum.pressure,
                   pressureMin: datum.pressureMin,
                   pressureMax: datum.pressureMax,
                   distanceMax: datum.distanceMax,
@@ -420,7 +439,7 @@
                   kind: kind,
                   device: datum.device,
                   position: position,
-                  delta: offset,
+                  delta: state.deltaTo(position),
                   buttons: datum.buttons,
                   obscured: datum.obscured,
                   pressureMin: datum.pressureMin,
@@ -437,6 +456,7 @@
                   synthesized: true,
                 );
               }
+              state.lastPosition = position;
             }
             final Offset scrollDelta =
                 Offset(datum.scrollDeltaX, datum.scrollDeltaY) / devicePixelRatio;
diff --git a/packages/flutter/lib/src/gestures/events.dart b/packages/flutter/lib/src/gestures/events.dart
index 6f68aec..dca70a9 100644
--- a/packages/flutter/lib/src/gestures/events.dart
+++ b/packages/flutter/lib/src/gestures/events.dart
@@ -346,21 +346,21 @@
     double orientation = 0.0,
     double tilt = 0.0,
   }) : super(
-    timeStamp: timeStamp,
-    kind: kind,
-    device: device,
-    position: position,
-    obscured: obscured,
-    pressure: pressure,
-    pressureMin: pressureMin,
-    pressureMax: pressureMax,
-    distance: distance,
-    distanceMax: distanceMax,
-    radiusMin: radiusMin,
-    radiusMax: radiusMax,
-    orientation: orientation,
-    tilt: tilt
-  );
+         timeStamp: timeStamp,
+         kind: kind,
+         device: device,
+         position: position,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+       );
 }
 
 /// The device is no longer tracking the pointer.
@@ -383,18 +383,18 @@
     double radiusMin = 0.0,
     double radiusMax = 0.0,
   }) : super(
-    timeStamp: timeStamp,
-    kind: kind,
-    device: device,
-    position: null,
-    obscured: obscured,
-    pressure: pressure,
-    pressureMin: pressureMin,
-    pressureMax: pressureMax,
-    distanceMax: distanceMax,
-    radiusMin: radiusMin,
-    radiusMax: radiusMax,
-  );
+         timeStamp: timeStamp,
+         kind: kind,
+         device: device,
+         position: null,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distanceMax: distanceMax,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+       );
 }
 
 /// The pointer has moved with respect to the device while the pointer is not
@@ -433,28 +433,28 @@
     double tilt = 0.0,
     bool synthesized = false,
   }) : super(
-    timeStamp: timeStamp,
-    kind: kind,
-    device: device,
-    position: position,
-    delta: delta,
-    buttons: buttons,
-    down: false,
-    obscured: obscured,
-    pressure: pressure,
-    pressureMin: pressureMin,
-    pressureMax: pressureMax,
-    distance: distance,
-    distanceMax: distanceMax,
-    size: size,
-    radiusMajor: radiusMajor,
-    radiusMinor: radiusMinor,
-    radiusMin: radiusMin,
-    radiusMax: radiusMax,
-    orientation: orientation,
-    tilt: tilt,
-    synthesized: synthesized,
-  );
+         timeStamp: timeStamp,
+         kind: kind,
+         device: device,
+         position: position,
+         delta: delta,
+         buttons: buttons,
+         down: false,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         synthesized: synthesized,
+       );
 }
 
 /// The pointer has moved with respect to the device while the pointer is not
@@ -493,28 +493,28 @@
     double tilt = 0.0,
     bool synthesized = false,
   }) : super(
-    timeStamp: timeStamp,
-    kind: kind,
-    device: device,
-    position: position,
-    delta: delta,
-    buttons: buttons,
-    down: false,
-    obscured: obscured,
-    pressure: pressure,
-    pressureMin: pressureMin,
-    pressureMax: pressureMax,
-    distance: distance,
-    distanceMax: distanceMax,
-    size: size,
-    radiusMajor: radiusMajor,
-    radiusMinor: radiusMinor,
-    radiusMin: radiusMin,
-    radiusMax: radiusMax,
-    orientation: orientation,
-    tilt: tilt,
-    synthesized: synthesized,
-  );
+         timeStamp: timeStamp,
+         kind: kind,
+         device: device,
+         position: position,
+         delta: delta,
+         buttons: buttons,
+         down: false,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         synthesized: synthesized,
+       );
 
   /// Creates an enter event from a [PointerHoverEvent].
   ///
@@ -586,28 +586,28 @@
     double tilt = 0.0,
     bool synthesized = false,
   }) : super(
-    timeStamp: timeStamp,
-    kind: kind,
-    device: device,
-    position: position,
-    delta: delta,
-    buttons: buttons,
-    down: false,
-    obscured: obscured,
-    pressure: pressure,
-    pressureMin: pressureMin,
-    pressureMax: pressureMax,
-    distance: distance,
-    distanceMax: distanceMax,
-    size: size,
-    radiusMajor: radiusMajor,
-    radiusMinor: radiusMinor,
-    radiusMin: radiusMin,
-    radiusMax: radiusMax,
-    orientation: orientation,
-    tilt: tilt,
-    synthesized: synthesized,
-  );
+         timeStamp: timeStamp,
+         kind: kind,
+         device: device,
+         position: position,
+         delta: delta,
+         buttons: buttons,
+         down: false,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         synthesized: synthesized,
+       );
 
   /// Creates an exit event from a [PointerHoverEvent].
   ///
@@ -668,27 +668,27 @@
     double orientation = 0.0,
     double tilt = 0.0,
   }) : super(
-    timeStamp: timeStamp,
-    pointer: pointer,
-    kind: kind,
-    device: device,
-    position: position,
-    buttons: buttons,
-    down: true,
-    obscured: obscured,
-    pressure: pressure,
-    pressureMin: pressureMin,
-    pressureMax: pressureMax,
-    distance: 0.0,
-    distanceMax: distanceMax,
-    size: size,
-    radiusMajor: radiusMajor,
-    radiusMinor: radiusMinor,
-    radiusMin: radiusMin,
-    radiusMax: radiusMax,
-    orientation: orientation,
-    tilt: tilt
-  );
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         buttons: buttons,
+         down: true,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: 0.0,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+       );
 }
 
 /// The pointer has moved with respect to the device while the pointer is in
@@ -725,30 +725,30 @@
     int platformData = 0,
     bool synthesized = false,
   }) : super(
-    timeStamp: timeStamp,
-    pointer: pointer,
-    kind: kind,
-    device: device,
-    position: position,
-    delta: delta,
-    buttons: buttons,
-    down: true,
-    obscured: obscured,
-    pressure: pressure,
-    pressureMin: pressureMin,
-    pressureMax: pressureMax,
-    distance: 0.0,
-    distanceMax: distanceMax,
-    size: size,
-    radiusMajor: radiusMajor,
-    radiusMinor: radiusMinor,
-    radiusMin: radiusMin,
-    radiusMax: radiusMax,
-    orientation: orientation,
-    tilt: tilt,
-    platformData: platformData,
-    synthesized: synthesized,
-  );
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         delta: delta,
+         buttons: buttons,
+         down: true,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: 0.0,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         platformData: platformData,
+         synthesized: synthesized,
+       );
 }
 
 /// The pointer has stopped making contact with the device.
@@ -777,27 +777,27 @@
     double orientation = 0.0,
     double tilt = 0.0,
   }) : super(
-    timeStamp: timeStamp,
-    pointer: pointer,
-    kind: kind,
-    device: device,
-    position: position,
-    buttons: buttons,
-    down: false,
-    obscured: obscured,
-    pressure: pressure,
-    pressureMin: pressureMin,
-    pressureMax: pressureMax,
-    distance: distance,
-    distanceMax: distanceMax,
-    size: size,
-    radiusMajor: radiusMajor,
-    radiusMinor: radiusMinor,
-    radiusMin: radiusMin,
-    radiusMax: radiusMax,
-    orientation: orientation,
-    tilt: tilt
-  );
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         buttons: buttons,
+         down: false,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+       );
 }
 
 /// An event that corresponds to a discrete pointer signal.
@@ -815,12 +815,12 @@
     int device = 0,
     Offset position = Offset.zero,
   }) : super(
-    timeStamp: timeStamp,
-    pointer: pointer,
-    kind: kind,
-    device: device,
-    position: position,
-  );
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+       );
 }
 
 /// The pointer issued a scroll event.
@@ -843,11 +843,11 @@
        assert(position != null),
        assert(scrollDelta != null),
        super(
-    timeStamp: timeStamp,
-    kind: kind,
-    device: device,
-    position: position,
-  );
+         timeStamp: timeStamp,
+         kind: kind,
+         device: device,
+         position: position,
+       );
 
   /// The amount to scroll, in logical pixels.
   final Offset scrollDelta;
@@ -885,25 +885,25 @@
     double orientation = 0.0,
     double tilt = 0.0,
   }) : super(
-    timeStamp: timeStamp,
-    pointer: pointer,
-    kind: kind,
-    device: device,
-    position: position,
-    buttons: buttons,
-    down: false,
-    obscured: obscured,
-    pressure: pressure,
-    pressureMin: pressureMin,
-    pressureMax: pressureMax,
-    distance: distance,
-    distanceMax: distanceMax,
-    size: size,
-    radiusMajor: radiusMajor,
-    radiusMinor: radiusMinor,
-    radiusMin: radiusMin,
-    radiusMax: radiusMax,
-    orientation: orientation,
-    tilt: tilt
-  );
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         buttons: buttons,
+         down: false,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+       );
 }
diff --git a/packages/flutter/test/gestures/gesture_binding_test.dart b/packages/flutter/test/gestures/gesture_binding_test.dart
index 31f1477..f2924a6 100644
--- a/packages/flutter/test/gestures/gesture_binding_test.dart
+++ b/packages/flutter/test/gestures/gesture_binding_test.dart
@@ -195,13 +195,14 @@
     final List<PointerEvent> events = PointerEventConverter.expand(
       packet.data, ui.window.devicePixelRatio).toList();
 
-    expect(events.length, 5);
+    expect(events.length, 6);
     expect(events[0].runtimeType, equals(PointerAddedEvent));
     expect(events[1].runtimeType, equals(PointerHoverEvent));
     expect(events[1].delta, equals(const Offset(5.0, 7.0)));
     expect(events[2].runtimeType, equals(PointerDownEvent));
     expect(events[3].runtimeType, equals(PointerCancelEvent));
-    expect(events[4].runtimeType, equals(PointerRemovedEvent));
+    expect(events[4].runtimeType, equals(PointerHoverEvent));
+    expect(events[5].runtimeType, equals(PointerRemovedEvent));
   });
 
   test('Can expand pointer scroll events', () {