You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koreader/frontend/ui/reader/readerpaging.lua

689 lines
20 KiB
Lua

local InputContainer = require("ui/widget/container/inputcontainer")
local Screen = require("ui/screen")
local Geom = require("ui/geometry")
local Input = require("ui/input")
local GestureRange = require("ui/gesturerange")
local Device = require("ui/device")
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
local Math = require("optmath")
local ReaderPaging = InputContainer:new{
current_page = 0,
number_of_pages = 0,
visible_area = nil,
page_area = nil,
show_overlap_enable = DSHOWOVERLAP,
overlap = Screen:scaleByDPI(20),
flip_steps = {0,1,2,5,10,20,50,100}
}
function ReaderPaging:init()
if Device:hasKeyboard() then
self.key_events = {
GotoNextPage = {
{Input.group.PgFwd}, doc = "go to next page",
event = "GotoPageRel", args = 1 },
GotoPrevPage = {
{Input.group.PgBack}, doc = "go to previous page",
event = "GotoPageRel", args = -1 },
GotoFirst = {
{"1"}, doc = "go to start", event = "GotoPercent", args = 0},
Goto11 = {
{"2"}, doc = "go to 11%", event = "GotoPercent", args = 11},
Goto22 = {
{"3"}, doc = "go to 22%", event = "GotoPercent", args = 22},
Goto33 = {
{"4"}, doc = "go to 33%", event = "GotoPercent", args = 33},
Goto44 = {
{"5"}, doc = "go to 44%", event = "GotoPercent", args = 44},
Goto55 = {
{"6"}, doc = "go to 55%", event = "GotoPercent", args = 55},
Goto66 = {
{"7"}, doc = "go to 66%", event = "GotoPercent", args = 66},
Goto77 = {
{"8"}, doc = "go to 77%", event = "GotoPercent", args = 77},
Goto88 = {
{"9"}, doc = "go to 88%", event = "GotoPercent", args = 88},
GotoLast = {
{"0"}, doc = "go to end", event = "GotoPercent", args = 100},
}
end
self.number_of_pages = self.ui.document.info.number_of_pages
end
-- This method will be called in onSetDimensions handler
function ReaderPaging:initGesListener()
self.ges_events = {
TapForward = {
GestureRange:new{
ges = "tap",
range = Geom:new{
x = Screen:getWidth()/4,
y = Screen:getHeight()/4,
w = 3*Screen:getWidth()/4,
h = 2*Screen:getHeight()/3,
}
}
},
TapBackward = {
GestureRange:new{
ges = "tap",
range = Geom:new{
x = 0,
y = Screen:getHeight()/4,
w = Screen:getWidth()/4,
h = 2*Screen:getHeight()/3,
}
}
},
ToggleFlipping = {
GestureRange:new{
ges = "tap",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth()/8,
h = Screen:getHeight()/8
}
}
},
Swipe = {
GestureRange:new{
ges = "swipe",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
},
TwoFingerSwipe = {
GestureRange:new{
ges = "two_finger_swipe",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
},
Pan = {
GestureRange:new{
ges = "pan",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
},
rate = 4.0,
}
},
PanRelease = {
GestureRange:new{
ges = "pan_release",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
},
}
},
}
end
function ReaderPaging:onReadSettings(config)
self.page_positions = config:readSetting("page_positions") or {}
self:gotoPage(config:readSetting("last_page") or 1)
local soe = config:readSetting("show_overlap_enable")
if not soe then
self.show_overlap_enable = soe
end
end
function ReaderPaging:onCloseDocument()
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.current_page/self.number_of_pages)
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)
DEBUG("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)
DEBUG("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:onToggleFlipping()
self.view.flipping_visible = not self.view.flipping_visible
self.flipping_mode = self.view.flipping_visible
self.flipping_page = self.current_page
if self.flipping_mode then
self:updateOriginalPage(self.current_page)
else
self:updateOriginalPage(nil)
end
self.ui:handleEvent(Event:new("SetFlippingMode", self.flipping_mode))
self.ui:handleEvent(Event:new("SetHinting", not self.flipping_mode))
self.ui:handleEvent(Event:new("ReZoom"))
UIManager:setDirty(self.view.dialog, "partial")
end
function ReaderPaging:updateOriginalPage(page)
self.original_page = page
end
function ReaderPaging:updateFlippingPage(page)
self.flipping_page = page
end
function ReaderPaging:flipping(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:onSwipe(arg, ges)
if self.flipping_mode then
self:flipping(self.flipping_page, ges)
self:updateFlippingPage(self.current_page)
elseif ges.direction == "west" then
self:onPagingRel(1)
elseif ges.direction == "east" then
self:onPagingRel(-1)
else
UIManager.full_refresh = true
end
return true
end
function ReaderPaging:onTwoFingerSwipe(arg, ges)
if self.original_page then
self:gotoPage(self.original_page)
end
end
function ReaderPaging:onPan(arg, ges)
if self.flipping_mode then
self:flipping(self.flipping_page, ges)
elseif ges.direction == "north" then
self:onPanningRel(ges.distance)
elseif ges.direction == "south" then
self:onPanningRel(-ges.distance)
end
return true
end
function ReaderPaging:onPanRelease(arg, ges)
if self.flipping_mode then
self:updateFlippingPage(self.current_page)
else
UIManager.full_refresh = true
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)
self.current_page = new_page_no
if orig ~= "scrolling" then
self.ui:handleEvent(Event:new("InitScrollPageStates", orig))
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)
DEBUG("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
--[[
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.page
else
return self.current_page
end
end
function ReaderPaging:onInitScrollPageStates(orig)
DEBUG("init scroll page states", orig)
if self.view.page_scroll 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)
--DEBUG("init new state", state)
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 = 0, y = 0}
visible_area.w, visible_area.h = blank_area.w, blank_area.h
visible_area.x, visible_area.y = state.visible_area.x, state.visible_area.y
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
return state
end
function ReaderPaging:updateBottomPageState(state, blank_area, offset)
local visible_area = Geom:new{x = 0, y = 0}
visible_area.w, visible_area.h = blank_area.w, blank_area.h
visible_area.x = state.page_area.x
visible_area.y = state.visible_area.y + state.visible_area.h - visible_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
return state
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
local state = self:updateTopPageState(top_page_state, blank_area, offset)
--DEBUG("updated state", state)
local page_states = {}
if state.visible_area.h > 0 then
table.insert(page_states, state)
end
--DEBUG("blank area", blank_area)
local current_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 self.current_page == self.number_of_pages then break end
self:gotoPage(current_page + 1, "scrolling")
current_page = current_page + 1
local state = self:getNextPageState(blank_area, Geom:new{})
--DEBUG("new state", state)
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
local state = self:updateBottomPageState(bottom_page_state, blank_area, offset)
--DEBUG("updated state", state)
local page_states = {}
if state.visible_area.h > 0 then
table.insert(page_states, state)
end
--DEBUG("blank area", blank_area)
local current_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 self.current_page == 1 then break end
self:gotoPage(current_page - 1, "scrolling")
current_page = current_page - 1
local state = self:getPrevPageState(blank_area, Geom:new{})
--DEBUG("new state", state)
table.insert(page_states, 1, state)
end
end
return page_states
end
function ReaderPaging:onScrollPanRel(diff)
DEBUG("pan relative height:", diff)
local blank_area = Geom:new{}
blank_area:setSizeTo(self.view.dimen)
if diff > 0 then
local first_page_state = table.remove(self.view.page_states, 1)
local offset = Geom:new{
x = 0,
y = diff,
}
self.view.page_states = self:genPageStatesFromTop(first_page_state, blank_area, offset)
end
if diff < 0 then
local last_page_state = table.remove(self.view.page_states)
local offset = Geom:new{
x = 0,
y = diff
}
self.view.page_states = self:genPageStatesFromBottom(last_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, "fast")
end
function ReaderPaging:onScrollPageRel(diff)
DEBUG("scroll relative page:", diff)
local blank_area = Geom:new{}
blank_area:setSizeTo(self.view.dimen)
if diff > 0 then
local last_page_state = table.remove(self.view.page_states)
local offset = Geom:new{
x = 0,
y = last_page_state.visible_area.h - self.overlap
}
self.view.page_states = self:genPageStatesFromTop(last_page_state, blank_area, offset)
end
if diff < 0 then
local first_page_state = table.remove(self.view.page_states, 1)
local offset = Geom:new{
x = 0,
y = -first_page_state.visible_area.h + self.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)
end
function ReaderPaging:onGotoPageRel(diff)
DEBUG("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
x_pan_off = self.visible_area.w * diff
else
-- 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
self:gotoPage(self.current_page + diff)
-- 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
-- reset dim_area
--self.view.dim_area.h = 0
--self.view.dim_area.w = 0
--
else
-- not end of page yet, goto next view
-- adjust panning step according to overlap
if self.show_overlap_enable then
if x_pan_off > self.overlap then
-- moving to next view, move view
x_pan_off = x_pan_off - self.overlap
elseif x_pan_off < -self.overlap then
x_pan_off = x_pan_off + self.overlap
end
if y_pan_off > self.overlap then
y_pan_off = y_pan_off - self.overlap
elseif y_pan_off < -self.overlap then
y_pan_off = y_pan_off + self.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)
end
-- 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 float 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
function ReaderPaging:onSetDimensions()
-- update listening according to new screen dimen
if Device:isTouchDevice() then
self:initGesListener()
end
end
-- wrapper for bounds checking
function ReaderPaging:gotoPage(number, orig)
if number == self.current_page then
return true
end
if number > self.number_of_pages
or number < 1 then
DEBUG("wrong page number: "..number.."!")
return false
end
DEBUG("going to page number", number)
-- this is an event to allow other controllers to be aware of this change
self.ui:handleEvent(Event:new("PageUpdate", number, orig))
return true
end
function ReaderPaging:onGotoPage(number)
self:gotoPage(number)
return true
end
return ReaderPaging