When multiswipes are enabled, this fixes the long-standing complaint that swiping to open the menu could unintentionally trigger some light panning. With the introduction of multiswipes, this problem has become more noticeable.
962 lines
34 KiB
Lua
962 lines
34 KiB
Lua
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local Geom = require("ui/geometry")
|
|
local Input = require("device").input
|
|
local Device = require("device")
|
|
local Screen = Device.screen
|
|
local Event = require("ui/event")
|
|
local UIManager = require("ui/uimanager")
|
|
local Math = require("optmath")
|
|
local logger = require("logger")
|
|
local _ = require("gettext")
|
|
|
|
|
|
local function copyPageState(page_state)
|
|
return {
|
|
page = page_state.page,
|
|
zoom = page_state.zoom,
|
|
rotation = page_state.rotation,
|
|
gamma = page_state.gamma,
|
|
offset = page_state.offset:copy(),
|
|
visible_area = page_state.visible_area:copy(),
|
|
page_area = page_state.page_area:copy(),
|
|
}
|
|
end
|
|
|
|
|
|
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,
|
|
overlap = Screen:scaleBySize(DOVERLAPPIXELS),
|
|
|
|
inverse_reading_order = false,
|
|
page_flipping_mode = false,
|
|
bookmark_flipping_mode = false,
|
|
flip_steps = {0,1,2,5,10,20,50,100}
|
|
}
|
|
|
|
function ReaderPaging:init()
|
|
self.key_events = {}
|
|
if Device:hasKeys() then
|
|
self.key_events.GotoNextPage = {
|
|
{Input.group.PgFwd}, doc = "go to next page",
|
|
event = "PagingRel", args = 1,
|
|
}
|
|
self.key_events.GotoPrevPage = {
|
|
{Input.group.PgBack}, doc = "go to previous page",
|
|
event = "PagingRel", args = -1,
|
|
}
|
|
end
|
|
if Device:hasKeyboard() then
|
|
self.key_events.GotoFirst = {
|
|
{"1"}, doc = "go to start", event = "GotoPercent", args = 0,
|
|
}
|
|
self.key_events.Goto11 = {
|
|
{"2"}, doc = "go to 11%", event = "GotoPercent", args = 11,
|
|
}
|
|
self.key_events.Goto22 = {
|
|
{"3"}, doc = "go to 22%", event = "GotoPercent", args = 22,
|
|
}
|
|
self.key_events.Goto33 = {
|
|
{"4"}, doc = "go to 33%", event = "GotoPercent", args = 33,
|
|
}
|
|
self.key_events.Goto44 = {
|
|
{"5"}, doc = "go to 44%", event = "GotoPercent", args = 44,
|
|
}
|
|
self.key_events.Goto55 = {
|
|
{"6"}, doc = "go to 55%", event = "GotoPercent", args = 55,
|
|
}
|
|
self.key_events.Goto66 = {
|
|
{"7"}, doc = "go to 66%", event = "GotoPercent", args = 66,
|
|
}
|
|
self.key_events.Goto77 = {
|
|
{"8"}, doc = "go to 77%", event = "GotoPercent", args = 77,
|
|
}
|
|
self.key_events.Goto88 = {
|
|
{"9"}, doc = "go to 88%", event = "GotoPercent", args = 88,
|
|
}
|
|
self.key_events.GotoLast = {
|
|
{"0"}, doc = "go to end", event = "GotoPercent", args = 100,
|
|
}
|
|
end
|
|
self.number_of_pages = self.ui.document.info.number_of_pages
|
|
self.ui.menu:registerToMainMenu(self)
|
|
end
|
|
|
|
function ReaderPaging:onReaderReady()
|
|
self:setupTouchZones()
|
|
end
|
|
|
|
function ReaderPaging:setupTapTouchZones()
|
|
local forward_zone = {
|
|
ratio_x = DTAP_ZONE_FORWARD.x, ratio_y = DTAP_ZONE_FORWARD.y,
|
|
ratio_w = DTAP_ZONE_FORWARD.w, ratio_h = DTAP_ZONE_FORWARD.h,
|
|
}
|
|
local backward_zone = {
|
|
ratio_x = DTAP_ZONE_BACKWARD.x, ratio_y = DTAP_ZONE_BACKWARD.y,
|
|
ratio_w = DTAP_ZONE_BACKWARD.w, ratio_h = DTAP_ZONE_BACKWARD.h,
|
|
}
|
|
|
|
if self.inverse_reading_order then
|
|
forward_zone.ratio_x = 1 - forward_zone.ratio_x - forward_zone.ratio_w
|
|
backward_zone.ratio_x = 1 - backward_zone.ratio_x - backward_zone.ratio_w
|
|
end
|
|
|
|
self.ui:registerTouchZones({
|
|
{
|
|
id = "tap_forward",
|
|
ges = "tap",
|
|
screen_zone = forward_zone,
|
|
handler = function() return self:onTapForward() end
|
|
},
|
|
{
|
|
id = "tap_backward",
|
|
ges = "tap",
|
|
screen_zone = backward_zone,
|
|
handler = function() return self:onTapBackward() end
|
|
},
|
|
})
|
|
end
|
|
|
|
-- This method will be called in onSetDimensions handler
|
|
function ReaderPaging:setupTouchZones()
|
|
-- deligate gesture listener to readerui
|
|
self.ges_events = {}
|
|
self.onGesture = nil
|
|
|
|
if not Device:isTouchDevice() then return end
|
|
|
|
self:setupTapTouchZones()
|
|
self.ui:registerTouchZones({
|
|
{
|
|
id = "paging_swipe",
|
|
ges = "swipe",
|
|
screen_zone = {
|
|
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
|
|
},
|
|
handler = function(ges) return self:onSwipe(nil, ges) end
|
|
},
|
|
{
|
|
id = "paging_pan",
|
|
ges = "pan",
|
|
rate = self.pan_rate,
|
|
screen_zone = {
|
|
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
|
|
},
|
|
handler = function(ges) return self:onPan(nil, ges) end
|
|
},
|
|
{
|
|
id = "paging_pan_release",
|
|
ges = "pan_release",
|
|
screen_zone = {
|
|
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
|
|
},
|
|
handler = function(ges) return self:onPanRelease(nil, ges) end
|
|
},
|
|
})
|
|
end
|
|
|
|
function ReaderPaging:onReadSettings(config)
|
|
self.page_positions = config:readSetting("page_positions") or {}
|
|
self:_gotoPage(config:readSetting("last_page") or 1)
|
|
self.show_overlap_enable = config:readSetting("show_overlap_enable")
|
|
if self.show_overlap_enable == nil then
|
|
self.show_overlap_enable = DSHOWOVERLAP
|
|
end
|
|
self.flipping_zoom_mode = config:readSetting("flipping_zoom_mode") or "page"
|
|
self.flipping_scroll_mode = config:readSetting("flipping_scroll_mode") or false
|
|
self.inverse_reading_order = config:readSetting("inverse_reading_order") or false
|
|
end
|
|
|
|
function ReaderPaging:onSaveSettings()
|
|
-- TODO: only save current_page page position
|
|
self.ui.doc_settings:saveSetting("page_positions", self.page_positions)
|
|
self.ui.doc_settings:saveSetting("last_page", self:getTopPage())
|
|
self.ui.doc_settings:saveSetting("percent_finished", self:getLastPercent())
|
|
self.ui.doc_settings:saveSetting("show_overlap_enable", self.show_overlap_enable)
|
|
self.ui.doc_settings:saveSetting("flipping_zoom_mode", self.flipping_zoom_mode)
|
|
self.ui.doc_settings:saveSetting("flipping_scroll_mode", self.flipping_scroll_mode)
|
|
self.ui.doc_settings:saveSetting("inverse_reading_order", self.inverse_reading_order)
|
|
end
|
|
|
|
function ReaderPaging:getLastProgress()
|
|
return self:getTopPage()
|
|
end
|
|
|
|
function ReaderPaging:getLastPercent()
|
|
if self.current_page > 0 and self.number_of_pages > 0 then
|
|
return self.current_page/self.number_of_pages
|
|
end
|
|
end
|
|
|
|
function ReaderPaging:addToMainMenu(menu_items)
|
|
-- FIXME: repeated code with page overlap menu for readerrolling
|
|
-- needs to keep only one copy of the logic as for the DRY principle.
|
|
-- The difference between the two menus is only the enabled func.
|
|
local page_overlap_menu = {
|
|
{
|
|
text = _("Page overlap"),
|
|
checked_func = function()
|
|
return self.show_overlap_enable
|
|
end,
|
|
callback = function()
|
|
self.show_overlap_enable = not self.show_overlap_enable
|
|
if not self.show_overlap_enable then
|
|
self.view:resetDimArea()
|
|
end
|
|
end,
|
|
separator = true,
|
|
},
|
|
}
|
|
local overlap_enabled_func = function() return self.show_overlap_enable end
|
|
for _, menu_entry in ipairs(self.view:genOverlapStyleMenu(overlap_enabled_func)) do
|
|
table.insert(page_overlap_menu, menu_entry)
|
|
end
|
|
menu_items.page_overlap = {
|
|
text = _("Page overlap"),
|
|
enabled_func = function()
|
|
return not self.view.page_scroll and self.zoom_mode ~= "page"
|
|
and not self.zoom_mode:find("height")
|
|
end,
|
|
sub_item_table = page_overlap_menu,
|
|
}
|
|
menu_items.read_from_right_to_left = {
|
|
text = _("Read from right to left"),
|
|
checked_func = function() return self.inverse_reading_order end,
|
|
callback = function()
|
|
self.inverse_reading_order = not self.inverse_reading_order
|
|
self:setupTapTouchZones()
|
|
end,
|
|
}
|
|
end
|
|
|
|
function ReaderPaging:onColorRenderingUpdate()
|
|
self.ui.document:updateColorRendering()
|
|
end
|
|
|
|
--[[
|
|
Set reading position on certain page
|
|
Page position is a fractional number ranging from 0 to 1, indicating the read
|
|
percentage on certain page. With the position information on each page whenever
|
|
users change font size, page margin or line spacing or close and reopen the
|
|
book, the page view will be roughly the same.
|
|
--]]
|
|
function ReaderPaging:setPagePosition(page, pos)
|
|
logger.dbg("set page position", pos)
|
|
self.page_positions[page] = pos
|
|
end
|
|
|
|
--[[
|
|
Get reading position on certain page
|
|
--]]
|
|
function ReaderPaging:getPagePosition(page)
|
|
-- Page number ought to be integer, somehow I notice that with
|
|
-- fractional page number the reader runs silently well, but the
|
|
-- number won't fit to retrieve page position.
|
|
page = math.floor(page)
|
|
logger.dbg("get page position", self.page_positions[page])
|
|
return self.page_positions[page] or 0
|
|
end
|
|
|
|
function ReaderPaging:onTapForward()
|
|
self:onPagingRel(1)
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onTapBackward()
|
|
self:onPagingRel(-1)
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onTogglePageFlipping()
|
|
self.view.flipping_visible = not self.view.flipping_visible
|
|
self.page_flipping_mode = self.view.flipping_visible
|
|
self.flipping_page = self.current_page
|
|
|
|
if self.page_flipping_mode then
|
|
self:updateOriginalPage(self.current_page)
|
|
self:enterFlippingMode()
|
|
else
|
|
self:updateOriginalPage(nil)
|
|
self:exitFlippingMode()
|
|
end
|
|
self.ui:handleEvent(Event:new("SetHinting", not self.page_flipping_mode))
|
|
self.ui:handleEvent(Event:new("ReZoom"))
|
|
UIManager:setDirty(self.view.dialog, "partial")
|
|
end
|
|
|
|
function ReaderPaging:onToggleBookmarkFlipping()
|
|
self.bookmark_flipping_mode = not self.bookmark_flipping_mode
|
|
|
|
if self.bookmark_flipping_mode then
|
|
self.orig_flipping_mode = self.view.flipping_visible
|
|
self.orig_dogear_mode = self.view.dogear_visible
|
|
|
|
self.view.flipping_visible = true
|
|
self.view.dogear_visible = true
|
|
self.bm_flipping_orig_page = self.current_page
|
|
self:enterFlippingMode()
|
|
else
|
|
self.view.flipping_visible = self.orig_flipping_mode
|
|
self.view.dogear_visible = self.orig_dogear_mode
|
|
self:exitFlippingMode()
|
|
self:_gotoPage(self.bm_flipping_orig_page)
|
|
end
|
|
self.ui:handleEvent(Event:new("SetHinting", not self.bookmark_flipping_mode))
|
|
self.ui:handleEvent(Event:new("ReZoom"))
|
|
UIManager:setDirty(self.view.dialog, "partial")
|
|
end
|
|
|
|
function ReaderPaging:enterFlippingMode()
|
|
self.orig_reflow_mode = self.view.document.configurable.text_wrap
|
|
self.orig_scroll_mode = self.view.page_scroll
|
|
self.orig_zoom_mode = self.view.zoom_mode
|
|
logger.dbg("store zoom mode", self.orig_zoom_mode)
|
|
self.view.document.configurable.text_wrap = 0
|
|
self.view.page_scroll = self.flipping_scroll_mode
|
|
Input.disable_double_tap = false
|
|
self.ui:handleEvent(Event:new("EnterFlippingMode", self.flipping_zoom_mode))
|
|
end
|
|
|
|
function ReaderPaging:exitFlippingMode()
|
|
self.view.document.configurable.text_wrap = self.orig_reflow_mode
|
|
self.view.page_scroll = self.orig_scroll_mode
|
|
Input.disable_double_tap = true
|
|
self.flipping_zoom_mode = self.view.zoom_mode
|
|
self.flipping_scroll_mode = self.view.page_scroll
|
|
logger.dbg("restore zoom mode", self.orig_zoom_mode)
|
|
self.ui:handleEvent(Event:new("ExitFlippingMode", self.orig_zoom_mode))
|
|
end
|
|
|
|
function ReaderPaging:updateOriginalPage(page)
|
|
self.original_page = page
|
|
end
|
|
|
|
function ReaderPaging:updateFlippingPage(page)
|
|
self.flipping_page = page
|
|
end
|
|
|
|
function ReaderPaging:pageFlipping(flipping_page, flipping_ges)
|
|
local whole = self.number_of_pages
|
|
local steps = #self.flip_steps
|
|
local stp_proportion = flipping_ges.distance / Screen:getWidth()
|
|
local abs_proportion = flipping_ges.distance / Screen:getHeight()
|
|
if flipping_ges.direction == "east" then
|
|
self:_gotoPage(flipping_page - self.flip_steps[math.ceil(steps*stp_proportion)])
|
|
elseif flipping_ges.direction == "west" then
|
|
self:_gotoPage(flipping_page + self.flip_steps[math.ceil(steps*stp_proportion)])
|
|
elseif flipping_ges.direction == "south" then
|
|
self:_gotoPage(flipping_page - math.floor(whole*abs_proportion))
|
|
elseif flipping_ges.direction == "north" then
|
|
self:_gotoPage(flipping_page + math.floor(whole*abs_proportion))
|
|
end
|
|
UIManager:setDirty(self.view.dialog, "partial")
|
|
end
|
|
|
|
function ReaderPaging:bookmarkFlipping(flipping_page, flipping_ges)
|
|
if flipping_ges.direction == "east" then
|
|
self.ui:handleEvent(Event:new("GotoPreviousBookmark", flipping_page))
|
|
elseif flipping_ges.direction == "west" then
|
|
self.ui:handleEvent(Event:new("GotoNextBookmark", flipping_page))
|
|
end
|
|
UIManager:setDirty(self.view.dialog, "partial")
|
|
end
|
|
|
|
function ReaderPaging:onSwipe(_, ges)
|
|
if self.bookmark_flipping_mode then
|
|
self:bookmarkFlipping(self.current_page, ges)
|
|
elseif self.page_flipping_mode and self.original_page then
|
|
self:_gotoPage(self.original_page)
|
|
elseif ges.direction == "west" then
|
|
if self.inverse_reading_order then
|
|
self:onPagingRel(-1)
|
|
else
|
|
self:onPagingRel(1)
|
|
end
|
|
elseif ges.direction == "east" then
|
|
if self.inverse_reading_order then
|
|
self:onPagingRel(1)
|
|
else
|
|
self:onPagingRel(-1)
|
|
end
|
|
else
|
|
-- update footer (time & battery)
|
|
self.view.footer:updateFooter()
|
|
-- trigger full refresh
|
|
UIManager:setDirty(nil, "full")
|
|
end
|
|
end
|
|
|
|
function ReaderPaging:onPan(_, ges)
|
|
if self.bookmark_flipping_mode then
|
|
return true
|
|
elseif self.page_flipping_mode then
|
|
if self.view.zoom_mode == "page" then
|
|
self:pageFlipping(self.flipping_page, ges)
|
|
else
|
|
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
|
|
self:onPanningRel(self.last_pan_relative_y - ges[relative_type].y)
|
|
self.last_pan_relative_y = ges[relative_type].y
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onPanRelease(_, ges)
|
|
if self.page_flipping_mode then
|
|
if self.view.zoom_mode == "page" then
|
|
self:updateFlippingPage(self.current_page)
|
|
else
|
|
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")
|
|
end
|
|
end
|
|
|
|
function ReaderPaging:onZoomModeUpdate(new_mode)
|
|
-- we need to remember zoom mode to handle page turn event
|
|
self.zoom_mode = new_mode
|
|
end
|
|
|
|
function ReaderPaging:onPageUpdate(new_page_no, orig_mode)
|
|
self.current_page = new_page_no
|
|
if self.view.page_scroll and orig_mode ~= "scrolling" then
|
|
self.ui:handleEvent(Event:new("InitScrollPageStates", orig_mode))
|
|
end
|
|
end
|
|
|
|
function ReaderPaging:onViewRecalculate(visible_area, page_area)
|
|
-- we need to remember areas to handle page turn event
|
|
self.visible_area = visible_area:copy()
|
|
self.page_area = page_area
|
|
end
|
|
|
|
function ReaderPaging:onGotoPercent(percent)
|
|
logger.dbg("goto document offset in percent:", percent)
|
|
local dest = math.floor(self.number_of_pages * percent / 100)
|
|
if dest < 1 then dest = 1 end
|
|
if dest > self.number_of_pages then
|
|
dest = self.number_of_pages
|
|
end
|
|
self:_gotoPage(dest)
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onPagingRel(diff)
|
|
if self.view.page_scroll then
|
|
self:onScrollPageRel(diff)
|
|
else
|
|
self:onGotoPageRel(diff)
|
|
end
|
|
self:setPagePosition(self:getTopPage(), self:getTopPosition())
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onPanningRel(diff)
|
|
if self.view.page_scroll then
|
|
self:onScrollPanRel(diff)
|
|
end
|
|
self:setPagePosition(self:getTopPage(), self:getTopPosition())
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:getBookLocation()
|
|
return self.view:getViewContext()
|
|
end
|
|
|
|
function ReaderPaging:onRestoreBookLocation(saved_location)
|
|
if self.view.page_scroll then
|
|
self.view:restoreViewContext(saved_location)
|
|
self:_gotoPage(self.view.page_states[1].page, "scrolling")
|
|
else
|
|
-- gotoPage will emit PageUpdate event, which will trigger recalculate
|
|
-- in ReaderView and resets the view context. So we need to call
|
|
-- restoreViewContext after gotoPage
|
|
self:_gotoPage(saved_location[1].page)
|
|
self.view:restoreViewContext(saved_location)
|
|
end
|
|
self:setPagePosition(self:getTopPage(), self:getTopPosition())
|
|
return true
|
|
end
|
|
|
|
--[[
|
|
Get read percentage on current page.
|
|
--]]
|
|
function ReaderPaging:getTopPosition()
|
|
if self.view.page_scroll then
|
|
local state = self.view.page_states[1]
|
|
return (state.visible_area.y - state.page_area.y)/state.page_area.h
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Get page number of the page drawn at the very top part of the screen.
|
|
--]]
|
|
function ReaderPaging:getTopPage()
|
|
if self.view.page_scroll then
|
|
local state = self.view.page_states[1]
|
|
return state and state.page or self.current_page
|
|
else
|
|
return self.current_page
|
|
end
|
|
end
|
|
|
|
function ReaderPaging:onInitScrollPageStates(orig_mode)
|
|
logger.dbg("init scroll page states", orig_mode)
|
|
if self.view.page_scroll and self.view.state.page then
|
|
self.orig_page = self.current_page
|
|
self.view.page_states = {}
|
|
local blank_area = Geom:new{}
|
|
blank_area:setSizeTo(self.view.dimen)
|
|
while blank_area.h > 0 do
|
|
local offset = Geom:new{}
|
|
-- caculate position in current page
|
|
if self.current_page == self.orig_page then
|
|
local page_area = self.view:getPageArea(
|
|
self.view.state.page,
|
|
self.view.state.zoom,
|
|
self.view.state.rotation)
|
|
offset.y = page_area.h * self:getPagePosition(self.current_page)
|
|
end
|
|
local state = self:getNextPageState(blank_area, offset)
|
|
table.insert(self.view.page_states, state)
|
|
if blank_area.h > 0 then
|
|
blank_area.h = blank_area.h - self.view.page_gap.height
|
|
end
|
|
if blank_area.h > 0 then
|
|
self:_gotoPage(self.current_page + 1, "scrolling")
|
|
end
|
|
end
|
|
self:_gotoPage(self.orig_page, "scrolling")
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onUpdateScrollPageRotation(rotation)
|
|
for _, state in ipairs(self.view.page_states) do
|
|
state.rotation = rotation
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onUpdateScrollPageGamma(gamma)
|
|
for _, state in ipairs(self.view.page_states) do
|
|
state.gamma = gamma
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:getNextPageState(blank_area, offset)
|
|
local page_area = self.view:getPageArea(
|
|
self.view.state.page,
|
|
self.view.state.zoom,
|
|
self.view.state.rotation)
|
|
local visible_area = Geom:new{x = 0, y = 0}
|
|
visible_area.w, visible_area.h = blank_area.w, blank_area.h
|
|
visible_area.x, visible_area.y = page_area.x, page_area.y
|
|
visible_area = visible_area:shrinkInside(page_area, offset.x, offset.y)
|
|
-- shrink blank area by the height of visible area
|
|
blank_area.h = blank_area.h - visible_area.h
|
|
return {
|
|
page = self.view.state.page,
|
|
zoom = self.view.state.zoom,
|
|
rotation = self.view.state.rotation,
|
|
gamma = self.view.state.gamma,
|
|
offset = Geom:new{ x = self.view.state.offset.x, y = 0},
|
|
visible_area = visible_area,
|
|
page_area = page_area,
|
|
}
|
|
end
|
|
|
|
function ReaderPaging:getPrevPageState(blank_area, offset)
|
|
local page_area = self.view:getPageArea(
|
|
self.view.state.page,
|
|
self.view.state.zoom,
|
|
self.view.state.rotation)
|
|
local visible_area = Geom:new{x = 0, y = 0}
|
|
visible_area.w, visible_area.h = blank_area.w, blank_area.h
|
|
visible_area.x = page_area.x
|
|
visible_area.y = page_area.y + page_area.h - visible_area.h
|
|
visible_area = visible_area:shrinkInside(page_area, offset.x, offset.y)
|
|
-- shrink blank area by the height of visible area
|
|
blank_area.h = blank_area.h - visible_area.h
|
|
return {
|
|
page = self.view.state.page,
|
|
zoom = self.view.state.zoom,
|
|
rotation = self.view.state.rotation,
|
|
gamma = self.view.state.gamma,
|
|
offset = Geom:new{ x = self.view.state.offset.x, y = 0},
|
|
visible_area = visible_area,
|
|
page_area = page_area,
|
|
}
|
|
end
|
|
|
|
function ReaderPaging:updateTopPageState(state, blank_area, offset)
|
|
local visible_area = Geom:new{
|
|
x = state.visible_area.x,
|
|
y = state.visible_area.y,
|
|
w = blank_area.w,
|
|
h = blank_area.h,
|
|
}
|
|
if state.page == self.number_of_pages then
|
|
visible_area:offsetWithin(state.page_area, offset.x, offset.y)
|
|
else
|
|
visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y)
|
|
end
|
|
-- shrink blank area by the height of visible area
|
|
blank_area.h = blank_area.h - visible_area.h
|
|
state.visible_area = visible_area
|
|
end
|
|
|
|
function ReaderPaging:updateBottomPageState(state, blank_area, offset)
|
|
local visible_area = Geom:new{
|
|
x = state.page_area.x,
|
|
y = state.visible_area.y + state.visible_area.h - blank_area.h,
|
|
w = blank_area.w,
|
|
h = blank_area.h,
|
|
}
|
|
if state.page == 1 then
|
|
visible_area:offsetWithin(state.page_area, offset.x, offset.y)
|
|
else
|
|
visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y)
|
|
end
|
|
-- shrink blank area by the height of visible area
|
|
blank_area.h = blank_area.h - visible_area.h
|
|
state.visible_area = visible_area
|
|
end
|
|
|
|
function ReaderPaging:genPageStatesFromTop(top_page_state, blank_area, offset)
|
|
-- Offset should always be greater than 0
|
|
-- otherwise if offset is less than 0 the height of blank area will be
|
|
-- larger than 0 even if page area is much larger than visible area,
|
|
-- which will trigger the drawing of next page leaving part of current
|
|
-- page undrawn. This should also be true for generating from bottom.
|
|
if offset.y < 0 then offset.y = 0 end
|
|
self:updateTopPageState(top_page_state, blank_area, offset)
|
|
local page_states = {}
|
|
if top_page_state.visible_area.h > 0 then
|
|
-- offset does not scroll pass top_page_state
|
|
table.insert(page_states, top_page_state)
|
|
end
|
|
local state
|
|
local current_page = top_page_state.page
|
|
while blank_area.h > 0 do
|
|
blank_area.h = blank_area.h - self.view.page_gap.height
|
|
if blank_area.h > 0 then
|
|
if current_page == self.number_of_pages then break end
|
|
self:_gotoPage(current_page + 1, "scrolling")
|
|
current_page = current_page + 1
|
|
state = self:getNextPageState(blank_area, Geom:new{})
|
|
table.insert(page_states, state)
|
|
end
|
|
end
|
|
return page_states
|
|
end
|
|
|
|
function ReaderPaging:genPageStatesFromBottom(bottom_page_state, blank_area, offset)
|
|
-- scroll up offset should always be less than 0
|
|
if offset.y > 0 then offset.y = 0 end
|
|
-- find out number of pages need to be removed from current view
|
|
self:updateBottomPageState(bottom_page_state, blank_area, offset)
|
|
local page_states = {}
|
|
if bottom_page_state.visible_area.h > 0 then
|
|
table.insert(page_states, bottom_page_state)
|
|
end
|
|
-- fill up current view from bottom to top
|
|
local state
|
|
local current_page = bottom_page_state.page
|
|
while blank_area.h > 0 do
|
|
blank_area.h = blank_area.h - self.view.page_gap.height
|
|
if blank_area.h > 0 then
|
|
if current_page == 1 then break end
|
|
self:_gotoPage(current_page - 1, "scrolling")
|
|
current_page = current_page - 1
|
|
state = self:getPrevPageState(blank_area, Geom:new{})
|
|
table.insert(page_states, 1, state)
|
|
end
|
|
end
|
|
return page_states
|
|
end
|
|
|
|
function ReaderPaging:onScrollPanRel(diff)
|
|
if diff == 0 then return true end
|
|
logger.dbg("pan relative height:", diff)
|
|
local offset = Geom:new{x = 0, y = diff}
|
|
local blank_area = Geom:new{}
|
|
blank_area:setSizeTo(self.view.dimen)
|
|
local new_page_states
|
|
if diff > 0 then
|
|
-- pan to scroll down
|
|
local first_page_state = copyPageState(self.view.page_states[1])
|
|
new_page_states = self:genPageStatesFromTop(
|
|
first_page_state, blank_area, offset)
|
|
elseif diff < 0 then
|
|
local last_page_state = copyPageState(
|
|
self.view.page_states[#self.view.page_states])
|
|
new_page_states = self:genPageStatesFromBottom(
|
|
last_page_state, blank_area, offset)
|
|
end
|
|
if #new_page_states == 0 then
|
|
-- if we are already at the top of first page or bottom of the last
|
|
-- page, new_page_states will be empty, in this case, nothing to update
|
|
return true
|
|
end
|
|
self.view.page_states = new_page_states
|
|
-- update current pageno to the very last part in current view
|
|
self:_gotoPage(self.view.page_states[#self.view.page_states].page,
|
|
"scrolling")
|
|
UIManager:setDirty(self.view.dialog, "partial")
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:calculateOverlap()
|
|
local footer_height = (self.view.footer_visible and 1 or 0) * self.view.footer.bar_height
|
|
return self.overlap + footer_height
|
|
end
|
|
|
|
function ReaderPaging:onScrollPageRel(page_diff)
|
|
if page_diff == 0 then return true end
|
|
if page_diff > 0 then
|
|
-- page down, last page should be moved to top
|
|
local last_page_state = table.remove(self.view.page_states)
|
|
local last_visible_area = last_page_state.visible_area
|
|
if last_page_state.page == self.number_of_pages and
|
|
last_visible_area.y + last_visible_area.h >= last_page_state.page_area.h then
|
|
table.insert(self.view.page_states, last_page_state)
|
|
self.ui:handleEvent(Event:new("EndOfBook"))
|
|
return true
|
|
end
|
|
|
|
local blank_area = Geom:new{}
|
|
blank_area:setSizeTo(self.view.dimen)
|
|
local overlap = self:calculateOverlap()
|
|
local offset = Geom:new{
|
|
x = 0,
|
|
y = last_visible_area.h - overlap
|
|
}
|
|
self.view.page_states = self:genPageStatesFromTop(last_page_state, blank_area, offset)
|
|
elseif page_diff < 0 then
|
|
-- page up, first page should be moved to bottom
|
|
local blank_area = Geom:new{}
|
|
blank_area:setSizeTo(self.view.dimen)
|
|
local overlap = self:calculateOverlap()
|
|
local first_page_state = table.remove(self.view.page_states, 1)
|
|
local offset = Geom:new{
|
|
x = 0,
|
|
y = -first_page_state.visible_area.h + overlap
|
|
}
|
|
self.view.page_states = self:genPageStatesFromBottom(
|
|
first_page_state, blank_area, offset)
|
|
end
|
|
-- update current pageno to the very last part in current view
|
|
self:_gotoPage(self.view.page_states[#self.view.page_states].page, "scrolling")
|
|
UIManager:setDirty(self.view.dialog, "partial")
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onGotoPageRel(diff)
|
|
logger.dbg("goto relative page:", diff)
|
|
local new_va = self.visible_area:copy()
|
|
local x_pan_off, y_pan_off = 0, 0
|
|
|
|
if self.zoom_mode:find("width") then
|
|
y_pan_off = self.visible_area.h * diff
|
|
elseif self.zoom_mode:find("height") then
|
|
-- negative x panning if writing direction is right to left
|
|
local direction = self.ui.document.configurable.writing_direction
|
|
x_pan_off = self.visible_area.w * diff * (direction == 1 and -1 or 1)
|
|
elseif self.zoom_mode:find("column") then
|
|
-- zoom mode for two-column navigation
|
|
|
|
y_pan_off = self.visible_area.h * diff
|
|
y_pan_off = Math.roundAwayFromZero(y_pan_off)
|
|
new_va.x = Math.roundAwayFromZero(self.visible_area.x)
|
|
new_va.y = Math.roundAwayFromZero(self.visible_area.y+y_pan_off)
|
|
-- intra-column navigation (vertical), this is the default behavior
|
|
-- if we do not reach the end of a column
|
|
|
|
if new_va:notIntersectWith(self.page_area) then
|
|
-- if we leave the page, we must either switch to the other column
|
|
-- or switch to another page (we are crossing the end of a column)
|
|
|
|
x_pan_off = self.visible_area.w * diff
|
|
x_pan_off = Math.roundAwayFromZero(x_pan_off)
|
|
new_va.x = Math.roundAwayFromZero(self.visible_area.x+x_pan_off)
|
|
new_va.y = Math.roundAwayFromZero(self.visible_area.y)
|
|
-- inter-column displacement (horizontal)
|
|
|
|
if new_va:notIntersectWith(self.page_area) then
|
|
-- if we leave the page with horizontal displacement, then we are
|
|
-- already in the border column, we must turn the page
|
|
|
|
local new_page = self.current_page + diff
|
|
if diff > 0 and new_page == self.number_of_pages + 1 then
|
|
self.ui:handleEvent(Event:new("EndOfBook"))
|
|
else
|
|
self:_gotoPage(new_page)
|
|
end
|
|
|
|
if y_pan_off < 0 then
|
|
-- if we are going back to previous page, reset view area
|
|
-- to bottom right of previous page, end of second column
|
|
self.view:PanningUpdate(self.page_area.w, self.page_area.h)
|
|
end
|
|
|
|
else
|
|
-- if we do not leave the page with horizontal displacement,
|
|
-- it means that we can stay on this page and switch column
|
|
|
|
if diff > 0 then
|
|
-- end of first column, set view area to the top right of
|
|
-- current page, beginning of second column
|
|
self.view:PanningUpdate(self.page_area.w, -self.page_area.h)
|
|
else
|
|
-- move backwards to the first column, set the view area to the
|
|
-- bottom left of the current page
|
|
self.view:PanningUpdate(-self.page_area.w, self.page_area.h)
|
|
end
|
|
end
|
|
|
|
-- if we are here, the panning has already been updated so return
|
|
return true
|
|
end
|
|
|
|
elseif self.zoom_mode ~= "free" then -- do nothing in "free" zoom mode
|
|
-- must be fit content or page zoom mode
|
|
if self.visible_area.w == self.page_area.w then
|
|
y_pan_off = self.visible_area.h * diff
|
|
else
|
|
x_pan_off = self.visible_area.w * diff
|
|
end
|
|
end
|
|
-- adjust offset to help with page turn decision
|
|
-- we dont take overlap into account here yet, otherwise new_va will
|
|
-- always intersect with page_area
|
|
x_pan_off = Math.roundAwayFromZero(x_pan_off)
|
|
y_pan_off = Math.roundAwayFromZero(y_pan_off)
|
|
new_va.x = Math.roundAwayFromZero(self.visible_area.x+x_pan_off)
|
|
new_va.y = Math.roundAwayFromZero(self.visible_area.y+y_pan_off)
|
|
|
|
if new_va:notIntersectWith(self.page_area) then
|
|
-- view area out of page area, do a page turn
|
|
local new_page = self.current_page + diff
|
|
if diff > 0 and new_page == self.number_of_pages + 1 then
|
|
self.ui:handleEvent(Event:new("EndOfBook"))
|
|
else
|
|
self:_gotoPage(new_page)
|
|
end
|
|
-- if we are going back to previous page, reset
|
|
-- view area to bottom of previous page
|
|
if x_pan_off < 0 then
|
|
self.view:PanningUpdate(self.page_area.w, 0)
|
|
elseif y_pan_off < 0 then
|
|
self.view:PanningUpdate(0, self.page_area.h)
|
|
end
|
|
else
|
|
-- not end of page yet, goto next view
|
|
-- adjust panning step according to overlap
|
|
local overlap = self:calculateOverlap()
|
|
if x_pan_off > overlap then
|
|
-- moving to next view, move view
|
|
x_pan_off = x_pan_off - overlap
|
|
elseif x_pan_off < -overlap then
|
|
x_pan_off = x_pan_off + overlap
|
|
end
|
|
if y_pan_off > overlap then
|
|
y_pan_off = y_pan_off - overlap
|
|
elseif y_pan_off < -overlap then
|
|
y_pan_off = y_pan_off + overlap
|
|
end
|
|
-- we have to calculate again to count into overlap
|
|
new_va.x = Math.roundAwayFromZero(self.visible_area.x+x_pan_off)
|
|
new_va.y = Math.roundAwayFromZero(self.visible_area.y+y_pan_off)
|
|
-- fit new view area into page area
|
|
new_va:offsetWithin(self.page_area, 0, 0)
|
|
-- calculate panning offsets
|
|
local panned_x = new_va.x - self.visible_area.x
|
|
local panned_y = new_va.y - self.visible_area.y
|
|
-- adjust for crazy floating point overflow...
|
|
if math.abs(panned_x) < 1 then
|
|
panned_x = 0
|
|
end
|
|
if math.abs(panned_y) < 1 then
|
|
panned_y = 0
|
|
end
|
|
-- singal panning update
|
|
self.view:PanningUpdate(panned_x, panned_y)
|
|
-- update dime area in ReaderView
|
|
if self.show_overlap_enable then
|
|
self.view.dim_area.h = new_va.h - math.abs(panned_y)
|
|
self.view.dim_area.w = new_va.w - math.abs(panned_x)
|
|
if panned_y < 0 then
|
|
self.view.dim_area.y = new_va.y - panned_y
|
|
else
|
|
self.view.dim_area.y = 0
|
|
end
|
|
if panned_x < 0 then
|
|
self.view.dim_area.x = new_va.x - panned_x
|
|
else
|
|
self.view.dim_area.x = 0
|
|
end
|
|
end
|
|
-- update self.visible_area
|
|
self.visible_area = new_va
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onRedrawCurrentPage()
|
|
self.ui:handleEvent(Event:new("PageUpdate", self.current_page))
|
|
return true
|
|
end
|
|
|
|
-- wrapper for bounds checking
|
|
function ReaderPaging:_gotoPage(number, orig_mode)
|
|
if number == self.current_page or not number then
|
|
-- update footer even if we stay on the same page (like when
|
|
-- viewing the bottom part of a page from a top part view)
|
|
self.view.footer:updateFooter()
|
|
return true
|
|
end
|
|
if number > self.number_of_pages or number < 1 then
|
|
logger.warn("wrong page number: "..number.."!")
|
|
return false
|
|
end
|
|
-- this is an event to allow other controllers to be aware of this change
|
|
self.ui:handleEvent(Event:new("PageUpdate", number, orig_mode))
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onGotoPage(number)
|
|
self:_gotoPage(number)
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onGotoRelativePage(number)
|
|
self:_gotoPage(self.current_page + number)
|
|
return true
|
|
end
|
|
|
|
function ReaderPaging:onGotoPercentage(percentage)
|
|
if percentage < 0 then percentage = 0 end
|
|
if percentage > 1 then percentage = 1 end
|
|
self:_gotoPage(math.floor(percentage*self.number_of_pages))
|
|
return true
|
|
end
|
|
|
|
return ReaderPaging
|