Revamp "flash_ui" handling (#7118)

* Wherever possible, do an actual dumb invert on the Screen BB instead of repainting the widget, *then* inverting it (which is what the "invert" flag does).
* Instead of playing with nextTick/tickAfterNext delays, explicitly fence stuff with forceRePaint
* And, in the few cases where said Mk. 7 quirk kicks in, make the fences more marked by using a well-placed WAIT_FOR_UPDATE_COMPLETE

* Fix an issue in Button where show/hide & enable/disable where actually all toggles, which meant that duplicate calls or timing issues would do the wrong thing. (This broke dimming some icons, and mistakenly dropped the background from FM chevrons, for example).
* Speaking of, fix Button's hide/show to actually restore the background properly (there was a stupid typo in the variable name)
* Still in Button, fix the insanity of the double repaint on rounded buttons. Turns out it made sense, after all (and was related to said missing background, and bad interaction with invert & text with no background).
* KeyValuePage suffered from a similar issue with broken highlights (all black) because of missing backgrounds.
* In ConfigDialog, only instanciate IconButtons once (because every tab switch causes a full instantiation; and the initial display implies a full instanciation and an initial tab switch). Otherwise, both instances linger, and catch taps, and as such, double highlights.
* ConfigDialog: Restore the "don't repaint ReaderUI" when switching between similarly sized tabs (re #6131). I never could reproduce that on eInk, and I can't now on the emulator, so I'm assuming @poire-z fixed it during the swap to SVG icons.
* KeyValuePage: Only instanciate Buttons once (again, this is a widget that goes through a full init every page). Again, caused highlight/dimming issues because buttons were stacked.
* Menu: Ditto.
* TouchMenu: Now home of the gnarliest unhilight heuristics, because of the sheer amount of different things that can happen (and/or thanks to stuff not flagged covers_fullscreen properly ;p).

* Bump base
https://github.com/koreader/koreader-base/pull/1280
https://github.com/koreader/koreader-base/pull/1282
https://github.com/koreader/koreader-base/pull/1283
https://github.com/koreader/koreader-base/pull/1284

* Bump android-luajit-launcher
https://github.com/koreader/android-luajit-launcher/pull/284
https://github.com/koreader/android-luajit-launcher/pull/285
https://github.com/koreader/android-luajit-launcher/pull/286
https://github.com/koreader/android-luajit-launcher/pull/287
reviewable/pr7134/r1
NiLuJe 3 years ago committed by GitHub
parent 475bb648b0
commit 3060dc81af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1 +1 @@
Subproject commit 395936d25688e8bb19d67133ce1c24206f602f8e
Subproject commit dec3df4e5a96d7a039b8e6aeac4000c34c76a83a

@ -123,6 +123,7 @@ function SkimToWidget:init()
enabled = true,
width = self.button_width,
show_parent = self,
vsync = true,
callback = function()
self:goToPage(self.curr_page - 1)
end,
@ -135,6 +136,7 @@ function SkimToWidget:init()
enabled = true,
width = self.button_width,
show_parent = self,
vsync = true,
callback = function()
self:goToPage(self.curr_page - 10)
end,
@ -147,6 +149,7 @@ function SkimToWidget:init()
enabled = true,
width = self.button_width,
show_parent = self,
vsync = true,
callback = function()
self:goToPage(self.curr_page + 1)
end,
@ -159,6 +162,7 @@ function SkimToWidget:init()
enabled = true,
width = self.button_width,
show_parent = self,
vsync = true,
callback = function()
self:goToPage(self.curr_page + 10)
end,
@ -193,6 +197,7 @@ function SkimToWidget:init()
enabled = true,
width = self.button_width,
show_parent = self,
vsync = not G_reader_settings:isTrue("refresh_on_chapter_boundaries"),
callback = function()
local page = self.ui.toc:getNextChapter(self.curr_page)
if page and page >=1 and page <= self.page_count then
@ -212,6 +217,7 @@ function SkimToWidget:init()
enabled = true,
width = self.button_width,
show_parent = self,
vsync = not G_reader_settings:isTrue("refresh_on_chapter_boundaries"),
callback = function()
local page = self.ui.toc:getPreviousChapter(self.curr_page)
if page and page >=1 and page <= self.page_count then
@ -231,6 +237,7 @@ function SkimToWidget:init()
enabled = true,
width = self.button_width,
show_parent = self,
vsync = true,
callback = function()
self:goToByEvent("GotoNextBookmarkFromPage")
end,
@ -248,6 +255,7 @@ function SkimToWidget:init()
enabled = true,
width = self.button_width,
show_parent = self,
vsync = true,
callback = function()
self:goToByEvent("GotoPreviousBookmarkFromPage")
end,

@ -320,7 +320,6 @@ The first option ("auto") tries to automatically align reflowed text as it is in
item_text = tableOfNumbersToTableOfStrings(FONT_SCALE_FACTORS),
item_align_center = 1.0,
spacing = 15,
height = 60,
item_font_size = FONT_SCALE_DISPLAY_SIZE,
args = FONT_SCALE_FACTORS,
values = FONT_SCALE_FACTORS,

@ -7,7 +7,8 @@ local Event = require("ui/event")
local Geom = require("ui/geometry")
local dbg = require("dbg")
local logger = require("logger")
local util = require("ffi/util")
local ffiUtil = require("ffi/util")
local util = require("util")
local _ = require("gettext")
local Input = Device.input
local Screen = Device.screen
@ -528,7 +529,7 @@ dbg:guard(UIManager, 'schedule',
--- Schedules task in a certain amount of seconds (fractions allowed) from now.
function UIManager:scheduleIn(seconds, action, ...)
local when = { util.gettime() }
local when = { ffiUtil.gettime() }
local s = math.floor(seconds)
local usecs = (seconds - s) * MILLION
when[1] = when[1] + s
@ -770,9 +771,42 @@ function UIManager:ToggleNightMode(night_mode)
end
end
--- Get top widget.
--- Get top widget (name if possible, ref otherwise).
function UIManager:getTopWidget()
return ((self._window_stack[#self._window_stack] or {}).widget or {}).name
local top = self._window_stack[#self._window_stack]
if not top or not top.widget then
return nil
end
if top.widget.name then
return top.widget.name
end
return top.widget
end
--- Check if a widget is still in the window stack, or is a subwidget of a widget still in the window stack
function UIManager:isSubwidgetShown(widget, max_depth)
for i = #self._window_stack, 1, -1 do
local matched, depth = util.arrayReferences(self._window_stack[i].widget, widget, max_depth)
if matched then
return matched, depth, self._window_stack[i].widget
end
end
return false
end
--- Same, but only check window-level widgets (e.g., what's directly registered in the window stack), don't recurse
function UIManager:isWidgetShown(widget)
for i = #self._window_stack, 1, -1 do
if self._window_stack[i].widget == widget then
return true
end
end
return false
end
-- Returns the region of the previous refresh
function UIManager:getPreviousRefreshRegion()
return self._last_refresh_region
end
--- Signals to quit.
@ -822,7 +856,7 @@ function UIManager:discardEvents(set_or_seconds)
else -- we expect a number
usecs = set_or_seconds * MILLION
end
local now = { util.gettime() }
local now = { ffiUtil.gettime() }
local now_us = now[1] * MILLION + now[2]
self._discard_events_till = now_us + usecs
end
@ -833,7 +867,7 @@ function UIManager:sendEvent(event)
-- Ensure discardEvents
if self._discard_events_till then
local now = { util.gettime() }
local now = { ffiUtil.gettime() }
local now_us = now[1] * MILLION + now[2]
if now_us < self._discard_events_till then
return
@ -897,7 +931,7 @@ function UIManager:broadcastEvent(event)
end
function UIManager:_checkTasks()
local now = { util.gettime() }
local now = { ffiUtil.gettime() }
local now_us = now[1] * MILLION + now[2]
local wait_until = nil
@ -1193,6 +1227,8 @@ function UIManager:_repaint()
refresh.region.w = ALIGN_UP(refresh.region.w + (x_fixup * 2), 8)
refresh.region.h = ALIGN_UP(refresh.region.h + (y_fixup * 2), 8)
end
-- Remember the refresh region
self._last_refresh_region = refresh.region
Screen[refresh_methods[refresh.mode]](Screen,
refresh.region.x, refresh.region.y,
refresh.region.w, refresh.region.h,
@ -1212,6 +1248,10 @@ function UIManager:forceRePaint()
self:_repaint()
end
function UIManager:waitForVSync()
Screen:refreshWaitForLast()
end
-- Used to repaint a specific sub-widget that isn't on the _window_stack itself
-- Useful to avoid repainting a complex widget when we just want to invert an icon, for instance.
-- No safety checks on x & y *by design*. I want this to blow up if used wrong.
@ -1222,6 +1262,14 @@ function UIManager:widgetRepaint(widget, x, y)
widget:paintTo(Screen.bb, x, y)
end
-- Same idea, but does a simple invertRect, without actually repainting anything
function UIManager:widgetInvert(widget, x, y, w, h)
if not widget then return end
logger.dbg("Explicit widgetInvert:", widget.name or widget.id or tostring(widget), "@ (", x, ",", y, ")")
Screen.bb:invertRect(x, y, w or widget.dimen.w, h or widget.dimen.h)
end
function UIManager:setInputTimeout(timeout)
self.INPUT_TIMEOUT = timeout or 200*1000
end

@ -40,6 +40,7 @@ local Button = InputContainer:new{
preselect = false,
callback = nil,
enabled = true,
hidden = false,
allow_hold_when_disabled = false,
margin = 0,
bordersize = Size.border.button,
@ -53,6 +54,7 @@ local Button = InputContainer:new{
text_font_face = "cfont",
text_font_size = 20,
text_font_bold = true,
vsync = nil, -- when "flash_ui" is enabled, allow bundling the highlight with the callback, and fence that batch away from the unhighlight. Avoid delays when callback requires a "partial" on Kobo Mk. 7, c.f., ffi/framebuffer_mxcfb for more details.
}
function Button:init()
@ -164,28 +166,26 @@ function Button:onUnfocus()
end
function Button:enable()
self.enabled = true
if self.text then
if self.enabled then
if not self.enabled then
if self.text then
self.label_widget.fgcolor = Blitbuffer.COLOR_BLACK
self.enabled = true
else
self.label_widget.fgcolor = Blitbuffer.COLOR_DARK_GRAY
self.label_widget.dim = false
self.enabled = true
end
else
self.label_widget.dim = not self.enabled
end
end
function Button:disable()
self.enabled = false
if self.text then
if self.enabled then
self.label_widget.fgcolor = Blitbuffer.COLOR_BLACK
else
if self.enabled then
if self.text then
self.label_widget.fgcolor = Blitbuffer.COLOR_DARK_GRAY
self.enabled = false
else
self.label_widget.dim = true
self.enabled = false
end
else
self.label_widget.dim = not self.enabled
end
end
@ -198,17 +198,19 @@ function Button:enableDisable(enable)
end
function Button:hide()
if self.icon then
self.frame.orig_background = self[1].background
if self.icon and not self.hidden then
self.frame.orig_background = self.frame.background
self.frame.background = nil
self.label_widget.hide = true
self.hidden = true
end
end
function Button:show()
if self.icon then
if self.icon and self.hidden then
self.label_widget.hide = false
self.frame.background = self[1].old_background
self.frame.background = self.frame.orig_background
self.hidden = false
end
end
@ -225,42 +227,74 @@ function Button:onTapSelectButton()
if G_reader_settings:isFalse("flash_ui") then
self.callback()
else
-- For most of our buttons, we can't avoid that initial repaint...
self[1].invert = true
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
-- NOTE: This completely insane double repaint is needed to avoid cosmetic issues with FrameContainer's rounded corners on Text buttons...
-- On the upside, we now actually get to *see* those rounded corners (as the highlight), where it was a simple square before.
-- c.f., #4554 & #4541
-- NOTE: self[1] -> self.frame, if you're confused about what this does vs. onFocus/onUnfocus ;).
if self.text then
-- We only want the button's *highlight* to have rounded corners (otherwise they're redundant, same color as the bg).
-- The nil check is to discriminate the default from callers that explicitly request a specific radius.
if self[1].radius == nil then
self[1].radius = Size.radius.button
-- And here, it's easier to just invert the bg/fg colors ourselves,
-- so as to preserve the rounded corners in one step.
self[1].background = self[1].background:invert()
self.label_widget.fgcolor = self.label_widget.fgcolor:invert()
-- We do *NOT* set the invert flag, because it just adds an invertRect step at the end of the paintTo process,
-- and we've already taken care of inversion in a way that won't mangle the rounded corners.
else
self[1].invert = true
end
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
-- But do make sure the invert flag is set in both cases, mainly for the early return check below
self[1].invert = true
else
self[1].invert = true
UIManager:widgetInvert(self[1], self[1].dimen.x, self[1].dimen.y)
end
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
-- And we also often have to delay the callback to both see the flash and/or avoid tearing artefacts w/ fast refreshes...
UIManager:tickAfterNext(function()
self.callback()
if not self[1] or not self[1].invert or not self[1].dimen then
-- widget no more there (destroyed, re-init'ed by setText(), or not inverted: nothing to invert back
return
end
self[1].invert = false
-- Since we kill the corners, we only need a single repaint.
-- Force the repaint *now*, so we don't have to delay the callback to see the highlight...
if not self.vsync then
-- NOTE: Allow bundling the highlight with the callback when we request vsync, to prevent further delays
UIManager:forceRePaint() -- Ensures we have a chance to see the highlight
end
self.callback()
UIManager:forceRePaint() -- Ensures whatever the callback wanted to paint will be shown *now*...
if self.vsync then
-- NOTE: This is mainly useful when the callback caused a REAGL update that we do not explicitly fence already,
-- (i.e., Kobo Mk. 7).
UIManager:waitForVSync() -- ...and that the EPDC will not wait to coalesce it with the *next* update,
-- because that would have a chance to noticeably delay it until the unhighlight.
end
if not self[1] or not self[1].invert or not self[1].dimen then
-- If the widget no longer exists (destroyed, re-init'ed by setText(), or not inverted: nothing to invert back
return true
end
-- If the callback closed our parent (which ought to have been the top level widget), abort early
if UIManager:getTopWidget() ~= self.show_parent then
return true
end
self[1].invert = false
if self.text then
if self[1].radius == Size.radius.button then
self[1].radius = nil
self[1].background = self[1].background:invert()
self.label_widget.fgcolor = self.label_widget.fgcolor:invert()
end
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
else
UIManager:widgetInvert(self[1], self[1].dimen.x, self[1].dimen.y)
end
-- If the button was disabled, switch to UI to make sure the gray comes through unharmed ;).
UIManager:setDirty(nil, function()
return self.enabled and "fast" or "ui", self[1].dimen
end)
--UIManager:forceRePaint() -- Ensures the unhighlight happens now, instead of potentially waiting and having it batched with something else.
end
elseif self.tap_input then
self:onInput(self.tap_input)

@ -104,14 +104,19 @@ function CheckButton:onTapCheckButton()
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
UIManager:tickAfterNext(function()
self.callback()
self[1].invert = false
UIManager:widgetRepaint(self[1], self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
-- Force the repaint *now*, so we don't have to delay the callback to see the invert...
UIManager:forceRePaint()
self.callback()
--UIManager:forceRePaint() -- Unnecessary, the check/uncheck process involves too many repaints already
--UIManager:waitForVSync()
self[1].invert = false
UIManager:widgetRepaint(self[1], self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
--UIManager:forceRePaint()
end
elseif self.tap_input then
self:onInput(self.tap_input)

@ -623,7 +623,7 @@ function ConfigOption:init()
-- Add it to our CenterContainer
table.insert(option_items_container, option_items_group)
--add line of item to the second last place in the focusmanager so the menubar stay at the bottom
table.insert(self.config.layout, #self.config.layout,self:_itemGroupToLayoutLine(option_items_group))
table.insert(self.config.layout, #self.config.layout, self:_itemGroupToLayoutLine(option_items_group))
table.insert(horizontal_group, option_items_container)
table.insert(vertical_group, horizontal_group)
end -- if show ~= false
@ -672,41 +672,44 @@ local MenuBar = FrameContainer:new{
padding = 0,
background = Blitbuffer.COLOR_WHITE,
}
function MenuBar:init()
local icon_sep_width = Size.padding.button
local line_thickness = Size.line.thick
local config_options = self.config_dialog.config_options
local menu_items = {}
local icon_width = Screen:scaleBySize(DGENERIC_ICON_SIZE)
local icon_height = icon_width
local icons_width = (icon_width + 2*icon_sep_width) * #config_options
local bar_height = icon_height + 2*Size.padding.default
for c = 1, #config_options do
local menu_icon = IconButton:new{
show_parent = self.config_dialog,
icon = config_options[c].icon,
width = icon_width,
height = icon_height,
callback = function()
self.config_dialog:handleEvent(Event:new("ShowConfigPanel", c))
end,
}
menu_items[c] = menu_icon
if not self.menu_items then
self.menu_items = {}
for c = 1, #config_options do
local menu_icon = IconButton:new{
show_parent = self.config_dialog,
icon = config_options[c].icon,
width = icon_width,
height = icon_height,
callback = function()
self.config_dialog:handleEvent(Event:new("ShowConfigPanel", c))
end,
}
self.menu_items[c] = menu_icon
end
end
table.insert(self.config_dialog.layout,menu_items) --for the focusmanager
table.insert(self.config_dialog.layout, self.menu_items) -- for the focusmanager
local available_width = Screen:getWidth() - icons_width
-- local padding = math.floor(available_width / #menu_items / 2) -- all for padding
-- local padding = math.floor(available_width / #menu_items / 2 / 2) -- half padding, half spacing ?
local padding = math.min(math.floor(available_width / #menu_items / 2), Screen:scaleBySize(20)) -- as in TouchMenuBar
-- local padding = math.floor(available_width / #self.menu_items / 2) -- all for padding
-- local padding = math.floor(available_width / #self.menu_items / 2 / 2) -- half padding, half spacing ?
local padding = math.min(math.floor(available_width / #self.menu_items / 2), Screen:scaleBySize(20)) -- as in TouchMenuBar
if padding > 0 then
for c = 1, #menu_items do
menu_items[c].padding_left = padding
menu_items[c].padding_right = padding
menu_items[c]:update()
for c = 1, #self.menu_items do
self.menu_items[c].padding_left = padding
self.menu_items[c].padding_right = padding
self.menu_items[c]:update()
end
available_width = available_width - 2*padding*#menu_items
available_width = available_width - 2*padding*#self.menu_items
end
local spacing_width = math.ceil(available_width / (#menu_items+1))
local spacing_width = math.ceil(available_width / (#self.menu_items+1))
local icon_sep_black = LineWidget:new{
background = Blitbuffer.COLOR_BLACK,
@ -740,17 +743,17 @@ function MenuBar:init()
local menu_bar = HorizontalGroup:new{}
local line_bar = HorizontalGroup:new{}
for c = 1, #menu_items do
for c = 1, #self.menu_items do
table.insert(menu_bar, spacing)
table.insert(line_bar, spacing_line)
if c == self.panel_index then
table.insert(menu_bar, icon_sep_black)
table.insert(line_bar, sep_line)
table.insert(menu_bar, menu_items[c])
table.insert(menu_bar, self.menu_items[c])
table.insert(line_bar, LineWidget:new{
background = Blitbuffer.COLOR_WHITE,
dimen = Geom:new{
w = menu_items[c]:getSize().w,
w = self.menu_items[c]:getSize().w,
h = line_thickness,
}
})
@ -759,10 +762,10 @@ function MenuBar:init()
else
table.insert(menu_bar, icon_sep_white)
table.insert(line_bar, sep_line)
table.insert(menu_bar, menu_items[c])
table.insert(menu_bar, self.menu_items[c])
table.insert(line_bar, LineWidget:new{
dimen = Geom:new{
w = menu_items[c]:getSize().w,
w = self.menu_items[c]:getSize().w,
h = line_thickness,
}
})
@ -779,7 +782,6 @@ function MenuBar:init()
menu_bar,
}
table.insert(self, vertical_menu)
end
--[[
@ -853,14 +855,22 @@ end
function ConfigDialog:update()
self.layout = {}
self.config_menubar = MenuBar:new{
config_dialog = self,
panel_index = self.panel_index,
}
if self.config_menubar then
self.config_menubar:clear()
self.config_menubar.panel_index = self.panel_index
self.config_menubar:init()
else
self.config_menubar = MenuBar:new{
config_dialog = self,
panel_index = self.panel_index,
}
end
self.config_panel = ConfigPanel:new{
index = self.panel_index,
config_dialog = self,
}
self.dialog_frame = FrameContainer:new{
background = Blitbuffer.COLOR_WHITE,
padding_bottom = 0, -- ensured by MenuBar
@ -896,9 +906,7 @@ function ConfigDialog:onShowConfigPanel(index)
-- NOTE: And we also only need to repaint what's behind us when switching to a smaller dialog...
-- This is trickier than in touchmenu, because dimen appear to fluctuate before/after painting...
-- So we've settled instead for the amount of lines in the panel, as line-height is constant.
-- NOTE: line/widget-height is actually not constant (e.g. the font size widget on the emulator),
-- so do it only when the new nb of widgets is strictly greater than the previous one.
local keep_bg = old_layout_h and #self.layout > old_layout_h
local keep_bg = old_layout_h and #self.layout >= old_layout_h
UIManager:setDirty((self.is_fresh or keep_bg) and self or "all", function()
local refresh_dimen =
old_dimen and old_dimen:combine(self.dialog_frame.dimen)

@ -97,19 +97,26 @@ function IconButton:onTapIconButton()
else
self.image.invert = true
-- For ConfigDialog icons, we can't avoid that initial repaint...
UIManager:widgetRepaint(self.image, self.dimen.x + self.padding_left, self.dimen.y + self.padding_top)
UIManager:widgetInvert(self.image, self.dimen.x + self.padding_left, self.dimen.y + self.padding_top)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
-- And, we usually need to delay the callback for the same reasons as Button...
UIManager:tickAfterNext(function()
self.callback()
-- Force the repaint *now*, so we don't have to delay the callback to see the invert...
UIManager:forceRePaint()
self.callback()
UIManager:forceRePaint()
--UIManager:waitForVSync()
-- If the callback closed our parent (which may not always be the top-level widget, or even *a* window-level widget; e.g., the Home/+ buttons in the FM), we're done
if UIManager:getTopWidget() == self.show_parent or UIManager:isSubwidgetShown(self.show_parent) then
self.image.invert = false
UIManager:widgetRepaint(self.image, self.dimen.x + self.padding_left, self.dimen.y + self.padding_top)
UIManager:widgetInvert(self.image, self.dimen.x + self.padding_left, self.dimen.y + self.padding_top)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
end)
--UIManager:forceRePaint()
end
end
return true
end

@ -182,7 +182,7 @@ function ImageWidget:_loadfile()
-- Now, if that was *also* one of our icons, and it has an alpha channel,
-- compose it against a background-colored BB now, and cache *that*.
-- This helps us avoid repeating alpha-blending steps down the line,
-- and also ensures icon highlights/unhilights behave sensibly.
-- and also ensures icon highlights/unhighlights behave sensibly.
if self.is_icon then
local bbtype = self._bb:getType()
if bbtype == Blitbuffer.TYPE_BB8A or bbtype == Blitbuffer.TYPE_BBRGB32 then

@ -196,6 +196,10 @@ function InfoMessage:init()
end
function InfoMessage:onCloseWidget()
if self._delayed_show_action then
UIManager:unschedule(self._delayed_show_action)
self._delayed_show_action = nil
end
if self.invisible then
-- Still invisible, no setDirty needed
return true
@ -253,6 +257,7 @@ end
function InfoMessage:dismiss()
if self._delayed_show_action then
UIManager:unschedule(self._delayed_show_action)
self._delayed_show_action = nil
end
self.dismiss_callback()
UIManager:close(self)

@ -252,6 +252,7 @@ function KeyValueItem:init()
self[1] = FrameContainer:new{
padding = frame_padding,
bordersize = 0,
background = Blitbuffer.COLOR_WHITE,
HorizontalGroup:new{
dimen = self.dimen:copy(),
LeftContainer:new{
@ -281,17 +282,33 @@ function KeyValueItem:onTap()
self.callback()
else
self[1].invert = true
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:widgetInvert(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
UIManager:tickAfterNext(function()
self.callback()
-- Force the repaint *now*, so we don't have to delay the callback to see the invert...
UIManager:forceRePaint()
self.callback()
UIManager:forceRePaint()
--UIManager:waitForVSync()
-- Has to be scheduled *after* the dict delays for the lookup history pages...
UIManager:scheduleIn(0.75, function()
self[1].invert = false
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
-- Skip the repaint if we've ended up below something, which is likely.
if UIManager:getTopWidget() ~= self.show_parent then
-- It's generally tricky to get accurate dimensions out of whatever was painted above us,
-- so cheat by comparing against the previous refresh region...
if self[1].dimen:intersectWith(UIManager:getPreviousRefreshRegion()) then
return true
end
end
UIManager:widgetInvert(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "ui", self[1].dimen
end)
--UIManager:forceRePaint()
end)
end
end
@ -351,7 +368,7 @@ function KeyValuePage:init()
-- return button
--- @todo: alternative icon if BD.mirroredUILayout()
self.page_return_arrow = Button:new{
self.page_return_arrow = self.page_return_arrow or Button:new{
icon = "back.top",
callback = function() self:onReturn() end,
bordersize = 0,
@ -366,25 +383,25 @@ function KeyValuePage:init()
chevron_left, chevron_right = chevron_right, chevron_left
chevron_first, chevron_last = chevron_last, chevron_first
end
self.page_info_left_chev = Button:new{
self.page_info_left_chev = self.page_info_left_chev or Button:new{
icon = chevron_left,
callback = function() self:prevPage() end,
bordersize = 0,
show_parent = self,
}
self.page_info_right_chev = Button:new{
self.page_info_right_chev = self.page_info_right_chev or Button:new{
icon = chevron_right,
callback = function() self:nextPage() end,
bordersize = 0,
show_parent = self,
}
self.page_info_first_chev = Button:new{
self.page_info_first_chev = self.page_info_first_chev or Button:new{
icon = chevron_first,
callback = function() self:goToPage(1) end,
bordersize = 0,
show_parent = self,
}
self.page_info_last_chev = Button:new{
self.page_info_last_chev = self.page_info_last_chev or Button:new{
icon = chevron_last,
callback = function() self:goToPage(self.pages) end,
bordersize = 0,
@ -408,7 +425,7 @@ function KeyValuePage:init()
self.page_info_first_chev:hide()
self.page_info_last_chev:hide()
self.page_info_text = Button:new{
self.page_info_text = self.page_info_text or Button:new{
text = "",
hold_input = {
title = _("Enter page number"),

@ -472,22 +472,30 @@ function MenuItem:onTapSelect(arg, ges)
coroutine.resume(co)
else
self[1].invert = true
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:widgetInvert(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
UIManager:tickAfterNext(function()
logger.dbg("creating coroutine for menu select")
local co = coroutine.create(function()
self.menu:onMenuSelect(self.table, pos)
end)
coroutine.resume(co)
self[1].invert = false
--UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(self.show_parent, function()
return "ui", self[1].dimen
end)
-- Force the repaint *now*, so we don't have to delay the callback to see the invert...
UIManager:forceRePaint()
logger.dbg("creating coroutine for menu select")
local co = coroutine.create(function()
self.menu:onMenuSelect(self.table, pos)
end)
coroutine.resume(co)
UIManager:forceRePaint()
--UIManager:waitForVSync()
self[1].invert = false
-- We assume a tap anywhere updates the full menu, so, forgo this, much like in TouchMenu
--[[
UIManager:widgetInvert(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "ui", self[1].dimen
end)
--]]
--UIManager:forceRePaint()
end
return true
end
@ -498,18 +506,23 @@ function MenuItem:onHoldSelect(arg, ges)
self.menu:onMenuHold(self.table, pos)
else
self[1].invert = true
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:widgetInvert(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self[1].dimen
end)
UIManager:tickAfterNext(function()
self.menu:onMenuHold(self.table, pos)
self[1].invert = false
--UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(self.show_parent, function()
return "ui", self[1].dimen
end)
-- Force the repaint *now*, so we don't have to delay the callback to see the invert...
UIManager:forceRePaint()
self.menu:onMenuHold(self.table, pos)
UIManager:forceRePaint()
--UIManager:waitForVSync()
self[1].invert = false
UIManager:widgetInvert(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "ui", self[1].dimen
end)
--UIManager:forceRePaint()
end
return true
end
@ -666,25 +679,25 @@ function Menu:init()
chevron_left, chevron_right = chevron_right, chevron_left
chevron_first, chevron_last = chevron_last, chevron_first
end
self.page_info_left_chev = Button:new{
self.page_info_left_chev = self.page_info_left_chev or Button:new{
icon = chevron_left,
callback = function() self:onPrevPage() end,
bordersize = 0,
show_parent = self.show_parent,
}
self.page_info_right_chev = Button:new{
self.page_info_right_chev = self.page_info_right_chev or Button:new{
icon = chevron_right,
callback = function() self:onNextPage() end,
bordersize = 0,
show_parent = self.show_parent,
}
self.page_info_first_chev = Button:new{
self.page_info_first_chev = self.page_info_first_chev or Button:new{
icon = chevron_first,
callback = function() self:onFirstPage() end,
bordersize = 0,
show_parent = self.show_parent,
}
self.page_info_last_chev = Button:new{
self.page_info_last_chev = self.page_info_last_chev or Button:new{
icon = chevron_last,
callback = function() self:onLastPage() end,
bordersize = 0,
@ -752,10 +765,10 @@ function Menu:init()
end
end
self.page_info_text = Button:new{
self.page_info_text = self.page_info_text or Button:new{
text = "",
hold_input = {
title = title_goto ,
title = title_goto,
type = type_goto,
hint_func = hint_func,
buttons = buttons,
@ -776,7 +789,7 @@ function Menu:init()
}
-- return button
self.page_return_arrow = Button:new{
self.page_return_arrow = self.page_return_arrow or Button:new{
icon = "back.top",
callback = function()
if self.onReturn then self:onReturn() end

@ -119,14 +119,19 @@ function RadioButton:onTapCheckButton()
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
UIManager:tickAfterNext(function()
self.callback()
self.frame.invert = false
UIManager:widgetRepaint(self.frame, self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
-- Force the repaint *now*, so we don't have to delay the callback to see the invert...
UIManager:forceRePaint()
self.callback()
--UIManager:forceRePaint() -- Unnecessary, the check/uncheck process involves too many repaints already
--UIManager:waitForVSync()
self.frame.invert = false
UIManager:widgetRepaint(self.frame, self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
--UIManager:forceRePaint()
end
elseif self.tap_input then
self:onInput(self.tap_input)
@ -151,7 +156,8 @@ function RadioButton:check(callback)
self._radio_button = self._checked_widget
self.checked = true
self:update()
UIManager:setDirty(self.parent, function()
UIManager:widgetRepaint(self.frame, self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
end
@ -160,7 +166,8 @@ function RadioButton:unCheck()
self._radio_button = self._unchecked_widget
self.checked = false
self:update()
UIManager:setDirty(self.parent, function()
UIManager:widgetRepaint(self.frame, self.dimen.x, self.dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
end

@ -158,26 +158,51 @@ function TouchMenuItem:onTapSelect(arg, ges)
if G_reader_settings:isFalse("flash_ui") then
self.menu:onMenuSelect(self.item)
else
-- The item frame's width stops at the text width, but we want it to match the menu's length instead
local highlight_dimen = self.item_frame.dimen
highlight_dimen.w = self.item_frame.width
self.item_frame.invert = true
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:widgetInvert(self.item_frame, highlight_dimen.x, highlight_dimen.y, highlight_dimen.w)
UIManager:setDirty(nil, function()
return "fast", self.dimen
return "fast", highlight_dimen
end)
-- yield to main UI loop to invert item
UIManager:tickAfterNext(function()
self.menu:onMenuSelect(self.item)
self.item_frame.invert = false
-- NOTE: We can *usually* optimize that repaint away, as most entries in the menu will at least trigger a menu repaint ;).
-- But when stuff doesn't repaint the menu and keeps it open, we need to do it.
-- Since it's an *un*highlight containing text, we make it "ui" and not "fast", both so it won't mangle text,
-- and because "fast" can have some weird side-effects on some devices in this specific instance...
if self.item.hold_keep_menu_open or self.item.keep_menu_open then
--UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
-- Force the repaint *now*, so we don't have to delay the callback to see the invert...
UIManager:forceRePaint()
self.menu:onMenuSelect(self.item)
UIManager:forceRePaint()
--UIManager:waitForVSync()
self.item_frame.invert = false
-- NOTE: We can *usually* optimize that repaint away, as most entries in the menu will at least trigger a menu repaint ;).
-- But when stuff doesn't repaint the menu and keeps it open, we need to do it.
-- Since it's an *un*highlight containing text, we make it "ui" and not "fast", both so it won't mangle text,
-- and because "fast" can have some weird side-effects on some devices in this specific instance...
if self.item.hold_keep_menu_open or self.item.keep_menu_open then
local top_widget = UIManager:getTopWidget()
-- If the callback opened a full-screen widget, we're done
if top_widget.covers_fullscreen then
return true
end
-- If we're still on top, or if a modal was opened outside of our highlight region, we can unhighlight safely
if top_widget == self.menu or highlight_dimen:notIntersectWith(UIManager:getPreviousRefreshRegion()) then
UIManager:widgetInvert(self.item_frame, highlight_dimen.x, highlight_dimen.y, highlight_dimen.w)
UIManager:setDirty(nil, function()
return "ui", highlight_dimen
end)
else
-- That leaves modals that might have been displayed on top of the highlighted menu entry, in which case,
-- we can't take any shortcuts, as it would invert/paint *over* the popop.
-- Instead, fence the callback to avoid races, and repaint the *full* widget stack properly.
UIManager:waitForVSync()
UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen
return "ui", highlight_dimen
end)
end
end)
end
--UIManager:forceRePaint()
end
return true
end
@ -192,22 +217,28 @@ function TouchMenuItem:onHoldSelect(arg, ges)
if G_reader_settings:isFalse("flash_ui") then
self.menu:onMenuHold(self.item)
else
-- The item frame's width stops at the text width, but we want it to match the menu's length instead
local highlight_dimen = self.item_frame.dimen
highlight_dimen.w = self.item_frame.width
self.item_frame.invert = true
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:widgetInvert(self.item_frame, highlight_dimen.x, highlight_dimen.y, highlight_dimen.w)
UIManager:setDirty(nil, function()
return "fast", self.dimen
return "fast", highlight_dimen
end)
UIManager:tickAfterNext(function()
self.menu:onMenuHold(self.item)
end)
UIManager:scheduleIn(0.5, function()
self.item_frame.invert = false
-- NOTE: For some reason, this is finicky (I end up with a solid black bar, i.e., text gets inverted, but not the bg?!)
--UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen
end)
-- Force the repaint *now*, so we don't have to delay the callback to see the invert...
UIManager:forceRePaint()
self.menu:onMenuHold(self.item)
UIManager:forceRePaint()
--UIManager:waitForVSync()
self.item_frame.invert = false
UIManager:widgetInvert(self.item_frame, highlight_dimen.x, highlight_dimen.y, highlight_dimen.w)
UIManager:setDirty(nil, function()
return "ui", highlight_dimen
end)
--UIManager:forceRePaint()
end
return true
end
@ -452,7 +483,7 @@ function TouchMenu:init()
self.key_events.Press = { {"Press"}, doc = "chose selected item" }
local icons = {}
for _,v in ipairs(self.tab_item_table) do
for _, v in ipairs(self.tab_item_table) do
table.insert(icons, v.icon)
end
self.bar = TouchMenuBar:new{
@ -798,20 +829,16 @@ function TouchMenu:onMenuSelect(item)
callback = item.callback_func()
end
if callback then
-- put stuff in scheduler so we can see
-- the effect of inverted menu item
UIManager:tickAfterNext(function()
-- Provide callback with us, so it can call our
-- closemenu() or updateItems() when it sees fit
-- (if not providing checked or checked_fund, caller
-- must set keep_menu_open=true if that is wished)
callback(self)
if refresh then
self:updateItems()
elseif not item.keep_menu_open then
self:closeMenu()
end
end)
-- Provide callback with us, so it can call our
-- closemenu() or updateItems() when it sees fit
-- (if not providing checked or checked_fund, caller
-- must set keep_menu_open=true if that is wished)
callback(self)
if refresh then
self:updateItems()
elseif not item.keep_menu_open then
self:closeMenu()
end
end
else
table.insert(self.item_table_stack, self.item_table)
@ -842,17 +869,15 @@ function TouchMenu:onMenuHold(item)
callback = item.hold_callback_func()
end
if callback then
UIManager:tickAfterNext(function()
-- With hold, the default is to keep menu open, as we're
-- most often showing a ConfirmBox that can be cancelled
-- (provide hold_keep_menu_open=false to override)
if item.hold_keep_menu_open == false then
self:closeMenu()
end
-- Provide callback with us, so it can call our
-- closemenu() or updateItems() when it sees fit
callback(self)
end)
-- With hold, the default is to keep menu open, as we're
-- most often showing a ConfirmBox that can be cancelled
-- (provide hold_keep_menu_open=false to override)
if item.hold_keep_menu_open == false then
self:closeMenu()
end
-- Provide callback with us, so it can call our
-- closemenu() or updateItems() when it sees fit
callback(self)
end
elseif item.help_text or type(item.help_text_func) == "function" then
local help_text = item.help_text

@ -384,6 +384,33 @@ function util.arrayContains(t, v, cb)
return false
end
--- Test whether array t contains a reference to array n (at any depth at or below m)
---- @param t Lua table (array only)
---- @param n Lua table (array only)
---- @int m Max nesting level
function util.arrayReferences(t, n, m, l)
if not m then m = 15 end
if not l then l = 0 end
if l > m then
return false
end
if type(t) == "table" then
if t == n then
return true, l
end
for _, v in ipairs(t) do
local matched, depth = util.arrayReferences(v, n, m, l + 1)
if matched then
return matched, depth
end
end
end
return false
end
-- Merge t2 into t1, overwriting existing elements if they already exist
-- Probably not safe with nested tables (c.f., https://stackoverflow.com/q/1283388)
---- @param t1 Lua table

@ -1 +1 @@
Subproject commit af9b9d30c10451a3be423d8fb55f05945a3133f6
Subproject commit 1884a151f66bb465e684b4f1c7ae02243eb9bc44

@ -72,6 +72,7 @@ function ReaderProgress:init()
}
}
end
self.covers_fullscreen = true -- hint for UIManager:_repaint()
self[1] = FrameContainer:new{
width = self.width,
height = self.height,

Loading…
Cancel
Save