2
0
mirror of https://github.com/koreader/koreader synced 2024-10-31 21:20:20 +00:00
koreader/frontend/apps/reader/modules/readerview.lua
Qingping Hou 1c5543358c readerfooter(fix): avoid setting mini footer to invisible when full progress bar is on
Our previous assumption is user will only choose between full or min
bar. The does not hold anymore as many more info has been added to the
mini bar and sometimes user might want to have both of them on. This
patch makes the reader behavior consistent when both bars are set to be
on for a document.
2016-07-23 18:00:37 -07:00

796 lines
25 KiB
Lua

local AlphaContainer = require("ui/widget/container/alphacontainer")
local ReaderFlipping = require("apps/reader/modules/readerflipping")
local ReaderFooter = require("apps/reader/modules/readerfooter")
local ReaderDogear = require("apps/reader/modules/readerdogear")
local OverlapGroup = require("ui/widget/overlapgroup")
local ImageWidget = require("ui/widget/imagewidget")
local UIManager = require("ui/uimanager")
local Screen = require("device").screen
local Geom = require("ui/geometry")
local Event = require("ui/event")
local dbg = require("dbg")
local Blitbuffer = require("ffi/blitbuffer")
local _ = require("gettext")
local ReaderView = OverlapGroup:new{
document = nil,
-- single page state
state = {
page = nil,
pos = 0,
zoom = 1.0,
rotation = 0,
gamma = 1.0,
offset = nil,
bbox = nil,
},
outer_page_color = Blitbuffer.gray(DOUTER_PAGE_COLOR/15),
-- hightlight with "lighten" or "underscore" or "invert"
highlight = {
lighten_factor = 0.2,
temp_drawer = "invert",
temp = {},
saved_drawer = "lighten",
saved = {},
},
highlight_visible = true,
-- PDF/DjVu continuous paging
page_scroll = nil,
page_bgcolor = Blitbuffer.gray(DBACKGROUND_COLOR/15),
page_states = {},
scroll_mode = "vertical",
page_gap = {
width = Screen:scaleBySize(8),
height = Screen:scaleBySize(8),
color = Blitbuffer.COLOR_GREY,
},
-- DjVu page rendering mode (used in djvu.c:drawPage())
render_mode = DRENDER_MODE, -- default to COLOR
-- Crengine view mode
view_mode = DCREREADER_VIEW_MODE, -- default to page mode
hinting = true,
-- visible area within current viewing page
visible_area = Geom:new{x = 0, y = 0},
-- dimen for current viewing page
page_area = Geom:new{},
-- dimen for area to dim
dim_area = nil,
-- has footer
footer_visible = nil,
-- has dogear
dogear_visible = false,
-- in flipping state
flipping_visible = false,
-- auto save settings after turning pages
auto_save_paging_count = 0,
autoSaveSettings = function()end
}
function ReaderView:init()
-- fix recalculate from close document pageno
self.state.page = nil
-- fix inherited dim_area for following opened documents
self:resetDimArea()
self:addWidgets()
self.ui:registerPostInitCallback(function()
self.ui.menu:registerToMainMenu(self.footer)
end)
self.emitHintPageEvent = function()
self.ui:handleEvent(Event:new("HintPage", self.hinting))
end
end
function ReaderView:resetDimArea()
self.dim_area = Geom:new{w = 0, h = 0}
end
function ReaderView:addWidgets()
self.dogear = ReaderDogear:new{
view = self,
ui = self.ui,
}
self.footer = ReaderFooter:new{
view = self,
ui = self.ui,
}
self.flipping = ReaderFlipping:new{
view = self,
ui = self.ui,
}
self.arrow = AlphaContainer:new{
alpha = 0.6,
ImageWidget:new{
file = "resources/icons/appbar.control.expand.png",
}
}
self[1] = self.dogear
self[2] = self.footer
self[3] = self.flipping
end
function ReaderView:resetLayout()
for i, widget in ipairs(self) do
widget:resetLayout()
end
end
function ReaderView:paintTo(bb, x, y)
dbg("painting", self.visible_area, "to", x, y)
if self.page_scroll then
self:drawPageBackground(bb, x, y)
else
self:drawPageSurround(bb, x, y)
end
-- draw page content
if self.ui.document.info.has_pages then
if self.page_scroll then
self:drawScrollPages(bb, x, y)
else
self:drawSinglePage(bb, x, y)
end
else
if self.view_mode == "page" then
self:drawPageView(bb, x, y)
elseif self.view_mode == "scroll" then
self:drawScrollView(bb, x, y)
end
end
-- dim last read area
if self.dim_area.w ~= 0 and self.dim_area.h ~= 0 then
--dbg("dim area", self.dim_area)
if self.page_overlap_style == "dim" then
bb:dimRect(
self.dim_area.x, self.dim_area.y,
self.dim_area.w, self.dim_area.h
)
elseif self.page_overlap_style == "arrow" then
self.arrow:paintTo(bb, 0, self.dim_area.h)
end
end
-- draw saved highlight
if self.highlight_visible then
self:drawSavedHighlight(bb, x, y)
end
-- draw temporary highlight
if self.highlight.temp then
self:drawTempHighlight(bb, x, y)
end
-- paint dogear
if self.dogear_visible then
self.dogear:paintTo(bb, x, y)
end
-- paint footer
if self.footer_visible then
self.footer:paintTo(bb, x, y)
end
-- paint flipping
if self.flipping_visible then
self.flipping:paintTo(bb, x, y)
end
-- stop activity indicator
self.ui:handleEvent(Event:new("StopActivityIndicator"))
end
--[[
Given coordinates on the screen return position in original page
]]--
function ReaderView:screenToPageTransform(pos)
if self.ui.document.info.has_pages then
if self.page_scroll then
return self:getScrollPagePosition(pos)
else
return self:getSinglePagePosition(pos)
end
else
pos.page = self.ui.document:getCurrentPage()
-- local last_y = self.ui.document:getCurrentPos()
dbg("document has no pages at", pos)
return pos
end
end
--[[
Given rectangle in original page return rectangle on the screen
]]--
function ReaderView:pageToScreenTransform(page, rect)
if self.ui.document.info.has_pages then
if self.page_scroll then
return self:getScrollPageRect(page, rect)
else
return self:getSinglePageRect(rect)
end
else
return rect
end
end
--[[
Get page area on screen for a given page number
--]]
function ReaderView:getScreenPageArea(page)
if self.ui.document.info.has_pages then
local area = Geom:new{x = 0, y = 0}
if self.page_scroll then
for _, state in ipairs(self.page_states) do
if page ~= state.page then
area.y = area.y + state.visible_area.h + state.offset.y
area.y = area.y + self.page_gap.height
else
area.x = state.offset.x
area.w = state.visible_area.w
area.h = state.visible_area.h
return area
end
end
else
area.x = self.state.offset.x
area.y = self.state.offset.y
area.w = self.visible_area.w
area.h = self.visible_area.h
return area
end
else
return self.dimen
end
end
function ReaderView:drawPageBackground(bb, x, y)
bb:paintRect(x, y, self.dimen.w, self.dimen.h, self.page_bgcolor)
end
function ReaderView:drawPageSurround(bb, x, y)
if self.dimen.h > self.visible_area.h then
bb:paintRect(x, y, self.dimen.w, self.state.offset.y, self.outer_page_color)
bb:paintRect(x, y + self.dimen.h - self.state.offset.y - 1,
self.dimen.w, self.state.offset.y + 1, self.outer_page_color)
end
if self.dimen.w > self.visible_area.w then
bb:paintRect(x, y, self.state.offset.x, self.dimen.h, self.outer_page_color)
bb:paintRect(x + self.dimen.w - self.state.offset.x - 1, y,
self.state.offset.x + 1, self.dimen.h, self.outer_page_color)
end
end
function ReaderView:drawScrollPages(bb, x, y)
local pos = Geom:new{x = x , y = y}
for page, state in ipairs(self.page_states) do
self.ui.document:drawPage(
bb,
pos.x + state.offset.x,
pos.y + state.offset.y,
state.visible_area,
state.page,
state.zoom,
state.rotation,
state.gamma,
self.render_mode)
pos.y = pos.y + state.visible_area.h
-- draw page gap if not the last part
if page ~= #self.page_states then
self:drawPageGap(bb, pos.x, pos.y)
pos.y = pos.y + self.page_gap.height
end
end
UIManager:nextTick(self.emitHintPageEvent)
end
function ReaderView:getCurrentPageList()
local pages = {}
if self.ui.document.info.has_pages then
if self.page_scroll then
for _, state in ipairs(self.page_states) do
table.insert(pages, state.page)
end
else
table.insert(pages, self.state.page)
end
end
return pages
end
function ReaderView:getScrollPagePosition(pos)
local x_p, y_p
local x_s, y_s = pos.x, pos.y
for _, state in ipairs(self.page_states) do
if y_s < state.visible_area.h + state.offset.y then
y_p = (state.visible_area.y + y_s - state.offset.y) / state.zoom
x_p = (state.visible_area.x + x_s - state.offset.x) / state.zoom
return {
x = x_p,
y = y_p,
page = state.page,
zoom = state.zoom,
rotation = state.rotation,
}
else
y_s = y_s - state.visible_area.h - self.page_gap.height
end
end
end
function ReaderView:getScrollPageRect(page, rect_p)
local rect_s = Geom:new{}
for _, state in ipairs(self.page_states) do
local trans_p = Geom:new(rect_p):copy()
trans_p:transformByScale(state.zoom, state.zoom)
if page == state.page and state.visible_area:intersectWith(trans_p) then
rect_s.x = rect_s.x + state.offset.x + trans_p.x - state.visible_area.x
rect_s.y = rect_s.y + state.offset.y + trans_p.y - state.visible_area.y
rect_s.w = trans_p.w
rect_s.h = trans_p.h
return rect_s
end
rect_s.y = rect_s.y + state.visible_area.h + self.page_gap.height
end
end
function ReaderView:drawPageGap(bb, x, y)
if self.scroll_mode == "vertical" then
bb:paintRect(x, y, self.dimen.w, self.page_gap.height, self.page_gap.color)
elseif self.scroll_mode == "horizontal" then
bb:paintRect(x, y, self.page_gap.width, self.dimen.h, self.page_gap.color)
end
end
function ReaderView:drawSinglePage(bb, x, y)
self.ui.document:drawPage(
bb,
x + self.state.offset.x,
y + self.state.offset.y,
self.visible_area,
self.state.page,
self.state.zoom,
self.state.rotation,
self.state.gamma,
self.render_mode)
UIManager:nextTick(self.emitHintPageEvent)
end
function ReaderView:getSinglePagePosition(pos)
local x_s, y_s = pos.x, pos.y
return {
x = (self.visible_area.x + x_s - self.state.offset.x) / self.state.zoom,
y = (self.visible_area.y + y_s - self.state.offset.y) / self.state.zoom,
page = self.state.page,
zoom = self.state.zoom,
rotation = self.state.rotation,
}
end
function ReaderView:getSinglePageRect(rect_p)
local rect_s = Geom:new{}
local trans_p = Geom:new(rect_p):copy()
trans_p:transformByScale(self.state.zoom, self.state.zoom)
if self.visible_area:intersectWith(trans_p) then
rect_s.x = self.state.offset.x + trans_p.x - self.visible_area.x
rect_s.y = self.state.offset.y + trans_p.y - self.visible_area.y
rect_s.w = trans_p.w
rect_s.h = trans_p.h
return rect_s
end
end
function ReaderView:drawPageView(bb, x, y)
self.ui.document:drawCurrentViewByPage(
bb,
x + self.state.offset.x,
y + self.state.offset.y,
self.visible_area,
self.state.page)
end
function ReaderView:drawScrollView(bb, x, y)
self.ui.document:drawCurrentViewByPos(
bb,
x + self.state.offset.x,
y + self.state.offset.y,
self.visible_area,
self.state.pos)
end
function ReaderView:drawTempHighlight(bb, x, y)
for page, boxes in pairs(self.highlight.temp) do
for i = 1, #boxes do
local rect = self:pageToScreenTransform(page, boxes[i])
if rect then
self:drawHighlightRect(bb, x, y, rect, self.highlight.temp_drawer)
end
end
end
end
function ReaderView:drawSavedHighlight(bb, x, y)
if self.ui.document.info.has_pages then
self:drawPageSavedHighlight(bb, x, y)
else
self:drawXPointerSavedHighlight(bb, x, y)
end
end
function ReaderView:drawPageSavedHighlight(bb, x, y)
local pages = self:getCurrentPageList()
for _, page in pairs(pages) do
local items = self.highlight.saved[page]
if not items then items = {} end
for i = 1, #items do
local item = items[i]
local pos0, pos1 = item.pos0, item.pos1
local boxes = self.ui.document:getPageBoxesFromPositions(page, pos0, pos1)
if boxes then
for _, box in pairs(boxes) do
local rect = self:pageToScreenTransform(page, box)
if rect then
self:drawHighlightRect(bb, x, y, rect, item.drawer or self.highlight.saved_drawer)
end
end -- end for each box
end -- end if boxes
end -- end for each highlight
end -- end for each page
end
function ReaderView:drawXPointerSavedHighlight(bb, x, y)
for page, _ in pairs(self.highlight.saved) do
local items = self.highlight.saved[page]
if not items then items = {} end
for j = 1, #items do
local item = items[j]
local pos0, pos1 = item.pos0, item.pos1
local boxes = self.ui.document:getScreenBoxesFromPositions(pos0, pos1)
if boxes then
for _, box in pairs(boxes) do
local rect = self:pageToScreenTransform(page, box)
if rect then
self:drawHighlightRect(bb, x, y, rect, item.drawer or self.highlight.saved_drawer)
end
end -- end for each box
end -- end if boxes
end -- end for each highlight
end -- end for all saved highlight
end
function ReaderView:drawHighlightRect(bb, _x, _y, rect, drawer)
local x, y, w, h = rect.x, rect.y, rect.w, rect.h
if drawer == "underscore" then
self.highlight.line_width = self.highlight.line_width or 2
self.highlight.line_color = self.highlight.line_color or Blitbuffer.gray(0.33)
bb:paintRect(x, y+h-1, w,
self.highlight.line_width,
self.highlight.line_color)
elseif drawer == "lighten" then
bb:lightenRect(x, y, w, h, self.highlight.lighten_factor)
elseif drawer == "invert" then
bb:invertRect(x, y, w, h)
end
end
function ReaderView:getPageArea(page, zoom, rotation)
if self.use_bbox then
return self.ui.document:getUsedBBoxDimensions(page, zoom, rotation)
else
return self.ui.document:getPageDimensions(page, zoom, rotation)
end
end
--[[
This method is supposed to be only used by ReaderPaging
--]]
function ReaderView:recalculate()
if self.ui.document.info.has_pages and self.state.page then
self.page_area = self:getPageArea(
self.state.page,
self.state.zoom,
self.state.rotation)
-- reset our size
self.visible_area:setSizeTo(self.dimen)
if self.ui.document.configurable.writing_direction == 0 then
-- starts from left top of page_area
self.visible_area.x = self.page_area.x
self.visible_area.y = self.page_area.y
else
-- start from right top of page_area
self.visible_area.x = self.page_area.x + self.page_area.w - self.visible_area.w
self.visible_area.y = self.page_area.y
end
-- and recalculate it according to page size
self.visible_area:offsetWithin(self.page_area, 0, 0)
-- clear dim area
self.dim_area.w = 0
self.dim_area.h = 0
self.ui:handleEvent(
Event:new("ViewRecalculate", self.visible_area, self.page_area))
else
self.visible_area:setSizeTo(self.dimen)
end
self.state.offset = Geom:new{x = 0, y = 0}
if self.dimen.h > self.visible_area.h then
self.state.offset.y = (self.dimen.h - self.visible_area.h) / 2
end
if self.dimen.w > self.visible_area.w then
self.state.offset.x = (self.dimen.w - self.visible_area.w) / 2
end
-- flag a repaint so self:paintTo will be called
UIManager:setDirty(self.dialog, "partial")
end
function ReaderView:PanningUpdate(dx, dy)
dbg("pan by", dx, dy)
local old = self.visible_area:copy()
self.visible_area:offsetWithin(self.page_area, dx, dy)
if self.visible_area ~= old then
-- flag a repaint
UIManager:setDirty(self.dialog, "partial")
dbg("on pan: page_area", self.page_area)
dbg("on pan: visible_area", self.visible_area)
self.ui:handleEvent(
Event:new("ViewRecalculate", self.visible_area, self.page_area))
end
return true
end
function ReaderView:PanningStart(x, y)
dbg("panning start", x, y)
if not self.panning_visible_area then
self.panning_visible_area = self.visible_area:copy()
end
self.visible_area = self.panning_visible_area:copy()
self.visible_area:offsetWithin(self.page_area, x, y)
self.ui:handleEvent(Event:new("ViewRecalculate", self.visible_area, self.page_area))
UIManager:setDirty(self.dialog, "partial")
end
function ReaderView:PanningStop()
self.panning_visible_area = nil
end
function ReaderView:SetZoomCenter(x, y)
local old = self.visible_area:copy()
self.visible_area:centerWithin(self.page_area, x, y)
if self.visible_area ~= old then
self.ui:handleEvent(Event:new("ViewRecalculate", self.visible_area, self.page_area))
UIManager:setDirty(self.dialog, "partial")
end
end
function ReaderView:getViewContext()
if self.page_scroll then
return self.page_states
else
return {
{
page = self.state.page,
pos = self.state.pos,
zoom = self.state.zoom,
rotation = self.state.rotation,
gamma = self.state.gamma,
offset = self.state.offset:copy(),
bbox = self.state.bbox,
},
self.visible_area:copy(),
self.page_area:copy(),
}
end
end
function ReaderView:restoreViewContext(ctx)
if self.page_scroll then
self.page_states = ctx
else
self.state = ctx[1]
self.visible_area = ctx[2]
self.page_area = ctx[3]
end
end
function ReaderView:onSetScreenMode(new_mode, rotation)
if new_mode == "landscape" or new_mode == "portrait" then
self.screen_mode = new_mode
if rotation ~= nil then
Screen:setRotationMode(rotation)
else
Screen:setScreenMode(new_mode)
end
UIManager:setDirty(self.dialog, "full")
self.ui:handleEvent(Event:new("SetDimensions", Screen:getSize()))
self.ui:handleEvent(Event:new("InitScrollPageStates"))
end
self.cur_rotation_mode = Screen.cur_rotation_mode
return true
end
function ReaderView:onSetDimensions(dimensions)
self:resetLayout()
self.dimen = dimensions
-- recalculate view
self:recalculate()
end
function ReaderView:onRestoreDimensions(dimensions)
--dbg("restore dimen", dimensions)
self:resetLayout()
self.dimen = dimensions
-- recalculate view
self:recalculate()
end
function ReaderView:onSetFullScreen(full_screen)
self.footer_visible = not full_screen
self.ui:handleEvent(Event:new("SetDimensions", Screen:getSize()))
end
function ReaderView:onSetScrollMode(page_scroll)
self.page_scroll = page_scroll
self:recalculate()
self.ui:handleEvent(Event:new("InitScrollPageStates"))
end
function ReaderView:onReadSettings(config)
local screen_mode
self.render_mode = config:readSetting("render_mode") or 0
if self.ui.document.info.has_pages then
screen_mode = config:readSetting("screen_mode") or G_reader_settings:readSetting("kopt_screen_mode") or "portrait"
else
screen_mode = config:readSetting("screen_mode") or G_reader_settings:readSetting("copt_screen_mode") or "portrait"
end
if screen_mode then
Screen:setScreenMode(screen_mode)
self:onSetScreenMode(screen_mode, config:readSetting("rotation_mode"))
end
self.state.gamma = config:readSetting("gamma") or DGLOBALGAMMA
local full_screen = config:readSetting("kopt_full_screen") or self.document.configurable.full_screen
if full_screen == 0 then
self.footer_visible = false
end
self:resetLayout()
local page_scroll = config:readSetting("kopt_page_scroll") or self.document.configurable.page_scroll
self.page_scroll = page_scroll == 1 and true or false
self.highlight.saved = config:readSetting("highlight") or {}
self.page_overlap_style = config:readSetting("page_overlap_style") or "dim"
end
function ReaderView:onPageUpdate(new_page_no)
self.state.page = new_page_no
self:recalculate()
self.highlight.temp = {}
UIManager:nextTick(self.autoSaveSettings)
end
function ReaderView:onPosUpdate(new_pos)
self.state.pos = new_pos
self:recalculate()
self.highlight.temp = {}
UIManager:nextTick(self.autoSaveSettings)
end
function ReaderView:onZoomUpdate(zoom)
self.state.zoom = zoom
self:recalculate()
self.highlight.temp = {}
end
function ReaderView:onBBoxUpdate(bbox)
self.use_bbox = bbox and true or false
end
function ReaderView:onRotationUpdate(rotation)
self.state.rotation = rotation
self:recalculate()
end
function ReaderView:onGammaUpdate(gamma)
self.state.gamma = gamma
if self.page_scroll then
self.ui:handleEvent(Event:new("UpdateScrollPageGamma", gamma))
end
end
function ReaderView:onFontSizeUpdate()
self.ui:handleEvent(Event:new("ReZoom"))
end
function ReaderView:onDefectSizeUpdate()
self.ui:handleEvent(Event:new("ReZoom"))
end
function ReaderView:onPageCrop()
self.ui:handleEvent(Event:new("ReZoom"))
end
function ReaderView:onMarginUpdate()
self.ui:handleEvent(Event:new("ReZoom"))
end
function ReaderView:onSetViewMode(new_mode)
self.view_mode = new_mode
self.ui.document:setViewMode(new_mode)
self.ui:handleEvent(Event:new("ChangeViewMode"))
return true
end
function ReaderView:onSaveSettings()
self.ui.doc_settings:saveSetting("render_mode", self.render_mode)
self.ui.doc_settings:saveSetting("screen_mode", self.screen_mode)
self.ui.doc_settings:saveSetting("rotation_mode", self.cur_rotation_mode)
self.ui.doc_settings:saveSetting("gamma", self.state.gamma)
self.ui.doc_settings:saveSetting("highlight", self.highlight.saved)
self.ui.doc_settings:saveSetting("page_overlap_style", self.page_overlap_style)
end
function ReaderView:getRenderModeMenuTable()
local view = self
local function make_mode(text, mode)
return {
text = text,
checked_func = function() return view.render_mode == mode end,
callback = function() view.render_mode = mode end,
}
end
return {
text = _("DjVu render mode"),
sub_item_table = {
make_mode(_("COLOUR (works for both colour and b&w pages)"), 0),
make_mode(_("BLACK & WHITE (for b&w pages only, much faster)"), 1),
make_mode(_("COLOUR ONLY (slightly faster than COLOUR)"), 2),
make_mode(_("MASK ONLY (for b&w pages only)"), 3),
make_mode(_("COLOUR BACKGROUND (show only background)"), 4),
make_mode(_("COLOUR FOREGROUND (show only foreground)"), 5),
}
}
end
local page_overlap_styles = {
arrow = _("Arrow"),
dim = _("Gray out"),
}
function ReaderView:genOverlapStyleMenu()
local view = self
local get_overlap_style = function(style)
return {
text = page_overlap_styles[style],
checked_func = function()
return view.page_overlap_style == style
end,
callback = function()
view.page_overlap_style = style
end
}
end
return {
get_overlap_style("arrow"),
get_overlap_style("dim"),
}
end
function ReaderView:onCloseDocument()
self.hinting = false
-- stop any in fly HintPage event
UIManager:unschedule(self.emitHintPageEvent)
end
function ReaderView:onReaderReady()
if DAUTO_SAVE_PAGING_COUNT ~= nil then
if DAUTO_SAVE_PAGING_COUNT <= 0 then
self.autoSaveSettings = function()
self.ui:saveSettings()
end
else
self.autoSaveSettings = function()
if self.auto_save_paging_count == DAUTO_SAVE_PAGING_COUNT then
self.ui:saveSettings()
self.auto_save_paging_count = 0
else
self.auto_save_paging_count = self.auto_save_paging_count + 1
end
end
end
end
end
return ReaderView