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
pull/3993/head
NiLuJe 6 years ago committed by GitHub
parent bdbaa7dfdd
commit 5871132c25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -1 +1 @@
Subproject commit 1b7e584a27a460dc8375ad910cd2b92161776da9
Subproject commit 24150115ab25c5021176247a241caaed838718d4

@ -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

@ -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

@ -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

@ -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{

@ -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,

@ -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

@ -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()

@ -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,
}

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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)

@ -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

@ -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

@ -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

@ -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)

@ -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

@ -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)

@ -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)

@ -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

@ -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

@ -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()

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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)

@ -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"}

@ -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 . . ."

@ -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

@ -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

@ -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

@ -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

Loading…
Cancel
Save