You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koreader/frontend/ui/widget/checkbutton.lua

240 lines
6.8 KiB
Lua

--[[--
Button widget that shows a checkmark (``) when checked and an empty box (``)
when unchecked.
Example:
local CheckButton = require("ui/widget/CheckButton")
local parent_widget = OverlapGroup:new{}
table.insert(parent_widget, CheckButton:new{
text = _("Show password"),
callback = function() end,
})
UIManager:show(parent_widget)
]]
local Blitbuffer = require("ffi/blitbuffer")
local CheckMark = require("ui/widget/checkmark")
local Font = require("ui/font")
local FrameContainer = require("ui/widget/container/framecontainer")
local GestureRange = require("ui/gesturerange")
local HorizontalGroup = require("ui/widget/horizontalgroup")
local InputContainer = require("ui/widget/container/inputcontainer")
local RadioMark = require("ui/widget/radiomark")
local TextBoxWidget = require("ui/widget/textboxwidget")
local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2 years ago
local CheckButton = InputContainer:extend{
callback = nil,
hold_callback = nil,
checkable = true, -- empty space when false
checked = false,
enabled = true,
radio = false, -- radio mark when true
face = Font:getFace("smallinfofont"),
background = Blitbuffer.COLOR_WHITE,
text = nil,
parent = nil, -- parent widget, must be set by the caller
width = nil, -- default value: parent widget's input widget width
-- If the parent widget has no input widget, the width must be set by the caller.
}
function CheckButton:init()
self:initCheckButton(self.checked)
end
function CheckButton:initCheckButton(checked)
self.checked = checked
if self.radio then
self._checkmark = RadioMark:new{
checkable = self.checkable,
checked = self.checked,
enabled = self.enabled,
face = self.face,
parent = self.parent or self,
show_parent = self.show_parent or self,
}
else
self._checkmark = CheckMark:new{
checkable = self.checkable,
checked = self.checked,
enabled = self.enabled,
face = self.face,
parent = self.parent or self,
show_parent = self.show_parent or self,
}
end
self._textwidget = TextBoxWidget:new{
text = self.text,
face = self.face,
width = (self.width or self.parent._input_widget.width) - self._checkmark.dimen.w,
fgcolor = self.enabled and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY,
}
local textbox_shift = math.max(0, self._checkmark.baseline - self._textwidget:getBaseline())
self._verticalgroup = VerticalGroup:new{
align = "left",
VerticalSpan:new{
width = textbox_shift,
},
self._textwidget,
}
self._horizontalgroup = HorizontalGroup:new{
align = "top",
self._checkmark,
self._verticalgroup,
}
self._frame = FrameContainer:new{
bordersize = 0,
background = self.background,
margin = 0,
padding = 0,
show_parent = self.show_parent or self,
self._horizontalgroup,
}
self.dimen = self._frame:getSize()
self[1] = self._frame
self.ges_events = {
TapCheckButton = {
GestureRange:new{
ges = "tap",
range = self.dimen,
},
},
HoldCheckButton = {
GestureRange:new{
ges = "hold",
range = self.dimen,
},
},
-- Safe-guard for when used inside a MovableContainer
HoldReleaseCheckButton = {
GestureRange:new{
ges = "hold_release",
range = self.dimen,
},
}
}
end
function CheckButton:onTapCheckButton()
if not self.enabled then return true end
if self.tap_input then
self:onInput(self.tap_input)
elseif type(self.tap_input_func) == "function" then
self:onInput(self.tap_input_func())
else
if G_reader_settings:isFalse("flash_ui") then
if not self.radio then
self:toggleCheck()
end
if self.callback then
self.callback()
end
else
-- c.f., ui/widget/iconbutton for the canonical documentation about the flash_ui code flow
local highlight_dimen = self.dimen
-- Highlight
--
Enable HW dithering in a few key places (#4541) * Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4) * FileManager and co. (where appropriate, i.e., when covers are shown) * Book Status * Reader, where appropriate: * CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone). * Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices). * ScreenSaver * ImageViewer * Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it). (The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu). * Hunted down a few redundant repaints (unneeded setDirty("all") calls), either by switching the widget to nil when only a refresh was needed, and not a repaint, or by passing the appropritate widget to setDirty. (Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard). There were also a few instances of 'em right behind a widget close. * Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog. We unfortunately do need to do it when switching tabs, because of their variable heights. * On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes! * Fix another debug guard in Kobo sysfs_light * Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not. PS: (Almost :100: commits! :D)
5 years ago
self[1].invert = true
UIManager:widgetInvert(self[1], highlight_dimen.x, highlight_dimen.y, highlight_dimen.w)
UIManager:setDirty(nil, "fast", highlight_dimen)
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
3 years ago
UIManager:forceRePaint()
UIManager:yieldToEPDC()
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
3 years ago
-- Unhighlight
--
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
3 years ago
self[1].invert = false
UIManager:widgetInvert(self[1], highlight_dimen.x, highlight_dimen.y, highlight_dimen.w)
UIManager:setDirty(nil, "ui", highlight_dimen)
-- Callback
--
if not self.radio then
self:toggleCheck()
end
if self.callback then
self.callback()
end
UIManager:forceRePaint()
end
end
return true
end
function CheckButton:onHoldCheckButton()
-- If we're going to process this hold, we must make
-- sure to also handle its hold_release below, so it's
-- not propagated up to a MovableContainer
self._hold_handled = nil
if self.enabled then
if self.hold_callback then
self.hold_callback()
self._hold_handled = true
elseif self.hold_input then
self:onInput(self.hold_input, true)
self._hold_handled = true
elseif type(self.hold_input_func) == "function" then
self:onInput(self.hold_input_func(), true)
self._hold_handled = true
end
end
return true
end
function CheckButton:onHoldReleaseCheckButton()
if self._hold_handled then
self._hold_handled = nil
return true
end
return false
end
function CheckButton:toggleCheck()
self:initCheckButton(not self.checked)
UIManager:setDirty(self.parent, function()
return "ui", self.dimen
end)
end
function CheckButton:enable()
self.enabled = true
self:initCheckButton(self.checked)
UIManager:setDirty(self.parent, function()
return "ui", self.dimen
end)
end
function CheckButton:disable()
self.enabled = false
self:initCheckButton(false)
UIManager:setDirty(self.parent, function()
return "ui", self.dimen
end)
end
function CheckButton:onFocus()
if not self.enabled then
return false
end
self._frame.invert = true
return true
end
function CheckButton:onUnfocus()
if not self.enabled then
return false
end
self._frame.invert = false
return true
end
return CheckButton