Browse Source

normalize wheel speed in input observer

dsehnal 3 years ago
parent
commit
9ee1439299
3 changed files with 72 additions and 18 deletions
  1. 2 0
      CHANGELOG.md
  2. 4 4
      src/mol-canvas3d/controls/trackball.ts
  3. 66 14
      src/mol-util/input/input-observer.ts

+ 2 - 0
CHANGELOG.md

@@ -12,6 +12,8 @@ Note that since we don't clearly distinguish between a public and private interf
 - Add dynamicBonds to ``Structure`` props (force re-calc on model change)
     - Expose as optional param in root structure transform helper
 - Add overpaint support to geometry exporters
+- ``InputObserver`` improvements
+  - normalize wheel speed across browsers/platforms
 
 ## [v2.2.0] - 2021-07-31
 

+ 4 - 4
src/mol-canvas3d/controls/trackball.ts

@@ -390,15 +390,15 @@ namespace TrackballControls {
             _isInteracting = false;
         }
 
-        function onWheel({ x, y, dx, dy, dz, buttons, modifiers }: WheelInput) {
+        function onWheel({ x, y, spinX, spinY, dz, buttons, modifiers }: WheelInput) {
             if (outsideViewport(x, y)) return;
 
-            const delta = absMax(dx, dy, dz);
+            const delta = absMax(spinX * 0.075, spinY * 0.075, dz * 0.0001);
             if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
-                _zoomEnd[1] += delta * 0.0001;
+                _zoomEnd[1] += delta;
             }
             if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
-                _focusEnd[1] += delta * 0.0001;
+                _focusEnd[1] += delta;
             }
         }
 

+ 66 - 14
src/mol-util/input/input-observer.ts

@@ -144,6 +144,8 @@ export type WheelInput = {
     dx: number,
     dy: number,
     dz: number,
+    spinX: number,
+    spinY: number
 } & BaseInput
 
 export type ClickInput = {
@@ -290,14 +292,14 @@ namespace InputObserver {
         attach();
 
         return {
-            get noScroll () { return noScroll; },
-            set noScroll (value: boolean) { noScroll = value; },
-            get noContextMenu () { return noContextMenu; },
-            set noContextMenu (value: boolean) { noContextMenu = value; },
+            get noScroll() { return noScroll; },
+            set noScroll(value: boolean) { noScroll = value; },
+            get noContextMenu() { return noContextMenu; },
+            set noContextMenu(value: boolean) { noContextMenu = value; },
 
-            get width () { return width; },
-            get height () { return height; },
-            get pixelRatio () { return pixelRatio(); },
+            get width() { return width; },
+            get height() { return height; },
+            get pixelRatio() { return pixelRatio(); },
 
             ...events,
 
@@ -305,7 +307,7 @@ namespace InputObserver {
         };
 
         function attach() {
-            element.addEventListener('contextmenu', onContextMenu as any, false );
+            element.addEventListener('contextmenu', onContextMenu as any, false);
 
             element.addEventListener('wheel', onMouseWheel as any, false);
             element.addEventListener('mousedown', onMouseDown as any, false);
@@ -335,7 +337,7 @@ namespace InputObserver {
             if (disposed) return;
             disposed = true;
 
-            element.removeEventListener( 'contextmenu', onContextMenu as any, false );
+            element.removeEventListener('contextmenu', onContextMenu as any, false);
 
             element.removeEventListener('wheel', onMouseWheel as any, false);
             element.removeEventListener('mousedown', onMouseDown as any, false);
@@ -544,7 +546,7 @@ namespace InputObserver {
             eventOffset(pointerEnd, ev);
             if (Vec2.distance(pointerEnd, pointerDown) < 4) {
                 const { pageX, pageY } = ev;
-                const [ x, y ] = pointerEnd;
+                const [x, y] = pointerEnd;
 
                 click.next({ x, y, pageX, pageY, buttons, button, modifiers: getModifierKeys() });
             }
@@ -553,7 +555,7 @@ namespace InputObserver {
         function onPointerMove(ev: PointerEvent) {
             eventOffset(pointerEnd, ev);
             const { pageX, pageY } = ev;
-            const [ x, y ] = pointerEnd;
+            const [x, y] = pointerEnd;
             const inside = insideBounds(pointerEnd);
             move.next({ x, y, pageX, pageY, buttons, button, modifiers: getModifierKeys(), inside });
 
@@ -569,7 +571,7 @@ namespace InputObserver {
             const isStart = dragging === DraggingState.Started;
             if (isStart && !mask(ev.clientX, ev.clientY)) return;
 
-            const [ dx, dy ] = pointerDelta;
+            const [dx, dy] = pointerDelta;
             drag.next({ x, y, dx, dy, pageX, pageY, buttons, button, modifiers: getModifierKeys(), isStart });
 
             Vec2.copy(pointerStart, pointerEnd);
@@ -581,7 +583,7 @@ namespace InputObserver {
 
             eventOffset(pointerEnd, ev);
             const { pageX, pageY } = ev;
-            const [ x, y ] = pointerEnd;
+            const [x, y] = pointerEnd;
 
             if (noScroll) {
                 ev.preventDefault();
@@ -601,7 +603,7 @@ namespace InputObserver {
             buttons = button = ButtonsType.Flag.Auxilary;
 
             if (dx || dy || dz) {
-                wheel.next({ x, y, pageX, pageY, dx, dy, dz, buttons, button, modifiers: getModifierKeys() });
+                wheel.next({ x, y, pageX, pageY, ...normalizeWheel(ev), buttons, button, modifiers: getModifierKeys() });
             }
         }
 
@@ -648,4 +650,54 @@ namespace InputObserver {
     }
 }
 
+
+// Adapted from https://stackoverflow.com/a/30134826
+// License: https://creativecommons.org/licenses/by-sa/3.0/
+function normalizeWheel(event: any) {
+    // Reasonable defaults
+    const PIXEL_STEP = 10;
+    const LINE_HEIGHT = 40;
+    const PAGE_HEIGHT = 800;
+    let spinX = 0, spinY = 0,
+        dx = 0, dy = 0, dz = 0;       // pixelX, pixelY, pixelZ
+
+    // Legacy
+    if ('detail' in event) { spinY = event.detail; }
+    if ('wheelDelta' in event) { spinY = -event.wheelDelta / 120; }
+    if ('wheelDeltaY' in event) { spinY = -event.wheelDeltaY / 120; }
+    if ('wheelDeltaX' in event) { spinX = -event.wheelDeltaX / 120; }
+
+    // side scrolling on FF with DOMMouseScroll
+    if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) {
+        spinX = spinY;
+        spinY = 0;
+    }
+
+    dx = spinX * PIXEL_STEP;
+    dy = spinY * PIXEL_STEP;
+
+    if ('deltaY' in event) { dy = event.deltaY; }
+    if ('deltaX' in event) { dx = event.deltaX; }
+    if ('deltaZ' in event) { dz = event.deltaX; }
+
+    if ((dx || dy || dz) && event.deltaMode) {
+        if (event.deltaMode == 1) {          // delta in LINE units
+            dx *= LINE_HEIGHT;
+            dy *= LINE_HEIGHT;
+            dz *= LINE_HEIGHT;
+        } else {                             // delta in PAGE units
+            dx *= PAGE_HEIGHT;
+            dy *= PAGE_HEIGHT;
+            dz *= PAGE_HEIGHT;
+        }
+    }
+
+    // Fall-back if spin cannot be determined
+    if (dx && !spinX) { spinX = (dx < 1) ? -1 : 1; }
+    if (dy && !spinY) { spinY = (dy < 1) ? -1 : 1; }
+
+    return { spinX, spinY, dx, dy, dz };
+}
+
+
 export { InputObserver };