Enable HW dithering in a few key places (#4541)

* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
  * FileManager and co. (where appropriate, i.e., when covers are shown)
  * Book Status
  * Reader, where appropriate:
    * CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
    * Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
  * ScreenSaver
  * ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
  (The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
  either by switching the widget to nil when only a refresh was needed, and not a repaint,
  or by passing the appropritate widget to setDirty.
  (Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
  There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
  We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.

PS: (Almost 💯 commits! :D)
pull/4556/head
NiLuJe 5 years ago committed by GitHub
parent 5d21990b0e
commit 812e595608
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1 +1 @@
Subproject commit bef742eb7e04d8e1dc5948f8827bf4760158dc45
Subproject commit afd0c6c5a6d50e15ecfede981f5c8bcb768ad43a

@ -159,7 +159,7 @@ function FileManager:init()
function file_chooser:onPathChanged(path) -- luacheck: ignore
FileManager.instance.path_text:setText(truncatePath(filemanagerutil.abbreviate(path)))
UIManager:setDirty(FileManager.instance, function()
return "partial", FileManager.instance.path_text.dimen
return "partial", FileManager.instance.path_text.dimen, FileManager.instance.dithered
end)
return true
end

@ -417,7 +417,7 @@ function ReaderFooter:addToMainMenu(menu_items)
end
if should_update then
self:updateFooter()
UIManager:setDirty("all", "partial")
UIManager:setDirty(nil, "ui")
end
end,
}
@ -436,7 +436,7 @@ function ReaderFooter:addToMainMenu(menu_items)
callback = function()
self.settings.disable_progress_bar = not self.settings.disable_progress_bar
self:updateFooter()
UIManager:setDirty("all", "partial")
UIManager:setDirty(nil, "ui")
end,
},
getMinibarOption("toc_markers", self.setTocMarkers),
@ -579,6 +579,8 @@ function ReaderFooter:_updateFooterText()
end
self.text_container.dimen.w = self.text_width
self.horizontal_group:resetLayout()
-- NOTE: This is essentially preventing us from truly using "fast" for panning,
-- since it'll get coalesced in the "fast" panning update, upgrading it to "ui".
UIManager:setDirty(self.view.dialog, function()
return "ui", self.footer_content.dimen
end)

@ -152,7 +152,8 @@ function ReaderStatus:showStatus(before_show_callback)
if before_show_callback then
before_show_callback()
end
UIManager:show(status_page)
status_page.dithered = true
UIManager:show(status_page, "full")
end
function ReaderStatus:onReadSettings(config)

@ -76,7 +76,7 @@ function ReaderToc:onPageUpdate(pageno)
if self:isChapterEnd(pageno, 0) then
self.chapter_refresh = true
elseif self:isChapterBegin(pageno, 0) and self.chapter_refresh then
UIManager:setDirty("all", "full")
UIManager:setDirty(nil, "full")
self.chapter_refresh = false
else
self.chapter_refresh = false

@ -213,6 +213,23 @@ function ReaderView:paintTo(bb, x, y)
end
-- stop activity indicator
self.ui:handleEvent(Event:new("StopActivityIndicator"))
-- Most pages should not require dithering
self.dialog.dithered = nil
-- For KOpt, let the user choose.
if self.ui.document.info.has_pages then
if self.document.configurable.hw_dithering == 1 then
self.dialog.dithered = true
end
else
-- Whereas for CRe,
-- If we're attempting to show a large enough amount of image data, request dithering (without triggering another repaint ;)).
local img_count, img_coverage = self.ui.document:getDrawnImagesStatistics()
-- With some nil guards because this may not be implemented in every engine ;).
if img_count and img_count > 0 and img_coverage and img_coverage >= 0.075 then
self.dialog.dithered = true
end
end
end
--[[
@ -545,6 +562,9 @@ end
This method is supposed to be only used by ReaderPaging
--]]
function ReaderView:recalculate()
-- Start by resetting the dithering flag early, so it doesn't carry over from the previous page.
self.dialog.dithered = nil
if self.ui.document.info.has_pages and self.state.page then
self.page_area = self:getPageArea(
self.state.page,
@ -579,6 +599,7 @@ function ReaderView:recalculate()
self.state.offset.x = (self.dimen.w - self.visible_area.w) / 2
end
-- flag a repaint so self:paintTo will be called
-- NOTE: This is also unfortunately called during panning, essentially making sure we'll never be using "fast" for pans ;).
UIManager:setDirty(self.dialog, "partial")
end
@ -588,7 +609,7 @@ function ReaderView:PanningUpdate(dx, dy)
self.visible_area:offsetWithin(self.page_area, dx, dy)
if self.visible_area ~= old then
-- flag a repaint
UIManager:setDirty(self.dialog, "partial")
UIManager:setDirty(self.dialog, "fast")
logger.dbg("on pan: page_area", self.page_area)
logger.dbg("on pan: visible_area", self.visible_area)
self.ui:handleEvent(
@ -605,7 +626,7 @@ function ReaderView:PanningStart(x, y)
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")
UIManager:setDirty(self.dialog, "fast")
end
function ReaderView:PanningStop()

@ -26,6 +26,7 @@ local Device = {
needsTouchScreenProbe = no,
hasClipboard = yes, -- generic internal clipboard on all devices
hasEinkScreen = yes,
canHWDither = no,
hasColorScreen = no,
hasBGRFrameBuffer = no,
canToggleGSensor = no,
@ -190,7 +191,9 @@ function Device:onPowerEvent(ev)
self.orig_rotation_mode = nil
end
require("ui/screensaver"):show()
self.screen:refreshFull()
if self:needsScreenRefreshAfterResume() then
self.screen:refreshFull()
end
self.screen_saver_mode = true
UIManager:scheduleIn(0.1, function()
local network_manager = require("ui/network/manager")

@ -32,6 +32,8 @@ local Kobo = Generic:new{
touch_mirrored_x = true,
-- enforce portrait mode on Kobos
isAlwaysPortrait = yes,
-- we don't need an extra refreshFull on resume, thank you very much.
needsScreenRefreshAfterResume = no,
-- the internal storage mount point users can write to
internal_storage_mount_point = "/mnt/onboard/",
-- currently only the Aura One and Forma have coloured frontlights

@ -103,9 +103,9 @@ end
dbg:guard(SysfsLight, 'setNaturalBrightness',
function(self, brightness, warmth)
assert(brightness >= 0 and brightness <= 100,
assert(brightness == nil or (brightness >= 0 and brightness <= 100),
"Wrong brightness value given!")
assert(warmth >= 0 and warmth <= 100,
assert(warmth == nil or (warmth >= 0 and warmth <= 100),
"Wrong warmth value given!")
end)

@ -251,6 +251,16 @@ This can also be used to remove some gray background or to convert a grayscale o
end,
name_text_hold_callback = optionsutil.showValues,
},
{
name = "hw_dithering",
name_text = S.HW_DITHERING,
toggle = {S.ON, S.OFF},
values = {1, 0},
default_value = 0,
advanced = true,
show = Device:hasEinkScreen() and Device:canHWDither(),
name_text_hold_callback = optionsutil.showValues,
},
{
name = "forced_ocr",
name_text = S.FORCED_OCR,

@ -32,6 +32,7 @@ S.EMBEDDED_FONTS = _("Embedded Fonts")
S.WRITING_DIR = _("Writing Direction")
S.PROGRESS_BAR = _("Progress Bar")
S.FORCED_OCR = _("Forced OCR")
S.HW_DITHERING = _("Dithering")
S.INVERSE_READING_ORDER = _("Inverse Order")
S.ON = _("on")

@ -387,7 +387,8 @@ function Screensaver:show(event, fallback_message)
covers_fullscreen = covers_fullscreen,
}
self.left_msg.modal = true
-- refresh whole screen for other types
-- Refresh whole screen for other types
self.left_msg.dithered = true
UIManager:show(self.left_msg, "full")
end
end
@ -400,8 +401,7 @@ function Screensaver:close()
UIManager:scheduleIn(screensaver_delay_number, function()
logger.dbg("close screensaver")
if self.left_msg then
UIManager:close(self.left_msg)
UIManager:setDirty("all", "full")
UIManager:close(self.left_msg, "full")
self.left_msg = nil
end
end)

@ -56,7 +56,9 @@ function UIManager:init()
self._entered_poweroff_stage = true;
Screen:setRotationMode(0)
require("ui/screensaver"):show("poweroff", _("Powered off"))
Screen:refreshFull()
if Device:needsScreenRefreshAfterResume() then
Screen:refreshFull()
end
UIManager:nextTick(function()
Device:saveSettings()
self:broadcastEvent(Event:new("Close"))
@ -67,7 +69,9 @@ function UIManager:init()
self._entered_poweroff_stage = true;
Screen:setRotationMode(0)
require("ui/screensaver"):show("reboot", _("Rebooting..."))
Screen:refreshFull()
if Device:needsScreenRefreshAfterResume() then
Screen:refreshFull()
end
UIManager:nextTick(function()
Device:saveSettings()
self:broadcastEvent(Event:new("Close"))
@ -261,13 +265,14 @@ For refreshtype & refreshregion see description of setDirty().
---- @param refreshregion a Geom object
---- @int x
---- @int y
---- @param refreshdither an optional bool
---- @see setDirty
function UIManager:show(widget, refreshtype, refreshregion, x, y)
function UIManager:show(widget, refreshtype, refreshregion, x, y, refreshdither)
if not widget then
logger.dbg("widget not exist to be shown")
return
end
logger.dbg("show widget", widget.id or widget.name or "unknown")
logger.dbg("show widget:", widget.id or widget.name or tostring(widget))
self._running = true
local window = {x = x or 0, y = y or 0, widget = widget}
@ -281,7 +286,7 @@ function UIManager:show(widget, refreshtype, refreshregion, x, y)
end
end
-- and schedule it to be painted
self:setDirty(widget, refreshtype, refreshregion)
self:setDirty(widget, refreshtype, refreshregion, refreshdither)
-- tell the widget that it is shown now
widget:handleEvent(Event:new("Show"))
-- check if this widget disables double tap gesture
@ -300,13 +305,14 @@ For refreshtype & refreshregion see description of setDirty().
---- @param widget a widget object
---- @param refreshtype "full", "flashpartial", "flashui", "partial", "ui", "fast"
---- @param refreshregion a Geom object
---- @param refreshdither an optional bool
---- @see setDirty
function UIManager:close(widget, refreshtype, refreshregion)
function UIManager:close(widget, refreshtype, refreshregion, refreshdither)
if not widget then
logger.dbg("widget to be closed does not exist")
return
end
logger.dbg("close widget", widget.id or widget.name)
logger.dbg("close widget:", widget.name or widget.id or tostring(widget))
local dirty = false
-- Ensure all the widgets can get onFlushSettings event.
widget:handleEvent(Event:new("FlushSettings"))
@ -317,10 +323,19 @@ function UIManager:close(widget, refreshtype, refreshregion)
-- then remove all references to that widget on stack and refresh
for i = #self._window_stack, 1, -1 do
if self._window_stack[i].widget == widget then
self._dirty[self._window_stack[i].widget] = nil
table.remove(self._window_stack, i)
dirty = true
elseif self._window_stack[i].widget.disable_double_tap == false then
Input.disable_double_tap = false
else
-- If anything else on the stack was dithered, honor the hint
if self._window_stack[i].widget.dithered then
refreshdither = true
logger.dbg("Lower widget", self._window_stack[i].widget.name or self._window_stack[i].widget.id or tostring(self._window_stack[i].widget), "was dithered, honoring the dithering hint")
end
if self._window_stack[i].widget.disable_double_tap == false then
Input.disable_double_tap = false
end
end
end
if dirty and not widget.invisible then
@ -328,7 +343,7 @@ function UIManager:close(widget, refreshtype, refreshregion)
for i = 1, #self._window_stack do
self:setDirty(self._window_stack[i].widget)
end
self:_refresh(refreshtype, refreshregion)
self:_refresh(refreshtype, refreshregion, refreshdither)
end
end
@ -464,6 +479,8 @@ NOTE: You'll notice a trend on UI elements that are usually shown *over* some ki
That said, depending on your use case, using "ui" onClose can be a perfectly valid decision, and will ensure
never seeing a flash because of that widget.
The final parameter (refreshdither) is an optional hint for devices with hardware dithering support that this repaint
could benefit from dithering (i.e., it contains an image).
@usage
@ -475,15 +492,32 @@ UIManager:setDirty(self.widget, function() return "ui", self.someelement.dimen e
---- @param widget a widget object
---- @param refreshtype "full", "flashpartial", "flashui", "partial", "ui", "fast"
---- @param refreshregion a Geom object
function UIManager:setDirty(widget, refreshtype, refreshregion)
---- @param refreshdither an optional bool
function UIManager:setDirty(widget, refreshtype, refreshregion, refreshdither)
if widget then
if widget == "all" then
-- special case: set all top-level widgets as being "dirty".
for i = 1, #self._window_stack do
self._dirty[self._window_stack[i].widget] = true
-- If any of 'em were dithered, honor their dithering hint
if self._window_stack[i].widget.dithered then
-- NOTE: That works when refreshtype is NOT a function,
-- which is why _repaint does another pass of this check ;).
refreshdither = true
end
end
elseif not widget.invisible then
self._dirty[widget] = true
-- We only ever check the dirty flag on top-level widgets, so only set it there!
-- NOTE: Enable verbose debug to catch misbehaving widgets via our post-guard.
for i = 1, #self._window_stack do
if self._window_stack[i].widget == widget then
self._dirty[widget] = true
end
end
-- Again, if it's flagged as dithered, honor that
if widget.dithered then
refreshdither = true
end
end
end
-- handle refresh information
@ -493,23 +527,23 @@ function UIManager:setDirty(widget, refreshtype, refreshregion)
if dbg.is_on then
-- FIXME: We can't consume the return values of refreshtype by running it, because for a reason that is beyond me (scoping? gc?), that renders it useless later, meaning we then enqueue refreshes with bogus arguments...
-- Thankfully, we can track them in _refresh()'s logging very soon after that...
logger.dbg("setDirty via a func from widget", widget and (widget.name or widget.id or tostring(widget)))
logger.dbg("setDirty via a func from widget", widget and (widget.name or widget.id or tostring(widget)) or "nil")
end
else
-- otherwise, enqueue refresh
self:_refresh(refreshtype, refreshregion)
self:_refresh(refreshtype, refreshregion, refreshdither)
if dbg.is_on then
if refreshregion then
logger.dbg("setDirty", refreshtype and refreshtype or "nil", "from widget", widget and (widget.name or widget.id or tostring(widget)) or "nil", "w/ region", refreshregion.x, refreshregion.y, refreshregion.w, refreshregion.h)
logger.dbg("setDirty", refreshtype and refreshtype or "nil", "from widget", widget and (widget.name or widget.id or tostring(widget)) or "nil", "w/ region", refreshregion.x, refreshregion.y, refreshregion.w, refreshregion.h, refreshdither and "AND w/ HW dithering" or "")
else
logger.dbg("setDirty", refreshtype and refreshtype or "nil", "from widget", widget and (widget.name or widget.id or tostring(widget)) or "nil", "w/ NO region")
logger.dbg("setDirty", refreshtype and refreshtype or "nil", "from widget", widget and (widget.name or widget.id or tostring(widget)) or "nil", "w/ NO region", refreshdither and "AND w/ HW dithering" or "")
end
end
end
end
dbg:guard(UIManager, 'setDirty',
nil,
function(self, widget, refreshtype, refreshregion)
function(self, widget, refreshtype, refreshregion, refreshdither)
if not widget or widget == "all" then return end
-- when debugging, we check if we get handed a valid widget,
-- which would be a dialog that was previously passed via show()
@ -680,6 +714,8 @@ end
-- precedence of refresh modes:
local refresh_modes = { fast = 1, ui = 2, partial = 3, flashui = 4, flashpartial = 5, full = 6 }
-- NOTE: We might want to introduce a "force_fast" that points to fast, but has the highest priority,
-- for the few cases where we might *really* want to enforce fast (for stuff like panning or skimming?).
-- refresh methods in framebuffer implementation
local refresh_methods = {
fast = "refreshFast",
@ -704,6 +740,20 @@ local function update_mode(mode1, mode2)
end
end
--[[
Compares dither hints.
Dither always wins.
--]]
local function update_dither(dither1, dither2)
if dither1 and not dither2 then
logger.dbg("update_dither: Update dither hint", dither2, "to", dither1)
return dither1
else
return dither2
end
end
--[[--
Enqueues a refresh.
@ -716,9 +766,20 @@ UIManager that a certain part of the screen is to be refreshed.
Rect() that specifies the region to be updated
optional, update will affect whole screen if not specified.
Note that this should be the exception.
@param dither
Bool, a hint to request hardware dithering (if supported)
optional, no dithering requested if not specified or not supported.
--]]
function UIManager:_refresh(mode, region)
if not mode then return end
function UIManager:_refresh(mode, region, dither)
if not mode then
-- If we're trying to float a dither hint up from a lower widget after a close, mode might be nil...
-- So use the lowest priority refresh mode (short of fast, because that'd do half-toning).
if dither then
mode = "ui"
else
return
end
end
if not region and mode == "full" then
self.refresh_count = 0 -- reset counter on explicit full refresh
end
@ -763,6 +824,9 @@ function UIManager:_refresh(mode, region)
-- if no region is specified, define default region
region = region or Geom:new{w=Screen:getWidth(), h=Screen:getHeight()}
-- if no dithering hint was specified, don't request dithering
dither = dither or false
-- NOTE: While, ideally, we shouldn't merge refreshes w/ different waveform modes,
-- this allows us to optimize away a number of quirks of our rendering stack
-- (f.g., multiple setDirty calls queued when showing/closing a widget because of update mechanisms),
@ -775,16 +839,18 @@ function UIManager:_refresh(mode, region)
local combined = region:combine(self._refresh_stack[i].region)
-- update the mode, if needed
mode = update_mode(mode, self._refresh_stack[i].mode)
-- dithering hints are viral, one is enough to infect the whole queue
dither = update_dither(dither, self._refresh_stack[i].dither)
-- remove colliding refresh
table.remove(self._refresh_stack, i)
-- and try again with combined data
return self:_refresh(mode, combined)
return self:_refresh(mode, combined, dither)
end
end
-- if we've stopped hitting collisions, enqueue the refresh
logger.dbg("_refresh: Enqueued", mode, "update for region", region.x, region.y, region.w, region.h)
table.insert(self._refresh_stack, {mode = mode, region = region})
logger.dbg("_refresh: Enqueued", mode, "update for region", region.x, region.y, region.w, region.h, dither and "w/ HW dithering" or "")
table.insert(self._refresh_stack, {mode = mode, region = region, dither = dither})
end
--- Repaints dirty widgets.
@ -792,6 +858,8 @@ function UIManager:_repaint()
-- flag in which we will record if we did any repaints at all
-- will trigger a refresh if set.
local dirty = false
-- remember if any of our repaints were dithered
local dithered = false
-- We don't need to call paintTo() on widgets that are under
-- a widget that covers the full screen
@ -820,13 +888,21 @@ function UIManager:_repaint()
-- trigger repaint
dirty = true
-- if any of 'em were dithered, we'll want to dither the final refresh
if widget.widget.dithered then
logger.dbg("_repaint: it was dithered, infecting the refresh queue")
dithered = true
end
end
end
-- execute pending refresh functions
for _, refreshfunc in ipairs(self._refresh_func_stack) do
local refreshtype, region = refreshfunc()
if refreshtype then self:_refresh(refreshtype, region) end
local refreshtype, region, dither = refreshfunc()
-- honor dithering hints from *anywhere* in the dirty stack
dither = update_dither(dither, dithered)
if refreshtype then self:_refresh(refreshtype, region, dither) end
end
self._refresh_func_stack = {}
@ -845,7 +921,8 @@ function UIManager:_repaint()
-- but checkBounds & getPhysicalRect will sanitize that in mxc_update @ ffi/framebuffer_mxcfb ;).
Screen[refresh_methods[refresh.mode]](Screen,
refresh.region.x - 1, refresh.region.y - 1,
refresh.region.w + 2, refresh.region.h + 2)
refresh.region.w + 2, refresh.region.h + 2,
refresh.dither)
end
self._refresh_stack = {}
self.refresh_counted = false
@ -855,6 +932,16 @@ function UIManager:forceRePaint()
self:_repaint()
end
-- Used to repaint a specific sub-widget that isn't on the _window_stack itself
-- Useful to avoid repainting a complex widget when we just want to invert an icon, for instance.
-- No safety checks on x & y *by design*. I want this to blow up if used wrong.
function UIManager:widgetRepaint(widget, x, y)
if not widget then return end
logger.dbg("Explicit widgetRepaint:", widget.name or widget.id or tostring(widget), "@ (", x, ",", y, ")")
widget:paintTo(Screen.bb, x, y)
end
function UIManager:setInputTimeout(timeout)
self.INPUT_TIMEOUT = timeout or 200*1000
end

@ -111,6 +111,8 @@ function BookStatusWidget:init()
padding = 0,
self:getStatusContent(screen_size.w),
}
self.dithered = true
end
function BookStatusWidget:getStats()
@ -254,7 +256,9 @@ function BookStatusWidget:setStar(num)
table.insert(self.stars_container, stars_group)
UIManager:setDirty(nil, "ui")
-- Individual stars are Button, w/ flash_ui, they'll have their own flash.
-- And we need to redraw the full widget, because we don't know the coordinates of stars_container :/.
UIManager:setDirty(self, "ui", nil, true)
return true
end
@ -540,7 +544,7 @@ function BookStatusWidget:onConfigChoose(values, name, event, args, events, posi
if values then
self:onChangeBookStatus(args, position)
end
UIManager:setDirty("all", "ui")
UIManager:setDirty(nil, "ui", nil, true)
end)
end
@ -558,7 +562,7 @@ function BookStatusWidget:onSwipe(arg, ges_ev)
do end -- luacheck: ignore 541
else -- diagonal swipe
-- trigger full refresh
UIManager:setDirty(nil, "full")
UIManager:setDirty(nil, "full", nil, true)
-- a long diagonal swipe may also be used for taking a screenshot,
-- so let it propagate
return false
@ -568,8 +572,7 @@ end
function BookStatusWidget:onClose()
self:saveSummary()
-- NOTE: Flash on close to avoid ghosting, since we show an image.
UIManager:setDirty("all", "flashpartial")
UIManager:close(self)
UIManager:close(self, "flashpartial")
return true
end

@ -193,16 +193,18 @@ function Button:onTapSelectButton()
if G_reader_settings:isFalse("flash_ui") then
self.callback()
else
-- NOTE: Flag all widgets as dirty to force a repaint, so we actually get to see the highlight.
-- (For some reason (wrong widget passed to setDirty?), we never saw the effects on the FM chevrons without this hack).
self[1].invert = true
UIManager:setDirty("all", function()
-- For most of our buttons, we can't avoid that initial repaint...
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
-- And we also often have to delay the callback to both see the flash and/or avoid tearing artefacts w/ fast refreshes...
UIManager:tickAfterNext(function()
self.callback()
self[1].invert = false
UIManager:setDirty("all", function()
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
end)

@ -96,14 +96,16 @@ function CheckButton:onTapCheckButton()
if G_reader_settings:isFalse("flash_ui") then
self.callback()
else
self.invert = true
UIManager:setDirty(self.show_parent, function()
self[1].invert = true
UIManager:widgetRepaint(self[1], self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
UIManager:tickAfterNext(function()
self.callback()
self.invert = false
UIManager:setDirty(self.show_parent, function()
self[1].invert = false
UIManager:widgetRepaint(self[1], self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
end)

@ -711,6 +711,7 @@ Widget that displays config menubar and config panel
local ConfigDialog = FocusManager:new{
--is_borderless = false,
panel_index = 1,
is_fresh = true,
}
function ConfigDialog:init()
@ -783,7 +784,7 @@ end
function ConfigDialog:onCloseWidget()
-- NOTE: As much as we would like to flash here, don't, because of adverse interactions with touchmenu that might lead to a double flash...
UIManager:setDirty("all", function()
UIManager:setDirty(nil, function()
return "partial", self.dialog_frame.dimen
end)
end
@ -794,10 +795,11 @@ function ConfigDialog:onShowConfigPanel(index)
self:update()
-- NOTE: Keep that one as UI to avoid delay when both this and the topmenu are shown.
-- Plus, this is also called for each tab anyway, so that wouldn't have been great.
UIManager:setDirty("all", function()
UIManager:setDirty(self.is_fresh and self or "all", function()
local refresh_dimen =
old_dimen and old_dimen:combine(self.dialog_frame.dimen)
or self.dialog_frame.dimen
self.is_fresh = false
return "ui", refresh_dimen
end)
return true

@ -551,7 +551,7 @@ function DictQuickLookup:update()
dimen = self.region,
self.movable,
}
UIManager:setDirty("all", function()
UIManager:setDirty(self, function()
local update_region = self.dict_frame and self.dict_frame.dimen and self.dict_frame.dimen:combine(orig_dimen) or orig_dimen
logger.dbg("update dict region", update_region)
return "partial", update_region

@ -276,7 +276,7 @@ function FrontLightWidget:setProgress(num, step, num_warmth)
-- Reset container height to what it actually contains
self.fl_container.dimen.h = vertical_group:getSize().h
UIManager:setDirty("all", "ui")
UIManager:setDirty(self, "ui")
return true
end
@ -596,7 +596,7 @@ function FrontLightWidget:naturalLightConfigOpen()
self.nl_configure_open = true
-- Move to the bottom to make place for the new widget
self[1].align="bottom"
UIManager:setDirty("all", "ui")
UIManager:setDirty(self, "ui")
end
function FrontLightWidget:naturalLightConfigClose()
@ -606,7 +606,7 @@ function FrontLightWidget:naturalLightConfigClose()
self.configure_button:enable()
self.nl_configure_open = false
self[1].align="center"
UIManager:setDirty("all", "ui")
UIManager:setDirty(self, "ui")
end
return FrontLightWidget

@ -96,14 +96,17 @@ function IconButton:onTapIconButton()
self.callback()
else
self.image.invert = true
UIManager:setDirty(self.show_parent, function()
-- For ConfigDialog icons, we can't avoid that initial repaint...
UIManager:widgetRepaint(self.image, self.dimen.x + self.padding_left, self.dimen.y + self.padding_top)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
-- Make sure button reacts before doing callback
-- And, we usually need to delay the callback for the same reasons as Button...
UIManager:tickAfterNext(function()
self.callback()
self.image.invert = false
UIManager:setDirty(self.show_parent, function()
UIManager:widgetRepaint(self.image, self.dimen.x + self.padding_left, self.dimen.y + self.padding_top)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
end)

@ -392,16 +392,22 @@ function ImageViewer:update()
self.main_frame,
}
}
UIManager:setDirty("all", function()
-- NOTE: We use UI instead of partial, because we do NOT want to end up using a REAGL waveform...
-- NOTE: Disabling dithering here makes for a perfect test-case of how well it works:
-- page turns will show color quantization artefacts (i.e., banding) like crazy,
-- while a long touch will trigger a dithered, flashing full-refresh that'll make everything shiny :).
self.dithered = true
UIManager:setDirty(self, function()
local update_region = self.main_frame.dimen:combine(orig_dimen)
logger.dbg("update image region", update_region)
return "partial", update_region
return "ui", update_region, true
end)
end
function ImageViewer:onShow()
self.dithered = true
UIManager:setDirty(self, function()
return "full", self.main_frame.dimen
return "full", self.main_frame.dimen, true
end)
return true
end
@ -508,7 +514,8 @@ function ImageViewer:onHoldRelease(_, ges)
self._pan_relative_y = ges.pos.y - self._pan_relative_y
if math.abs(self._pan_relative_x) < self.pan_threshold and math.abs(self._pan_relative_y) < self.pan_threshold then
-- Hold with no move (or less than pan_threshold): use this to trigger full refresh
UIManager:setDirty(nil, "full")
self.dithered = true
UIManager:setDirty(nil, "full", nil, true)
else
self:panBy(-self._pan_relative_x, -self._pan_relative_y)
end
@ -601,6 +608,7 @@ function ImageViewer:onCloseWidget()
if self._images_list and self._images_list_disposable and self._images_list.free then
self._images_list.free()
end
-- NOTE: Assume there's no image beneath us, so, no dithering request
UIManager:setDirty(nil, function()
return "flashui", self.main_frame.dimen
end)

@ -334,8 +334,9 @@ function ImageWidget:panBy(x, y)
if new_offset_x ~= self._offset_x or new_offset_y ~= self._offset_y then
self._offset_x = new_offset_x
self._offset_y = new_offset_y
self.dithered = true
UIManager:setDirty("all", function()
return "partial", self.dimen
return "ui", self.dimen, true
end)
end
-- return new center ratio, so caller can use them later to create a new

@ -234,13 +234,15 @@ function KeyValueItem:onTap()
self.callback()
else
self[1].invert = true
UIManager:setDirty(self.show_parent, function()
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
UIManager:tickAfterNext(function()
self.callback()
self[1].invert = false
UIManager:setDirty(self.show_parent, function()
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "ui", self[1].dimen
end)
end)
@ -553,7 +555,7 @@ function KeyValuePage:onReturn()
if self.callback_return then
self:callback_return()
UIManager:close(self)
UIManager:setDirty("all", "ui")
UIManager:setDirty(nil, "ui")
end
end

@ -403,19 +403,21 @@ function MenuItem:onTapSelect(arg, ges)
coroutine.resume(co)
else
self[1].invert = true
UIManager:setDirty(self.show_parent, function()
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
UIManager:tickAfterNext(function()
self[1].invert = false
UIManager:setDirty(self.show_parent, function()
return "ui", self[1].dimen
end)
logger.dbg("creating coroutine for menu select")
local co = coroutine.create(function()
self.menu:onMenuSelect(self.table, pos)
end)
coroutine.resume(co)
self[1].invert = false
--UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(self.show_parent, function()
return "ui", self[1].dimen
end)
end)
end
return true
@ -427,15 +429,17 @@ function MenuItem:onHoldSelect(arg, ges)
self.menu:onMenuHold(self.table, pos)
else
self[1].invert = true
UIManager:setDirty(self.show_parent, function()
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
UIManager:tickAfterNext(function()
self.menu:onMenuHold(self.table, pos)
self[1].invert = false
--UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(self.show_parent, function()
return "ui", self[1].dimen
end)
self.menu:onMenuHold(self.table, pos)
end)
end
return true
@ -587,25 +591,25 @@ function Menu:init()
icon = "resources/icons/appbar.chevron.left.png",
callback = function() self:onPrevPage() end,
bordersize = 0,
show_parent = self,
show_parent = self.show_parent,
}
self.page_info_right_chev = Button:new{
icon = "resources/icons/appbar.chevron.right.png",
callback = function() self:onNextPage() end,
bordersize = 0,
show_parent = self,
show_parent = self.show_parent,
}
self.page_info_first_chev = Button:new{
icon = "resources/icons/appbar.chevron.first.png",
callback = function() self:onFirstPage() end,
bordersize = 0,
show_parent = self,
show_parent = self.show_parent,
}
self.page_info_last_chev = Button:new{
icon = "resources/icons/appbar.chevron.last.png",
callback = function() self:onLastPage() end,
bordersize = 0,
show_parent = self,
show_parent = self.show_parent,
}
self.page_info_spacer = HorizontalSpan:new{
width = Screen:scaleBySize(32),
@ -699,7 +703,7 @@ function Menu:init()
if self.onReturn then self:onReturn() end
end,
bordersize = 0,
show_parent = self,
show_parent = self.show_parent,
readonly = self.return_arrow_propagation,
}
self.page_return_arrow:hide()
@ -953,7 +957,7 @@ function Menu:updateItems(select_number)
self.path_text.text = self:truncatePath(self.path)
end
UIManager:setDirty("all", function()
UIManager:setDirty(self.show_parent, function()
local refresh_dimen =
old_dimen and old_dimen:combine(self.dimen)
or self.dimen

@ -122,7 +122,7 @@ function MultiInputDialog:init()
},
self.dialog_frame,
}
UIManager:setDirty("all", "full")
UIManager:setDirty(self, "ui")
end
function MultiInputDialog:getFields()

@ -353,7 +353,7 @@ function NaturalLightWidget:createMainContent(width, height)
table.insert(self.fl_container, vertical_group)
-- Reset container height to what it actually contains
self.fl_container.dimen.h = vertical_group:getSize().h
UIManager:setDirty("all", "ui")
UIManager:setDirty(self, "ui")
return self.fl_container
end

@ -116,14 +116,17 @@ function RadioButton:onTapCheckButton()
if G_reader_settings:isFalse("flash_ui") then
self.callback()
else
self.invert = true
UIManager:setDirty(self.show_parent, function()
-- While I'd like to only flash the button itself, we have to make do with flashing the full width of the TextWidget...
self.frame.invert = true
UIManager:widgetRepaint(self.frame, self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
UIManager:tickAfterNext(function()
self.callback()
self.invert = false
UIManager:setDirty(self.show_parent, function()
self.frame.invert = false
UIManager:widgetRepaint(self.frame, self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
end)

@ -49,8 +49,9 @@ function ScreenSaverWidget:update()
height = self.height,
self.widget,
}
self.dithered = true
self[1] = self.main_frame
UIManager:setDirty("all", function()
UIManager:setDirty(self, function()
local update_region = self.main_frame.dimen
return "partial", update_region
end)
@ -66,14 +67,12 @@ end
function ScreenSaverWidget:onTap(_, ges)
if ges.pos:intersectWith(self.main_frame.dimen) then
self:onClose()
UIManager:setDirty("all", "full")
end
return true
end
function ScreenSaverWidget:onClose()
UIManager:close(self)
UIManager:setDirty("all", "full")
UIManager:close(self, "full")
return true
end

@ -200,7 +200,7 @@ function TextViewer:init()
dimen = self.region,
self.movable,
}
UIManager:setDirty("all", function()
UIManager:setDirty(self, function()
local update_region = self.frame.dimen:combine(orig_dimen)
logger.dbg("update region", update_region)
return "partial", update_region

@ -141,7 +141,8 @@ function TouchMenuItem:onTapSelect(arg, ges)
self.menu:onMenuSelect(self.item)
else
self.item_frame.invert = true
UIManager:setDirty(self.show_parent, function()
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
-- yield to main UI loop to invert item
@ -153,6 +154,7 @@ function TouchMenuItem:onTapSelect(arg, ges)
-- Since it's an *un*highlight containing text, we make it "ui" and not "fast", both so it won't mangle text,
-- and because "fast" can have some weird side-effects on some devices in this specific instance...
if self.item.hold_keep_menu_open or self.item.keep_menu_open then
--UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen
end)
@ -173,7 +175,8 @@ function TouchMenuItem:onHoldSelect(arg, ges)
self.menu:onMenuHold(self.item)
else
self.item_frame.invert = true
UIManager:setDirty(self.show_parent, function()
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
UIManager:tickAfterNext(function()
@ -181,6 +184,8 @@ function TouchMenuItem:onHoldSelect(arg, ges)
end)
UIManager:scheduleIn(0.5, function()
self.item_frame.invert = false
-- NOTE: For some reason, this is finicky (I end up with a solid black bar, i.e., text gets inverted, but not the bg?!)
--UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen
end)
@ -431,13 +436,13 @@ function TouchMenu:init()
icon = "resources/icons/appbar.chevron.left.png",
callback = function() self:onPrevPage() end,
bordersize = 0,
show_parent = self,
show_parent = self.show_parent,
}
self.page_info_right_chev = Button:new{
icon = "resources/icons/appbar.chevron.right.png",
callback = function() self:onNextPage() end,
bordersize = 0,
show_parent = self,
show_parent = self.show_parent,
}
self.page_info_left_chev:hide()
self.page_info_right_chev:hide()
@ -518,8 +523,8 @@ function TouchMenu:init()
end
function TouchMenu:onCloseWidget()
-- NOTE: We pass a nil region to ensure a full-screen flash to avoid ghosting
UIManager:setDirty(nil, "flashui", nil)
-- NOTE: We don't pass a region in order to ensure a full-screen flash to avoid ghosting
UIManager:setDirty(nil, "flashui")
end
function TouchMenu:_recalculatePageLayout()
@ -605,7 +610,8 @@ function TouchMenu:updateItems()
-- NOTE: We use a slightly ugly hack to detect a brand new menu vs. a tab switch,
-- in order to optionally flash on initial menu popup...
UIManager:setDirty("all", function()
-- NOTE: Also avoid repainting what's underneath us on initial popup.
UIManager:setDirty(self.is_fresh and self.show_parent or "all", function()
local refresh_dimen =
old_dimen and old_dimen:combine(self.dimen)
or self.dimen

@ -133,7 +133,9 @@ function VirtualKey:update_keyboard(want_flash, want_fast)
if want_fast then
refresh_type = "fast"
end
UIManager:setDirty(self.keyboard, function()
-- Only repaint the key itself, not the full board...
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
logger.dbg("update key region", self[1].dimen)
return refresh_type, self[1].dimen
end)

@ -146,7 +146,7 @@ function BookInfoManager:createDB()
-- Check version (not updated by previous exec if already there)
local res = db_conn:exec("SELECT value FROM config where key='version';")
if res[1][1] ~= BOOKINFO_DB_VERSION then
logger.warn("BookInfo cache DB schema updated from version ", res[1][1], "to version", BOOKINFO_DB_VERSION)
logger.warn("BookInfo cache DB schema updated from version", res[1][1], "to version", BOOKINFO_DB_VERSION)
logger.warn("Deleting existing", self.db_location, "to recreate it")
db_conn:close()
os.remove(self.db_location)
@ -451,7 +451,7 @@ function BookInfoManager:extractBookInfo(filepath, cover_specs)
-- release memory used by uncompressed data:
cover_data = nil -- luacheck: no unused
dbrow.cover_dataz = SQ3.blob(cover_dataz) -- cast to blob for sqlite
logger.dbg("cover for", filename, "scaled by", scale_factor, "=>", cbb_w, "x", cbb_h, "(compressed from ", dbrow.cover_datalen, " to ", cover_dataz:len())
logger.dbg("cover for", filename, "scaled by", scale_factor, "=>", cbb_w, "x", cbb_h, ", compressed from", dbrow.cover_datalen, "to", cover_dataz:len())
end
end
end
@ -554,7 +554,7 @@ function BookInfoManager:collectSubprocesses()
-- have caused a terminateBackgroundJobs() - if we're here, it's
-- that user has left reader in FileBrower and went away)
if util.gettime() > self.subprocesses_last_added_ts + self.subprocesses_killall_timeout_seconds then
logger.warn("Some subprocess were running for too long, killing them")
logger.warn("Some subprocesses were running for too long, killing them")
self:terminateBackgroundJobs()
-- we'll collect them next time we're run
end

@ -77,6 +77,7 @@ function CoverMenu:updateItems(select_number)
collectgarbage()
-- Specific UI building implementation (defined in some other module)
self._has_cover_images = false
self:_updateItemsBuildUI()
-- Set the local variables with the things we know
@ -89,11 +90,12 @@ function CoverMenu:updateItems(select_number)
if self.show_path then
self.path_text.text = self:truncatePath(self.path)
end
UIManager:setDirty("all", function()
self.show_parent.dithered = self._has_cover_images
UIManager:setDirty(self.show_parent, function()
local refresh_dimen =
old_dimen and old_dimen:combine(self.dimen)
or self.dimen
return "partial", refresh_dimen
return "partial", refresh_dimen, self.show_parent.dithered
end)
-- As additionally done in FileChooser:updateItems()
@ -137,13 +139,14 @@ function CoverMenu:updateItems(select_number)
item:update()
if item.bookinfo_found then
logger.dbg(" found", item.text)
self.show_parent.dithered = item._has_cover_image
local refreshfunc = function()
if item.refresh_dimen then
-- MosaicMenuItem may exceed its own dimen in its paintTo
-- with its "description" hint
return "ui", item.refresh_dimen
return "ui", item.refresh_dimen, self.show_parent.dithered
else
return "ui", item[1].dimen
return "ui", item[1].dimen, self.show_parent.dithered
end
end
UIManager:setDirty(self.show_parent, refreshfunc)

@ -288,6 +288,9 @@ function ListMenuItem:update()
wimage,
}
}
-- Let menu know it has some item with images
self.menu._has_cover_images = true
self._has_cover_image = true
else
-- empty element the size of an image
wleft = CenterContainer:new{

@ -498,6 +498,9 @@ function MosaicMenuItem:update()
image,
}
}
-- Let menu know it has some item with images
self.menu._has_cover_images = true
self._has_cover_image = true
else
-- add Series metadata if requested
if bookinfo.series then

@ -173,14 +173,16 @@ function DoubleKeyValueItem:onTap()
UIManager:close(info)
else
self[1].invert = true
UIManager:setDirty(self.show_parent, function()
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
UIManager:tickAfterNext(function()
self.callback()
UIManager:close(info)
self[1].invert = false
UIManager:setDirty(self.show_parent, function()
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "ui", self[1].dimen
end)
end)

@ -260,8 +260,7 @@ function GoodreadsBook:onAnyKeyPressed()
end
function GoodreadsBook:onClose()
UIManager:setDirty("all")
UIManager:close(self)
UIManager:close(self, "flashui")
return true
end

@ -235,8 +235,7 @@ function KoboLight:addToMainMenu(menu_items)
(self:disabled() and _("Do you want to enable the frontlight gesture controller?") or _("Do you want to disable the frontlight gesture controller?")),
ok_text = self:disabled() and _("Enable") or _("Disable"),
ok_callback = function()
UIManager:close(image)
UIManager:setDirty("all", "full")
UIManager:close(image, "full")
UIManager:show(InfoMessage:new{
text = T(_("You have %1 the frontlight gesture controller. It will take effect on next restart."),
self:disabled() and _("enabled") or _("disabled"))
@ -245,8 +244,7 @@ function KoboLight:addToMainMenu(menu_items)
end,
cancel_text = _("Close"),
cancel_callback = function()
UIManager:close(image)
UIManager:setDirty("all", "full")
UIManager:close(image, "full")
end,
})
UIManager:setDirty("all", "full")

Loading…
Cancel
Save