New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JDK-8298060: Fix precision bug in gesture recognizer classes #966
Changes from 2 commits
e359d86
eef709a
0477a1d
ce9979f
db6b33c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,76 +25,72 @@ | |
|
||
package com.sun.javafx.tk.quantum; | ||
|
||
import com.sun.glass.events.KeyEvent; | ||
import com.sun.glass.events.TouchEvent; | ||
|
||
import java.security.AccessController; | ||
import java.security.PrivilegedAction; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import javafx.util.Duration; | ||
import javafx.event.EventType; | ||
import javafx.scene.input.RotateEvent; | ||
import javafx.animation.Animation; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import com.sun.glass.events.KeyEvent; | ||
import com.sun.glass.events.TouchEvent; | ||
|
||
import javafx.animation.Interpolator; | ||
import javafx.animation.KeyFrame; | ||
import javafx.animation.KeyValue; | ||
import javafx.animation.Timeline; | ||
import javafx.beans.property.DoubleProperty; | ||
import javafx.beans.property.SimpleDoubleProperty; | ||
import javafx.scene.input.RotateEvent; | ||
import javafx.util.Duration; | ||
|
||
class RotateGestureRecognizer implements GestureRecognizer { | ||
private ViewScene scene; | ||
private static final double MAX_INITIAL_VELOCITY = 500; | ||
private static final double ROTATION_INERTIA_MILLIS = 1500; | ||
private static final long ROTATION_INERTIA_THRESHOLD_NANOS = TimeUnit.MILLISECONDS.toNanos(300); | ||
|
||
// gesture will be activated if |rotation| > rotationThresholdDegrees | ||
private static double rotationThresholdDegrees = 5; | ||
private static boolean rotationInertiaEnabled = true; | ||
|
||
// gesture will be activated if |rotation| > ROTATATION_THRESHOLD | ||
private static double ROTATATION_THRESHOLD = 5; //in degrees | ||
private static boolean ROTATION_INERTIA_ENABLED = true; | ||
private static double MAX_INITIAL_VELOCITY = 500; | ||
private static double ROTATION_INERTIA_MILLIS = 1500; | ||
static { | ||
@SuppressWarnings("removal") | ||
var dummy = AccessController.doPrivileged((PrivilegedAction<Void>) () -> { | ||
String s = System.getProperty("com.sun.javafx.gestures.rotate.threshold"); | ||
if (s != null) { | ||
ROTATATION_THRESHOLD = Double.valueOf(s); | ||
rotationThresholdDegrees = Double.valueOf(s); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we catch a NumberFormatException and re-throw it with a meaningful text message? Or the current behavior is ok? ditto on line 64 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose that would be out of scope for this PR. I agree though that it would be good to mention that the double that couldn't be parsed is the one we got from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that sort of change seems out of scope. |
||
} | ||
s = System.getProperty("com.sun.javafx.gestures.rotate.inertia"); | ||
if (s != null) { | ||
ROTATION_INERTIA_ENABLED = Boolean.valueOf(s); | ||
rotationInertiaEnabled = Boolean.valueOf(s); | ||
} | ||
return null; | ||
}); | ||
} | ||
|
||
private RotateRecognitionState state = RotateRecognitionState.IDLE; | ||
private Timeline inertiaTimeline = new Timeline(); | ||
private DoubleProperty inertiaRotationVelocity = new SimpleDoubleProperty(); | ||
private double initialInertiaRotationVelocity = 0; | ||
private double rotationStartTime = 0; | ||
private double lastTouchEventTime = 0; | ||
private final Timeline inertiaTimeline = new Timeline(); | ||
private final DoubleProperty inertiaRotationVelocity = new SimpleDoubleProperty(); | ||
private final Map<Long, TouchPointTracker> trackers = new HashMap<>(); // from MultiTouchTracker | ||
|
||
// from MultiTouchTracker | ||
Map<Long, TouchPointTracker> trackers = | ||
new HashMap<Long, TouchPointTracker>(); | ||
private ViewScene scene; | ||
private RotateRecognitionState state = RotateRecognitionState.IDLE; | ||
private double initialInertiaRotationVelocity; | ||
private long rotationStartNanos; | ||
|
||
int modifiers; | ||
boolean direct; | ||
private int modifiers; | ||
private boolean direct; | ||
|
||
//private int touchCount; | ||
private int currentTouchCount = 0; | ||
private int currentTouchCount; | ||
private boolean touchPointsSetChanged; | ||
private boolean touchPointsPressed; | ||
int touchPointsInEvent; | ||
long touchPointID1 = -1; | ||
long touchPointID2 = -1; | ||
double centerX, centerY; | ||
double centerAbsX, centerAbsY; | ||
private long touchPointID1 = -1; | ||
private long touchPointID2 = -1; | ||
private double centerX, centerY; | ||
private double centerAbsX, centerAbsY; | ||
|
||
double currentRotation; | ||
double angleReference; | ||
double totalRotation = 0; | ||
double inertiaLastTime = 0; | ||
private double currentRotation; | ||
private double angleReference; | ||
private double totalRotation; | ||
private double inertiaLastTime; | ||
|
||
RotateGestureRecognizer(final ViewScene scene) { | ||
this.scene = scene; | ||
|
@@ -115,27 +111,25 @@ public void notifyBeginTouchEvent(long time, int modifiers, boolean isDirect, | |
params(modifiers, isDirect); | ||
touchPointsSetChanged = false; | ||
touchPointsPressed = false; | ||
touchPointsInEvent = 0; | ||
} | ||
|
||
@Override | ||
public void notifyNextTouchEvent(long time, int type, long touchId, | ||
int x, int y, int xAbs, int yAbs) { | ||
touchPointsInEvent++; | ||
switch(type) { | ||
case TouchEvent.TOUCH_PRESSED: | ||
touchPointsSetChanged = true; | ||
touchPointsPressed = true; | ||
touchPressed(touchId, time, x, y, xAbs, yAbs); | ||
touchPressed(touchId, x, y, xAbs, yAbs); | ||
break; | ||
case TouchEvent.TOUCH_STILL: | ||
break; | ||
case TouchEvent.TOUCH_MOVED: | ||
touchMoved(touchId, time, x, y, xAbs, yAbs); | ||
touchMoved(touchId, x, y, xAbs, yAbs); | ||
break; | ||
case TouchEvent.TOUCH_RELEASED: | ||
touchPointsSetChanged = true; | ||
touchReleased(touchId, time, x, y, xAbs, yAbs); | ||
touchReleased(touchId); | ||
break; | ||
default: | ||
throw new RuntimeException("Error in Rotate gesture recognition: " | ||
|
@@ -219,8 +213,7 @@ private void assignActiveTouchpoints() { | |
} | ||
|
||
@Override | ||
public void notifyEndTouchEvent(long time) { | ||
lastTouchEventTime = time; | ||
public void notifyEndTouchEvent(long nanos) { | ||
if (currentTouchCount != trackers.size()) { | ||
throw new RuntimeException("Error in Rotate gesture recognition: " | ||
+ "touch count is wrong: " + currentTouchCount); | ||
|
@@ -230,9 +223,10 @@ public void notifyEndTouchEvent(long time) { | |
if (state == RotateRecognitionState.ACTIVE) { | ||
sendRotateFinishedEvent(); | ||
} | ||
if (ROTATION_INERTIA_ENABLED && (state == RotateRecognitionState.PRE_INERTIA || state == RotateRecognitionState.ACTIVE)) { | ||
double timeFromLastRotation = ((double)time - rotationStartTime) / 1000000; | ||
if (timeFromLastRotation < 300) { | ||
if (rotationInertiaEnabled && (state == RotateRecognitionState.PRE_INERTIA || state == RotateRecognitionState.ACTIVE)) { | ||
long nanosSinceLastRotation = nanos - rotationStartNanos; | ||
|
||
if (nanosSinceLastRotation < ROTATION_INERTIA_THRESHOLD_NANOS) { | ||
state = RotateRecognitionState.INERTIA; | ||
// activate inertia | ||
inertiaLastTime = 0; | ||
|
@@ -247,10 +241,7 @@ else if (initialInertiaRotationVelocity < -MAX_INITIAL_VELOCITY) | |
new KeyValue(inertiaRotationVelocity, initialInertiaRotationVelocity, Interpolator.LINEAR)), | ||
new KeyFrame( | ||
Duration.millis(ROTATION_INERTIA_MILLIS * Math.abs(initialInertiaRotationVelocity) / MAX_INITIAL_VELOCITY), | ||
event -> { | ||
//stop inertia | ||
reset(); | ||
}, | ||
event -> reset(), // stop inertia | ||
new KeyValue(inertiaRotationVelocity, 0, Interpolator.LINEAR)) | ||
); | ||
inertiaTimeline.playFromStart(); | ||
|
@@ -268,7 +259,7 @@ else if (initialInertiaRotationVelocity < -MAX_INITIAL_VELOCITY) | |
if (currentTouchCount == 1) { | ||
if (state == RotateRecognitionState.ACTIVE) { | ||
sendRotateFinishedEvent(); | ||
if (ROTATION_INERTIA_ENABLED) { | ||
if (rotationInertiaEnabled) { | ||
//prepare for inertia | ||
state = RotateRecognitionState.PRE_INERTIA; | ||
} else { | ||
|
@@ -297,7 +288,7 @@ else if (initialInertiaRotationVelocity < -MAX_INITIAL_VELOCITY) | |
} else { | ||
currentRotation = getNormalizedDelta(angleReference, newAngle); | ||
if (state == RotateRecognitionState.TRACKING) { | ||
if (Math.abs(currentRotation) > ROTATATION_THRESHOLD) { | ||
if (Math.abs(currentRotation) > rotationThresholdDegrees) { | ||
state = RotateRecognitionState.ACTIVE; | ||
sendRotateStartedEvent(); | ||
} | ||
|
@@ -307,10 +298,11 @@ else if (initialInertiaRotationVelocity < -MAX_INITIAL_VELOCITY) | |
totalRotation += currentRotation; | ||
sendRotateEvent(false); | ||
angleReference = newAngle; | ||
double timePassed = ((double)time - rotationStartTime) / 1000000000; | ||
if (timePassed > 1e-4) { | ||
initialInertiaRotationVelocity = currentRotation / timePassed; | ||
rotationStartTime = time; | ||
long nanosPassed = nanos - rotationStartNanos; | ||
|
||
if (nanosPassed > INITIAL_VELOCITY_THRESHOLD_NANOS) { | ||
initialInertiaRotationVelocity = currentRotation / nanosPassed * NANOS_TO_SECONDS; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this correct? shouldn't it be |
||
rotationStartNanos = nanos; | ||
} | ||
} | ||
} | ||
|
@@ -374,19 +366,19 @@ private void sendRotateFinishedEvent() { | |
}, scene.getAccessControlContext()); | ||
} | ||
|
||
public void params(int modifiers, boolean direct) { | ||
private void params(int modifiers, boolean direct) { | ||
this.modifiers = modifiers; | ||
this.direct = direct; | ||
} | ||
|
||
public void touchPressed(long id, long nanos, int x, int y, int xAbs, int yAbs) { | ||
private void touchPressed(long id, int x, int y, int xAbs, int yAbs) { | ||
currentTouchCount++; | ||
TouchPointTracker tracker = new TouchPointTracker(); | ||
tracker.update(nanos, x, y, xAbs, yAbs); | ||
tracker.update(x, y, xAbs, yAbs); | ||
trackers.put(id, tracker); | ||
} | ||
|
||
public void touchReleased(long id, long nanos, int x, int y, int xAbs, int yAbs) { | ||
private void touchReleased(long id) { | ||
if (state != RotateRecognitionState.FAILURE) { | ||
TouchPointTracker tracker = trackers.get(id); | ||
if (tracker == null) { | ||
|
@@ -400,7 +392,7 @@ public void touchReleased(long id, long nanos, int x, int y, int xAbs, int yAbs) | |
currentTouchCount--; | ||
} | ||
|
||
public void touchMoved(long id, long nanos, int x, int y, int xAbs, int yAbs) { | ||
private void touchMoved(long id, int x, int y, int xAbs, int yAbs) { | ||
if (state == RotateRecognitionState.FAILURE) { | ||
return; | ||
} | ||
|
@@ -412,10 +404,10 @@ public void touchMoved(long id, long nanos, int x, int y, int xAbs, int yAbs) { | |
throw new RuntimeException("Error in rotate gesture " | ||
+ "recognition: reported unknown touch point"); | ||
} | ||
tracker.update(nanos, x, y, xAbs, yAbs); | ||
tracker.update(x, y, xAbs, yAbs); | ||
} | ||
|
||
void reset() { | ||
private void reset() { | ||
state = RotateRecognitionState.IDLE; | ||
touchPointID1 = -1; | ||
touchPointID2 = -1; | ||
|
@@ -427,7 +419,7 @@ private static class TouchPointTracker { | |
double x, y; | ||
double absX, absY; | ||
|
||
public void update(long nanos, double x, double y, double absX, double absY) { | ||
public void update(double x, double y, double absX, double absY) { | ||
this.x = x; | ||
this.y = y; | ||
this.absX = absX; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
100_000L ?