mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add auto-scroll with keyboard shortcut
This commit is contained in:
parent
0229c684f3
commit
e028bbb3f6
@ -63,8 +63,11 @@ last_change_spine_item_request = {}
|
|||||||
def _check_for_scroll_end(func, obj, args, report):
|
def _check_for_scroll_end(func, obj, args, report):
|
||||||
before = window.pageYOffset
|
before = window.pageYOffset
|
||||||
func.apply(obj, args)
|
func.apply(obj, args)
|
||||||
|
|
||||||
|
now = performance.now()
|
||||||
|
scroll_animator.sync(now)
|
||||||
|
|
||||||
if window.pageYOffset is before:
|
if window.pageYOffset is before:
|
||||||
now = Date.now()
|
|
||||||
csi = current_spine_item()
|
csi = current_spine_item()
|
||||||
if last_change_spine_item_request.name is csi.name and now - last_change_spine_item_request.at < 2000:
|
if last_change_spine_item_request.name is csi.name and now - last_change_spine_item_request.at < 2000:
|
||||||
return False
|
return False
|
||||||
@ -128,10 +131,10 @@ def scroll_by_page(direction):
|
|||||||
|
|
||||||
def handle_shortcut(sc_name, evt):
|
def handle_shortcut(sc_name, evt):
|
||||||
if sc_name is 'down':
|
if sc_name is 'down':
|
||||||
scroll_animator.start(DIRECTION.Down)
|
scroll_animator.start(DIRECTION.Down, False)
|
||||||
return True
|
return True
|
||||||
if sc_name is 'up':
|
if sc_name is 'up':
|
||||||
scroll_animator.start(DIRECTION.Up)
|
scroll_animator.start(DIRECTION.Up, False)
|
||||||
return True
|
return True
|
||||||
if sc_name is 'start_of_file':
|
if sc_name is 'start_of_file':
|
||||||
goto_boundary(-1)
|
goto_boundary(-1)
|
||||||
@ -157,13 +160,29 @@ def handle_shortcut(sc_name, evt):
|
|||||||
if sc_name is 'pagedown':
|
if sc_name is 'pagedown':
|
||||||
scroll_by_page(1)
|
scroll_by_page(1)
|
||||||
return True
|
return True
|
||||||
|
if sc_name is 'toggle_autoscroll':
|
||||||
|
if scroll_animator.auto and scroll_animator.is_running():
|
||||||
|
cancel_scroll()
|
||||||
|
else:
|
||||||
|
scroll_animator.start(DIRECTION.Down, True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
if sc_name.startsWith('scrollspeed_'):
|
||||||
|
scroll_animator.sync()
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def layout(is_single_page):
|
def layout(is_single_page):
|
||||||
|
cancel_scroll()
|
||||||
set_css(document.body, margin='0', border_width='0', padding='0')
|
set_css(document.body, margin='0', border_width='0', padding='0')
|
||||||
line_height.doc_style = window.getComputedStyle(document.body)
|
line_height.doc_style = window.getComputedStyle(document.body)
|
||||||
|
|
||||||
|
def cancel_scroll():
|
||||||
|
scroll_animator.stop()
|
||||||
|
|
||||||
|
def is_scroll_end(pos):
|
||||||
|
return !(0 <= pos <= document_height() - window.innerHeight)
|
||||||
|
|
||||||
DIRECTION = {'Up': -1, 'Down': 1}
|
DIRECTION = {'Up': -1, 'Down': 1}
|
||||||
class ScrollAnimator:
|
class ScrollAnimator:
|
||||||
@ -171,17 +190,23 @@ class ScrollAnimator:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.animation_id = None
|
self.animation_id = None
|
||||||
|
self.auto = False
|
||||||
|
|
||||||
def start(self, direction):
|
def is_running(self):
|
||||||
|
return self.animation_id != None
|
||||||
|
|
||||||
|
def start(self, direction, auto):
|
||||||
now = performance.now()
|
now = performance.now()
|
||||||
self.end_time = now + self.DURATION
|
self.end_time = now + self.DURATION
|
||||||
|
|
||||||
if self.animation_id is None or direction != self.direction:
|
if !self.is_running() or direction != self.direction or auto != self.auto:
|
||||||
|
self.paused = self.direction if self.auto and not auto else False
|
||||||
self.stop()
|
self.stop()
|
||||||
|
self.auto = auto
|
||||||
self.direction = direction
|
self.direction = direction
|
||||||
self.start_time = now
|
self.start_time = now
|
||||||
self.start_offset = window.pageYOffset
|
self.start_offset = window.pageYOffset
|
||||||
self.animation_id = window.requestAnimationFrame(self.smooth_scroll)
|
self.animation_id = window.requestAnimationFrame(self.auto_scroll if auto else self.smooth_scroll)
|
||||||
|
|
||||||
def smooth_scroll(self, ts):
|
def smooth_scroll(self, ts):
|
||||||
duration = (self.end_time - self.start_time)
|
duration = (self.end_time - self.start_time)
|
||||||
@ -194,14 +219,40 @@ class ScrollAnimator:
|
|||||||
if progress < 1:
|
if progress < 1:
|
||||||
self.animation_id = window.requestAnimationFrame(self.smooth_scroll)
|
self.animation_id = window.requestAnimationFrame(self.smooth_scroll)
|
||||||
else:
|
else:
|
||||||
self.animation_id = None
|
|
||||||
amt = window.pageYOffset - self.start_offset
|
amt = window.pageYOffset - self.start_offset
|
||||||
if abs(amt) < 3 and duration is self.DURATION and !(0 <= scroll_target <= document_height() - window.innerHeight):
|
if abs(amt) < 3 and duration is self.DURATION and is_scroll_end(scroll_target):
|
||||||
get_boss().send_message('next_spine_item', previous=self.direction is DIRECTION.Up)
|
get_boss().send_message('next_spine_item', previous=self.direction is DIRECTION.Up)
|
||||||
|
elif self.paused:
|
||||||
|
self.start(self.paused, True)
|
||||||
else:
|
else:
|
||||||
|
self.animation_id = None
|
||||||
report_human_scroll(amt)
|
report_human_scroll(amt)
|
||||||
|
|
||||||
|
def auto_scroll(self, ts):
|
||||||
|
elapsed = max(0, ts - self.start_time) # max to account for jitter
|
||||||
|
scroll_target = self.start_offset
|
||||||
|
scroll_target += Math.trunc(self.direction * elapsed * line_height() * opts.lines_per_sec_auto) / 1000
|
||||||
|
|
||||||
|
window.scrollTo(0, scroll_target)
|
||||||
|
scroll_finished = is_scroll_end(scroll_target)
|
||||||
|
|
||||||
|
# report every second
|
||||||
|
if elapsed >= 1000:
|
||||||
|
self.sync(ts)
|
||||||
|
|
||||||
|
if scroll_finished:
|
||||||
|
self.stop()
|
||||||
|
else:
|
||||||
|
self.animation_id = window.requestAnimationFrame(self.auto_scroll)
|
||||||
|
|
||||||
|
def sync(self, ts):
|
||||||
|
if self.auto:
|
||||||
|
report_human_scroll(window.pageYOffset - self.start_offset)
|
||||||
|
self.start_time = ts or performance.now()
|
||||||
|
self.start_offset = window.pageYOffset
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
self.auto = False
|
||||||
if self.animation_id is not None:
|
if self.animation_id is not None:
|
||||||
window.cancelAnimationFrame(self.animation_id)
|
window.cancelAnimationFrame(self.animation_id)
|
||||||
self.animation_id = None
|
self.animation_id = None
|
||||||
|
@ -12,7 +12,7 @@ from read_book.extract import get_elements
|
|||||||
from read_book.flow_mode import (
|
from read_book.flow_mode import (
|
||||||
anchor_funcs as flow_anchor_funcs, flow_onwheel, flow_to_scroll_fraction,
|
anchor_funcs as flow_anchor_funcs, flow_onwheel, flow_to_scroll_fraction,
|
||||||
handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut,
|
handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut,
|
||||||
layout as flow_layout, scroll_by_page as flow_scroll_by_page
|
layout as flow_layout, scroll_by_page as flow_scroll_by_page, cancel_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 (
|
||||||
@ -108,6 +108,7 @@ class IframeBoss:
|
|||||||
'set_reference_mode': self.set_reference_mode,
|
'set_reference_mode': self.set_reference_mode,
|
||||||
'wheel_from_margin': self.wheel_from_margin,
|
'wheel_from_margin': self.wheel_from_margin,
|
||||||
'window_size': self.received_window_size,
|
'window_size': self.received_window_size,
|
||||||
|
'overlay_shown': cancel_scroll,
|
||||||
}
|
}
|
||||||
self.comm = IframeClient(handlers)
|
self.comm = IframeClient(handlers)
|
||||||
self.last_window_ypos = 0
|
self.last_window_ypos = 0
|
||||||
@ -245,8 +246,8 @@ class IframeBoss:
|
|||||||
apply_font_size()
|
apply_font_size()
|
||||||
|
|
||||||
def change_scroll_speed(self, data):
|
def change_scroll_speed(self, data):
|
||||||
if data.lines_per_sec_smooth?:
|
if data.lines_per_sec_auto?:
|
||||||
opts.lines_per_sec_smooth = data.lines_per_sec_smooth
|
opts.lines_per_sec_auto = data.lines_per_sec_auto
|
||||||
|
|
||||||
def change_stylesheet(self, data):
|
def change_stylesheet(self, data):
|
||||||
opts.user_stylesheet = data.sheet or ''
|
opts.user_stylesheet = data.sheet or ''
|
||||||
|
@ -12,7 +12,7 @@ from session import defaults
|
|||||||
|
|
||||||
CONTAINER = unique_id('standalone-scrolling-settings')
|
CONTAINER = unique_id('standalone-scrolling-settings')
|
||||||
MIN_SCROLL_SPEED = 0.5
|
MIN_SCROLL_SPEED = 0.5
|
||||||
MAX_SCROLL_SPEED = 50
|
MAX_SCROLL_SPEED = 5
|
||||||
|
|
||||||
|
|
||||||
def restore_defaults():
|
def restore_defaults():
|
||||||
|
@ -17,6 +17,7 @@ def update_settings(settings):
|
|||||||
opts.cover_preserve_aspect_ratio = v'!!settings.cover_preserve_aspect_ratio'
|
opts.cover_preserve_aspect_ratio = v'!!settings.cover_preserve_aspect_ratio'
|
||||||
opts.hide_tooltips = settings.hide_tooltips
|
opts.hide_tooltips = settings.hide_tooltips
|
||||||
opts.is_dark_theme = v'!!settings.is_dark_theme'
|
opts.is_dark_theme = v'!!settings.is_dark_theme'
|
||||||
|
opts.lines_per_sec_auto = settings.lines_per_sec_auto
|
||||||
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)
|
||||||
|
@ -270,16 +270,22 @@ def shortcuts_definition():
|
|||||||
_('Go to a specified book location or position'),
|
_('Go to a specified book location or position'),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
'toggle_autoscroll': desc(
|
||||||
|
"Ctrl+ ",
|
||||||
|
'scroll',
|
||||||
|
_('Toggle auto-scroll'),
|
||||||
|
),
|
||||||
|
|
||||||
'scrollspeed_increase': desc(
|
'scrollspeed_increase': desc(
|
||||||
"Alt+ArrowUp",
|
"Alt+ArrowUp",
|
||||||
'scroll',
|
'scroll',
|
||||||
_('Smooth scroll faster'),
|
_('Auto scroll faster'),
|
||||||
),
|
),
|
||||||
|
|
||||||
'scrollspeed_decrease': desc(
|
'scrollspeed_decrease': desc(
|
||||||
"Alt+ArrowDown",
|
"Alt+ArrowDown",
|
||||||
'scroll',
|
'scroll',
|
||||||
_('Smooth scroll slower'),
|
_('Auto scroll slower'),
|
||||||
),
|
),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -339,6 +339,8 @@ class View:
|
|||||||
|
|
||||||
def overlay_visibility_changed(self, visible):
|
def overlay_visibility_changed(self, visible):
|
||||||
if self.iframe_wrapper.send_message:
|
if self.iframe_wrapper.send_message:
|
||||||
|
if visible:
|
||||||
|
self.iframe_wrapper.send_message('overlay_shown')
|
||||||
self.iframe_wrapper.send_message('set_forward_keypresses', forward=v'!!visible')
|
self.iframe_wrapper.send_message('set_forward_keypresses', forward=v'!!visible')
|
||||||
if ui_operations.overlay_visibility_changed:
|
if ui_operations.overlay_visibility_changed:
|
||||||
ui_operations.overlay_visibility_changed(visible)
|
ui_operations.overlay_visibility_changed(visible)
|
||||||
@ -708,6 +710,7 @@ class View:
|
|||||||
'hide_tooltips': sd.get('hide_tooltips'),
|
'hide_tooltips': sd.get('hide_tooltips'),
|
||||||
'cover_preserve_aspect_ratio': sd.get('cover_preserve_aspect_ratio'),
|
'cover_preserve_aspect_ratio': sd.get('cover_preserve_aspect_ratio'),
|
||||||
'paged_wheel_scrolls_by_screen': sd.get('paged_wheel_scrolls_by_screen'),
|
'paged_wheel_scrolls_by_screen': sd.get('paged_wheel_scrolls_by_screen'),
|
||||||
|
'lines_per_sec_auto': sd.get('lines_per_sec_auto'),
|
||||||
'lines_per_sec_smooth': sd.get('lines_per_sec_smooth'),
|
'lines_per_sec_smooth': sd.get('lines_per_sec_smooth'),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1000,7 +1003,7 @@ class View:
|
|||||||
self.iframe_wrapper.send_message('change_font_size', base_font_size=get_session_data().get('base_font_size'))
|
self.iframe_wrapper.send_message('change_font_size', base_font_size=get_session_data().get('base_font_size'))
|
||||||
|
|
||||||
def update_scroll_speed(self, amt):
|
def update_scroll_speed(self, amt):
|
||||||
self.iframe_wrapper.send_message('change_scroll_speed', lines_per_sec_smooth=change_scroll_speed(amt))
|
self.iframe_wrapper.send_message('change_scroll_speed', lines_per_sec_auto=change_scroll_speed(amt))
|
||||||
|
|
||||||
def update_color_scheme(self):
|
def update_color_scheme(self):
|
||||||
cs = self.get_color_scheme(True)
|
cs = self.get_color_scheme(True)
|
||||||
|
@ -37,6 +37,7 @@ defaults = {
|
|||||||
'header': {},
|
'header': {},
|
||||||
'hide_tooltips': False,
|
'hide_tooltips': False,
|
||||||
'keyboard_shortcuts': {},
|
'keyboard_shortcuts': {},
|
||||||
|
'lines_per_sec_auto': 1,
|
||||||
'lines_per_sec_smooth': 30,
|
'lines_per_sec_smooth': 30,
|
||||||
'margin_bottom': 20,
|
'margin_bottom': 20,
|
||||||
'margin_left': 20,
|
'margin_left': 20,
|
||||||
@ -64,6 +65,7 @@ is_local_setting = {
|
|||||||
'columns_per_screen': True,
|
'columns_per_screen': True,
|
||||||
'controls_help_shown_count': True,
|
'controls_help_shown_count': True,
|
||||||
'current_color_scheme': True,
|
'current_color_scheme': True,
|
||||||
|
'lines_per_sec_auto': True,
|
||||||
'lines_per_sec_smooth': True,
|
'lines_per_sec_smooth': True,
|
||||||
'margin_bottom': True,
|
'margin_bottom': True,
|
||||||
'margin_left': True,
|
'margin_left': True,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user