Momentum scrolling in flow mode

This commit is contained in:
Kovid Goyal 2016-08-22 14:09:15 +05:30
parent 6f93debe6e
commit 5eaccd6619
2 changed files with 60 additions and 7 deletions

View File

@ -1,6 +1,6 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals
from __python__ import hash_literals, bound_methods
from dom import set_css
from read_book.globals import get_boss
@ -99,7 +99,56 @@ def flow_onkeydown(evt):
def layout(is_single_page):
set_css(document.body, margin='0', border_width='0', padding='0')
class FlickAnimator:
SPEED_FACTOR = 0.04
DECEL_TIME_CONSTANT = 325 # milliseconds
VELOCITY_HISTORY = 300 # milliseconds
MIMUMUM_VELOCITY = 100 # pixels/sec
def __init__(self):
self.animation_id = None
def start(self, gesture):
self.vertical = gesture.axis is 'vertical'
now = window.performance.now()
points = times = None
for i, t in enumerate(gesture.times):
if now - t < self.VELOCITY_HISTORY:
points, times = gesture.points[i:], gesture.times[i:]
break
if times and times.length > 1:
elapsed = (times[-1] - times[0]) / 1000
if elapsed > 0 and points.length > 1:
delta = points[0] - points[-1]
velocity = delta / elapsed
if abs(velocity) > self.MIMUMUM_VELOCITY:
self.amplitude = self.SPEED_FACTOR * velocity
self.start_time = now
self.animation_id = window.requestAnimationFrame(self.auto_scroll)
def auto_scroll(self, ts):
if self.animation_id is None:
return
elapsed = window.performance.now() - self.start_time
delta = self.amplitude * Math.exp(-elapsed / self.DECEL_TIME_CONSTANT)
if abs(delta) >= 1:
delta = Math.round(delta)
if self.vertical:
window.scrollBy(0, delta)
else:
window.scrollBy(delta, 0)
self.animation_id = window.requestAnimationFrame(self.auto_scroll)
def stop(self):
if self.animation_id is not None:
window.cancelAnimationFrame(self.animation_id)
self.animation_id = None
flick_animator = FlickAnimator()
def handle_gesture(gesture):
flick_animator.stop()
if gesture.type is 'swipe':
if gesture.points.length > 1 and not gesture.is_held:
delta = gesture.points[-2] - gesture.points[-1]
@ -107,5 +156,5 @@ def handle_gesture(gesture):
scroll_by(delta)
else:
window.scrollBy(delta, 0)
if not gesture.active:
print('TODO: Implement momentum scrolling')
if not gesture.active and not gesture.is_held:
flick_animator.start(gesture)

View File

@ -16,7 +16,7 @@ def copy_touch(t):
return {
'identifier':t.identifier,
'page_x':v'[t.pageX]', 'page_y':v'[t.pageY]', 'viewport_x':v'[t.clientX]', 'viewport_y':v'[t.clientY]',
'active':True, 'mtime':now, 'ctime':now, 'is_held':False
'active':True, 'mtimes':v'[now]', 'ctime':now, 'is_held':False, 'x_velocity': 0, 'y_velocity': 0
}
def max_displacement(points):
@ -47,11 +47,14 @@ def interpret_single_gesture(touch, gesture_id):
ans.type = 'swipe'
ans.axis = 'vertical' if delta_y > delta_x else 'horizontal'
ans.points = pts = touch.viewport_y if ans.axis is 'vertical' else touch.viewport_x
ans.times = touch.mtimes
positive = pts[-1] > pts[0]
if ans.axis is 'vertical':
ans.direction = 'down' if positive else 'up'
ans.velocity = touch.y_velocity
else:
ans.direction = 'right' if positive else 'left'
ans.velocity = touch.x_velocity
return ans
return ans
@ -97,7 +100,7 @@ class TouchHandler:
for tid in self.ongoing_touches:
t = self.ongoing_touches[tid]
if t.active:
if now - t.mtime > 3000:
if now - t.mtimes[-1] > 3000:
expired.push(t.identifier)
for tid in expired:
v'delete self.ongoing_touches[tid]'
@ -125,7 +128,7 @@ class TouchHandler:
found_hold = False
for touchid in self.ongoing_touches:
touch = self.ongoing_touches[touchid]
if touch.active and now - touch.mtime > HOLD_THRESHOLD:
if touch.active and now - touch.mtimes[-1] > HOLD_THRESHOLD:
touch.is_held = True
found_hold = True
if found_hold:
@ -146,7 +149,8 @@ class TouchHandler:
self.start_hold_timer()
def update_touch(self, t, touch):
t.mtime = window.performance.now()
now = window.performance.now()
t.mtimes.push(now)
t.page_x.push(touch.pageX), t.page_y.push(touch.pageY)
t.viewport_x.push(touch.clientX), t.viewport_y.push(touch.clientY)