diff --git a/src/pyj/read_book/flow_mode.pyj b/src/pyj/read_book/flow_mode.pyj index 401ebaf1eb..ebf89dda61 100644 --- a/src/pyj/read_book/flow_mode.pyj +++ b/src/pyj/read_book/flow_mode.pyj @@ -1,6 +1,6 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal -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) diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 545ed5422c..1079f62249 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -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)