dev@347: /** dev@347: * Created by lucast on 24/05/2017. dev@347: */ dev@347: import wavesUI from 'waves-ui-piper'; dev@347: import * as Hammer from 'hammerjs'; dev@347: import {TimePixelMapper} from '../playhead/PlayHeadHelpers'; dev@347: dev@347: // TODO this is named as such as a reminder that it needs to be re-factored dev@347: export function attachTouchHandlerBodges(element: HTMLElement, dev@347: timeline: Timeline) { dev@347: interface Point { dev@347: x: number; dev@347: y: number; dev@347: } dev@347: dev@347: let zoomGestureJustEnded = false; dev@347: dev@347: const pixelToExponent: Function = wavesUI.utils.scales.linear() dev@347: .domain([0, 100]) // 100px => factor 2 dev@347: .range([0, 1]); dev@347: dev@347: const calculateDistance: (p1: Point, p2: Point) => number = (p1, p2) => { dev@347: return Math.pow( dev@347: Math.pow(p2.x - p1.x, 2) + dev@347: Math.pow(p2.y - p1.y, 2), 0.5); dev@347: }; dev@347: dev@347: const calculateMidPoint: (p1: Point, p2: Point) => Point = (p1, p2) => { dev@347: return { dev@347: x: 0.5 * (p1.x + p2.x), dev@347: y: 0.5 * (p1.y + p2.y) dev@347: }; dev@347: }; dev@347: dev@347: const hammertime = new Hammer.Manager(element, { dev@347: recognizers: [ dev@347: [Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }] dev@347: ] dev@347: }); dev@347: dev@347: // it seems HammerJs binds the event to the window? dev@347: // causing these events to propagate to other components? dev@347: let initialZoom; dev@347: let initialDistance; dev@347: let offsetAtPanStart; dev@347: let startX; dev@347: let isZooming; dev@347: dev@347: const scroll = (ev) => { dev@347: if (ev.center.x - startX === 0) { dev@347: return; dev@347: } dev@347: dev@347: if (zoomGestureJustEnded) { dev@347: zoomGestureJustEnded = false; dev@347: console.log('Skip this event: likely a single touch dangling from pinch'); dev@347: return; dev@347: } dev@347: timeline.timeContext.offset = offsetAtPanStart + dev@347: timeline.timeContext.timeToPixel.invert(ev.deltaX); dev@347: timeline.tracks.update(); dev@347: }; dev@347: dev@347: const zoom = (ev) => { dev@347: if (ev.touches.length < 2) { dev@347: return; dev@347: } dev@347: dev@347: ev.preventDefault(); dev@347: const minZoom = timeline.state.minZoom; dev@347: const maxZoom = timeline.state.maxZoom; dev@347: const p1: Point = { dev@347: x: ev.touches[0].clientX, dev@347: y: ev.touches[0].clientY dev@347: }; dev@347: const p2: Point = { dev@347: x: ev.touches[1].clientX, dev@347: y: ev.touches[1].clientY dev@347: }; dev@347: const distance = calculateDistance(p1, p2); dev@347: const midPoint = calculateMidPoint(p1, p2); dev@347: dev@347: const lastCenterTime = dev@347: timeline.timeContext.timeToPixel.invert(midPoint.x); dev@347: dev@347: const exponent = pixelToExponent(distance - initialDistance); dev@347: const targetZoom = initialZoom * Math.pow(2, exponent); dev@347: dev@347: timeline.timeContext.zoom = dev@347: Math.min(Math.max(targetZoom, minZoom), maxZoom); dev@347: dev@347: const newCenterTime = dev@347: timeline.timeContext.timeToPixel.invert(midPoint.x); dev@347: dev@347: timeline.timeContext.offset += newCenterTime - lastCenterTime; dev@347: timeline.tracks.update(); dev@347: }; dev@347: hammertime.on('panstart', (ev) => { dev@347: offsetAtPanStart = timeline.timeContext.offset; dev@347: startX = ev.center.x; dev@347: }); dev@347: hammertime.on('panleft', scroll); dev@347: hammertime.on('panright', scroll); dev@347: dev@347: element.addEventListener('touchstart', (e) => { dev@347: if (e.touches.length < 2) { dev@347: return; dev@347: } dev@347: dev@347: isZooming = true; dev@347: initialZoom = timeline.timeContext.zoom; dev@347: dev@347: initialDistance = calculateDistance({ dev@347: x: e.touches[0].clientX, dev@347: y: e.touches[0].clientY dev@347: }, { dev@347: x: e.touches[1].clientX, dev@347: y: e.touches[1].clientY dev@347: }); dev@347: }); dev@347: element.addEventListener('touchend', () => { dev@347: if (isZooming) { dev@347: isZooming = false; dev@347: zoomGestureJustEnded = true; dev@347: } dev@347: }); dev@347: element.addEventListener('touchmove', zoom); dev@347: } dev@347: dev@347: export function naivePagingMapper(timeline: Timeline): TimePixelMapper { dev@347: return (currentTime: number) => { dev@347: const currentOffset = timeline.timeContext.offset; dev@347: const offsetTimestamp = currentOffset dev@347: + currentTime; dev@347: dev@347: const visibleDuration = timeline.timeContext.visibleDuration; dev@347: const mustPageForward = offsetTimestamp > visibleDuration; dev@347: const mustPageBackward = currentTime < -currentOffset; dev@347: dev@347: if (mustPageForward) { dev@347: const hasSkippedMultiplePages = dev@347: offsetTimestamp - visibleDuration > visibleDuration; dev@347: dev@347: timeline.timeContext.offset = hasSkippedMultiplePages ? dev@347: -currentTime + 0.5 * visibleDuration : dev@347: currentOffset - visibleDuration; dev@347: } dev@347: dev@347: if (mustPageBackward) { dev@347: const hasSkippedMultiplePages = dev@347: currentTime + visibleDuration < -currentOffset; dev@347: timeline.timeContext.offset = hasSkippedMultiplePages ? dev@347: -currentTime + 0.5 * visibleDuration : dev@347: currentOffset + visibleDuration; dev@347: } dev@347: dev@347: if (mustPageForward || mustPageBackward) { dev@347: timeline.tracks.update(); dev@347: } dev@347: // dev@347: return timeline.timeContext.timeToPixel(timeline.offset + currentTime); dev@347: }; dev@347: }