From 5a1b7f178c211580e313d11d60bcff795ddba5d2 Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Wed, 5 Jun 2024 08:27:03 -0700 Subject: [PATCH 01/10] remove duplicate event listeners --- .../browser-event-manager.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/modules/browser-event-manager/browser-event-manager.ts b/src/modules/browser-event-manager/browser-event-manager.ts index 5674406..5df4d8b 100644 --- a/src/modules/browser-event-manager/browser-event-manager.ts +++ b/src/modules/browser-event-manager/browser-event-manager.ts @@ -15,7 +15,7 @@ export class BrowserEventManager { activatedEvents: string[] = []; eventHandlers: [string, any][] = []; bounds: DOMRect; - + listening: boolean; static eventPool = { atlas: { x: 0, y: 0 }, }; @@ -50,6 +50,7 @@ export class BrowserEventManager { this.runtime = runtime; this.unsubscribe = runtime.world.addLayoutSubscriber(this.layoutSubscriber.bind(this)); this.bounds = element.getBoundingClientRect(); + this.listening = false; this.options = { simulationRate: 0, ...(options || {}), @@ -69,9 +70,6 @@ export class BrowserEventManager { this.onPointerMove(this.pointerMoveEvent); } }); - - // @todo temp. - this.activateEvents(); } updateBounds() { @@ -80,7 +78,7 @@ export class BrowserEventManager { } layoutSubscriber(type: string) { - if (type === 'event-activation') { + if (type === 'event-activation' && this.listening == false) { this.activateEvents(); } } @@ -92,6 +90,7 @@ export class BrowserEventManager { } activateEvents() { + this.listening = true; this.element.addEventListener('pointermove', this._realPointerMove); this.element.addEventListener('pointerup', this.onPointerUp); this.element.addEventListener('pointerdown', this.onPointerDown); @@ -292,6 +291,25 @@ export class BrowserEventManager { } stop() { + this.listening = false; + this.element.removeEventListener('pointermove', this._realPointerMove); + this.element.removeEventListener('pointerup', this.onPointerUp); + this.element.removeEventListener('pointerdown', this.onPointerDown); + + // Normal events. + this.element.removeEventListener('mousedown', this.onPointerEvent); + this.element.removeEventListener('mouseup', this.onPointerEvent); + this.element.removeEventListener('pointercancel', this.onPointerEvent); + + // Edge-cases + this.element.removeEventListener('wheel', this.onWheelEvent); + + // Touch events. + this.element.removeEventListener('touchstart', this.onTouchEvent); + this.element.removeEventListener('touchcancel', this.onTouchEvent); + this.element.removeEventListener('touchend', this.onTouchEvent); + this.element.removeEventListener('touchmove', this.onTouchEvent); + // Unbind all events. this.unsubscribe(); for (const [event, handler] of this.eventHandlers) { From 9b4d05e006e276f47a696d450db94d4087b9bc6e Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Sun, 9 Jun 2024 15:40:55 -0700 Subject: [PATCH 02/10] add panning/scrolling models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit we’ve found that adding a touch-model interaction on-top of the canvas-panel isn’t working. Instead, it would work better if it was built into the Atlas interaction model. This helps address some of this more cleanly: 1. it addresses the fact that the browser-event-manager registers duplicate event listeners without removing them 2. it removes the `touch-action: none;` and `pointer-events: none;` which was preventing the window from listening to scroll based touches. 3. it replaces the above with making sure that `e.preventDefault()` is called when we don’t want to scroll because this does the same thing [see this](https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action) `Applications using Touch events disable the browser handling of gestures by calling preventDefault(), but should also use touch-action to ensure the browser knows the intent of the application before any event listeners have been invoked` 4. With this set, we can now use `e.preventDefault()` in various places to determine when we want the event to be passed to the window. 5. I also added some custom `user-select` settings to make sure that a long touch doesn’t try to select in Safari 6. I also found that binding touch-move to the window was causing issues, so I bound it to the parent element of the canvas. New interaction modes: 1. **ignoreSingleFingerTouch** when this is enabled a single finger touch does nothing, instead of panning, it allows the window to scroll. 2. **enablePanOnWait** in this interaction model it assumes that a user will wait a brief moment between when the press and when the move when the intent is to “pan.” **Question: can I modify the popMotionController arguments in CanvasPanel — is this a matter of defining and setting them in the `useAtlas` setup in CanvasPanel? --- .../popmotion-controller.ts | 98 +++++++++++++++++-- src/modules/react-reconciler/Atlas.tsx | 2 +- .../presets/default-preset.ts | 1 + .../react-reconciler/presets/static-preset.ts | 1 + stories/annotations.stories.tsx | 13 ++- 5 files changed, 103 insertions(+), 12 deletions(-) diff --git a/src/modules/popmotion-controller/popmotion-controller.ts b/src/modules/popmotion-controller/popmotion-controller.ts index 83dbc43..351955d 100644 --- a/src/modules/popmotion-controller/popmotion-controller.ts +++ b/src/modules/popmotion-controller/popmotion-controller.ts @@ -6,6 +6,10 @@ import { RuntimeController } from '../../types'; import { easingFunctions } from '../../utility/easing-functions'; import { toBox } from '../../utility/to-box'; +const INTENT_PAN = 'pan'; +const INTENT_SCROLL = 'scroll'; +const INTENT_GESTURE = 'gesture'; + export type PopmotionControllerConfig = { zoomOutFactor?: number; zoomInFactor?: number; @@ -23,6 +27,11 @@ export type PopmotionControllerConfig = { devicePixelRatio?: number; enableWheel?: boolean; enableClickToZoom?: boolean; + debug?: boolean; + ignoreSingleFingerTouch?: boolean; + enablePanOnWait?: boolean; + panOnWaitDelay?: number; + parentElement?: HTMLElement | null; onPanInSketchMode?: () => void; }; @@ -47,16 +56,30 @@ export const defaultConfig: Required = { devicePixelRatio: 1, // Flags enableWheel: true, - enableClickToZoom: false, + enableClickToZoom: true, + ignoreSingleFingerTouch: true, + enablePanOnWait: true, + panOnWaitDelay: 40, + debug: true, onPanInSketchMode: () => { // no-op }, + parentElement: null, }; export const popmotionController = (config: PopmotionControllerConfig = {}): RuntimeController => { return { start: function (runtime) { - const { zoomWheelConstant, enableWheel, enableClickToZoom } = { + const { + zoomWheelConstant, + enableWheel, + enableClickToZoom, + ignoreSingleFingerTouch, + enablePanOnWait, + panOnWaitDelay, + debug, + parentElement, + } = { ...defaultConfig, ...config, }; @@ -165,9 +188,16 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run } } + function resetState() { + currentDistance = 0; + intent = ''; + setDebugBorder(); + touchStartTime = 0; + } + function onMouseUp() { runtime.world.constraintBounds(); - currentDistance = 0; + resetState(); } function onMouseDown(e: MouseEvent & { atlas: { x: number; y: number } }) { @@ -187,6 +217,7 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run } function onWindowMouseUp() { + resetState(); if (state.isPressing) { if (runtime.mode === 'explore') { runtime.world.constraintBounds(); @@ -196,14 +227,23 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run } let currentDistance = 0; + // the performance.now() time at 'touch-start' + let touchStartTime = 0; + // what the user's intent would be for the behavior + let intent = ''; function onTouchStart(e: TouchEvent & { atlasTouches: Array<{ id: number; x: number; y: number }> }) { if (runtime.mode === 'explore') { if (e.atlasTouches.length === 1) { - e.preventDefault(); + touchStartTime = performance.now(); + if (ignoreSingleFingerTouch == false) { + // this prevents the touch propagation to the window, and thus doesn't drag the page + e.preventDefault(); + } state.pointerStart.x = e.atlasTouches[0].x; state.pointerStart.y = e.atlasTouches[0].y; } if (e.atlasTouches.length === 2) { + intent = INTENT_GESTURE; e.preventDefault(); const x1 = e.atlasTouches[0].x; const x2 = e.atlasTouches[1].x; @@ -224,12 +264,21 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run } } + function setDebugBorder(border = '1px solid transparent') { + if (debug == false) { + return; + } + const el = document.querySelector('.atlas') as HTMLElement; + if (el) { + el.style.border = border; + } + } + function onTouchMove(e: TouchEvent & { atlasTouches: Array<{ id: number; x: number; y: number }> }) { let clientX = null; let clientY = null; let isMulti = false; let newDistance = 0; - if (state.isPressing && e.touches.length === 2) { // We have 2? const x1 = e.touches[0].clientX; @@ -244,8 +293,27 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run { x: e.touches[1].clientX, y: e.touches[1].clientY } ); isMulti = true; + setDebugBorder('1px solid blue'); } + if (state.isPressing && e.touches.length === 1) { + if (enablePanOnWait) { + // if there is a delay between the touch-start and the 1st touch-move of < xms, then treat that as a PAN, + // anything faster is a window scroll + if (performance.now() - touchStartTime < panOnWaitDelay && intent == '') { + intent = INTENT_SCROLL; + setDebugBorder('1px solid red'); + } + if (intent == '') { + setDebugBorder('1px solid green'); + intent = INTENT_PAN; + } + } + // if we are ignoring a single finger touch, or it's a window-scroll, just 'return' + if ((intent == '' && ignoreSingleFingerTouch == true) || intent == INTENT_SCROLL) { + // have CanvasPanel do nothing... scroll the page + return; + } const touch = e.touches[0]; clientX = touch.clientX; clientY = touch.clientY; @@ -277,6 +345,12 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run } currentDistance = newDistance; } + + if (intent == INTENT_PAN) { + // if we're panning, prevent default + // this does the same thing as touchEvents: none; pointerEvents: none; + e.preventDefault(); + } } function onMouseMove(e: MouseEvent | PointerEvent) { @@ -327,11 +401,14 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run runtime.world.addEventListener('touchstart', onTouchStart); runtime.world.addEventListener('mousedown', onMouseDown); - window.addEventListener('touchend', onWindowMouseUp); + runtime.world.addEventListener('touchend', onWindowMouseUp); window.addEventListener('mouseup', onWindowMouseUp); window.addEventListener('mousemove', onMouseMove); - window.addEventListener('touchmove', onTouchMove as any); + + if (parentElement) { + parentElement.addEventListener('touchmove', onTouchMove as any); + } if (enableClickToZoom) { runtime.world.activatedEvents.push('onClick'); @@ -378,12 +455,13 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run runtime.world.removeEventListener('touchstart', onTouchStart); runtime.world.removeEventListener('mousedown', onMouseDown); - window.removeEventListener('touchend', onWindowMouseUp); + runtime.world.removeEventListener('touchend', onWindowMouseUp); window.removeEventListener('mouseup', onWindowMouseUp); runtime.world.removeEventListener('mousemove', onMouseMove); - runtime.world.removeEventListener('touchmove', onMouseMove); - + if (parentElement) { + parentElement.removeEventListener('touchmove', onMouseMove); + } if (enableClickToZoom) { runtime.world.removeEventListener('click', onClick); } diff --git a/src/modules/react-reconciler/Atlas.tsx b/src/modules/react-reconciler/Atlas.tsx index ea9fa43..022060c 100644 --- a/src/modules/react-reconciler/Atlas.tsx +++ b/src/modules/react-reconciler/Atlas.tsx @@ -519,7 +519,7 @@ export const Atlas: React.FC< ) : (
@@ -278,6 +281,14 @@ export const mobileSize = () => {
+
+
+
+
+
+
+
+ this allows testing of scrolling ); }; From a4cda83c1651f333e4fd3ff6e0bf01cdad6ce451 Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Mon, 10 Jun 2024 05:26:23 -0700 Subject: [PATCH 03/10] fix typescript error --- src/modules/popmotion-controller/popmotion-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/popmotion-controller/popmotion-controller.ts b/src/modules/popmotion-controller/popmotion-controller.ts index 351955d..5d74b9a 100644 --- a/src/modules/popmotion-controller/popmotion-controller.ts +++ b/src/modules/popmotion-controller/popmotion-controller.ts @@ -460,7 +460,7 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run runtime.world.removeEventListener('mousemove', onMouseMove); if (parentElement) { - parentElement.removeEventListener('touchmove', onMouseMove); + (parentElement as any).removeEventListener('touchmove', onMouseMove); } if (enableClickToZoom) { runtime.world.removeEventListener('click', onClick); From ad9bcd0a6c08ce9d9d2247033c4dde381caffebc Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Mon, 10 Jun 2024 08:35:59 -0700 Subject: [PATCH 04/10] switching to use data-attributes --- .../browser-event-manager.ts | 4 ++++ .../popmotion-controller.ts | 17 ++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/modules/browser-event-manager/browser-event-manager.ts b/src/modules/browser-event-manager/browser-event-manager.ts index 5df4d8b..7aa09a3 100644 --- a/src/modules/browser-event-manager/browser-event-manager.ts +++ b/src/modules/browser-event-manager/browser-event-manager.ts @@ -70,6 +70,8 @@ export class BrowserEventManager { this.onPointerMove(this.pointerMoveEvent); } }); + + this.activateEvents(); } updateBounds() { @@ -79,6 +81,7 @@ export class BrowserEventManager { layoutSubscriber(type: string) { if (type === 'event-activation' && this.listening == false) { + console.log(`${this.listening} ${type}`); this.activateEvents(); } } @@ -90,6 +93,7 @@ export class BrowserEventManager { } activateEvents() { + console.log('activating events'); this.listening = true; this.element.addEventListener('pointermove', this._realPointerMove); this.element.addEventListener('pointerup', this.onPointerUp); diff --git a/src/modules/popmotion-controller/popmotion-controller.ts b/src/modules/popmotion-controller/popmotion-controller.ts index 5d74b9a..c98f952 100644 --- a/src/modules/popmotion-controller/popmotion-controller.ts +++ b/src/modules/popmotion-controller/popmotion-controller.ts @@ -191,7 +191,7 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run function resetState() { currentDistance = 0; intent = ''; - setDebugBorder(); + setDataAttribute(); touchStartTime = 0; } @@ -264,13 +264,9 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run } } - function setDebugBorder(border = '1px solid transparent') { - if (debug == false) { - return; - } - const el = document.querySelector('.atlas') as HTMLElement; - if (el) { - el.style.border = border; + function setDataAttribute(_intent?: string) { + if (parentElement) { + parentElement.dataset.intent = _intent; } } @@ -293,8 +289,8 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run { x: e.touches[1].clientX, y: e.touches[1].clientY } ); isMulti = true; - setDebugBorder('1px solid blue'); } + setDataAttribute(intent); if (state.isPressing && e.touches.length === 1) { if (enablePanOnWait) { @@ -302,13 +298,12 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run // anything faster is a window scroll if (performance.now() - touchStartTime < panOnWaitDelay && intent == '') { intent = INTENT_SCROLL; - setDebugBorder('1px solid red'); } if (intent == '') { - setDebugBorder('1px solid green'); intent = INTENT_PAN; } } + setDataAttribute(intent); // if we are ignoring a single finger touch, or it's a window-scroll, just 'return' if ((intent == '' && ignoreSingleFingerTouch == true) || intent == INTENT_SCROLL) { // have CanvasPanel do nothing... scroll the page From 7a263cbe7d2113a1aebe17705495fa0b885b3e4f Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Mon, 10 Jun 2024 08:48:09 -0700 Subject: [PATCH 05/10] move to data attributes --- .../browser-event-manager/browser-event-manager.ts | 3 +-- src/modules/popmotion-controller/popmotion-controller.ts | 8 +++++--- stories/annotations.stories.tsx | 4 ++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/modules/browser-event-manager/browser-event-manager.ts b/src/modules/browser-event-manager/browser-event-manager.ts index 7aa09a3..85a893b 100644 --- a/src/modules/browser-event-manager/browser-event-manager.ts +++ b/src/modules/browser-event-manager/browser-event-manager.ts @@ -71,6 +71,7 @@ export class BrowserEventManager { } }); + // this is necessary for CavnasPanel to initialize the event listener this.activateEvents(); } @@ -81,7 +82,6 @@ export class BrowserEventManager { layoutSubscriber(type: string) { if (type === 'event-activation' && this.listening == false) { - console.log(`${this.listening} ${type}`); this.activateEvents(); } } @@ -93,7 +93,6 @@ export class BrowserEventManager { } activateEvents() { - console.log('activating events'); this.listening = true; this.element.addEventListener('pointermove', this._realPointerMove); this.element.addEventListener('pointerup', this.onPointerUp); diff --git a/src/modules/popmotion-controller/popmotion-controller.ts b/src/modules/popmotion-controller/popmotion-controller.ts index c98f952..0eb2ab5 100644 --- a/src/modules/popmotion-controller/popmotion-controller.ts +++ b/src/modules/popmotion-controller/popmotion-controller.ts @@ -60,7 +60,7 @@ export const defaultConfig: Required = { ignoreSingleFingerTouch: true, enablePanOnWait: true, panOnWaitDelay: 40, - debug: true, + debug: false, onPanInSketchMode: () => { // no-op }, @@ -392,7 +392,7 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run // runtime.world.addEventListener('pointermove', pointerMove); runtime.world.addEventListener('mouseup', onMouseUp); - runtime.world.addEventListener('touchend', onMouseUp); + window.addEventListener('touchend', onMouseUp); runtime.world.addEventListener('touchstart', onTouchStart); runtime.world.addEventListener('mousedown', onMouseDown); @@ -402,6 +402,8 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run window.addEventListener('mousemove', onMouseMove); if (parentElement) { + // if this is bound to the window, then the entire interaction model goes haywire + // unclear 100% why parentElement.addEventListener('touchmove', onTouchMove as any); } @@ -450,7 +452,7 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run runtime.world.removeEventListener('touchstart', onTouchStart); runtime.world.removeEventListener('mousedown', onMouseDown); - runtime.world.removeEventListener('touchend', onWindowMouseUp); + window.removeEventListener('touchend', onWindowMouseUp); window.removeEventListener('mouseup', onWindowMouseUp); runtime.world.removeEventListener('mousemove', onMouseMove); diff --git a/stories/annotations.stories.tsx b/stories/annotations.stories.tsx index b6e5e79..2fb645c 100644 --- a/stories/annotations.stories.tsx +++ b/stories/annotations.stories.tsx @@ -271,6 +271,10 @@ export const mobileSize = () => { body { /* this background helps with identifying whether the window or the image is moving */ background: repeating-linear-gradient( 45deg, #606dbc, #606dbc 10px,#465298 10px, #465298 20px); + [data-intent] { border: 1px solid transparent; } + [data-intent='pan'] { border: 1px solid red; } + [data-intent='gesture'] { border: 1px solid blue; } + [data-intent='scroll'] { border: 1px solid green; } } `}
From fa2d152b1c8690a2fadd3bbe4b8beaa8b55a3b1e Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Mon, 10 Jun 2024 08:51:09 -0700 Subject: [PATCH 06/10] cleanup --- src/modules/popmotion-controller/popmotion-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/popmotion-controller/popmotion-controller.ts b/src/modules/popmotion-controller/popmotion-controller.ts index 0eb2ab5..bbc26c8 100644 --- a/src/modules/popmotion-controller/popmotion-controller.ts +++ b/src/modules/popmotion-controller/popmotion-controller.ts @@ -392,11 +392,11 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run // runtime.world.addEventListener('pointermove', pointerMove); runtime.world.addEventListener('mouseup', onMouseUp); - window.addEventListener('touchend', onMouseUp); + runtime.world.addEventListener('touchend', onMouseUp); runtime.world.addEventListener('touchstart', onTouchStart); runtime.world.addEventListener('mousedown', onMouseDown); - runtime.world.addEventListener('touchend', onWindowMouseUp); + window.addEventListener('touchend', onWindowMouseUp); window.addEventListener('mouseup', onWindowMouseUp); window.addEventListener('mousemove', onMouseMove); From 30e90ea51323cfcc0be07108622191aa45312c32 Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Mon, 10 Jun 2024 12:21:28 -0700 Subject: [PATCH 07/10] add meta key --- .../popmotion-controller.ts | 163 +++++++++--------- 1 file changed, 84 insertions(+), 79 deletions(-) diff --git a/src/modules/popmotion-controller/popmotion-controller.ts b/src/modules/popmotion-controller/popmotion-controller.ts index bbc26c8..084e91d 100644 --- a/src/modules/popmotion-controller/popmotion-controller.ts +++ b/src/modules/popmotion-controller/popmotion-controller.ts @@ -27,9 +27,9 @@ export type PopmotionControllerConfig = { devicePixelRatio?: number; enableWheel?: boolean; enableClickToZoom?: boolean; - debug?: boolean; ignoreSingleFingerTouch?: boolean; enablePanOnWait?: boolean; + requireMetaKeyForWheelZoom?: boolean; panOnWaitDelay?: number; parentElement?: HTMLElement | null; onPanInSketchMode?: () => void; @@ -57,10 +57,10 @@ export const defaultConfig: Required = { // Flags enableWheel: true, enableClickToZoom: true, - ignoreSingleFingerTouch: true, - enablePanOnWait: true, + ignoreSingleFingerTouch: false, + enablePanOnWait: false, + requireMetaKeyForWheelZoom: true, panOnWaitDelay: 40, - debug: false, onPanInSketchMode: () => { // no-op }, @@ -77,8 +77,8 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run ignoreSingleFingerTouch, enablePanOnWait, panOnWaitDelay, - debug, parentElement, + requireMetaKeyForWheelZoom, } = { ...defaultConfig, ...config, @@ -115,78 +115,78 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run // el.onpointercancel = pointerup_handler; // el.onpointerout = pointerup_handler; // el.onpointerleave = pointerup_handler; - const eventCache: PointerEvent[] = []; - const atlasPointsCache: any[] = []; - let prevDiff = -1; - function removeFromEventCache(e: PointerEvent) { - // Remove this event from the target's cache - for (let i = 0; i < eventCache.length; i++) { - if (eventCache[i].pointerId == e.pointerId) { - eventCache.splice(i, 1); - atlasPointsCache.splice(i, 1); - break; - } - } - } - - function pointerDown(e: PointerEvent) { - eventCache.push(e); - atlasPointsCache.push({ ...((e as any).atlas || {}) }); - } - function pointerMove(e: PointerEvent) { - for (let i = 0; i < eventCache.length; i++) { - if (e.pointerId == eventCache[i].pointerId) { - eventCache[i] = e; - atlasPointsCache[i] = { ...((e as any).atlas || {}) }; - break; - } - } - if (eventCache.length == 2) { - const curDiff = Math.abs(eventCache[0].clientX - eventCache[1].clientX); - - // - - 2 - - 6- - - - 10 - const xDiff = - atlasPointsCache[0].x > atlasPointsCache[1].x - ? atlasPointsCache[0].x - atlasPointsCache[1].x - : atlasPointsCache[1].x - atlasPointsCache[0].x; - const yDiff = - atlasPointsCache[0].y > atlasPointsCache[1].y - ? atlasPointsCache[0].y - atlasPointsCache[1].y - : atlasPointsCache[1].y - atlasPointsCache[0].y; - - if (prevDiff > 0) { - if (curDiff > prevDiff) { - runtime.world.zoomTo( - // Generating a zoom from the wheel delta - 0.95, - { x: xDiff / 2, y: yDiff / 2 }, - true - ); - } - if (curDiff < prevDiff) { - runtime.world.zoomTo( - // Generating a zoom from the wheel delta - 1.05, - { x: xDiff / 2, y: yDiff / 2 }, - true - ); - } - } - - // Cache the distance for the next move event - prevDiff = curDiff; - } - } - - function pointerUp(e: PointerEvent) { - // Remove this pointer from the cache and reset the target's - // background and border - removeFromEventCache(e); - // If the number of pointers down is less than two then reset diff tracker - if (eventCache.length < 2) { - prevDiff = -1; - } - } + // const eventCache: PointerEvent[] = []; + // const atlasPointsCache: any[] = []; + // let prevDiff = -1; + // function removeFromEventCache(e: PointerEvent) { + // // Remove this event from the target's cache + // for (let i = 0; i < eventCache.length; i++) { + // if (eventCache[i].pointerId == e.pointerId) { + // eventCache.splice(i, 1); + // atlasPointsCache.splice(i, 1); + // break; + // } + // } + // } + + // function pointerDown(e: PointerEvent) { + // eventCache.push(e); + // atlasPointsCache.push({ ...((e as any).atlas || {}) }); + // } + // function pointerMove(e: PointerEvent) { + // for (let i = 0; i < eventCache.length; i++) { + // if (e.pointerId == eventCache[i].pointerId) { + // eventCache[i] = e; + // atlasPointsCache[i] = { ...((e as any).atlas || {}) }; + // break; + // } + // } + // if (eventCache.length == 2) { + // const curDiff = Math.abs(eventCache[0].clientX - eventCache[1].clientX); + + // // - - 2 - - 6- - - - 10 + // const xDiff = + // atlasPointsCache[0].x > atlasPointsCache[1].x + // ? atlasPointsCache[0].x - atlasPointsCache[1].x + // : atlasPointsCache[1].x - atlasPointsCache[0].x; + // const yDiff = + // atlasPointsCache[0].y > atlasPointsCache[1].y + // ? atlasPointsCache[0].y - atlasPointsCache[1].y + // : atlasPointsCache[1].y - atlasPointsCache[0].y; + + // if (prevDiff > 0) { + // if (curDiff > prevDiff) { + // runtime.world.zoomTo( + // // Generating a zoom from the wheel delta + // 0.95, + // { x: xDiff / 2, y: yDiff / 2 }, + // true + // ); + // } + // if (curDiff < prevDiff) { + // runtime.world.zoomTo( + // // Generating a zoom from the wheel delta + // 1.05, + // { x: xDiff / 2, y: yDiff / 2 }, + // true + // ); + // } + // } + + // // Cache the distance for the next move event + // prevDiff = curDiff; + // } + // } + + // function pointerUp(e: PointerEvent) { + // // Remove this pointer from the cache and reset the target's + // // background and border + // removeFromEventCache(e); + // // If the number of pointers down is less than two then reset diff tracker + // if (eventCache.length < 2) { + // prevDiff = -1; + // } + // } function resetState() { currentDistance = 0; @@ -264,9 +264,9 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run } } - function setDataAttribute(_intent?: string) { + function setDataAttribute(value?: string, dataAttribute = 'intent') { if (parentElement) { - parentElement.dataset.intent = _intent; + parentElement.dataset[dataAttribute] = value; } } @@ -307,6 +307,7 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run // if we are ignoring a single finger touch, or it's a window-scroll, just 'return' if ((intent == '' && ignoreSingleFingerTouch == true) || intent == INTENT_SCROLL) { // have CanvasPanel do nothing... scroll the page + setDataAttribute('require-two-finger', 'notice'); return; } const touch = e.touches[0]; @@ -379,6 +380,10 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run function onWheel(e: WheelEvent & { atlas: { x: number; y: number } }) { const normalized = normalizeWheel(e); const zoomFactor = 1 + normalized.spinY / zoomWheelConstant; + if (requireMetaKeyForWheelZoom && e.metaKey == false) { + setDataAttribute('meta-required', 'notice'); + return; + } runtime.world.zoomTo( // Generating a zoom from the wheel delta zoomFactor, From 72dafb384c4943dfe766c38de76dcb1cbb4a4ba9 Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Mon, 10 Jun 2024 12:25:44 -0700 Subject: [PATCH 08/10] doc --- .../popmotion-controller/popmotion-controller.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/modules/popmotion-controller/popmotion-controller.ts b/src/modules/popmotion-controller/popmotion-controller.ts index 084e91d..4e40d9f 100644 --- a/src/modules/popmotion-controller/popmotion-controller.ts +++ b/src/modules/popmotion-controller/popmotion-controller.ts @@ -188,10 +188,14 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run // } // } + /** + * Resets the event state after the gesture of behavior has finished + */ function resetState() { currentDistance = 0; intent = ''; setDataAttribute(); + setDataAttribute(undefined, 'notice'); touchStartTime = 0; } @@ -264,6 +268,12 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run } } + /** + * Sets a data attribute to expose the current intent/behavior/note to the user + * + * @param value {string} - the data-attribute value + * @param dataAttribute {string} - the data-attribute name + */ function setDataAttribute(value?: string, dataAttribute = 'intent') { if (parentElement) { parentElement.dataset[dataAttribute] = value; From 9d508d944c49b07bf4643363bf6d7be6b757d09f Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Tue, 11 Jun 2024 05:45:48 -0700 Subject: [PATCH 09/10] cleanup and fix meta key --- .../popmotion-controller.ts | 107 +++--------------- 1 file changed, 14 insertions(+), 93 deletions(-) diff --git a/src/modules/popmotion-controller/popmotion-controller.ts b/src/modules/popmotion-controller/popmotion-controller.ts index 4e40d9f..f80cf2c 100644 --- a/src/modules/popmotion-controller/popmotion-controller.ts +++ b/src/modules/popmotion-controller/popmotion-controller.ts @@ -100,94 +100,9 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run 'onMouseMove', 'onTouchStart', 'onTouchEnd', - 'onTouchMove', - 'onPointerUp', - 'onPointerDown', - 'onPointerMove' + 'onTouchMove' ); - // el.onpointerdown = pointerdown_handler; - // el.onpointermove = pointermove_handler; - // - // // Use same handler for pointer{up,cancel,out,leave} events since - // // the semantics for these events - in this app - are the same. - // el.onpointerup = pointerup_handler; - // el.onpointercancel = pointerup_handler; - // el.onpointerout = pointerup_handler; - // el.onpointerleave = pointerup_handler; - // const eventCache: PointerEvent[] = []; - // const atlasPointsCache: any[] = []; - // let prevDiff = -1; - // function removeFromEventCache(e: PointerEvent) { - // // Remove this event from the target's cache - // for (let i = 0; i < eventCache.length; i++) { - // if (eventCache[i].pointerId == e.pointerId) { - // eventCache.splice(i, 1); - // atlasPointsCache.splice(i, 1); - // break; - // } - // } - // } - - // function pointerDown(e: PointerEvent) { - // eventCache.push(e); - // atlasPointsCache.push({ ...((e as any).atlas || {}) }); - // } - // function pointerMove(e: PointerEvent) { - // for (let i = 0; i < eventCache.length; i++) { - // if (e.pointerId == eventCache[i].pointerId) { - // eventCache[i] = e; - // atlasPointsCache[i] = { ...((e as any).atlas || {}) }; - // break; - // } - // } - // if (eventCache.length == 2) { - // const curDiff = Math.abs(eventCache[0].clientX - eventCache[1].clientX); - - // // - - 2 - - 6- - - - 10 - // const xDiff = - // atlasPointsCache[0].x > atlasPointsCache[1].x - // ? atlasPointsCache[0].x - atlasPointsCache[1].x - // : atlasPointsCache[1].x - atlasPointsCache[0].x; - // const yDiff = - // atlasPointsCache[0].y > atlasPointsCache[1].y - // ? atlasPointsCache[0].y - atlasPointsCache[1].y - // : atlasPointsCache[1].y - atlasPointsCache[0].y; - - // if (prevDiff > 0) { - // if (curDiff > prevDiff) { - // runtime.world.zoomTo( - // // Generating a zoom from the wheel delta - // 0.95, - // { x: xDiff / 2, y: yDiff / 2 }, - // true - // ); - // } - // if (curDiff < prevDiff) { - // runtime.world.zoomTo( - // // Generating a zoom from the wheel delta - // 1.05, - // { x: xDiff / 2, y: yDiff / 2 }, - // true - // ); - // } - // } - - // // Cache the distance for the next move event - // prevDiff = curDiff; - // } - // } - - // function pointerUp(e: PointerEvent) { - // // Remove this pointer from the cache and reset the target's - // // background and border - // removeFromEventCache(e); - // // If the number of pointers down is less than two then reset diff tracker - // if (eventCache.length < 2) { - // prevDiff = -1; - // } - // } - /** * Resets the event state after the gesture of behavior has finished */ @@ -390,10 +305,6 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run function onWheel(e: WheelEvent & { atlas: { x: number; y: number } }) { const normalized = normalizeWheel(e); const zoomFactor = 1 + normalized.spinY / zoomWheelConstant; - if (requireMetaKeyForWheelZoom && e.metaKey == false) { - setDataAttribute('meta-required', 'notice'); - return; - } runtime.world.zoomTo( // Generating a zoom from the wheel delta zoomFactor, @@ -402,9 +313,14 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run ); } - // runtime.world.addEventListener('pointerup', pointerUp); - // runtime.world.addEventListener('pointerdown', pointerDown); - // runtime.world.addEventListener('pointermove', pointerMove); + function onWheelGuard(e: WheelEvent) { + if (requireMetaKeyForWheelZoom && e.metaKey == false) { + setDataAttribute('meta-required', 'notice'); + e.stopPropagation(); + return false; + } + return true; + } runtime.world.addEventListener('mouseup', onMouseUp); runtime.world.addEventListener('touchend', onMouseUp); @@ -429,6 +345,10 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run if (enableWheel) { runtime.world.activatedEvents.push('onWheel'); + if (requireMetaKeyForWheelZoom) { + // add an event listener above the world to guard the wheel event if the 'meta' key is pressed + parentElement?.addEventListener('wheel', onWheelGuard as any, { passive: true, capture: true }); + } runtime.world.addEventListener('wheel', onWheel); } @@ -473,6 +393,7 @@ export const popmotionController = (config: PopmotionControllerConfig = {}): Run runtime.world.removeEventListener('mousemove', onMouseMove); if (parentElement) { (parentElement as any).removeEventListener('touchmove', onMouseMove); + (parentElement as any).removeEventListener('wheel', onWheelGuard, { passive: true, capture: true }); } if (enableClickToZoom) { runtime.world.removeEventListener('click', onClick); From a6aad8a904f172c42310874597d76eb7142a23fb Mon Sep 17 00:00:00 2001 From: Adam Brin Date: Thu, 13 Jun 2024 09:01:39 -0700 Subject: [PATCH 10/10] flip boolean --- src/modules/popmotion-controller/popmotion-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/popmotion-controller/popmotion-controller.ts b/src/modules/popmotion-controller/popmotion-controller.ts index f80cf2c..fb9bb62 100644 --- a/src/modules/popmotion-controller/popmotion-controller.ts +++ b/src/modules/popmotion-controller/popmotion-controller.ts @@ -59,7 +59,7 @@ export const defaultConfig: Required = { enableClickToZoom: true, ignoreSingleFingerTouch: false, enablePanOnWait: false, - requireMetaKeyForWheelZoom: true, + requireMetaKeyForWheelZoom: false, panOnWaitDelay: 40, onPanInSketchMode: () => { // no-op