From 5871132c2559f5456ca4b8398957a7e6832c190e Mon Sep 17 00:00:00 2001 From: NiLuJe Date: Sat, 2 Jun 2018 12:10:55 -0400 Subject: [PATCH] UI Behavior tweaks (#3983) * Switch all initial highlights to "fast" update i.e., everything that does an invert Plus a few other things that refresh small UI elements onTap Re #3130 * Tweak refreshtype for a number of widgets: * Fix iconbutton dimen * Make touchmenu flash on close & initial menu popup. Full-screen on close. * Use flashing updates when opening/closing dictionary popup. Full-screen on close. * Switch FileManager to partial. It's mostly text, and we want flash promotion there. * Make configdialog & menu flash on exit * Make FLWidget flash on close * virtualkeyboard: flash on layout change & popup. * Potentially not that great workaround to ensure we actually see the highlights in the FM's chevrons * Flash when closing BookStatus Widget * Optimize away a quirk of the dual "fast" update in touchmenu * Promote updates to flashing slightly more agressively. * Document what each refreshtype actually does. With a few guidelines on their optimal usecases. * Switch remaining scheduleIn(0.0) to nextTick() * Tighter scheduling timers Shaving a hundred ms off UI callbacks... * Cache FFI C Library namespace * Ask MuPDF to convert pixmaps to BGR on Kobo Fix #3949 * Mention koxtoolchain in the README re #3972 * Kindle: Handle *all* fonts via EXT_FONT_DIR instead of bind mounts insanity * Make black flashes in UI elements user-configurable (All or nothing). * Jot down some random KOA2 sysfs path --- README.md | 8 +- base | 2 +- frontend/apps/filemanager/filemanager.lua | 2 +- frontend/device/android/device.lua | 11 +- frontend/device/generic/device.lua | 3 + frontend/device/kindle/device.lua | 4 + frontend/device/kobo/device.lua | 1 + frontend/document/credocument.lua | 5 +- frontend/document/pdfdocument.lua | 19 ++- frontend/ui/elements/avoid_flashing_ui.lua | 12 ++ .../elements/common_settings_menu_table.lua | 1 + frontend/ui/message/streammessagequeue.lua | 5 +- frontend/ui/renderimage.lua | 8 ++ frontend/ui/uimanager.lua | 133 +++++++++++++++--- frontend/ui/widget/bookstatuswidget.lua | 9 +- frontend/ui/widget/button.lua | 16 +-- frontend/ui/widget/buttonprogresswidget.lua | 1 - frontend/ui/widget/checkbutton.lua | 16 +-- frontend/ui/widget/configdialog.lua | 11 +- frontend/ui/widget/dictquicklookup.lua | 11 +- frontend/ui/widget/filechooser.lua | 3 +- frontend/ui/widget/focusmanager.lua | 2 +- frontend/ui/widget/frontlightwidget.lua | 3 +- frontend/ui/widget/htmlboxwidget.lua | 6 + frontend/ui/widget/iconbutton.lua | 18 ++- frontend/ui/widget/imageviewer.lua | 2 +- frontend/ui/widget/keyvaluepage.lua | 4 +- frontend/ui/widget/menu.lua | 28 ++-- frontend/ui/widget/notification.lua | 2 +- frontend/ui/widget/radiobutton.lua | 16 +-- frontend/ui/widget/textviewer.lua | 4 +- frontend/ui/widget/touchmenu.lua | 52 ++++--- frontend/ui/widget/virtualkeyboard.lua | 21 +-- platform/android/llapp_main.lua | 3 +- platform/kindle/koreader.sh | 61 +------- plugins/coverbrowser.koplugin/covermenu.lua | 6 +- .../goodreads.koplugin/doublekeyvaluepage.lua | 4 +- plugins/newsdownloader.koplugin/main.lua | 3 +- plugins/zsync.koplugin/main.lua | 5 +- 39 files changed, 324 insertions(+), 197 deletions(-) create mode 100644 frontend/ui/elements/avoid_flashing_ui.lua diff --git a/README.md b/README.md index bca9fcd3a..086c9bc36 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ KOReader is a document viewer application, originally created for Kindle e-ink readers. It currently runs on Kindle, Kobo, PocketBook, Ubuntu Touch and Android devices. Developers can also run a KOReader emulator -for development purposes on desktop PCs with Linux, Windows and +for development purposes on desktop PCs with Linux, Windows and Mac OSX. Main features for users @@ -104,6 +104,12 @@ block you from building for Kobo or Kindle. Remove them if you get an ld error, `/usr/lib/gcc-cross/arm-linux-gnueabihf/4.8/../../../../arm-linux-gnueabihf/bin/ ld: cannot find -lglib-2.0` +**NOTE:** In the specific case of Kindle & Kobo targets, while we make some effort to support these Linaro/Ubuntu TCs, +they do *not* exactly target the proper devices. While your build will go fine, this may lead to runtime failure. +As time goes by, and/or the more bleeding-edge your distro is, the greater the risk for mismatch gets. +Thankfully, we have a distribution-agnostic solution for you: [koxtoolchain](https://github.com/koreader/koxtoolchain)! +This will allow you to build the *exact* same TCs used to build the nightlies, thanks to the magic of [crosstool-ng](https://github.com/crosstool-ng/crosstool-ng). + On Mac OS X you may need to install the following tools using [Homebrew](https://brew.sh/): ``` brew install nasm binutils libtool autoconf automake cmake makedepend sdl2 lua51 gettext pkg-config wget md5sha1sum diff --git a/base b/base index 1b7e584a2..24150115a 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit 1b7e584a27a460dc8375ad910cd2b92161776da9 +Subproject commit 24150115ab25c5021176247a241caaed838718d4 diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 833a43776..da834633b 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -161,7 +161,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 "ui", FileManager.instance.path_text.dimen + return "partial", FileManager.instance.path_text.dimen end) return true end diff --git a/frontend/device/android/device.lua b/frontend/device/android/device.lua index 69fd2421c..6c9240c4d 100644 --- a/frontend/device/android/device.lua +++ b/frontend/device/android/device.lua @@ -1,6 +1,7 @@ local Generic = require("device/generic/device") local _, android = pcall(require, "android") local ffi = require("ffi") +local C = ffi.C local logger = require("logger") local function yes() return true end @@ -26,11 +27,11 @@ function Device:init() event_map = require("device/android/event_map"), handleMiscEv = function(this, ev) logger.dbg("Android application event", ev.code) - if ev.code == ffi.C.APP_CMD_SAVE_STATE then + if ev.code == C.APP_CMD_SAVE_STATE then return "SaveState" - elseif ev.code == ffi.C.APP_CMD_GAINED_FOCUS then + elseif ev.code == C.APP_CMD_GAINED_FOCUS then this.device.screen:refreshFull() - elseif ev.code == ffi.C.APP_CMD_WINDOW_REDRAW_NEEDED then + elseif ev.code == C.APP_CMD_WINDOW_REDRAW_NEEDED then this.device.screen:refreshFull() end end, @@ -47,13 +48,13 @@ function Device:init() -- check if we have a keyboard if android.lib.AConfiguration_getKeyboard(android.app.config) - == ffi.C.ACONFIGURATION_KEYBOARD_QWERTY + == C.ACONFIGURATION_KEYBOARD_QWERTY then self.hasKeyboard = yes end -- check if we have a touchscreen if android.lib.AConfiguration_getTouchscreen(android.app.config) - ~= ffi.C.ACONFIGURATION_TOUCHSCREEN_NOTOUCH + ~= C.ACONFIGURATION_TOUCHSCREEN_NOTOUCH then self.isTouchDevice = yes end diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index f1d3741aa..4deb1ddd2 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -26,6 +26,7 @@ local Device = { needsTouchScreenProbe = no, hasClipboard = no, hasColorScreen = no, + hasBGRFrameBuffer = no, -- use these only as a last resort. We should abstract the functionality -- and have device dependent implementations in the corresponting @@ -65,6 +66,8 @@ function Device:init() return self.screen.isColorScreen() end + self.screen.isBGRFrameBuffer = self.hasBGRFrameBuffer + local is_eink = G_reader_settings:readSetting("eink") self.screen.eink = (is_eink == nil) or is_eink diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index e1c42ebf5..c58487aaa 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -248,6 +248,10 @@ local KindleOasis2 = Kindle:new{ hasFrontlight = yes, display_dpi = 300, touch_dev = "/dev/input/by-path/platform-30a30000.i2c-event", + + -- NOTE: Incomplete, but at least they're confirmed. + --batt_capacity_file = "/sys/class/power_supply/max77796-battery/capacity", + --is_charging_file = "/sys/class/power_supply/max77796-charger/charging", } local KindleBasic2 = Kindle:new{ diff --git a/frontend/device/kobo/device.lua b/frontend/device/kobo/device.lua index b7e0cb551..473613020 100644 --- a/frontend/device/kobo/device.lua +++ b/frontend/device/kobo/device.lua @@ -23,6 +23,7 @@ local Kobo = Generic:new{ model = "Kobo", isKobo = yes, isTouchDevice = yes, -- all of them are + hasBGRFrameBuffer = yes, -- has always been the case, even on 16bpp FWs -- most Kobos have X/Y switched for the touch screen touch_switch_xy = true, diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index 78b9ad4aa..2e1d8c431 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -7,6 +7,7 @@ local Geom = require("ui/geometry") local RenderImage = require("ui/renderimage") local Screen = require("device").screen local ffi = require("ffi") +local C = ffi.C local lfs = require("libs/libkoreader-lfs") local logger = require("logger") @@ -205,7 +206,7 @@ function CreDocument:getCoverPageImage() local data, size = self._document:getCoverPageImageData() if data and size then local image = RenderImage:renderImageData(data, size) - ffi.C.free(data) -- free the userdata we got from crengine + C.free(data) -- free the userdata we got from crengine return image end end @@ -215,7 +216,7 @@ function CreDocument:getImageFromPosition(pos, want_frames) if data and size then logger.dbg("CreDocument: got image data from position", data, size) local image = RenderImage:renderImageData(data, size, want_frames) - ffi.C.free(data) -- free the userdata we got from crengine + C.free(data) -- free the userdata we got from crengine return image end end diff --git a/frontend/document/pdfdocument.lua b/frontend/document/pdfdocument.lua index 193cb152a..34f072e90 100644 --- a/frontend/document/pdfdocument.lua +++ b/frontend/document/pdfdocument.lua @@ -1,12 +1,16 @@ local Cache = require("cache") local CacheItem = require("cacheitem") +local Device = require("device") local Document = require("document/document") local DrawContext = require("ffi/drawcontext") local KoptOptions = require("ui/data/koptoptions") local logger = require("logger") local util = require("util") +local ffi = require("ffi") +local C = ffi.C local pdf = nil + local PdfDocument = Document:new{ _document = false, is_pdf = true, @@ -25,6 +29,12 @@ function PdfDocument:init() -- and :postRenderPage() when mupdf is called without kopt involved. pdf.color = false self:updateColorRendering() + if pdf.bgr == nil then + pdf.bgr = false + if Device:hasBGRFrameBuffer() then + pdf.bgr = true + end + end self.koptinterface = require("document/koptinterface") self.configurable:loadDefaults(self.options) local ok @@ -134,7 +144,6 @@ end function PdfDocument:saveHighlight(pageno, item) self.is_edited = true - local ffi = require("ffi") -- will also need mupdf_h.lua to be evaluated once -- but this is guaranteed at this point local n = #item.pboxes @@ -152,13 +161,13 @@ function PdfDocument:saveHighlight(pageno, item) quadpoints[8*i-1] = item.pboxes[i].y end local page = self._document:openPage(pageno) - local annot_type = ffi.C.PDF_ANNOT_HIGHLIGHT + local annot_type = C.PDF_ANNOT_HIGHLIGHT if item.drawer == "lighten" then - annot_type = ffi.C.PDF_ANNOT_HIGHLIGHT + annot_type = C.PDF_ANNOT_HIGHLIGHT elseif item.drawer == "underscore" then - annot_type = ffi.C.PDF_ANNOT_UNDERLINE + annot_type = C.PDF_ANNOT_UNDERLINE elseif item.drawer == "strikeout" then - annot_type = ffi.C.PDF_ANNOT_STRIKEOUT + annot_type = C.PDF_ANNOT_STRIKEOUT end page:addMarkupAnnotation(quadpoints, n, annot_type) page:close() diff --git a/frontend/ui/elements/avoid_flashing_ui.lua b/frontend/ui/elements/avoid_flashing_ui.lua new file mode 100644 index 000000000..9b163b199 --- /dev/null +++ b/frontend/ui/elements/avoid_flashing_ui.lua @@ -0,0 +1,12 @@ +local _ = require("gettext") + +return { + text = _("Avoid mandatory black flashes in UI"), + checked_func = function() + return G_reader_settings:isTrue("avoid_flashing_ui") + end, + callback = function() + G_reader_settings:flipNilOrFalse("avoid_flashing_ui") + end, +} + diff --git a/frontend/ui/elements/common_settings_menu_table.lua b/frontend/ui/elements/common_settings_menu_table.lua index 66d51b4d3..06b4208d5 100644 --- a/frontend/ui/elements/common_settings_menu_table.lua +++ b/frontend/ui/elements/common_settings_menu_table.lua @@ -113,6 +113,7 @@ common_settings.screen = { require("ui/elements/screen_disable_double_tap_table"), require("ui/elements/flash_ui"), require("ui/elements/flash_keyboard"), + require("ui/elements/avoid_flashing_ui"), }, } if Screen.isColorScreen() then diff --git a/frontend/ui/message/streammessagequeue.lua b/frontend/ui/message/streammessagequeue.lua index 680339088..4a19d3dbf 100644 --- a/frontend/ui/message/streammessagequeue.lua +++ b/frontend/ui/message/streammessagequeue.lua @@ -5,6 +5,7 @@ local MessageQueue = require("ui/message/messagequeue") local _ = require("ffi/zeromq_h") local zmq = ffi.load("libs/libzmq.so.4") local czmq = ffi.load("libs/libczmq.so.1") +local C = ffi.C local StreamMessageQueue = MessageQueue:new{ host = nil, @@ -13,7 +14,7 @@ local StreamMessageQueue = MessageQueue:new{ function StreamMessageQueue:start() self.context = czmq.zctx_new(); - self.socket = czmq.zsocket_new(self.context, ffi.C.ZMQ_STREAM) + self.socket = czmq.zsocket_new(self.context, C.ZMQ_STREAM) self.poller = czmq.zpoller_new(self.socket, nil) local endpoint = string.format("tcp://%s:%d", self.host, self.port) logger.warn("connect to endpoint", endpoint) @@ -24,7 +25,7 @@ function StreamMessageQueue:start() local id_size = ffi.new("size_t[1]", 256) local buffer = ffi.new("uint8_t[?]", id_size[0]) -- @todo: check return of zmq_getsockopt - zmq.zmq_getsockopt(self.socket, ffi.C.ZMQ_IDENTITY, buffer, id_size) + zmq.zmq_getsockopt(self.socket, C.ZMQ_IDENTITY, buffer, id_size) self.id = ffi.string(buffer, id_size[0]) logger.dbg("id", #self.id, self.id) end diff --git a/frontend/ui/renderimage.lua b/frontend/ui/renderimage.lua index ed3cb181d..ed237ad52 100644 --- a/frontend/ui/renderimage.lua +++ b/frontend/ui/renderimage.lua @@ -3,6 +3,7 @@ Image rendering module. ]] local ffi = require("ffi") +local Device = require("device") local logger = require("logger") -- Will be loaded when needed @@ -66,6 +67,13 @@ end -- @treturn BlitBuffer function RenderImage:renderImageDataWithMupdf(data, size, width, height) if not Mupdf then Mupdf = require("ffi/mupdf") end + -- NOTE: Kobo's fb is BGR, not RGB. Handle the conversion in MuPDF if needed. + if Mupdf.bgr == nil then + Mupdf.bgr = false + if Device:hasBGRFrameBuffer() then + Mupdf.bgr = true + end + end local ok, image = pcall(Mupdf.renderImage, data, size, width, height) logger.dbg("Mupdf.renderImage", ok, image) if not ok then diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 93f1d4d9a..21eea2bad 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -185,7 +185,7 @@ Modal widget should be always on top. For refreshtype & refreshregion see description of setDirty(). ]] ---- @param widget a widget object ----- @param refreshtype "full", "partial", "ui", "fast" +---- @param refreshtype "full", "flashpartial", "flashui", "partial", "ui", "fast" ---- @param refreshregion a Geom object ---- @int x ---- @int y @@ -226,12 +226,12 @@ Unregisters a widget. For refreshtype & refreshregion see description of setDirty(). ]] ---- @param widget a widget object ----- @param refreshtype "full", "partial", "ui", "fast" +---- @param refreshtype "full", "flashpartial", "flashui", "partial", "ui", "fast" ---- @param refreshregion a Geom object ---- @see setDirty function UIManager:close(widget, refreshtype, refreshregion) if not widget then - logger.dbg("widget not exist to be closed") + logger.dbg("widget to be closed does not exist") return end logger.dbg("close widget", widget.id or widget.name) @@ -242,7 +242,7 @@ function UIManager:close(widget, refreshtype, refreshregion) widget:handleEvent(Event:new("CloseWidget")) -- make it disabled by default and check any widget that enables it Input.disable_double_tap = true - -- then remove all reference to that widget on stack and update + -- 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 table.remove(self._window_stack, i) @@ -322,6 +322,17 @@ function UIManager:nextTick(action) return self:scheduleIn(0, action) end +-- Useful to run UI callbacks ASAP without skipping repaints +function UIManager:tickAfterNext(action) + return self:nextTick(function() self:nextTick(action) end) +end +--[[ +-- NOTE: This appears to work *nearly* just as well, but does sometimes go too fast (might depend on kernel HZ & NO_HZ settings?) +function UIManager:tickAfterNext(action) + return self:scheduleIn(0.001, action) +end +--]] + --[[-- Unschedules an execution task. In order to unschedule anonymous functions, store a reference. @@ -349,6 +360,38 @@ the second parameter (refreshtype) can either specify a refreshtype (optionally in combination with a refreshregion - which is suggested) or a function that returns refreshtype AND refreshregion and is called after painting the widget. +Here's a quick rundown of what each refreshtype should be used for: +full: high-fidelity flashing refresh (f.g., large images). + Highest quality, but highest latency. + Don't abuse if you only want a flash (in this case, prefer flashpartial or flashui). +partial: medium fidelity refresh (f.g., text on a white background). + Can be promoted to flashing after FULL_REFRESH_COUNT refreshes. + Don't abuse to avoid spurious flashes. +ui: medium fidelity refresh (f.g., mixed content). + Should apply to most UI elements. +fast: low fidelity refresh (f.g., monochrome content). + Should apply to most highlighting effects achieved through inversion. + Note that if your highlighted element contains text, + you might want to keep the unhighlight refresh as "ui" instead, for crisper text. + (Or optimize that refresh away entirely, if you can get away with it). +flashui: like ui, but flashing. + Can be used when showing a UI element for the first time, to avoid ghosting. +flashpartial: like partial, but flashing (and not counting towards flashing promotions). + Can be used when closing an UI element, to avoid ghosting. + You can even drop the region in these cases, to ensure a fullscreen flash. + NOTE: On REAGL devices, "flashpartial" will NOT actually flash (by design). + As such, even onClose, you might prefer "flashui" in some rare instances. + +NOTE: You'll notice a trend on UI elements that are usually shown *over* some kind of text + of using "ui" onShow & onUpdate, but "partial" onClose. + This is by design: "partial" is what the reader uses, as it's tailor-made for pure text + over a white background, so this ensures we resume the usual flow of the reader. + The same dynamic is true for their flashing counterparts, in the rare instances we enforce flashes. + Any kind of "partial" refresh *will* count towards a flashing promotion after FULL_REFRESH_COUNT refreshes, + so making sure your stuff only applies to the proper region is key to avoiding spurious large black flashes. + 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. + @usage @@ -358,7 +401,7 @@ UIManager:setDirty(self.widget, function() return "ui", self.someelement.dimen e --]] ---- @param widget a widget object ----- @param refreshtype "full", "partial", "ui", "fast" +---- @param refreshtype "full", "flashpartial", "flashui", "partial", "ui", "fast" ---- @param refreshregion a Geom object function UIManager:setDirty(widget, refreshtype, refreshregion) if widget then @@ -375,9 +418,21 @@ function UIManager:setDirty(widget, refreshtype, refreshregion) if type(refreshtype) == "function" then -- callback, will be issued after painting table.insert(self._refresh_func_stack, refreshtype) + 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))) + end else -- otherwise, enqueue refresh self:_refresh(refreshtype, refreshregion) + 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) + 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") + end + end end end dbg:guard(UIManager, 'setDirty', @@ -395,6 +450,18 @@ dbg:guard(UIManager, 'setDirty', end end) +-- Clear the full repaint & refreshes queues. +-- NOTE: Beware! This doesn't take any prisonners! +-- You shouldn't have to resort to this unless in very specific circumstances! +-- plugins/coverbrowser.koplugin/covermenu.lua building a franken-menu out of buttondialogtitle & buttondialog +-- and wanting to avoid inheriting their original paint/refresh cycle being a prime example. +function UIManager:clearRenderStack() + logger.dbg("clearRenderStack: Clearing the full render stack!") + self._dirty = {} + self._refresh_func_stack = {} + self._refresh_stack = {} +end + function UIManager:insertZMQ(zeromq) table.insert(self._zeromqs, zeromq) return zeromq @@ -540,12 +607,14 @@ function UIManager:_checkTasks() end -- precedence of refresh modes: -local refresh_modes = { fast = 1, ui = 2, partial = 3, full = 4 } +local refresh_modes = { fast = 1, ui = 2, partial = 3, flashui = 4, flashpartial = 5, full = 6 } -- refresh methods in framebuffer implementation local refresh_methods = { fast = "refreshFast", ui = "refreshUI", partial = "refreshPartial", + flashui = "refreshFlashUI", + flashpartial = "refreshFlashPartial", full = "refreshFull", } @@ -556,6 +625,7 @@ Will return the mode that takes precedence. --]] local function update_mode(mode1, mode2) if refresh_modes[mode1] > refresh_modes[mode2] then + logger.dbg("update_mode: Update refresh mode", mode2, "to", mode1) return mode1 else return mode2 @@ -569,7 +639,7 @@ Widgets call this in their paintTo() method in order to notify UIManager that a certain part of the screen is to be refreshed. @param mode - refresh mode ("full", "partial", "ui", "fast") + refresh mode ("full", "flashpartial", "flashui", "partial", "ui", "fast") @param region Rect() that specifies the region to be updated optional, update will affect whole screen if not specified. @@ -580,16 +650,40 @@ function UIManager:_refresh(mode, region) if not region and mode == "full" then self.refresh_count = 0 -- reset counter on explicit full refresh end - -- special case: full screen partial update - -- will get promoted every self.FULL_REFRESH_COUNT updates + -- Handle downgrading flashing modes to non-flashing modes, according to user settings. + -- NOTE: Do it before "full" promotion and collision checks/update_mode. + if G_reader_settings:isTrue("avoid_flashing_ui") then + if mode == "flashui" then + mode = "ui" + logger.dbg("_refresh: downgraded flashui refresh to", mode) + elseif mode == "flashpartial" then + mode = "partial" + logger.dbg("_refresh: downgraded flashpartial refresh to", mode) + elseif mode == "partial" and region then + mode = "ui" + logger.dbg("_refresh: downgraded regional partial refresh to", mode) + end + end + -- special case: "partial" refreshes + -- will get promoted every self.FULL_REFRESH_COUNT refreshes -- since _refresh can be called mutiple times via setDirty called in - -- different widget before a real screen repaint, we should make sure + -- different widgets before a real screen repaint, we should make sure -- refresh_count is incremented by only once at most for each repaint - if not region and mode == "partial" and not self.refresh_counted then + -- NOTE: Ideally, we'd only check for "partial"" w/ no region set (that neatly narrows it down to just the reader). + -- In practice, we also want to promote refreshes in a few other places, except purely text-poor UI elements. + -- (Putting "ui" in that list is problematic with a number of UI elements, most notably, ReaderHighlight, + -- because it is implemented as "ui" over the full viewport, since we can't devise a proper bounding box). + -- So we settle for only "partial", but treating full-screen ones slightly differently. + if mode == "partial" and not self.refresh_counted then self.refresh_count = (self.refresh_count + 1) % self.FULL_REFRESH_COUNT if self.refresh_count == self.FULL_REFRESH_COUNT - 1 then - logger.dbg("promote refresh to full refresh") - mode = "full" + -- NOTE: Promote to "full" if no region (reader), to "flashui" otherwise (UI) + if region then + mode = "flashui" + else + mode = "full" + end + logger.dbg("_refresh: promote refresh to", mode) end self.refresh_counted = true end @@ -597,20 +691,27 @@ function UIManager:_refresh(mode, region) -- if no region is specified, define default region region = region or Geom:new{w=Screen:getWidth(), h=Screen:getHeight()} + -- 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), + -- as well as a few actually effective merges + -- (f.g., the disappearance of a selection HL with the following menu update). for i = 1, #self._refresh_stack do - -- check for collision with updates that are already enqueued + -- check for collision with refreshes that are already enqueued if region:intersectWith(self._refresh_stack[i].region) then -- combine both refreshes' regions local combined = region:combine(self._refresh_stack[i].region) -- update the mode, if needed mode = update_mode(mode, self._refresh_stack[i].mode) - -- remove colliding update + -- remove colliding refresh table.remove(self._refresh_stack, i) -- and try again with combined data return self:_refresh(mode, combined) end end - -- if we hit no (more) collides, enqueue the update + + -- 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}) end diff --git a/frontend/ui/widget/bookstatuswidget.lua b/frontend/ui/widget/bookstatuswidget.lua index bf336fb60..bc94d8ab8 100644 --- a/frontend/ui/widget/bookstatuswidget.lua +++ b/frontend/ui/widget/bookstatuswidget.lua @@ -245,7 +245,7 @@ function BookStatusWidget:setStar(num) table.insert(self.stars_container, stars_group) - UIManager:setDirty(nil, "partial") + UIManager:setDirty(nil, "ui") return true end @@ -527,11 +527,11 @@ function BookStatusWidget:generateSwitchGroup(width) end function BookStatusWidget:onConfigChoose(values, name, event, args, events, position) - UIManager:scheduleIn(0.05, function() + UIManager:tickAfterNext(function() if values then self:onChangeBookStatus(args, position) end - UIManager:setDirty("all") + UIManager:setDirty("all", "ui") end) end @@ -542,7 +542,8 @@ end function BookStatusWidget:onClose() self:saveSummary() - UIManager:setDirty("all") + -- NOTE: Flash on close to avoid ghosting, since we show an image. + UIManager:setDirty("all", "flashpartial") UIManager:close(self) return true end diff --git a/frontend/ui/widget/button.lua b/frontend/ui/widget/button.lua index e5585bf60..d257c35e4 100644 --- a/frontend/ui/widget/button.lua +++ b/frontend/ui/widget/button.lua @@ -191,17 +191,17 @@ function Button:onTapSelectButton() if G_reader_settings:isFalse("flash_ui") then self.callback() else - UIManager:scheduleIn(0.0, function() - self[1].invert = true - UIManager:setDirty(self.show_parent, function() - return "ui", self[1].dimen - end) + -- 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() + return "fast", self[1].dimen end) - UIManager:scheduleIn(0.1, function() + UIManager:tickAfterNext(function() self.callback() self[1].invert = false - UIManager:setDirty(self.show_parent, function() - return "ui", self[1].dimen + UIManager:setDirty("all", function() + return "fast", self[1].dimen end) end) end diff --git a/frontend/ui/widget/buttonprogresswidget.lua b/frontend/ui/widget/buttonprogresswidget.lua index d0ecf922a..2580ccb69 100644 --- a/frontend/ui/widget/buttonprogresswidget.lua +++ b/frontend/ui/widget/buttonprogresswidget.lua @@ -78,7 +78,6 @@ function ButtonProgressWidget:update() UIManager:setDirty(self.show_parrent, function() return "ui", self.dimen end) -UIManager:setDirty("all") end function ButtonProgressWidget:setPosition(position) diff --git a/frontend/ui/widget/checkbutton.lua b/frontend/ui/widget/checkbutton.lua index 16c856c91..6c6175755 100644 --- a/frontend/ui/widget/checkbutton.lua +++ b/frontend/ui/widget/checkbutton.lua @@ -94,17 +94,15 @@ function CheckButton:onTapCheckButton() if G_reader_settings:isFalse("flash_ui") then self.callback() else - UIManager:scheduleIn(0.0, function() - self.invert = true - UIManager:setDirty(self.show_parent, function() - return "ui", self.dimen - end) + self.invert = true + UIManager:setDirty(self.show_parent, function() + return "fast", self.dimen end) - UIManager:scheduleIn(0.1, function() + UIManager:tickAfterNext(function() self.callback() self.invert = false UIManager:setDirty(self.show_parent, function() - return "ui", self.dimen + return "fast", self.dimen end) end) end @@ -130,14 +128,14 @@ end function CheckButton:check() self:initCheckButton(true) UIManager:setDirty(self.parent, function() - return "partial", self.dimen + return "fast", self.dimen end) end function CheckButton:unCheck() self:initCheckButton(false) UIManager:setDirty(self.parent, function() - return "partial", self.dimen + return "fast", self.dimen end) end diff --git a/frontend/ui/widget/configdialog.lua b/frontend/ui/widget/configdialog.lua index b6bcda0ce..5ab8cf7a8 100644 --- a/frontend/ui/widget/configdialog.lua +++ b/frontend/ui/widget/configdialog.lua @@ -81,7 +81,7 @@ function OptionTextItem:onTapSelect() self.event, self.args, self.events, self.current_item) UIManager:setDirty(self.config, function() - return "ui", self[1].dimen + return "fast", self[1].dimen end) return true end @@ -144,7 +144,7 @@ function OptionIconItem:onTapSelect() self.event, self.args, self.events, self.current_item) UIManager:setDirty(self.config, function() - return "ui", self[1].dimen + return "fast", self[1].dimen end) return true end @@ -455,7 +455,7 @@ function ConfigOption:init() num_buttons = #self.options[c].values, position = self.options[c].default_pos, callback = function(arg) - UIManager:scheduleIn(0.05, function() + UIManager:tickAfterNext(function() self.config:onConfigChoice(self.options[c].name, self.options[c].values[arg]) self.config:onConfigEvent(self.options[c].event, self.options[c].args[arg]) UIManager:setDirty("all") @@ -723,6 +723,7 @@ function ConfigDialog:update() 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() return "partial", self.dialog_frame.dimen end) @@ -732,6 +733,8 @@ function ConfigDialog:onShowConfigPanel(index) self.panel_index = index local old_dimen = self.dialog_frame.dimen and self.dialog_frame.dimen:copy() 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() local refresh_dimen = old_dimen and old_dimen:combine(self.dialog_frame.dimen) @@ -761,7 +764,7 @@ function ConfigDialog:onConfigEvents(option_events, arg_index) end function ConfigDialog:onConfigChoose(values, name, event, args, events, position) - UIManager:scheduleIn(0.05, function() + UIManager:tickAfterNext(function() if values then self:onConfigChoice(name, values[position]) end diff --git a/frontend/ui/widget/dictquicklookup.lua b/frontend/ui/widget/dictquicklookup.lua index 05aec8b18..de41f51f7 100644 --- a/frontend/ui/widget/dictquicklookup.lua +++ b/frontend/ui/widget/dictquicklookup.lua @@ -552,9 +552,9 @@ function DictQuickLookup:update() self.movable, } UIManager:setDirty("all", function() - local update_region = self.dict_frame.dimen:combine(orig_dimen) + 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 "ui", update_region + return "partial", update_region end) end @@ -576,15 +576,16 @@ function DictQuickLookup:onCloseWidget() end end end + -- NOTE: Drop region to make it a full-screen flash UIManager:setDirty(nil, function() - return "partial", self.dict_frame.dimen + return "flashui", nil end) return true end function DictQuickLookup:onShow() UIManager:setDirty(self, function() - return "ui", self.dict_frame.dimen + return "flashui", self.dict_frame.dimen end) return true end @@ -774,7 +775,7 @@ function DictQuickLookup:onSwipe(arg, ges) self:changeToPrevDict() else if self.refresh_callback then self.refresh_callback() end - -- trigger full refresh + -- trigger a full-screen HQ flashing refresh UIManager:setDirty(nil, "full") -- a long diagonal swipe may also be used for taking a screenshot, -- so let it propagate diff --git a/frontend/ui/widget/filechooser.lua b/frontend/ui/widget/filechooser.lua index 35af00f49..593368df7 100644 --- a/frontend/ui/widget/filechooser.lua +++ b/frontend/ui/widget/filechooser.lua @@ -7,6 +7,7 @@ local UIManager = require("ui/uimanager") local ffi = require("ffi") local lfs = require("libs/libkoreader-lfs") local util = require("ffi/util") +local C = ffi.C local _ = require("gettext") local Screen = Device.screen local getFileNameSuffix = require("util").getFileNameSuffix @@ -18,7 +19,7 @@ int strcoll (const char *str1, const char *str2); -- string sort function respecting LC_COLLATE local function strcoll(str1, str2) - return ffi.C.strcoll(str1, str2) < 0 + return C.strcoll(str1, str2) < 0 end local function kobostrcoll(str1, str2) diff --git a/frontend/ui/widget/focusmanager.lua b/frontend/ui/widget/focusmanager.lua index 41dd51cc9..5687a2abb 100644 --- a/frontend/ui/widget/focusmanager.lua +++ b/frontend/ui/widget/focusmanager.lua @@ -82,7 +82,7 @@ function FocusManager:onFocusMove(args) -- we found a different object to focus current_item:handleEvent(Event:new("Unfocus")) self.layout[self.selected.y][self.selected.x]:handleEvent(Event:new("Focus")) - -- trigger a fast repaint, this seem to not count toward a fullscreen eink resfresh + -- trigger a fast repaint, this does not count toward a flashing eink resfresh -- TODO: is this really needed? UIManager:setDirty(self.show_parent or self, "fast") break diff --git a/frontend/ui/widget/frontlightwidget.lua b/frontend/ui/widget/frontlightwidget.lua index dd38b182e..62a7f0427 100644 --- a/frontend/ui/widget/frontlightwidget.lua +++ b/frontend/ui/widget/frontlightwidget.lua @@ -547,12 +547,13 @@ end function FrontLightWidget:onCloseWidget() UIManager:setDirty(nil, function() - return "partial", self.light_frame.dimen + return "flashpartial", self.light_frame.dimen end) return true end function FrontLightWidget:onShow() + -- NOTE: Keep this one as UI, it'll get coalesced... UIManager:setDirty(self, function() return "ui", self.light_frame.dimen end) diff --git a/frontend/ui/widget/htmlboxwidget.lua b/frontend/ui/widget/htmlboxwidget.lua index 9fed9a5a6..bb6d7467e 100644 --- a/frontend/ui/widget/htmlboxwidget.lua +++ b/frontend/ui/widget/htmlboxwidget.lua @@ -35,6 +35,12 @@ function HtmlBoxWidget:init() }, } end + if Mupdf.bgr == nil then + Mupdf.bgr = false + if Device:hasBGRFrameBuffer() then + Mupdf.bgr = true + end + end end function HtmlBoxWidget:setContent(body, css, default_font_size) diff --git a/frontend/ui/widget/iconbutton.lua b/frontend/ui/widget/iconbutton.lua index e7967d419..8b3f32f72 100644 --- a/frontend/ui/widget/iconbutton.lua +++ b/frontend/ui/widget/iconbutton.lua @@ -95,18 +95,16 @@ function IconButton:onTapIconButton() if G_reader_settings:isFalse("flash_ui") then self.callback() else - UIManager:scheduleIn(0.0, function() - self.image.invert = true - UIManager:setDirty(self.show_parent, function() - return "ui", self[1].dimen - end) + self.image.invert = true + UIManager:setDirty(self.show_parent, function() + return "fast", self.dimen end) - -- make sure button reacts before doing callback - UIManager:scheduleIn(0.1, function() + -- Make sure button reacts before doing callback + UIManager:tickAfterNext(function() self.callback() self.image.invert = false UIManager:setDirty(self.show_parent, function() - return "ui", self[1].dimen + return "fast", self.dimen end) end) end @@ -126,12 +124,12 @@ end function IconButton:onFocus() --quick and dirty, need better way to show focus - self.image.invert=true + self.image.invert = true return true end function IconButton:onUnfocus() - self.image.invert=false + self.image.invert = false return true end diff --git a/frontend/ui/widget/imageviewer.lua b/frontend/ui/widget/imageviewer.lua index 010854c57..7e0405112 100644 --- a/frontend/ui/widget/imageviewer.lua +++ b/frontend/ui/widget/imageviewer.lua @@ -602,7 +602,7 @@ function ImageViewer:onCloseWidget() self._images_list.free() end UIManager:setDirty(nil, function() - return "partial", self.main_frame.dimen + return "flashui", self.main_frame.dimen end) return true end diff --git a/frontend/ui/widget/keyvaluepage.lua b/frontend/ui/widget/keyvaluepage.lua index 7519d00a0..080327a7e 100644 --- a/frontend/ui/widget/keyvaluepage.lua +++ b/frontend/ui/widget/keyvaluepage.lua @@ -235,9 +235,9 @@ function KeyValueItem:onTap() else self[1].invert = true UIManager:setDirty(self.show_parent, function() - return "ui", self[1].dimen + return "fast", self[1].dimen end) - UIManager:scheduleIn(0.1, function() + UIManager:tickAfterNext(function() self.callback() self[1].invert = false UIManager:setDirty(self.show_parent, function() diff --git a/frontend/ui/widget/menu.lua b/frontend/ui/widget/menu.lua index 52e8dceac..c68fba57f 100644 --- a/frontend/ui/widget/menu.lua +++ b/frontend/ui/widget/menu.lua @@ -403,13 +403,14 @@ function MenuItem:onTapSelect(arg, ges) coroutine.resume(co) else self[1].invert = true - local refreshfunc = function() - return "ui", self[1].dimen - end - UIManager:setDirty(self.show_parent, refreshfunc) - UIManager:scheduleIn(0.1, function() + UIManager:setDirty(self.show_parent, function() + return "fast", self[1].dimen + end) + UIManager:tickAfterNext(function() self[1].invert = false - UIManager:setDirty(self.show_parent, refreshfunc) + 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) @@ -426,13 +427,14 @@ function MenuItem:onHoldSelect(arg, ges) self.menu:onMenuHold(self.table, pos) else self[1].invert = true - local refreshfunc = function() - return "ui", self[1].dimen - end - UIManager:setDirty(self.show_parent, refreshfunc) - UIManager:scheduleIn(0.1, function() + UIManager:setDirty(self.show_parent, function() + return "fast", self[1].dimen + end) + UIManager:tickAfterNext(function() self[1].invert = false - UIManager:setDirty(self.show_parent, refreshfunc) + UIManager:setDirty(self.show_parent, function() + return "ui", self[1].dimen + end) self.menu:onMenuHold(self.table, pos) end) end @@ -807,6 +809,8 @@ function Menu:onCloseWidget() -- For example, it's a dirty hack to use two menus(one this menu and one -- touch menu) in the filemanager in order to capture tap gesture to popup -- the filemanager menu. + -- NOTE: For the same reason, don't make it flash, + -- because that'll trigger when we close the FM and open a book... UIManager:setDirty(nil, "partial") end diff --git a/frontend/ui/widget/notification.lua b/frontend/ui/widget/notification.lua index 317fb6ab7..d52134581 100644 --- a/frontend/ui/widget/notification.lua +++ b/frontend/ui/widget/notification.lua @@ -73,7 +73,7 @@ end function Notification:onCloseWidget() UIManager:setDirty(nil, function() - return "partial", self[1][1].dimen + return "ui", self[1][1].dimen end) return true end diff --git a/frontend/ui/widget/radiobutton.lua b/frontend/ui/widget/radiobutton.lua index da55a1507..7a705faa4 100644 --- a/frontend/ui/widget/radiobutton.lua +++ b/frontend/ui/widget/radiobutton.lua @@ -113,17 +113,15 @@ function RadioButton:onTapCheckButton() if G_reader_settings:isFalse("flash_ui") then self.callback() else - UIManager:scheduleIn(0.0, function() - self.invert = true - UIManager:setDirty(self.show_parent, function() - return "ui", self.dimen - end) + self.invert = true + UIManager:setDirty(self.show_parent, function() + return "fast", self.dimen end) - UIManager:scheduleIn(0.1, function() + UIManager:tickAfterNext(function() self.callback() self.invert = false UIManager:setDirty(self.show_parent, function() - return "ui", self.dimen + return "fast", self.dimen end) end) end @@ -151,7 +149,7 @@ function RadioButton:check(callback) self.checked = true self:update() UIManager:setDirty(self.parent, function() - return "ui", self.dimen + return "fast", self.dimen end) end @@ -160,7 +158,7 @@ function RadioButton:unCheck() self.checked = false self:update() UIManager:setDirty(self.parent, function() - return "ui", self.dimen + return "fast", self.dimen end) end diff --git a/frontend/ui/widget/textviewer.lua b/frontend/ui/widget/textviewer.lua index d01ce1e42..df3c5e630 100644 --- a/frontend/ui/widget/textviewer.lua +++ b/frontend/ui/widget/textviewer.lua @@ -246,8 +246,8 @@ function TextViewer:onSwipe(arg, ges) self.scroll_text_w:scrollText(-1) return true else - -- trigger full refresh - UIManager:setDirty(nil, "full") + -- trigger a flashing text refresh + UIManager:setDirty(nil, "flashui", self.frame.dimen) -- a long diagonal swipe may also be used for taking a screenshot, -- so let it propagate return false diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua index 2b090d402..6c843dbae 100644 --- a/frontend/ui/widget/touchmenu.lua +++ b/frontend/ui/widget/touchmenu.lua @@ -105,7 +105,7 @@ function TouchMenuItem:init() self._underline_container = UnderlineContainer:new{ vertical_align = "center", - dimen =self.dimen, + dimen = self.dimen, self.item_frame } @@ -137,15 +137,20 @@ function TouchMenuItem:onTapSelect(arg, ges) else self.item_frame.invert = true UIManager:setDirty(self.show_parent, function() - return "ui", self.dimen + return "fast", self.dimen end) -- yield to main UI loop to invert item - UIManager:scheduleIn(0.1, function() + UIManager:tickAfterNext(function() self.menu:onMenuSelect(self.item) self.item_frame.invert = false + --[[ + -- NOTE: We can optimize that repaint away, every entry in our menu will make at least the menu repaint just after anyways ;). + -- Plus, leaving that unhighlight as "fast" can lead to weird side-effects, depending on devices. + -- If it turns out this need to go back in, consider switching it to "ui". UIManager:setDirty(self.show_parent, function() - return "ui", self.dimen + return "fast", self.dimen end) + --]] end) end return true @@ -161,13 +166,11 @@ function TouchMenuItem:onHoldSelect(arg, ges) if G_reader_settings:isFalse("flash_ui") then self.menu:onMenuHold(self.item) else - UIManager:scheduleIn(0.0, function() - self.item_frame.invert = true - UIManager:setDirty(self.show_parent, function() - return "ui", self.dimen - end) + self.item_frame.invert = true + UIManager:setDirty(self.show_parent, function() + return "fast", self.dimen end) - UIManager:scheduleIn(0.1, function() + UIManager:tickAfterNext(function() self.menu:onMenuHold(self.item) end) UIManager:scheduleIn(0.5, function() @@ -348,7 +351,7 @@ TouchMenu widget for hierarchical menus --]] local TouchMenu = FocusManager:new{ tab_item_table = {}, - -- for returnning in multi-level menus + -- for returning in multi-level menus item_table_stack = nil, item_table = nil, item_height = Size.item.height_large, @@ -363,6 +366,7 @@ local TouchMenu = FocusManager:new{ show_parent = nil, cur_tab = -1, close_callback = nil, + is_fresh = true, } function TouchMenu:init() @@ -440,7 +444,7 @@ function TouchMenu:init() self.page_info_text, self.page_info_right_chev } - --group for device info + -- group for device info self.time_info = TextWidget:new{ text = "", face = self.fface, @@ -508,7 +512,8 @@ function TouchMenu:init() end function TouchMenu:onCloseWidget() - UIManager:setDirty(nil, "partial", self.dimen) + -- NOTE: We pass a nil region to ensure a full-screen flash to avoid ghosting + UIManager:setDirty(nil, "flashui", nil) end function TouchMenu:_recalculatePageLayout() @@ -543,7 +548,7 @@ function TouchMenu:updateItems() self.item_group:clear() self.layout = {} table.insert(self.item_group, self.bar) - table.insert(self.layout, self.bar.icon_widgets) --for the focusmanager + table.insert(self.layout, self.bar.icon_widgets) -- for the focusmanager for c = 1, self.perpage do -- calculate index in item_table @@ -561,7 +566,7 @@ function TouchMenu:updateItems() } table.insert(self.item_group, item_tmp) if item_tmp:isEnabled() then - table.insert(self.layout, {[self.cur_tab] = item_tmp}) --for the focusmanager + table.insert(self.layout, {[self.cur_tab] = item_tmp}) -- for the focusmanager end if item.separator and c ~= self.perpage then -- insert split line @@ -590,13 +595,22 @@ function TouchMenu:updateItems() -- recalculate dimen based on new layout self.dimen.w = self.width self.dimen.h = self.item_group:getSize().h + self.bordersize*2 + self.padding*2 - self.selected = { x = self.cur_tab, y = 1 } --reset the position of the focusmanager + self.selected = { x = self.cur_tab, y = 1 } -- reset the position of the focusmanager + -- 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() local refresh_dimen = old_dimen and old_dimen:combine(self.dimen) or self.dimen - return "ui", refresh_dimen + local refresh_type = "ui" + if self.is_fresh then + refresh_type = "flashui" + -- Drop the region, too, to make it full-screen? May help when starting from a "small" menu. + --refresh_dimen = nil + self.is_fresh = false + end + return refresh_type, refresh_dimen end) end @@ -690,7 +704,7 @@ function TouchMenu:onMenuSelect(item) if callback then -- put stuff in scheduler so we can see -- the effect of inverted menu item - UIManager:scheduleIn(0.1, function() + UIManager:tickAfterNext(function() callback(self) if refresh then self:updateItems() @@ -726,7 +740,7 @@ function TouchMenu:onMenuHold(item) callback = item.hold_callback_func() end if callback then - UIManager:scheduleIn(0.1, function() + UIManager:tickAfterNext(function() if item.hold_may_update_menu then callback(function() self:updateItems() end) else diff --git a/frontend/ui/widget/virtualkeyboard.lua b/frontend/ui/widget/virtualkeyboard.lua index faabcdc93..6f80f8475 100644 --- a/frontend/ui/widget/virtualkeyboard.lua +++ b/frontend/ui/widget/virtualkeyboard.lua @@ -108,6 +108,8 @@ function VirtualKey:init() end function VirtualKey:update_keyboard() + -- NOTE: We could arguably use "fast" when inverted & "ui" when not, but it doesn't change much, + -- and doesn't help with the graphics quirks of repeated "fast" updates on some devices. UIManager:setDirty(self.keyboard, function() logger.dbg("update key region", self[1].dimen) return "fast", self[1].dimen @@ -129,7 +131,7 @@ function VirtualKey:onTapSelect() if self.callback then self.callback() end - UIManager:scheduleIn(0.1, function() self:invert(false) end) + UIManager:tickAfterNext(function() self:invert(false) end) else if self.callback then self.callback() @@ -145,7 +147,7 @@ function VirtualKey:onHoldSelect() if self.hold_callback then self.hold_callback() end - UIManager:scheduleIn(0.1, function() self:invert(false) end) + UIManager:tickAfterNext(function() self:invert(false) end) else if self.hold_callback then self.hold_callback() @@ -219,20 +221,23 @@ function VirtualKeyboard:onPressKey() return true end -function VirtualKeyboard:_refresh() - -- TODO: Ideally, ui onShow & partial onClose +function VirtualKeyboard:_refresh(want_flash) + local refresh_type = "partial" + if want_flash then + refresh_type = "flashui" + end UIManager:setDirty(self, function() - return "ui", self[1][1].dimen + return refresh_type, self[1][1].dimen end) end function VirtualKeyboard:onShow() - self:_refresh() + self:_refresh(true) return true end function VirtualKeyboard:onCloseWidget() - self:_refresh() + self:_refresh(false) return true end @@ -331,7 +336,7 @@ function VirtualKeyboard:setLayout(key) if self.utf8mode then self.umlautmode = false end end self:initLayout() - self:_refresh() + self:_refresh(true) end function VirtualKeyboard:addChar(key) diff --git a/platform/android/llapp_main.lua b/platform/android/llapp_main.lua index 57730f12b..cefd10e1a 100644 --- a/platform/android/llapp_main.lua +++ b/platform/android/llapp_main.lua @@ -3,6 +3,7 @@ A.dl.library_path = A.dl.library_path .. ":" .. A.dir .. "/libs" A.log_name = 'KOReader' local ffi = require("ffi") +local C = ffi.C ffi.cdef[[ char *getenv(const char *name); int putenv(const char *envvar); @@ -56,7 +57,7 @@ A.execute("chmod", "755", "./tar") A.execute("chmod", "755", "./zsync") -- set TESSDATA_PREFIX env var -ffi.C.putenv("TESSDATA_PREFIX=/sdcard/koreader/data") +C.putenv("TESSDATA_PREFIX=/sdcard/koreader/data") -- create fake command-line arguments arg = {"-d", file or "/sdcard"} diff --git a/platform/kindle/koreader.sh b/platform/kindle/koreader.sh index a3c228b07..a0c83d3a4 100755 --- a/platform/kindle/koreader.sh +++ b/platform/kindle/koreader.sh @@ -126,8 +126,8 @@ export TESSDATA_PREFIX="data" # export dict directory export STARDICT_DATA_DIR="data/dict" -# export external font directory -export EXT_FONT_DIR="/mnt/us/fonts" +# export external font directories (In order: stock, legacy custom, stock extra, font hack) +export EXT_FONT_DIR="/usr/java/lib/fonts;/mnt/us/fonts;/var/local/font/mnt;/mnt/us/linkfonts/fonts" # Only setup IPTables on evices where it makes sense to (FW 5.x & K4) if [ "${INIT_TYPE}" = "upstart" ] || [ "$(uname -r)" = "2.6.31-rt11-lab126" ]; then @@ -139,39 +139,6 @@ if [ "${INIT_TYPE}" = "upstart" ] || [ "$(uname -r)" = "2.6.31-rt11-lab126" ]; t iptables -A INPUT -i wlan0 -p udp --dport 8134 -j ACCEPT fi -# bind-mount system fonts -if ! grep ${KOREADER_DIR}/fonts/host /proc/mounts >/dev/null 2>&1; then - logmsg "Mounting system fonts . . ." - mount -o bind /usr/java/lib/fonts ${KOREADER_DIR}/fonts/host -fi - -# bind-mount altfonts -if [ -d /mnt/us/fonts ]; then - mkdir -p ${KOREADER_DIR}/fonts/altfonts - if ! grep ${KOREADER_DIR}/fonts/altfonts /proc/mounts >/dev/null 2>&1; then - logmsg "Mounting altfonts . . ." - mount -o bind /mnt/us/fonts ${KOREADER_DIR}/fonts/altfonts - fi -fi - -# bind-mount csp fonts -if [ -d /var/local/font/mnt ]; then - mkdir -p ${KOREADER_DIR}/fonts/cspfonts - if ! grep ${KOREADER_DIR}/fonts/cspfonts /proc/mounts >/dev/null 2>&1; then - logmsg "Mounting cspfonts . . ." - mount -o bind /var/local/font/mnt ${KOREADER_DIR}/fonts/cspfonts - fi -fi - -# bind-mount linkfonts -if [ -d /mnt/us/linkfonts/fonts ]; then - mkdir -p ${KOREADER_DIR}/fonts/linkfonts - if ! grep ${KOREADER_DIR}/fonts/linkfonts /proc/mounts >/dev/null 2>&1; then - logmsg "Mounting linkfonts . . ." - mount -o bind /mnt/us/linkfonts/fonts ${KOREADER_DIR}/fonts/linkfonts - fi -fi - # check if we need to disable the system passcode, because it messes with us in fun and interesting (and, more to the point, intractable) ways... # NOTE: The most egregious one being that it inhibits the outOfScreenSaver event on wakeup until the passcode is validated, which we can't do, since we capture all input... if [ -f "/var/local/system/userpasswdenabled" ]; then @@ -276,30 +243,6 @@ if pidof reader.lua >/dev/null 2>&1; then killall -TERM reader.lua fi -# unmount system fonts -if grep ${KOREADER_DIR}/fonts/host /proc/mounts >/dev/null 2>&1; then - logmsg "Unmounting system fonts . . ." - umount ${KOREADER_DIR}/fonts/host -fi - -# unmount altfonts -if grep ${KOREADER_DIR}/fonts/altfonts /proc/mounts >/dev/null 2>&1; then - logmsg "Unmounting altfonts . . ." - umount ${KOREADER_DIR}/fonts/altfonts -fi - -# unmount cspfonts -if grep ${KOREADER_DIR}/fonts/cspfonts /proc/mounts >/dev/null 2>&1; then - logmsg "Unmounting cspfonts . . ." - umount ${KOREADER_DIR}/fonts/cspfonts -fi - -# unmount linkfonts -if grep ${KOREADER_DIR}/fonts/linkfonts /proc/mounts >/dev/null 2>&1; then - logmsg "Unmounting linkfonts . . ." - umount ${KOREADER_DIR}/fonts/linkfonts -fi - # Resume volumd, if need be if [ "${VOLUMD_STOPPED}" = "yes" ]; then logmsg "Resuming volumd . . ." diff --git a/plugins/coverbrowser.koplugin/covermenu.lua b/plugins/coverbrowser.koplugin/covermenu.lua index 680f8b39a..3dd94988c 100644 --- a/plugins/coverbrowser.koplugin/covermenu.lua +++ b/plugins/coverbrowser.koplugin/covermenu.lua @@ -91,7 +91,7 @@ function CoverMenu:updateItems(select_number) local refresh_dimen = old_dimen and old_dimen:combine(self.dimen) or self.dimen - return "ui", refresh_dimen + return "partial", refresh_dimen end) -- As additionally done in FileChooser:updateItems() @@ -205,6 +205,8 @@ function CoverMenu:updateItems(select_number) -- Close original ButtonDialogTitle (it has not yet been painted -- on screen, so we won't see it) UIManager:close(self.file_dialog) + -- And clear the rendering stack to avoid inheriting its dirty/refresh queue + UIManager:clearRenderStack() -- Replace Book information callback to use directly our bookinfo orig_buttons[4][3].callback = function() @@ -320,6 +322,7 @@ function CoverMenu:onHistoryMenuHold(item) -- Close original ButtonDialog (it has not yet been painted -- on screen, so we won't see it) UIManager:close(self.histfile_dialog) + UIManager:clearRenderStack() -- Replace Book information callback to use directly our bookinfo orig_buttons[2][2].callback = function() @@ -471,6 +474,7 @@ function CoverMenu:tapPlus() -- Close original ButtonDialogTitle (it has not yet been painted -- on screen, so we won't see it) UIManager:close(self.file_dialog) + UIManager:clearRenderStack() -- Add a new button to original buttons set table.insert(orig_buttons, {}) -- separator diff --git a/plugins/goodreads.koplugin/doublekeyvaluepage.lua b/plugins/goodreads.koplugin/doublekeyvaluepage.lua index 488511a46..83b0c7905 100644 --- a/plugins/goodreads.koplugin/doublekeyvaluepage.lua +++ b/plugins/goodreads.koplugin/doublekeyvaluepage.lua @@ -174,9 +174,9 @@ function DoubleKeyValueItem:onTap() else self[1].invert = true UIManager:setDirty(self.show_parent, function() - return "ui", self[1].dimen + return "fast", self[1].dimen end) - UIManager:scheduleIn(0.1, function() + UIManager:tickAfterNext(function() self.callback() UIManager:close(info) self[1].invert = false diff --git a/plugins/newsdownloader.koplugin/main.lua b/plugins/newsdownloader.koplugin/main.lua index 063f8961e..8399fcc2c 100644 --- a/plugins/newsdownloader.koplugin/main.lua +++ b/plugins/newsdownloader.koplugin/main.lua @@ -10,6 +10,7 @@ local NetworkMgr = require("ui/network/manager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local dateparser = require("lib.dateparser") local ffi = require("ffi") +local C = ffi.C local logger = require("logger") local util = require("util") local _ = require("gettext") @@ -355,7 +356,7 @@ function NewsDownloader:removeNewsButKeepFeedConfig() local entry_path = news_download_dir_path .. "/" .. entry local entry_mode = lfs.attributes(entry_path, "mode") if entry_mode == "file" then - ffi.C.remove(entry_path) + C.remove(entry_path) elseif entry_mode == "directory" then FFIUtil.purgeDir(entry_path) end diff --git a/plugins/zsync.koplugin/main.lua b/plugins/zsync.koplugin/main.lua index 4e8a7af9e..736f0ad75 100644 --- a/plugins/zsync.koplugin/main.lua +++ b/plugins/zsync.koplugin/main.lua @@ -6,6 +6,7 @@ local DEBUG = require("dbg") local _ = require("gettext") local ffi = require("ffi") +local C = ffi.C ffi.cdef[[ int remove(const char *); int rmdir(const char *); @@ -131,13 +132,13 @@ local function clearDirectory(dir, rmdir) local path = dir.."/"..f local mode = lfs.attributes(path, "mode") if mode == "file" then - ffi.C.remove(path) + C.remove(path) elseif mode == "directory" and f ~= "." and f ~= ".." then clearDirectory(path, true) end end if rmdir then - ffi.C.rmdir(dir) + C.rmdir(dir) end end