mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Viewer: In flow mode, implement drag scrolling. Fixes #1880707 [there is no auto scroll when to try text select](https://bugs.launchpad.net/calibre/+bug/1880707)
This commit is contained in:
parent
ddceafc9ac
commit
0ce941104d
@ -254,6 +254,7 @@ class ScrollAnimator:
|
||||
return self.is_running() and (self.auto or self.auto_timer is not None)
|
||||
|
||||
def start(self, direction, auto):
|
||||
cancel_drag_scroll()
|
||||
if self.wait:
|
||||
return
|
||||
|
||||
@ -274,6 +275,9 @@ class ScrollAnimator:
|
||||
|
||||
def smooth_scroll(self, ts):
|
||||
duration = self.end_time - self.start_time
|
||||
if duration <= 0:
|
||||
self.animation_id = None
|
||||
return
|
||||
progress = max(0, min(1, (ts - self.start_time) / duration)) # max/min to account for jitter
|
||||
scroll_target = self.start_offset
|
||||
scroll_target += Math.trunc(self.direction * progress * duration * line_height() * opts.lines_per_sec_smooth) / 1000
|
||||
@ -374,6 +378,7 @@ class FlickAnimator:
|
||||
self.animation_id = None
|
||||
|
||||
def start(self, gesture):
|
||||
cancel_drag_scroll()
|
||||
self.vertical = gesture.axis is 'vertical'
|
||||
now = window.performance.now()
|
||||
points = times = None
|
||||
@ -411,6 +416,70 @@ class FlickAnimator:
|
||||
|
||||
flick_animator = FlickAnimator()
|
||||
|
||||
|
||||
class DragScroller:
|
||||
|
||||
DURATION = 100 # milliseconds
|
||||
|
||||
def __init__(self):
|
||||
self.animation_id = None
|
||||
self.direction = 1
|
||||
self.speed_factor = 1
|
||||
self.start_time = self.end_time = 0
|
||||
self.start_offset = 0
|
||||
|
||||
def is_running(self):
|
||||
return self.animation_id is not None
|
||||
|
||||
def smooth_scroll(self, ts):
|
||||
duration = self.end_time - self.start_time
|
||||
if duration <= 0:
|
||||
self.animation_id = None
|
||||
self.start(self.direction, self.speed_factor)
|
||||
return
|
||||
progress = max(0, min(1, (ts - self.start_time) / duration)) # max/min to account for jitter
|
||||
scroll_target = self.start_offset
|
||||
scroll_target += Math.trunc(self.direction * progress * duration * line_height() * opts.lines_per_sec_smooth * self.speed_factor) / 1000
|
||||
window.scrollTo(0, scroll_target)
|
||||
|
||||
if progress < 1:
|
||||
self.animation_id = window.requestAnimationFrame(self.smooth_scroll)
|
||||
else:
|
||||
self.animation_id = None
|
||||
self.start(self.direction, self.speed_factor)
|
||||
|
||||
def start(self, direction, speed_factor):
|
||||
now = window.performance.now()
|
||||
self.end_time = now + self.DURATION
|
||||
if not self.is_running() or direction is not self.direction or speed_factor is not self.speed_factor:
|
||||
self.stop()
|
||||
self.direction = direction
|
||||
self.speed_factor = speed_factor
|
||||
self.start_time = now
|
||||
self.start_offset = window.pageYOffset
|
||||
self.animation_id = window.requestAnimationFrame(self.smooth_scroll)
|
||||
|
||||
def stop(self):
|
||||
if self.animation_id is not None:
|
||||
window.cancelAnimationFrame(self.animation_id)
|
||||
self.animation_id = None
|
||||
|
||||
|
||||
drag_scroller = DragScroller()
|
||||
|
||||
|
||||
def cancel_drag_scroll():
|
||||
drag_scroller.stop()
|
||||
|
||||
|
||||
def start_drag_scroll(delta):
|
||||
limit = opts.margin_top if delta < 0 else opts.margin_bottom
|
||||
direction = 1 if delta >= 0 else -1
|
||||
speed_factor = min(abs(delta), limit) / limit
|
||||
speed_factor *= (2 - speed_factor) # QuadOut Easing curve
|
||||
drag_scroller.start(direction, 2 * speed_factor)
|
||||
|
||||
|
||||
def handle_gesture(gesture):
|
||||
flick_animator.stop()
|
||||
if gesture.type is 'swipe':
|
||||
|
@ -22,10 +22,11 @@ from read_book.extract import get_elements
|
||||
from read_book.find import reset_find_caches, select_search_result
|
||||
from read_book.flow_mode import (
|
||||
anchor_funcs as flow_anchor_funcs, auto_scroll_action as flow_auto_scroll_action,
|
||||
ensure_selection_visible, flow_onwheel, flow_to_scroll_fraction,
|
||||
handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut,
|
||||
layout as flow_layout, scroll_by_page as flow_scroll_by_page,
|
||||
scroll_to_extend_annotation as flow_annotation_scroll
|
||||
cancel_drag_scroll, ensure_selection_visible, flow_onwheel,
|
||||
flow_to_scroll_fraction, handle_gesture as flow_handle_gesture,
|
||||
handle_shortcut as flow_handle_shortcut, layout as flow_layout,
|
||||
scroll_by_page as flow_scroll_by_page,
|
||||
scroll_to_extend_annotation as flow_annotation_scroll, start_drag_scroll
|
||||
)
|
||||
from read_book.footnotes import is_footnote_link
|
||||
from read_book.globals import (
|
||||
@ -143,6 +144,7 @@ class IframeBoss:
|
||||
self.length_before = None
|
||||
|
||||
def on_overlay_visibility_changed(self, data):
|
||||
cancel_drag_scroll()
|
||||
if data.visible:
|
||||
self.forward_keypresses = True
|
||||
if self.auto_scroll_action:
|
||||
@ -166,6 +168,8 @@ class IframeBoss:
|
||||
window.addEventListener('resize', debounce(self.onresize, 500))
|
||||
window.addEventListener('wheel', self.onwheel, {'passive': False})
|
||||
window.addEventListener('keydown', self.onkeydown, {'passive': False})
|
||||
window.addEventListener('mousemove', self.onmousemove, {'passive': True})
|
||||
window.addEventListener('mouseup', self.onmouseup, {'passive': True})
|
||||
document.documentElement.addEventListener('contextmenu', self.oncontextmenu, {'passive': False})
|
||||
document.addEventListener('selectionchange', self.onselectionchange)
|
||||
self.color_scheme = data.color_scheme
|
||||
@ -201,6 +205,7 @@ class IframeBoss:
|
||||
(console.error or console.log)('There was an error in the JavaScript from within the book')
|
||||
|
||||
def display(self, data):
|
||||
cancel_drag_scroll()
|
||||
self.length_before = None
|
||||
self.content_ready = False
|
||||
clear_annot_id_uuid_map()
|
||||
@ -535,6 +540,22 @@ class IframeBoss:
|
||||
else:
|
||||
self.handle_wheel(evt)
|
||||
|
||||
def onmousemove(self, evt):
|
||||
if evt.buttons is not 1:
|
||||
return
|
||||
if 0 <= evt.clientY <= window.innerHeight or current_layout_mode() is not 'flow':
|
||||
cancel_drag_scroll()
|
||||
return
|
||||
sel = window.getSelection()
|
||||
if not sel:
|
||||
cancel_drag_scroll()
|
||||
return
|
||||
delta = evt.clientY if evt.clientY < 0 else (evt.clientY - window.innerHeight)
|
||||
start_drag_scroll(delta)
|
||||
|
||||
def onmouseup(self, evt):
|
||||
cancel_drag_scroll()
|
||||
|
||||
def onkeydown(self, evt):
|
||||
if current_layout_mode() is not 'flow' and evt.key is 'Tab':
|
||||
# Prevent the TAB key from shifting focus as it causes partial scrolling
|
||||
|
@ -22,6 +22,8 @@ def update_settings(settings):
|
||||
opts.lines_per_sec_smooth = settings.lines_per_sec_smooth
|
||||
opts.margin_left = max(0, settings.margin_left)
|
||||
opts.margin_right = max(0, settings.margin_right)
|
||||
opts.margin_top = max(0, settings.margin_top)
|
||||
opts.margin_bottom = max(0, settings.margin_bottom)
|
||||
opts.override_book_colors = settings.override_book_colors
|
||||
opts.paged_wheel_scrolls_by_screen = v'!!settings.paged_wheel_scrolls_by_screen'
|
||||
opts.paged_taps_scroll_by_screen = v'!!settings.paged_taps_scroll_by_screen'
|
||||
|
@ -840,6 +840,8 @@ class View:
|
||||
return {
|
||||
'margin_left': 0 if name is self.book.manifest.title_page_name else sd.get('margin_left'),
|
||||
'margin_right': 0 if name is self.book.manifest.title_page_name else sd.get('margin_right'),
|
||||
'margin_top': 0 if name is self.book.manifest.title_page_name else sd.get('margin_top'),
|
||||
'margin_bottom': 0 if name is self.book.manifest.title_page_name else sd.get('margin_bottom'),
|
||||
'read_mode': sd.get('read_mode'),
|
||||
'columns_per_screen': sd.get('columns_per_screen'),
|
||||
'color_scheme': cs,
|
||||
|
Loading…
x
Reference in New Issue
Block a user