mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Viewer: Implement auto-scroll in flow mode. Can be toggled by pressing Ctrl+Spacebar. Its speed can be controlled in Viewer preferences under scrolling behavior.
Viewer: Allow configuring the speed with which the viewer scolls wheh pressing the down key in flow mode (Viewer preferences->Scrolling behavior) Merge branch 'scrolling' of https://github.com/mgziminsky/calibre
This commit is contained in:
commit
fc5f471682
@ -6,10 +6,26 @@ from select import word_at_point
|
||||
|
||||
from dom import set_css
|
||||
from read_book.globals import current_spine_item, get_boss
|
||||
from read_book.settings import opts
|
||||
from read_book.viewport import scroll_viewport
|
||||
from utils import document_height, viewport_to_document
|
||||
|
||||
|
||||
def line_height():
|
||||
if not line_height.ans:
|
||||
ds = window.getComputedStyle(document.body)
|
||||
try:
|
||||
# will fail if line-height = "normal"
|
||||
lh = float(ds.lineHeight)
|
||||
except:
|
||||
try:
|
||||
lh = 1.2 * float(ds.fontSize)
|
||||
except:
|
||||
lh = 15
|
||||
line_height.ans = max(5, lh)
|
||||
return line_height.ans
|
||||
|
||||
|
||||
def flow_to_scroll_fraction(frac, on_initial_load):
|
||||
scroll_viewport.scroll_to(0, document_height() * frac)
|
||||
|
||||
@ -41,15 +57,15 @@ def add_small_scroll(amt):
|
||||
|
||||
|
||||
def report_human_scroll(amt):
|
||||
h = scroll_viewport.height()
|
||||
is_large_scroll = (abs(amt) / h) >= 0.5
|
||||
if amt > 0:
|
||||
h = scroll_viewport.height()
|
||||
is_large_scroll = (amt / h) >= 0.5
|
||||
if is_large_scroll:
|
||||
clear_small_scrolls()
|
||||
get_boss().report_human_scroll(amt / document_height())
|
||||
else:
|
||||
add_small_scroll(amt)
|
||||
else:
|
||||
elif amt is 0 or is_large_scroll:
|
||||
clear_small_scrolls()
|
||||
|
||||
|
||||
@ -59,8 +75,11 @@ last_change_spine_item_request = {}
|
||||
def _check_for_scroll_end(func, obj, args, report):
|
||||
before = window.pageYOffset
|
||||
func.apply(obj, args)
|
||||
|
||||
now = window.performance.now()
|
||||
scroll_animator.sync(now)
|
||||
|
||||
if window.pageYOffset is before:
|
||||
now = Date.now()
|
||||
csi = current_spine_item()
|
||||
if last_change_spine_item_request.name is csi.name and now - last_change_spine_item_request.at < 2000:
|
||||
return False
|
||||
@ -95,7 +114,7 @@ def flow_onwheel(evt):
|
||||
if evt.deltaMode is WheelEvent.DOM_DELTA_PIXEL:
|
||||
dy = evt.deltaY
|
||||
elif evt.deltaMode is WheelEvent.DOM_DELTA_LINE:
|
||||
dy = 15 * evt.deltaY
|
||||
dy = line_height() * evt.deltaY
|
||||
if evt.deltaMode is WheelEvent.DOM_DELTA_PAGE:
|
||||
dy = (scroll_viewport.height() - 30) * evt.deltaY
|
||||
if evt.deltaX:
|
||||
@ -110,24 +129,9 @@ def flow_onwheel(evt):
|
||||
elif Math.abs(dy) >= 1:
|
||||
scroll_by(dy)
|
||||
|
||||
smooth_y_data = {'last_event_at':0, 'up': False, 'timer':None, 'source':'wheel', 'pixels_per_ms': 0.2, 'scroll_interval':10, 'stop_scrolling_after':100}
|
||||
|
||||
def do_y_scroll():
|
||||
dy = (-1 if smooth_y_data.up else 1) * smooth_y_data.pixels_per_ms * smooth_y_data.scroll_interval
|
||||
if Math.abs(dy) >= 1 and scroll_by(dy):
|
||||
if Date.now() - smooth_y_data.last_event_at < smooth_y_data.stop_scrolling_after:
|
||||
smooth_y_data.timer = setTimeout(do_y_scroll, smooth_y_data.scroll_interval)
|
||||
|
||||
def smooth_y_scroll(up):
|
||||
clearTimeout(smooth_y_data.timer)
|
||||
smooth_y_data.last_event_at = Date.now()
|
||||
smooth_y_data.up = up
|
||||
do_y_scroll()
|
||||
|
||||
|
||||
@check_for_scroll_end
|
||||
def goto_boundary(y):
|
||||
scroll_viewport.scroll_to(window.pageXOffset, 0)
|
||||
def goto_boundary(dir):
|
||||
scroll_viewport.scroll_to(window.pageXOffset, 0 if dir is DIRECTION.Up else document_height())
|
||||
get_boss().report_human_scroll()
|
||||
|
||||
|
||||
@ -139,16 +143,16 @@ def scroll_by_page(direction):
|
||||
|
||||
def handle_shortcut(sc_name, evt):
|
||||
if sc_name is 'down':
|
||||
smooth_y_scroll(False)
|
||||
scroll_animator.start(DIRECTION.Down, False)
|
||||
return True
|
||||
if sc_name is 'up':
|
||||
smooth_y_scroll(True)
|
||||
scroll_animator.start(DIRECTION.Up, False)
|
||||
return True
|
||||
if sc_name is 'start_of_file':
|
||||
goto_boundary(-1)
|
||||
goto_boundary(DIRECTION.Up)
|
||||
return True
|
||||
if sc_name is 'end_of_file':
|
||||
goto_boundary(1)
|
||||
goto_boundary(DIRECTION.Down)
|
||||
return True
|
||||
if sc_name is 'left':
|
||||
window.scrollBy(-15, 0)
|
||||
@ -168,12 +172,154 @@ def handle_shortcut(sc_name, evt):
|
||||
if sc_name is 'pagedown':
|
||||
scroll_by_page(1)
|
||||
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
|
||||
|
||||
|
||||
def layout(is_single_page):
|
||||
line_height.ans = None
|
||||
scroll_animator.wait = False
|
||||
scroll_animator.sync()
|
||||
set_css(document.body, margin='0', border_width='0', padding='0')
|
||||
|
||||
# Pause auto-scroll while minimized
|
||||
document.addEventListener("visibilitychange", def():
|
||||
if (document.visibilityState is 'visible'):
|
||||
scroll_animator.sync()
|
||||
else:
|
||||
scroll_animator.pause()
|
||||
)
|
||||
|
||||
|
||||
def cancel_scroll():
|
||||
scroll_animator.stop()
|
||||
|
||||
|
||||
def is_scroll_end(pos):
|
||||
return not (0 <= pos <= document_height() - window.innerHeight)
|
||||
|
||||
DIRECTION = {'Up': -1, 'Down': 1}
|
||||
|
||||
|
||||
class ScrollAnimator:
|
||||
DURATION = 100 # milliseconds
|
||||
|
||||
def __init__(self):
|
||||
self.animation_id = None
|
||||
self.auto = False
|
||||
|
||||
def is_running(self):
|
||||
return self.animation_id is not None
|
||||
|
||||
def start(self, direction, auto):
|
||||
if self.wait:
|
||||
return
|
||||
|
||||
now = window.performance.now()
|
||||
self.end_time = now + self.DURATION
|
||||
clearTimeout(self.auto_timer)
|
||||
|
||||
if not self.is_running() or direction is not self.direction or auto is not self.auto:
|
||||
if self.auto and not auto:
|
||||
self.pause()
|
||||
self.stop()
|
||||
self.auto = auto
|
||||
self.direction = direction
|
||||
self.start_time = now
|
||||
self.start_offset = window.pageYOffset
|
||||
self.csi_idx = current_spine_item().index
|
||||
self.animation_id = window.requestAnimationFrame(self.auto_scroll if auto else self.smooth_scroll)
|
||||
|
||||
def smooth_scroll(self, ts):
|
||||
duration = self.end_time - self.start_time
|
||||
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
|
||||
|
||||
window.scrollTo(0, scroll_target)
|
||||
amt = window.pageYOffset - self.start_offset
|
||||
|
||||
if is_scroll_end(scroll_target) and (not opts.scroll_stop_boundaries or (abs(amt) < 3 and duration is self.DURATION)):
|
||||
# "Turn the page" if stop at boundaries option is false or
|
||||
# this is a new scroll action and we were already at the end
|
||||
self.animation_id = None
|
||||
self.wait = True
|
||||
report_human_scroll(amt)
|
||||
get_boss().send_message('next_spine_item', previous=self.direction is DIRECTION.Up)
|
||||
elif progress < 1:
|
||||
self.animation_id = window.requestAnimationFrame(self.smooth_scroll)
|
||||
elif self.paused:
|
||||
self.resume()
|
||||
else:
|
||||
self.animation_id = None
|
||||
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.pause()
|
||||
if opts.scroll_auto_boundary_delay >= 0:
|
||||
self.auto_timer = setTimeout(def():
|
||||
get_boss().send_message('next_spine_item', previous=self.direction is DIRECTION.Up)
|
||||
, opts.scroll_auto_boundary_delay * 1000)
|
||||
else:
|
||||
self.animation_id = window.requestAnimationFrame(self.auto_scroll)
|
||||
|
||||
def report(self):
|
||||
amt = window.pageYOffset - self.start_offset
|
||||
if abs(amt) > 0 and self.csi_idx is current_spine_item().index:
|
||||
report_human_scroll(amt)
|
||||
|
||||
def sync(self, ts):
|
||||
if self.auto:
|
||||
self.report()
|
||||
self.csi_idx = current_spine_item().index
|
||||
self.start_time = ts or window.performance.now()
|
||||
self.start_offset = window.pageYOffset
|
||||
else:
|
||||
self.resume()
|
||||
|
||||
def stop(self):
|
||||
self.auto = False
|
||||
if self.animation_id is not None:
|
||||
window.cancelAnimationFrame(self.animation_id)
|
||||
self.animation_id = None
|
||||
self.report()
|
||||
|
||||
def pause(self):
|
||||
if self.auto:
|
||||
self.paused = self.direction
|
||||
self.stop()
|
||||
else:
|
||||
self.paused = False
|
||||
|
||||
# Resume auto-scroll
|
||||
def resume(self):
|
||||
if self.paused:
|
||||
self.start(self.paused, True)
|
||||
self.paused = False
|
||||
|
||||
scroll_animator = ScrollAnimator()
|
||||
|
||||
|
||||
class FlickAnimator:
|
||||
|
||||
|
@ -12,7 +12,7 @@ from read_book.extract import get_elements
|
||||
from read_book.flow_mode import (
|
||||
anchor_funcs as flow_anchor_funcs, 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
|
||||
layout as flow_layout, scroll_by_page as flow_scroll_by_page, cancel_scroll as flow_cancel_auto_scroll
|
||||
)
|
||||
from read_book.footnotes import is_footnote_link
|
||||
from read_book.globals import (
|
||||
@ -91,27 +91,33 @@ class IframeBoss:
|
||||
self.forward_keypresses = False
|
||||
set_boss(self)
|
||||
handlers = {
|
||||
'initialize':self.initialize,
|
||||
'display': self.display,
|
||||
'scroll_to_anchor': self.on_scroll_to_anchor,
|
||||
'scroll_to_ref': self.on_scroll_to_ref,
|
||||
'scroll_to_frac': self.on_scroll_to_frac,
|
||||
'next_screen': self.on_next_screen,
|
||||
'change_font_size': self.change_font_size,
|
||||
'change_color_scheme': self.change_color_scheme,
|
||||
'gesture_from_margin': self.gesture_from_margin,
|
||||
'wheel_from_margin': self.wheel_from_margin,
|
||||
'change_font_size': self.change_font_size,
|
||||
'change_scroll_speed': self.change_scroll_speed,
|
||||
'display': self.display,
|
||||
'find': self.find,
|
||||
'window_size': self.received_window_size,
|
||||
'gesture_from_margin': self.gesture_from_margin,
|
||||
'get_current_cfi': self.get_current_cfi,
|
||||
'initialize':self.initialize,
|
||||
'modify_selection': self.modify_selection,
|
||||
'next_screen': self.on_next_screen,
|
||||
'scroll_to_anchor': self.on_scroll_to_anchor,
|
||||
'scroll_to_frac': self.on_scroll_to_frac,
|
||||
'scroll_to_ref': self.on_scroll_to_ref,
|
||||
'set_forward_keypresses': self.set_forward_keypresses,
|
||||
'set_reference_mode': self.set_reference_mode,
|
||||
'modify_selection': self.modify_selection,
|
||||
'wheel_from_margin': self.wheel_from_margin,
|
||||
'window_size': self.received_window_size,
|
||||
'overlay_shown': self.on_overlay_shown,
|
||||
}
|
||||
self.comm = IframeClient(handlers)
|
||||
self.last_window_ypos = 0
|
||||
self.length_before = None
|
||||
|
||||
def on_overlay_shown(self):
|
||||
if current_layout_mode() is 'flow':
|
||||
flow_cancel_auto_scroll()
|
||||
|
||||
def modify_selection(self, data):
|
||||
sel = window.getSelection()
|
||||
sel.modify('extend', data.direction, data.granularity)
|
||||
@ -243,6 +249,10 @@ class IframeBoss:
|
||||
opts.base_font_size = data.base_font_size
|
||||
apply_font_size()
|
||||
|
||||
def change_scroll_speed(self, data):
|
||||
if data.lines_per_sec_auto?:
|
||||
opts.lines_per_sec_auto = data.lines_per_sec_auto
|
||||
|
||||
def change_stylesheet(self, data):
|
||||
opts.user_stylesheet = data.sheet or ''
|
||||
apply_stylesheet()
|
||||
|
@ -12,17 +12,39 @@ from session import defaults
|
||||
|
||||
CONTAINER = unique_id('standalone-scrolling-settings')
|
||||
|
||||
# Scroll speeds in lines/sec
|
||||
MIN_SCROLL_SPEED_AUTO = 0.25
|
||||
MAX_SCROLL_SPEED_AUTO = 5
|
||||
|
||||
MIN_SCROLL_AUTO_DELAY = -1
|
||||
MAX_SCROLL_AUTO_DELAY = 10
|
||||
|
||||
MIN_SCROLL_SPEED_SMOOTH = 10
|
||||
MAX_SCROLL_SPEED_SMOOTH = 50
|
||||
|
||||
def restore_defaults():
|
||||
container = get_container()
|
||||
for control in container.querySelectorAll('input[name]'):
|
||||
control.checked = defaults[control.getAttribute('name')]
|
||||
val = defaults[control.getAttribute('name')]
|
||||
if control.type is 'checkbox':
|
||||
control.checked = val
|
||||
else:
|
||||
control.valueAsNumber = val
|
||||
|
||||
|
||||
def get_container():
|
||||
return document.getElementById(CONTAINER)
|
||||
|
||||
|
||||
def change_scroll_speed(amt):
|
||||
sd = get_session_data()
|
||||
lps = sd.get('lines_per_sec_auto')
|
||||
nlps = max(MIN_SCROLL_SPEED_AUTO, min(lps + amt, MAX_SCROLL_SPEED_AUTO))
|
||||
if nlps != lps:
|
||||
sd.set('lines_per_sec_auto', nlps)
|
||||
return nlps
|
||||
|
||||
|
||||
def create_scrolling_panel(container, apply_func, cancel_func):
|
||||
container.appendChild(E.div(id=CONTAINER, style='margin: 1rem'))
|
||||
container = container.lastChild
|
||||
@ -34,16 +56,54 @@ def create_scrolling_panel(container, apply_func, cancel_func):
|
||||
ans.checked = True
|
||||
return E.div(style='margin-top:1ex', E.label(ans, '\xa0' + text))
|
||||
|
||||
container.appendChild(E.div(style='margin-top:1ex', _(
|
||||
'Control how mouse based scrolling works in paged mode')))
|
||||
def spinner(name, text, **kwargs):
|
||||
ans = E.input(type='number', name=name, id=name)
|
||||
for key, val in Object.entries(kwargs):
|
||||
ans[key] = val
|
||||
ans.valueAsNumber = sd.get(name, defaults[name])
|
||||
return E.label("for"=name, text), ans
|
||||
|
||||
container.appendChild(E.div(style='margin-top:1ex', _('Control how mouse based scrolling works in paged mode')))
|
||||
container.appendChild(cb(
|
||||
'paged_wheel_scrolls_by_screen', _('Mouse wheel scrolls by screen fulls instead of pages')))
|
||||
container.appendChild(cb(
|
||||
'paged_margin_clicks_scroll_by_screen', _('Clicking on the margins scrolls by screen fulls instead of pages')))
|
||||
|
||||
container.appendChild(E.div(style='margin-top:1ex; border-top: solid 1px', '\xa0'))
|
||||
container.appendChild(E.hr())
|
||||
container.appendChild(E.div(style='margin-top:1ex', _('Control how smooth scrolling works in flow mode')))
|
||||
container.appendChild(cb(
|
||||
'book_scrollbar', _('Show a scrollbar')))
|
||||
'scroll_stop_boundaries',
|
||||
_('Stop at internal file boundaries when smooth scrolling by holding down the scroll key')
|
||||
))
|
||||
container.appendChild(
|
||||
E.div(style='display:grid;margin-top:1ex;align-items:center;grid-template-columns:25em min-content;grid-gap:1ex',
|
||||
*spinner(
|
||||
'lines_per_sec_smooth',
|
||||
_('Smooth scrolling speed in lines/sec'),
|
||||
step=5,
|
||||
min=MIN_SCROLL_SPEED_SMOOTH,
|
||||
max=MAX_SCROLL_SPEED_SMOOTH
|
||||
),
|
||||
*spinner(
|
||||
'lines_per_sec_auto',
|
||||
_('Auto scrolling speed in lines/sec'),
|
||||
step=MIN_SCROLL_SPEED_AUTO,
|
||||
min=MIN_SCROLL_SPEED_AUTO,
|
||||
max=MAX_SCROLL_SPEED_AUTO
|
||||
),
|
||||
*spinner(
|
||||
'scroll_auto_boundary_delay',
|
||||
_('Seconds to pause before auto-scrolling past internal file boundaries'),
|
||||
title=_('Use negative values to not auto-scroll past internal file boundaries'),
|
||||
step=0.25,
|
||||
min=MIN_SCROLL_AUTO_DELAY,
|
||||
max=MAX_SCROLL_AUTO_DELAY
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
container.appendChild(E.hr())
|
||||
container.appendChild(cb('book_scrollbar', _('Show a scrollbar')))
|
||||
|
||||
container.appendChild(create_button_box(restore_defaults, apply_func, cancel_func))
|
||||
|
||||
@ -57,8 +117,8 @@ def commit_scrolling(onchange):
|
||||
changed = False
|
||||
for control in container.querySelectorAll('input[name]'):
|
||||
name = control.getAttribute('name')
|
||||
val = control.checked
|
||||
if val is not sd.get(name):
|
||||
val = control.checked if control.type is 'checkbox' else control.valueAsNumber
|
||||
if val is not sd.get(name) and control.validity.valid:
|
||||
sd.set(name, val)
|
||||
changed = True
|
||||
if changed:
|
||||
|
@ -4,23 +4,28 @@ from __python__ import hash_literals
|
||||
|
||||
from elementmaker import E
|
||||
from read_book.globals import runtime
|
||||
from session import defaults
|
||||
|
||||
opts = {}
|
||||
|
||||
def update_settings(settings):
|
||||
settings = settings or {}
|
||||
opts.columns_per_screen = settings.columns_per_screen or {'portrait':0, 'landscape':0}
|
||||
opts.margin_left = max(0, settings.margin_left or 0)
|
||||
opts.margin_right = max(0, settings.margin_right or 0)
|
||||
opts.color_scheme = settings.color_scheme
|
||||
opts.base_font_size = max(8, min(settings.base_font_size or 16, 64))
|
||||
opts.user_stylesheet = settings.user_stylesheet or ''
|
||||
opts.hide_tooltips = settings.hide_tooltips
|
||||
opts.cover_preserve_aspect_ratio = v'!!settings.cover_preserve_aspect_ratio'
|
||||
settings = Object.assign({}, defaults, settings)
|
||||
opts.base_font_size = max(8, min(settings.base_font_size, 64))
|
||||
opts.bg_image_fade = settings.bg_image_fade or 'transparent'
|
||||
opts.paged_wheel_scrolls_by_screen = v'!!settings.paged_wheel_scrolls_by_screen'
|
||||
opts.color_scheme = settings.color_scheme
|
||||
opts.columns_per_screen = settings.columns_per_screen
|
||||
opts.cover_preserve_aspect_ratio = v'!!settings.cover_preserve_aspect_ratio'
|
||||
opts.hide_tooltips = settings.hide_tooltips
|
||||
opts.is_dark_theme = v'!!settings.is_dark_theme'
|
||||
opts.override_book_colors = settings.override_book_colors or 'never'
|
||||
opts.lines_per_sec_auto = settings.lines_per_sec_auto
|
||||
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.override_book_colors = settings.override_book_colors
|
||||
opts.paged_wheel_scrolls_by_screen = v'!!settings.paged_wheel_scrolls_by_screen'
|
||||
opts.scroll_auto_boundary_delay = settings.scroll_auto_boundary_delay
|
||||
opts.scroll_stop_boundaries = v'!!settings.scroll_stop_boundaries'
|
||||
opts.user_stylesheet = settings.user_stylesheet
|
||||
|
||||
update_settings()
|
||||
|
||||
|
@ -270,6 +270,24 @@ def shortcuts_definition():
|
||||
_('Go to a specified book location or position'),
|
||||
),
|
||||
|
||||
'toggle_autoscroll': desc(
|
||||
"Ctrl+ ",
|
||||
'scroll',
|
||||
_('Toggle auto-scroll'),
|
||||
),
|
||||
|
||||
'scrollspeed_increase': desc(
|
||||
"Alt+ArrowUp",
|
||||
'scroll',
|
||||
_('Auto scroll faster'),
|
||||
),
|
||||
|
||||
'scrollspeed_decrease': desc(
|
||||
"Alt+ArrowDown",
|
||||
'scroll',
|
||||
_('Auto scroll slower'),
|
||||
),
|
||||
|
||||
}
|
||||
return ans
|
||||
|
||||
|
@ -23,6 +23,7 @@ from read_book.overlay import Overlay
|
||||
from read_book.prefs.colors import resolve_color_scheme
|
||||
from read_book.prefs.font_size import change_font_size_by
|
||||
from read_book.prefs.head_foot import render_head_foot
|
||||
from read_book.prefs.scrolling import change_scroll_speed, MIN_SCROLL_SPEED_AUTO as SCROLL_SPEED_STEP
|
||||
from read_book.resources import load_resources
|
||||
from read_book.search import SearchOverlay, find_in_spine
|
||||
from read_book.shortcuts import create_shortcut_map
|
||||
@ -133,7 +134,7 @@ def margin_elem(sd, which, id, onclick, oncontextmenu):
|
||||
if onclick:
|
||||
ans.addEventListener('click', onclick)
|
||||
if oncontextmenu:
|
||||
ans.addEventListener('contextmenu', onclick)
|
||||
ans.addEventListener('contextmenu', oncontextmenu)
|
||||
if is_ios and which is 'margin_bottom' and not window.navigator.standalone and not /CriOS\//.test(window.navigator.userAgent):
|
||||
# On iOS Safari 100vh includes the size of the navbar and there is no way to
|
||||
# go fullscreen, so to make the bottom bar visible we add a margin to
|
||||
@ -200,29 +201,29 @@ class View:
|
||||
),
|
||||
)
|
||||
handlers = {
|
||||
'ready': self.on_iframe_ready,
|
||||
'bump_font_size': self.bump_font_size,
|
||||
'content_loaded': self.on_content_loaded,
|
||||
'error': self.on_iframe_error,
|
||||
'next_spine_item': self.on_next_spine_item,
|
||||
'next_section': self.on_next_section,
|
||||
'lookup_word': self.on_lookup_word,
|
||||
'find_in_spine': self.on_find_in_spine,
|
||||
'goto_doc_boundary': def(data): self.goto_doc_boundary(data.start);,
|
||||
'handle_keypress': self.on_handle_keypress,
|
||||
'handle_shortcut': self.on_handle_shortcut,
|
||||
'human_scroll': self.on_human_scroll,
|
||||
'lookup_word': self.on_lookup_word,
|
||||
'next_section': self.on_next_section,
|
||||
'next_spine_item': self.on_next_spine_item,
|
||||
'print': self.on_print,
|
||||
'ready': self.on_iframe_ready,
|
||||
'reference_item_changed': self.on_reference_item_changed,
|
||||
'report_cfi': self.on_report_cfi,
|
||||
'request_size': self.on_request_size,
|
||||
'scroll_to_anchor': self.on_scroll_to_anchor,
|
||||
'selectionchange': self.on_selection_change,
|
||||
'show_chrome': self.show_chrome,
|
||||
'show_footnote': self.on_show_footnote,
|
||||
'update_cfi': self.on_update_cfi,
|
||||
'update_progress_frac': self.on_update_progress_frac,
|
||||
'report_cfi': self.on_report_cfi,
|
||||
'update_toc_position': self.on_update_toc_position,
|
||||
'content_loaded': self.on_content_loaded,
|
||||
'show_chrome': self.show_chrome,
|
||||
'bump_font_size': self.bump_font_size,
|
||||
'find_in_spine': self.on_find_in_spine,
|
||||
'request_size': self.on_request_size,
|
||||
'show_footnote': self.on_show_footnote,
|
||||
'print': self.on_print,
|
||||
'human_scroll': self.on_human_scroll,
|
||||
'selectionchange': self.on_selection_change,
|
||||
'handle_shortcut': self.on_handle_shortcut,
|
||||
'handle_keypress': self.on_handle_keypress,
|
||||
'reference_item_changed': self.on_reference_item_changed,
|
||||
}
|
||||
entry_point = None if runtime.is_standalone_viewer else 'read_book.iframe'
|
||||
if runtime.is_standalone_viewer:
|
||||
@ -338,6 +339,8 @@ class View:
|
||||
|
||||
def overlay_visibility_changed(self, visible):
|
||||
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')
|
||||
if ui_operations.overlay_visibility_changed:
|
||||
ui_operations.overlay_visibility_changed(visible)
|
||||
@ -409,6 +412,10 @@ class View:
|
||||
self.iframe_wrapper.send_message('modify_selection', direction='backward', granularity='word')
|
||||
elif data.name is 'extend_selection_by_word':
|
||||
self.iframe_wrapper.send_message('modify_selection', direction='forward', granularity='word')
|
||||
elif data.name is 'scrollspeed_increase':
|
||||
self.update_scroll_speed(SCROLL_SPEED_STEP)
|
||||
elif data.name is 'scrollspeed_decrease':
|
||||
self.update_scroll_speed(-SCROLL_SPEED_STEP)
|
||||
|
||||
def on_selection_change(self, data):
|
||||
self.currently_showing.selected_text = data.text
|
||||
@ -703,6 +710,10 @@ class View:
|
||||
'hide_tooltips': sd.get('hide_tooltips'),
|
||||
'cover_preserve_aspect_ratio': sd.get('cover_preserve_aspect_ratio'),
|
||||
'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'),
|
||||
'scroll_auto_boundary_delay': sd.get('scroll_auto_boundary_delay'),
|
||||
'scroll_stop_boundaries': sd.get('scroll_stop_boundaries'),
|
||||
}
|
||||
|
||||
def show_name(self, name, initial_position=None):
|
||||
@ -993,6 +1004,9 @@ class View:
|
||||
def update_font_size(self):
|
||||
self.iframe_wrapper.send_message('change_font_size', base_font_size=get_session_data().get('base_font_size'))
|
||||
|
||||
def update_scroll_speed(self, amt):
|
||||
self.iframe_wrapper.send_message('change_scroll_speed', lines_per_sec_auto=change_scroll_speed(amt))
|
||||
|
||||
def update_color_scheme(self):
|
||||
cs = self.get_color_scheme(True)
|
||||
self.iframe_wrapper.send_message('change_color_scheme', color_scheme=cs)
|
||||
|
@ -9,72 +9,80 @@ from ajax import ajax_send
|
||||
|
||||
defaults = {
|
||||
# Book list settings
|
||||
'view_mode': 'cover_grid',
|
||||
'sort': 'timestamp.desc', # comma separated list of items of the form: field.order
|
||||
'copy_to_library_dupes': 'add;overwrite',
|
||||
'last_sort_order': {},
|
||||
'show_all_metadata': False, # show all metadata fields in the book details panel
|
||||
'copy_to_library_dupes': 'add;overwrite',
|
||||
'sort': 'timestamp.desc', # comma separated list of items of the form: field.order
|
||||
'view_mode': 'cover_grid',
|
||||
|
||||
# Tag Browser settings
|
||||
'partition_method': 'first letter', # other choices: 'disable', 'partition'
|
||||
'and_search_terms': False, # how to add search terms to the search expression from the Tag Browser
|
||||
'collapse_at': 25, # number of items at which sub-groups are created, 0 to disable
|
||||
'dont_collapse': '', # comma separated list of category names
|
||||
'sort_tags_by': 'name', # other choices: popularity, rating
|
||||
'hide_empty_categories': 'no',
|
||||
'and_search_terms': False, # how to add search terms to the search expression from the Tag Browser
|
||||
'partition_method': 'first letter', # other choices: 'disable', 'partition'
|
||||
'sort_tags_by': 'name', # other choices: popularity, rating
|
||||
|
||||
# Book reader settings
|
||||
'margin_right': 20,
|
||||
'margin_left': 20,
|
||||
'margin_top': 20,
|
||||
'margin_bottom': 20,
|
||||
'read_mode': 'paged',
|
||||
'cover_preserve_aspect_ratio': True,
|
||||
'max_text_height': 0,
|
||||
'max_text_width': 0,
|
||||
'columns_per_screen': {'portrait':0, 'landscape':0},
|
||||
'user_stylesheet': '',
|
||||
'background_image': None,
|
||||
'background_image_style': 'scaled',
|
||||
'background_image_fade': 0,
|
||||
'current_color_scheme': 'system',
|
||||
'user_color_schemes': {},
|
||||
'override_book_colors': 'never',
|
||||
'background_image_style': 'scaled',
|
||||
'background_image': None,
|
||||
'base_font_size': 16,
|
||||
'book_scrollbar': False,
|
||||
'columns_per_screen': {'portrait':0, 'landscape':0},
|
||||
'controls_help_shown_count': 0,
|
||||
'header': {},
|
||||
'cover_preserve_aspect_ratio': True,
|
||||
'current_color_scheme': 'system',
|
||||
'footer': {'right': 'progress'},
|
||||
'word_actions': v'[]',
|
||||
'header': {},
|
||||
'hide_tooltips': False,
|
||||
'keyboard_shortcuts': {},
|
||||
'book_scrollbar': False,
|
||||
'lines_per_sec_auto': 1,
|
||||
'lines_per_sec_smooth': 20,
|
||||
'margin_bottom': 20,
|
||||
'margin_left': 20,
|
||||
'margin_right': 20,
|
||||
'margin_top': 20,
|
||||
'max_text_height': 0,
|
||||
'max_text_width': 0,
|
||||
'override_book_colors': 'never',
|
||||
'paged_margin_clicks_scroll_by_screen': True,
|
||||
'paged_wheel_scrolls_by_screen': False,
|
||||
'read_mode': 'paged',
|
||||
'scroll_auto_boundary_delay': 5,
|
||||
'scroll_stop_boundaries': False,
|
||||
'standalone_font_settings': {},
|
||||
'standalone_misc_settings': {},
|
||||
'standalone_recently_opened': v'[]',
|
||||
'paged_wheel_scrolls_by_screen': False,
|
||||
'paged_margin_clicks_scroll_by_screen': True,
|
||||
'user_color_schemes': {},
|
||||
'user_stylesheet': '',
|
||||
'word_actions': v'[]',
|
||||
}
|
||||
|
||||
is_local_setting = {
|
||||
'margin_right': True,
|
||||
'margin_left': True,
|
||||
'margin_top': True,
|
||||
'background_image_fade': True,
|
||||
'background_image_style': True,
|
||||
'background_image': True,
|
||||
'base_font_size': True,
|
||||
'columns_per_screen': True,
|
||||
'controls_help_shown_count': True,
|
||||
'current_color_scheme': True,
|
||||
'lines_per_sec_auto': True,
|
||||
'lines_per_sec_smooth': True,
|
||||
'margin_bottom': True,
|
||||
'read_mode': 'paged',
|
||||
'margin_left': True,
|
||||
'margin_right': True,
|
||||
'margin_top': True,
|
||||
'max_text_height': True,
|
||||
'max_text_width': True,
|
||||
'columns_per_screen': True,
|
||||
'user_stylesheet': True,
|
||||
'background_image': True,
|
||||
'background_image_style': True,
|
||||
'background_image_fade': True,
|
||||
'current_color_scheme': True,
|
||||
'override_book_colors': True,
|
||||
'base_font_size': True,
|
||||
'controls_help_shown_count': True,
|
||||
'read_mode': 'paged',
|
||||
'scroll_auto_boundary_delay': True,
|
||||
'scroll_stop_boundaries': True,
|
||||
'standalone_font_settings': True,
|
||||
'standalone_misc_settings': True,
|
||||
'standalone_recently_opened': True,
|
||||
'user_stylesheet': True,
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user