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)
|
return self.is_running() and (self.auto or self.auto_timer is not None)
|
||||||
|
|
||||||
def start(self, direction, auto):
|
def start(self, direction, auto):
|
||||||
|
cancel_drag_scroll()
|
||||||
if self.wait:
|
if self.wait:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -274,6 +275,9 @@ class ScrollAnimator:
|
|||||||
|
|
||||||
def smooth_scroll(self, ts):
|
def smooth_scroll(self, ts):
|
||||||
duration = self.end_time - self.start_time
|
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
|
progress = max(0, min(1, (ts - self.start_time) / duration)) # max/min to account for jitter
|
||||||
scroll_target = self.start_offset
|
scroll_target = self.start_offset
|
||||||
scroll_target += Math.trunc(self.direction * progress * duration * line_height() * opts.lines_per_sec_smooth) / 1000
|
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
|
self.animation_id = None
|
||||||
|
|
||||||
def start(self, gesture):
|
def start(self, gesture):
|
||||||
|
cancel_drag_scroll()
|
||||||
self.vertical = gesture.axis is 'vertical'
|
self.vertical = gesture.axis is 'vertical'
|
||||||
now = window.performance.now()
|
now = window.performance.now()
|
||||||
points = times = None
|
points = times = None
|
||||||
@ -411,6 +416,70 @@ class FlickAnimator:
|
|||||||
|
|
||||||
flick_animator = 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):
|
def handle_gesture(gesture):
|
||||||
flick_animator.stop()
|
flick_animator.stop()
|
||||||
if gesture.type is 'swipe':
|
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.find import reset_find_caches, select_search_result
|
||||||
from read_book.flow_mode import (
|
from read_book.flow_mode import (
|
||||||
anchor_funcs as flow_anchor_funcs, auto_scroll_action as flow_auto_scroll_action,
|
anchor_funcs as flow_anchor_funcs, auto_scroll_action as flow_auto_scroll_action,
|
||||||
ensure_selection_visible, flow_onwheel, flow_to_scroll_fraction,
|
cancel_drag_scroll, ensure_selection_visible, flow_onwheel,
|
||||||
handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut,
|
flow_to_scroll_fraction, handle_gesture as flow_handle_gesture,
|
||||||
layout as flow_layout, scroll_by_page as flow_scroll_by_page,
|
handle_shortcut as flow_handle_shortcut, layout as flow_layout,
|
||||||
scroll_to_extend_annotation as flow_annotation_scroll
|
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.footnotes import is_footnote_link
|
||||||
from read_book.globals import (
|
from read_book.globals import (
|
||||||
@ -143,6 +144,7 @@ class IframeBoss:
|
|||||||
self.length_before = None
|
self.length_before = None
|
||||||
|
|
||||||
def on_overlay_visibility_changed(self, data):
|
def on_overlay_visibility_changed(self, data):
|
||||||
|
cancel_drag_scroll()
|
||||||
if data.visible:
|
if data.visible:
|
||||||
self.forward_keypresses = True
|
self.forward_keypresses = True
|
||||||
if self.auto_scroll_action:
|
if self.auto_scroll_action:
|
||||||
@ -166,6 +168,8 @@ class IframeBoss:
|
|||||||
window.addEventListener('resize', debounce(self.onresize, 500))
|
window.addEventListener('resize', debounce(self.onresize, 500))
|
||||||
window.addEventListener('wheel', self.onwheel, {'passive': False})
|
window.addEventListener('wheel', self.onwheel, {'passive': False})
|
||||||
window.addEventListener('keydown', self.onkeydown, {'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.documentElement.addEventListener('contextmenu', self.oncontextmenu, {'passive': False})
|
||||||
document.addEventListener('selectionchange', self.onselectionchange)
|
document.addEventListener('selectionchange', self.onselectionchange)
|
||||||
self.color_scheme = data.color_scheme
|
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')
|
(console.error or console.log)('There was an error in the JavaScript from within the book')
|
||||||
|
|
||||||
def display(self, data):
|
def display(self, data):
|
||||||
|
cancel_drag_scroll()
|
||||||
self.length_before = None
|
self.length_before = None
|
||||||
self.content_ready = False
|
self.content_ready = False
|
||||||
clear_annot_id_uuid_map()
|
clear_annot_id_uuid_map()
|
||||||
@ -535,6 +540,22 @@ class IframeBoss:
|
|||||||
else:
|
else:
|
||||||
self.handle_wheel(evt)
|
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):
|
def onkeydown(self, evt):
|
||||||
if current_layout_mode() is not 'flow' and evt.key is 'Tab':
|
if current_layout_mode() is not 'flow' and evt.key is 'Tab':
|
||||||
# Prevent the TAB key from shifting focus as it causes partial scrolling
|
# 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.lines_per_sec_smooth = settings.lines_per_sec_smooth
|
||||||
opts.margin_left = max(0, settings.margin_left)
|
opts.margin_left = max(0, settings.margin_left)
|
||||||
opts.margin_right = max(0, settings.margin_right)
|
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.override_book_colors = settings.override_book_colors
|
||||||
opts.paged_wheel_scrolls_by_screen = v'!!settings.paged_wheel_scrolls_by_screen'
|
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'
|
opts.paged_taps_scroll_by_screen = v'!!settings.paged_taps_scroll_by_screen'
|
||||||
|
@ -840,6 +840,8 @@ class View:
|
|||||||
return {
|
return {
|
||||||
'margin_left': 0 if name is self.book.manifest.title_page_name else sd.get('margin_left'),
|
'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_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'),
|
'read_mode': sd.get('read_mode'),
|
||||||
'columns_per_screen': sd.get('columns_per_screen'),
|
'columns_per_screen': sd.get('columns_per_screen'),
|
||||||
'color_scheme': cs,
|
'color_scheme': cs,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user