1 /* 2 * Copyright (c) 2008, 2015, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package ensemble; 33 34 import javafx.animation.*; 35 import javafx.beans.property.DoubleProperty; 36 import javafx.beans.property.SimpleDoubleProperty; 37 import javafx.event.Event; 38 import javafx.event.EventHandler; 39 import javafx.scene.Scene; 40 import javafx.scene.input.MouseEvent; 41 import javafx.scene.input.PickResult; 42 import javafx.scene.input.ScrollEvent; 43 import javafx.util.Duration; 44 45 public class ScrollEventSynthesizer implements EventHandler { 46 private static final int INERTIA_DURATION = 2400; 47 private static final double CLICK_THRESHOLD = 20; 48 private static final double CLICK_TIME_THRESHOLD = Integer.parseInt(System.getProperty("click", "400")); 49 private long startDrag; 50 private long lastDrag; 51 private long lastDragDelta; 52 private int startDragX; 53 private int startDragY; 54 private int lastDragX; 55 private int lastDragY; 56 private int lastDragStepX; 57 private int lastDragStepY; 58 private double dragVelocityX; 59 private double dragVelocityY; 60 private boolean clickThresholdBroken; 61 private Timeline inertiaTimeline = null; 62 private long lastClickTime = -1; 63 private boolean isFiredByMe = false; 64 65 public ScrollEventSynthesizer(Scene scene) { 66 scene.addEventFilter(MouseEvent.ANY, this); 67 scene.addEventFilter(ScrollEvent.ANY, this); 68 } 69 70 @Override public void handle(final Event e) { 71 if (isFiredByMe) return; 72 if (e instanceof ScrollEvent) { 73 final ScrollEvent se = (ScrollEvent)e; 74 // System.out.println("SCROLL touch = "+se.getTouchCount()+" target = "+se.getTarget()+" e = "+se.getEventType()+" dx="+se.getDeltaX()+" dy="+se.getDeltaY()+" tdx="+se.getTotalDeltaX()+" tdy="+se.getTotalDeltaY()); 75 if (se.getTouchCount() != 0) se.consume(); 76 } else if (e instanceof MouseEvent) { 77 final MouseEvent me = (MouseEvent)e; 78 // System.out.println("MOUSE "+e.getEventType()+" --> "+e.getTarget()); 79 if (e.getEventType() == MouseEvent.MOUSE_PRESSED) { 80 lastDragX = startDragX = (int)me.getX(); 81 lastDragY = startDragY = (int)me.getY(); 82 lastDrag = startDrag = System.currentTimeMillis(); 83 lastDragDelta = 0; 84 if(inertiaTimeline != null) inertiaTimeline.stop(); 85 clickThresholdBroken = false; 86 } else if (e.getEventType() == MouseEvent.MOUSE_DRAGGED) { 87 // Delta of this drag vs. last drag location (or start) 88 lastDragStepX = (int)me.getX() - lastDragX; 89 lastDragStepY = (int)me.getY() - lastDragY; 90 91 // Duration of this drag step. 92 lastDragDelta = System.currentTimeMillis() - lastDrag; 93 94 // Velocity of last drag increment. 95 dragVelocityX = (double)lastDragStepX/(double)lastDragDelta; 96 dragVelocityY = (double)lastDragStepY/(double)lastDragDelta; 97 98 // Snapshot of this drag event. 99 lastDragX = (int)me.getX(); 100 lastDragY = (int)me.getY(); 101 lastDrag = System.currentTimeMillis(); 102 103 // Calculate distance so far -- have we dragged enough to scroll? 104 final int dragX = (int)me.getX() - startDragX; 105 final int dragY = (int)me.getY() - startDragY; 106 double distance = Math.abs(Math.sqrt((dragX*dragX) + (dragY*dragY))); 107 108 int scrollDistX = lastDragStepX; 109 int scrollDistY = lastDragStepY; 110 if (!clickThresholdBroken && distance > CLICK_THRESHOLD) { 111 clickThresholdBroken = true; 112 scrollDistX = dragX; 113 scrollDistY = dragY; 114 } 115 if (clickThresholdBroken) { 116 Event.fireEvent(e.getTarget(), new ScrollEvent( 117 ScrollEvent.SCROLL, 118 me.getX(), me.getY(), 119 me.getSceneX(), me.getSceneY(), 120 me.isShiftDown(), me.isControlDown(), me.isAltDown(), me.isMetaDown(), true, false, 121 scrollDistX, scrollDistY, 122 scrollDistX, scrollDistY, 123 ScrollEvent.HorizontalTextScrollUnits.NONE, 0, 124 ScrollEvent.VerticalTextScrollUnits.NONE, 0, 125 0,new PickResult(me.getTarget(), me.getSceneX(), me.getSceneY()) 126 )); 127 128 /* 129 * final EventType<ScrollEvent> eventType, 130 double x, double y, 131 double screenX, double screenY, 132 boolean shiftDown, 133 boolean controlDown, 134 boolean altDown, 135 boolean metaDown, 136 boolean direct, 137 boolean inertia, 138 double deltaX, double deltaY, 139 double gestureDeltaX, double gestureDeltaY, 140 HorizontalTextScrollUnits textDeltaXUnits, double textDeltaX, 141 VerticalTextScrollUnits textDeltaYUnits, double textDeltaY, 142 int touchCount, 143 PickResult pickResult) 144 */ 145 } 146 } else if (e.getEventType() == MouseEvent.MOUSE_RELEASED) { 147 handleRelease(me); 148 } else if (e.getEventType() == MouseEvent.MOUSE_CLICKED) { 149 final long time = System.currentTimeMillis(); 150 // System.out.println("CLICKED clickThresholdBroken="+clickThresholdBroken+" timeSinceLast = "+ (time-lastClickTime)+" CONSUMED="+((time-lastClickTime) < CLICK_TIME_THRESHOLD)); 151 if (clickThresholdBroken || (lastClickTime != -1 && (time-lastClickTime) < CLICK_TIME_THRESHOLD)) e.consume(); 152 lastClickTime = time; 153 } 154 } 155 } 156 157 private void handleRelease(final MouseEvent me) { 158 if (clickThresholdBroken) { 159 // Calculate last instantaneous velocity. User may have stopped moving 160 // before they let go of the mouse. 161 final long time = System.currentTimeMillis() - lastDrag; 162 dragVelocityX = (double)lastDragStepX/(time + lastDragDelta); 163 dragVelocityY = (double)lastDragStepY/(time + lastDragDelta); 164 165 // determin if click or drag/flick 166 final int dragX = (int)me.getX() - startDragX; 167 final int dragY = (int)me.getY() - startDragY; 168 169 // calculate complete time from start to end of drag 170 final long totalTime = System.currentTimeMillis() - startDrag; 171 172 // if time is less than 300ms then considered a quick flick and whole time is used 173 final boolean quick = totalTime < 300; 174 175 // calculate velocity 176 double velocityX = quick ? (double)dragX / (double)totalTime : dragVelocityX; // pixels/ms 177 double velocityY = quick ? (double)dragY / (double)totalTime : dragVelocityY; // pixels/ms 178 179 // System.out.printf("dragVelocityX = %f, dragVelocityY = %f\n", dragVelocityX, dragVelocityY); 180 // System.out.printf("velocityX = %f, dragX = %d; velocityY = %f, dragY = %d; totalTime = %d\n", 181 // velocityX, dragX, velocityY, dragY, totalTime); 182 183 // calculate distance we would travel at this speed for INERTIA_DURATION ms of travel 184 final int distanceX = (int)(velocityX * INERTIA_DURATION); // distance 185 final int distanceY = (int)(velocityY * INERTIA_DURATION); // distance 186 // 187 DoubleProperty animatePosition = new SimpleDoubleProperty() { 188 double lastMouseX = me.getX(); 189 double lastMouseY = me.getY(); 190 @Override protected void invalidated() { 191 final double mouseX = me.getX() + (distanceX * get()); 192 final double mouseY = me.getY() + (distanceY * get()); 193 final double dragStepX = mouseX - lastMouseX; 194 final double dragStepY = mouseY - lastMouseY; 195 196 if (Math.abs(dragStepX) >= 1.0 || Math.abs(dragStepY) >= 1.0) { 197 Event.fireEvent(me.getTarget(), new ScrollEvent( 198 ScrollEvent.SCROLL, 199 me.getX(), me.getY(), 200 me.getSceneX(), me.getSceneY(), 201 me.isShiftDown(), me.isControlDown(), me.isAltDown(), me.isMetaDown(), 202 true, true, 203 dragStepX, dragStepY, 204 (distanceX * get()), (distanceY * get()), 205 ScrollEvent.HorizontalTextScrollUnits.NONE, 0, 206 ScrollEvent.VerticalTextScrollUnits.NONE, 0, 207 0,new PickResult(me.getTarget(), me.getSceneX(), me.getSceneY()) 208 )); 209 } 210 lastMouseX = mouseX; 211 lastMouseY = mouseY; 212 } 213 }; 214 215 // animate a slow down from current velocity to zero 216 inertiaTimeline = new Timeline( 217 new KeyFrame(Duration.ZERO, new KeyValue(animatePosition, 0)), 218 new KeyFrame(Duration.millis(INERTIA_DURATION), new KeyValue(animatePosition, 1d, 219 Interpolator.SPLINE(0.0513, 0.1131, 0.1368, 1.0000))) 220 ); 221 inertiaTimeline.play(); 222 } 223 } 224 }