mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Fix scrolling, add inertial scroll on non-eInk devices
Add a new reader module: ReaderScrolling, that exposes some Scrolling options to the menu (which are to be used by and implemented in ReaderPaging and ReaderRolling themselves) and implement some inertial scrolling logic used by both of them. Default to "Classic scrolling" which is the expected behaviour on phones and tablets. The old CreDocument buggy behaviour is available as "Turbo scrolling" for both Paging and Rolling documents. Added a "On release scrolling" option that might be useful on eInk to avoid dynamic pan/scrolling. Try to avoid bad interactions between pan and swipe, cancelling unwanted panning if we ended up doing a swipe or multiswipe.
This commit is contained in:
@@ -7,6 +7,7 @@ local Math = require("optmath")
|
||||
local MultiConfirmBox = require("ui/widget/multiconfirmbox")
|
||||
local Notification = require("ui/widget/notification")
|
||||
local ReaderZooming = require("apps/reader/modules/readerzooming")
|
||||
local TimeVal = require("ui/timeval")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local bit = require("bit")
|
||||
local logger = require("logger")
|
||||
@@ -32,7 +33,6 @@ local ReaderPaging = InputContainer:new{
|
||||
pan_rate = 30, -- default 30 ops, will be adjusted in readerui
|
||||
current_page = 0,
|
||||
number_of_pages = 0,
|
||||
last_pan_relative_y = 0,
|
||||
visible_area = nil,
|
||||
page_area = nil,
|
||||
show_overlap_enable = nil,
|
||||
@@ -41,7 +41,7 @@ local ReaderPaging = InputContainer:new{
|
||||
inverse_reading_order = nil,
|
||||
page_flipping_mode = false,
|
||||
bookmark_flipping_mode = false,
|
||||
flip_steps = {0,1,2,5,10,20,50,100}
|
||||
flip_steps = {0,1,2,5,10,20,50,100},
|
||||
}
|
||||
|
||||
function ReaderPaging:init()
|
||||
@@ -101,6 +101,7 @@ function ReaderPaging:init()
|
||||
{"0"}, doc = "go to end", event = "GotoPercent", args = 100,
|
||||
}
|
||||
end
|
||||
self.pan_interval = TimeVal:new{ usec = 1000000 / self.pan_rate }
|
||||
self.number_of_pages = self.ui.document.info.number_of_pages
|
||||
self.ui.menu:registerToMainMenu(self)
|
||||
end
|
||||
@@ -173,7 +174,6 @@ function ReaderPaging:setupTouchZones()
|
||||
{
|
||||
id = "paging_pan",
|
||||
ges = "pan",
|
||||
rate = self.pan_rate,
|
||||
screen_zone = {
|
||||
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
|
||||
},
|
||||
@@ -422,7 +422,43 @@ function ReaderPaging:bookmarkFlipping(flipping_page, flipping_ges)
|
||||
UIManager:setDirty(self.view.dialog, "partial")
|
||||
end
|
||||
|
||||
function ReaderPaging:onScrollSettingsUpdated(scroll_method, inertial_scroll_enabled, scroll_activation_delay)
|
||||
self.scroll_method = scroll_method
|
||||
self.scroll_activation_delay = TimeVal:new{ usec = scroll_activation_delay * 1000 }
|
||||
if inertial_scroll_enabled then
|
||||
self.ui.scrolling:setInertialScrollCallbacks(
|
||||
function(distance) -- do_scroll_callback
|
||||
if not self.ui.document then
|
||||
return false
|
||||
end
|
||||
UIManager.currently_scrolling = true
|
||||
local top_page, top_position = self:getTopPage(), self:getTopPosition()
|
||||
self:onPanningRel(distance)
|
||||
return not (top_page == self:getTopPage() and top_position == self:getTopPosition())
|
||||
end,
|
||||
function() -- scroll_done_callback
|
||||
UIManager.currently_scrolling = false
|
||||
UIManager:setDirty(self.view.dialog, "partial")
|
||||
end
|
||||
)
|
||||
else
|
||||
self.ui.scrolling:setInertialScrollCallbacks(nil, nil)
|
||||
end
|
||||
end
|
||||
|
||||
function ReaderPaging:onSwipe(_, ges)
|
||||
if self._pan_has_scrolled then
|
||||
-- We did some panning but released after a short amount of time,
|
||||
-- so this gesture ended up being a Swipe - and this swipe was
|
||||
-- not handled by the other modules (so, not opening the menus).
|
||||
-- Do as :onPanRelese() and ignore this swipe.
|
||||
self:onPanRelease() -- no arg, so we know there we come from here
|
||||
return true
|
||||
else
|
||||
self._pan_started = false
|
||||
UIManager.currently_scrolling = false
|
||||
self._pan_page_states_to_restore = nil
|
||||
end
|
||||
local direction = BD.flipDirectionIfMirroredUILayout(ges.direction)
|
||||
if self.bookmark_flipping_mode then
|
||||
self:bookmarkFlipping(self.current_page, ges)
|
||||
@@ -461,16 +497,86 @@ function ReaderPaging:onPan(_, ges)
|
||||
self.view:PanningStart(-ges.relative.x, -ges.relative.y)
|
||||
end
|
||||
elseif ges.direction == "north" or ges.direction == "south" then
|
||||
local relative_type = "relative"
|
||||
if self.ui.gesture and self.ui.gesture.multiswipes_enabled then
|
||||
relative_type = "relative_delayed"
|
||||
end
|
||||
-- this is only used when mouse wheel is used
|
||||
if ges.mousewheel_direction and not self.view.page_scroll then
|
||||
-- Mouse wheel generates a Pan event: in page mode, move one
|
||||
-- page per event. Scroll mode is handled in the 'else' branch
|
||||
-- and use the wheeled distance.
|
||||
self:onGotoViewRel(-1 * ges.mousewheel_direction)
|
||||
else
|
||||
self:onPanningRel(self.last_pan_relative_y - ges[relative_type].y)
|
||||
self.last_pan_relative_y = ges[relative_type].y
|
||||
elseif self.view.page_scroll then
|
||||
if not self._pan_started then
|
||||
self._pan_started = true
|
||||
-- Re-init state variables
|
||||
self._pan_has_scrolled = false
|
||||
self._pan_prev_relative_y = 0
|
||||
self._pan_to_scroll_later = 0
|
||||
self._pan_real_last_time = TimeVal.zero
|
||||
if ges.mousewheel_direction then
|
||||
self._pan_activation_time = false
|
||||
else
|
||||
self._pan_activation_time = ges.time + self.scroll_activation_delay
|
||||
end
|
||||
-- We will restore the previous position if this pan
|
||||
-- ends up being a swipe or a multiswipe
|
||||
-- Somehow, accumulating the distances scrolled in a self._pan_dist_to_restore
|
||||
-- so we can scroll these back may not always put us back to the original
|
||||
-- position (possibly because of these page_states?). It's safer
|
||||
-- to remember the original page_states and restore that. We can keep
|
||||
-- a reference to the original table as onPanningRel() will have this
|
||||
-- table replaced.
|
||||
self._pan_page_states_to_restore = self.view.page_states
|
||||
end
|
||||
local scroll_now = false
|
||||
if self._pan_activation_time and ges.time >= self._pan_activation_time then
|
||||
self._pan_activation_time = false -- We can go on, no need to check again
|
||||
end
|
||||
if not self._pan_activation_time and ges.time - self._pan_real_last_time >= self.pan_interval then
|
||||
scroll_now = true
|
||||
self._pan_real_last_time = ges.time
|
||||
end
|
||||
local scroll_dist = 0
|
||||
if self.scroll_method == self.ui.scrolling.SCROLL_METHOD_CLASSIC then
|
||||
-- Scroll by the distance the finger moved since last pan event,
|
||||
-- having the document follows the finger
|
||||
scroll_dist = self._pan_prev_relative_y - ges.relative.y
|
||||
self._pan_prev_relative_y = ges.relative.y
|
||||
if not self._pan_has_scrolled then
|
||||
-- Avoid checking this for each pan, no need once we have scrolled
|
||||
if self.ui.scrolling:cancelInertialScroll() or self.ui.scrolling:cancelledByTouch() then
|
||||
-- If this pan or its initial touch did cancel some inertial scrolling,
|
||||
-- ignore activation delay to allow continuous scrolling
|
||||
self._pan_activation_time = false
|
||||
scroll_now = true
|
||||
self._pan_real_last_time = ges.time
|
||||
end
|
||||
end
|
||||
self.ui.scrolling:accountManualScroll(scroll_dist, ges.time)
|
||||
elseif self.scroll_method == self.ui.scrolling.SCROLL_METHOD_TURBO then
|
||||
-- Legacy scrolling "buggy" behaviour, that can actually be nice
|
||||
-- Scroll by the distance from the initial finger position, this distance
|
||||
-- controlling the speed of the scrolling)
|
||||
if scroll_now then
|
||||
scroll_dist = -ges.relative.y
|
||||
end
|
||||
-- We don't accumulate in _pan_to_scroll_later
|
||||
elseif self.scroll_method == self.ui.scrolling.SCROLL_METHOD_ON_RELEASE then
|
||||
self._pan_to_scroll_later = -ges.relative.y
|
||||
if scroll_now then
|
||||
self._pan_has_scrolled = true -- so we really apply it later
|
||||
end
|
||||
scroll_dist = 0
|
||||
scroll_now = false
|
||||
end
|
||||
if scroll_now then
|
||||
local dist = self._pan_to_scroll_later + scroll_dist
|
||||
self._pan_to_scroll_later = 0
|
||||
if dist ~= 0 then
|
||||
self._pan_has_scrolled = true
|
||||
UIManager.currently_scrolling = true
|
||||
self:onPanningRel(dist)
|
||||
end
|
||||
else
|
||||
self._pan_to_scroll_later = self._pan_to_scroll_later + scroll_dist
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
@@ -484,12 +590,40 @@ function ReaderPaging:onPanRelease(_, ges)
|
||||
self.view:PanningStop()
|
||||
end
|
||||
else
|
||||
self.last_pan_relative_y = 0
|
||||
-- trigger full refresh to clear ghosting generated by previous fast refresh
|
||||
UIManager:setDirty(nil, "full")
|
||||
if self._pan_has_scrolled and self._pan_to_scroll_later ~= 0 then
|
||||
self:onPanningRel(self._pan_to_scroll_later)
|
||||
end
|
||||
self._pan_started = false
|
||||
self._pan_page_states_to_restore = nil
|
||||
UIManager.currently_scrolling = false
|
||||
if self._pan_has_scrolled then
|
||||
self._pan_has_scrolled = false
|
||||
-- Don't do any inertial scrolling if pan events come from
|
||||
-- a mousewheel (which may have itself some inertia)
|
||||
if (ges and ges.from_mousewheel) or not self.ui.scrolling:startInertialScroll() then
|
||||
UIManager:setDirty(self.view.dialog, "partial")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ReaderPaging:onHandledAsSwipe()
|
||||
if self._pan_started then
|
||||
-- Restore original position as this pan we've started handling
|
||||
-- has ended up being a multiswipe or handled as a swipe to open
|
||||
-- top or bottom menus
|
||||
if self._pan_has_scrolled then
|
||||
self.view.page_states = self._pan_page_states_to_restore
|
||||
self:_gotoPage(self.view.page_states[#self.view.page_states].page, "scrolling")
|
||||
UIManager:setDirty(self.view.dialog, "ui")
|
||||
end
|
||||
self._pan_page_states_to_restore = nil
|
||||
self._pan_started = false
|
||||
self._pan_has_scrolled = false
|
||||
UIManager.currently_scrolling = false
|
||||
end
|
||||
return true
|
||||
end
|
||||
function ReaderPaging:onZoomModeUpdate(new_mode)
|
||||
-- we need to remember zoom mode to handle page turn event
|
||||
self.zoom_mode = new_mode
|
||||
|
||||
Reference in New Issue
Block a user