2
0
mirror of https://github.com/koreader/koreader synced 2024-11-18 03:25:46 +00:00
koreader/frontend/ui/widget/touchmenu.lua

806 lines
27 KiB
Lua
Raw Normal View History

2017-09-13 14:56:20 +00:00
--[[--
TouchMenu widget for hierarchical menus.
]]
2017-04-29 08:38:09 +00:00
local Blitbuffer = require("ffi/blitbuffer")
local Button = require("ui/widget/button")
2013-10-18 20:38:07 +00:00
local CenterContainer = require("ui/widget/container/centercontainer")
local CheckMark = require("ui/widget/checkmark")
2017-04-29 08:38:09 +00:00
local Device = require("device")
local Event = require("ui/event")
local FocusManager = require("ui/widget/focusmanager")
2017-04-29 08:38:09 +00:00
local Font = require("ui/font")
local FrameContainer = require("ui/widget/container/framecontainer")
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
2013-10-18 20:38:07 +00:00
local HorizontalGroup = require("ui/widget/horizontalgroup")
local HorizontalSpan = require("ui/widget/horizontalspan")
local IconButton = require("ui/widget/iconbutton")
local InfoMessage = require("ui/widget/infomessage")
2017-04-29 08:38:09 +00:00
local InputContainer = require("ui/widget/container/inputcontainer")
local LeftContainer = require("ui/widget/container/leftcontainer")
local LineWidget = require("ui/widget/linewidget")
local RenderText = require("ui/rendertext")
2017-04-29 08:38:09 +00:00
local RightContainer = require("ui/widget/container/rightcontainer")
2017-09-13 14:56:20 +00:00
local Size = require("ui/size")
2017-04-29 08:38:09 +00:00
local TextWidget = require("ui/widget/textwidget")
2013-10-18 20:38:07 +00:00
local UIManager = require("ui/uimanager")
local UnderlineContainer = require("ui/widget/container/underlinecontainer")
2017-04-29 08:38:09 +00:00
local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
local util = require("ffi/util")
local _ = require("gettext")
2017-04-29 08:38:09 +00:00
local getMenuText = require("util").getMenuText
local Input = Device.input
local Screen = Device.screen
2013-03-14 05:06:42 +00:00
--[[
TouchMenuItem widget
--]]
2013-10-18 20:38:07 +00:00
local TouchMenuItem = InputContainer:new{
2014-03-13 13:52:43 +00:00
menu = nil,
vertical_align = "center",
item = nil,
dimen = nil,
2017-04-29 08:38:09 +00:00
face = Font:getFace("smallinfofont"),
2014-03-13 13:52:43 +00:00
show_parent = nil,
2013-03-14 05:06:42 +00:00
}
function TouchMenuItem:init()
2014-03-13 13:52:43 +00:00
self.ges_events = {
TapSelect = {
GestureRange:new{
ges = "tap",
range = self.dimen,
},
doc = "Select Menu Item",
2014-03-13 13:52:43 +00:00
},
HoldSelect = {
GestureRange:new{
ges = "hold",
range = self.dimen,
},
doc = "Hold Menu Item",
},
2014-03-13 13:52:43 +00:00
}
2014-05-01 10:37:12 +00:00
local item_enabled = self.item.enabled
if self.item.enabled_func then
item_enabled = self.item.enabled_func()
end
local item_checkable = false
local item_checked = self.item.checked
if self.item.checked_func then
item_checkable = true
item_checked = self.item.checked_func()
end
local checkmark_widget = CheckMark:new{
checkable = item_checkable,
checked = item_checked,
enabled = item_enabled,
}
local checked_widget = CheckMark:new{ -- for layout, to :getSize()
checked = true,
}
-- text_max_width should be the TouchMenuItem width minus the below
-- FrameContainer default paddings minus the checked widget width
local text_max_width = self.dimen.w - 2*Size.padding.default - checked_widget:getSize().w
local text = getMenuText(self.item)
if RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, text, true).x > text_max_width then
text = RenderText:truncateTextByWidth(text, self.face, text_max_width, true)
end
2014-03-13 13:52:43 +00:00
self.item_frame = FrameContainer:new{
width = self.dimen.w,
bordersize = 0,
color = Blitbuffer.COLOR_BLACK,
2014-03-13 13:52:43 +00:00
HorizontalGroup:new {
align = "center",
CenterContainer:new{
dimen = Geom:new{ w = checked_widget:getSize().w },
checkmark_widget,
},
2014-03-13 13:52:43 +00:00
TextWidget:new{
text = text,
fgcolor = item_enabled ~= false and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY,
2014-03-13 13:52:43 +00:00
face = self.face,
},
},
}
self._underline_container = UnderlineContainer:new{
vertical_align = "center",
dimen = self.dimen,
self.item_frame
}
self[1] = self._underline_container
function self:isEnabled()
return item_enabled ~= false and true
end
end
function TouchMenuItem:onFocus()
self._underline_container.color = Blitbuffer.COLOR_BLACK
return true
end
function TouchMenuItem:onUnfocus()
self._underline_container.color = Blitbuffer.COLOR_WHITE
return true
2013-03-14 05:06:42 +00:00
end
function TouchMenuItem:onTapSelect(arg, ges)
2014-05-01 10:37:12 +00:00
local enabled = self.item.enabled
if self.item.enabled_func then
enabled = self.item.enabled_func()
end
if enabled == false then return end
if G_reader_settings:isFalse("flash_ui") then
self.menu:onMenuSelect(self.item)
else
self.item_frame.invert = true
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)
2019-02-07 00:14:37 +00:00
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
-- yield to main UI loop to invert item
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
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)
2019-02-07 00:14:37 +00:00
--UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen
end)
end
end)
end
return true
2013-03-14 05:06:42 +00:00
end
function TouchMenuItem:onHoldSelect(arg, ges)
local enabled = self.item.enabled
if self.item.enabled_func then
enabled = self.item.enabled_func()
end
if enabled == false then return end
if G_reader_settings:isFalse("flash_ui") then
self.menu:onMenuHold(self.item)
else
self.item_frame.invert = true
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)
2019-02-07 00:14:37 +00:00
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function()
return "fast", self.dimen
end)
UIManager:tickAfterNext(function()
self.menu:onMenuHold(self.item)
end)
UIManager:scheduleIn(0.5, function()
self.item_frame.invert = false
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)
2019-02-07 00:14:37 +00:00
-- 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)
end)
end
return true
end
2013-03-14 05:06:42 +00:00
--[[
TouchMenuBar widget
--]]
2013-10-18 20:38:07 +00:00
local TouchMenuBar = InputContainer:new{
2014-03-13 13:52:43 +00:00
width = Screen:getWidth(),
icons = {},
-- touch menu that holds the bar, used for trigger repaint on icons
show_parent = nil,
menu = nil,
2013-03-14 05:06:42 +00:00
}
function TouchMenuBar:init()
2017-09-13 14:56:20 +00:00
local icon_sep_width = Size.span.vertical_default
local icons_sep_width = icon_sep_width * (#self.icons + 1)
2014-06-05 04:58:57 +00:00
-- we assume all icons are of the same width
2017-09-23 20:01:30 +00:00
local icon_width = Screen:scaleBySize(40)
local icon_height = icon_width
-- content_width is the width of all the icon images
2017-09-23 20:01:30 +00:00
local content_width = icon_width * #self.icons + icons_sep_width
2014-06-05 04:58:57 +00:00
local spacing_width = (self.width - content_width)/(#self.icons*2)
2017-09-23 20:01:30 +00:00
local icon_padding = math.min(spacing_width, Screen:scaleBySize(16))
self.height = icon_height + Size.span.vertical_large
2014-06-05 04:58:57 +00:00
self.show_parent = self.show_parent or self
self.bar_icon_group = HorizontalGroup:new{}
2014-03-13 13:52:43 +00:00
-- build up image widget for menu icon bar
self.icon_widgets = {}
-- hold icon seperators
self.icon_seps = {}
2014-03-13 13:52:43 +00:00
-- the start_seg for first icon_widget should be 0
-- we asign negative here to offset it in the loop
local start_seg = -icon_sep_width
local end_seg = start_seg
-- self.width is the screen width
-- content_width is the width of all the icon images
-- (2 * icon_padding * #self.icons) is the combined width of icons paddings
local stretch_width = self.width - content_width - (2 * icon_padding * #self.icons) + icon_sep_width
2014-03-13 13:52:43 +00:00
for k, v in ipairs(self.icons) do
local ib = IconButton:new{
show_parent = self.show_parent,
icon_file = v,
2017-09-23 20:01:30 +00:00
width = icon_width,
height = icon_height,
scale_for_dpi = false,
2014-03-13 13:52:43 +00:00
callback = nil,
padding_left = icon_padding,
padding_right = icon_padding,
menu = self.menu,
2014-03-13 13:52:43 +00:00
}
table.insert(self.icon_widgets, ib)
table.insert(self.menu.layout, ib) -- for the focusmanager
2014-03-13 13:52:43 +00:00
-- we have to use local variable here for closure callback
local _start_seg = end_seg + icon_sep_width
2014-03-13 13:52:43 +00:00
local _end_seg = _start_seg + self.icon_widgets[k]:getSize().w
if k == 1 then
self.bar_sep = LineWidget:new{
dimen = Geom:new{
w = self.width,
2017-09-13 14:56:20 +00:00
h = Size.line.thick,
2014-03-13 13:52:43 +00:00
},
empty_segments = {
{
s = _start_seg, e = _end_seg
}
},
}
end
local icon_sep = LineWidget:new{
style = k == 1 and "solid" or "none",
dimen = Geom:new{
w = icon_sep_width,
h = self.height,
}
}
-- no separator on the right
if k < #self.icons then
table.insert(self.icon_seps, icon_sep)
end
-- callback to set visual style
2014-03-13 13:52:43 +00:00
ib.callback = function()
self.bar_sep.empty_segments = {
{
s = _start_seg, e = _end_seg
}
}
for i, sep in ipairs(self.icon_seps) do
local current_icon, last_icon
if k == #self.icons then
current_icon = false
last_icon = i == k
else
current_icon = i == k - 1 or i == k
last_icon = false
end
-- if the active icon is the last icon then the empty bar segment has
-- to move over to the right by the width of a separator and the stretch width
if last_icon then
self.bar_sep.empty_segments = {
{
s = icon_sep_width + stretch_width + _start_seg, e = icon_sep_width + stretch_width + _end_seg
}
}
sep.style = "solid"
-- regular behavior
else
sep.style = current_icon and "solid" or "none"
end
end
2014-03-13 13:52:43 +00:00
self.menu:switchMenuTab(k)
end
table.insert(self.bar_icon_group, self.icon_widgets[k])
table.insert(self.bar_icon_group, icon_sep)
-- if we're at the before-last icon, add an extra span and the final separator
if k == #self.icons - 1 then
table.insert(self.bar_icon_group, HorizontalSpan:new{
width = stretch_width
})
-- need to create a new LineWidget otherwise it's just a reference to the same instance
local icon_sep_duplicate = LineWidget:new{
style = "none",
dimen = Geom:new{
w = icon_sep_width,
h = self.height,
}
}
table.insert(self.icon_seps, icon_sep_duplicate)
table.insert(self.bar_icon_group, icon_sep_duplicate)
end
2014-03-13 13:52:43 +00:00
end_seg = _end_seg
end
self[1] = FrameContainer:new{
bordersize = 0,
padding = 0,
VerticalGroup:new{
align = "left",
-- bar icons
self.bar_icon_group,
-- horizontal separate line
2014-03-13 13:52:43 +00:00
self.bar_sep
},
}
2014-06-05 04:58:57 +00:00
self.dimen = Geom:new{ w = self.width, h = self.height }
2013-03-14 05:06:42 +00:00
end
function TouchMenuBar:switchToTab(index)
-- a little safety check
-- don't auto-activate a non-existent index
if index > #self.icon_widgets then
index = #self.icon_widgets
end
self.icon_widgets[index].callback()
end
2013-03-14 05:06:42 +00:00
--[[
TouchMenu widget for hierarchical menus
2013-03-14 05:06:42 +00:00
--]]
local TouchMenu = FocusManager:new{
2014-03-13 13:52:43 +00:00
tab_item_table = {},
-- for returning in multi-level menus
2014-03-13 13:52:43 +00:00
item_table_stack = nil,
item_table = nil,
2017-09-13 14:56:20 +00:00
item_height = Size.item.height_large,
bordersize = Size.border.window,
2017-09-13 14:56:20 +00:00
padding = Size.padding.default,
2017-04-29 08:38:09 +00:00
fface = Font:getFace("ffont"),
2014-03-13 13:52:43 +00:00
width = nil,
height = nil,
page = 1,
max_per_page = 10,
-- for UIManager:setDirty
show_parent = nil,
cur_tab = -1,
close_callback = nil,
is_fresh = true,
2013-03-14 05:06:42 +00:00
}
function TouchMenu:init()
-- We won't include self.bordersize in our width calculations, so that
-- borders are pushed off-(screen-)width and so not visible.
-- We'll then be similar to bottom menu ConfigDialog (where this
-- nice effect is caused by some width calculations bug).
if not self.dimen then self.dimen = Geom:new{} end
2014-03-13 13:52:43 +00:00
self.show_parent = self.show_parent or self
if not self.close_callback then
self.close_callback = function()
UIManager:close(self.show_parent)
end
end
self.layout = {}
2014-03-13 13:52:43 +00:00
self.ges_events.TapCloseAllMenus = {
GestureRange:new{
ges = "tap",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
}
self.ges_events.Swipe = {
GestureRange:new{
ges = "swipe",
range = self.dimen,
}
}
self.key_events.Back = { {"Back"}, doc = "back to upper menu or close touchmenu" }
self.key_events.NextPage = { {Input.group.PgFwd}, doc = "next page" }
self.key_events.PrevPage = { {Input.group.PgBack}, doc = "previous page" }
self.key_events.Press = { {"Press"}, doc = "chose selected item" }
2014-03-13 13:52:43 +00:00
local icons = {}
for _,v in ipairs(self.tab_item_table) do
table.insert(icons, v.icon)
end
self.bar = TouchMenuBar:new{
width = self.width, -- will impose width and push left and right borders offscreen
2014-03-13 13:52:43 +00:00
icons = icons,
show_parent = self.show_parent,
menu = self,
}
self.item_group = VerticalGroup:new{
align = "center",
2014-03-13 13:52:43 +00:00
}
-- group for page info
self.page_info_left_chev = Button:new{
icon = "resources/icons/appbar.chevron.left.png",
callback = function() self:onPrevPage() end,
bordersize = 0,
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)
2019-02-07 00:14:37 +00:00
show_parent = self.show_parent,
}
self.page_info_right_chev = Button:new{
icon = "resources/icons/appbar.chevron.right.png",
callback = function() self:onNextPage() end,
bordersize = 0,
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)
2019-02-07 00:14:37 +00:00
show_parent = self.show_parent,
}
self.page_info_left_chev:hide()
self.page_info_right_chev:hide()
self.page_info_text = TextWidget:new{
2014-03-13 13:52:43 +00:00
text = "",
face = self.fface,
}
self.page_info = HorizontalGroup:new{
self.page_info_left_chev,
self.page_info_text,
self.page_info_right_chev
2014-03-13 13:52:43 +00:00
}
-- group for device info
2014-03-13 13:52:43 +00:00
self.time_info = TextWidget:new{
text = "",
face = self.fface,
2014-03-13 13:52:43 +00:00
}
self.device_info = HorizontalGroup:new{
self.time_info,
-- Add some span to balance up_button image included padding
HorizontalSpan:new{width = Size.span.horizontal_default},
}
local up_button = IconButton:new{
icon_file = "resources/icons/appbar.chevron.up.png",
show_parent = self.show_parent,
callback = function()
self:backToUpperMenu()
end,
}
local footer_width = self.width - self.padding*2
2017-09-13 14:56:20 +00:00
local footer_height = up_button:getSize().h + Size.line.thick
2014-03-13 13:52:43 +00:00
self.footer = HorizontalGroup:new{
LeftContainer:new{
dimen = Geom:new{ w = footer_width*0.33, h = footer_height},
up_button,
2014-03-13 13:52:43 +00:00
},
CenterContainer:new{
dimen = Geom:new{ w = footer_width*0.33, h = footer_height},
self.page_info,
2014-03-13 13:52:43 +00:00
},
RightContainer:new{
dimen = Geom:new{ w = footer_width*0.33, h = footer_height},
self.device_info,
2014-03-13 13:52:43 +00:00
}
}
self.menu_frame = FrameContainer:new{
2014-03-13 13:52:43 +00:00
padding = self.padding,
bordersize = self.bordersize,
background = Blitbuffer.COLOR_WHITE,
2014-03-13 13:52:43 +00:00
-- menubar and footer will be inserted in
-- item_group in updateItems
self.item_group,
}
-- This CenterContainer will make the left and right borders drawn
-- off-screen
self[1] = CenterContainer:new{
dimen = Screen:getSize(),
ignore = "height",
self.menu_frame
}
2014-03-13 13:52:43 +00:00
self.item_width = self.width - self.padding*2
2016-02-28 21:32:25 +00:00
self.split_line = HorizontalGroup:new{
-- pad with 10 pixel to align with the up arrow in footer
2017-09-13 14:56:20 +00:00
HorizontalSpan:new{width = Size.span.horizontal_default},
2016-02-28 21:32:25 +00:00
LineWidget:new{
background = Blitbuffer.COLOR_GRAY,
2016-02-28 21:32:25 +00:00
dimen = Geom:new{
w = self.item_width - 2*Size.span.horizontal_default,
2017-09-13 14:56:20 +00:00
h = Size.line.medium,
2016-02-28 21:32:25 +00:00
}
},
HorizontalSpan:new{width = Size.span.horizontal_default},
2016-02-28 21:32:25 +00:00
}
2017-09-13 14:56:20 +00:00
self.footer_top_margin = VerticalSpan:new{width = Size.span.vertical_default}
2016-02-28 21:32:25 +00:00
self.bar:switchToTab(self.last_index or 1)
2013-03-14 05:06:42 +00:00
end
2014-12-01 16:21:42 +00:00
function TouchMenu:onCloseWidget()
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)
2019-02-07 00:14:37 +00:00
-- NOTE: We don't pass a region in order to ensure a full-screen flash to avoid ghosting
UIManager:setDirty(nil, "flashui")
2014-12-01 16:21:42 +00:00
end
2016-02-28 21:32:25 +00:00
function TouchMenu:_recalculatePageLayout()
local content_height -- content == item_list + footer
2014-03-13 13:52:43 +00:00
2016-02-28 21:32:25 +00:00
local bar_height = self.bar:getSize().h
local footer_height = self.footer:getSize().h
if self.height then
content_height = self.height - bar_height
2014-03-13 13:52:43 +00:00
else
2016-02-28 21:32:25 +00:00
content_height = #self.item_table * self.item_height + footer_height
-- split line height
content_height = content_height + (#self.item_table - 1)
content_height = content_height + self.footer_top_margin:getSize().h
2014-03-13 13:52:43 +00:00
end
2016-02-28 21:32:25 +00:00
if content_height + bar_height > Screen:getHeight() then
content_height = Screen:getHeight() - bar_height
2014-03-13 13:52:43 +00:00
end
2016-02-28 21:32:25 +00:00
local item_list_content_height = content_height - footer_height
self.perpage = math.floor(item_list_content_height / self.item_height)
2014-03-13 13:52:43 +00:00
if self.perpage > self.max_per_page then
self.perpage = self.max_per_page
end
self.page_num = math.ceil(#self.item_table / self.perpage)
2013-03-14 05:06:42 +00:00
end
function TouchMenu:updateItems()
local old_dimen = self.dimen and self.dimen:copy()
2016-02-28 21:32:25 +00:00
self:_recalculatePageLayout()
2014-03-13 13:52:43 +00:00
self.item_group:clear()
self.layout = {}
2014-03-13 13:52:43 +00:00
table.insert(self.item_group, self.bar)
table.insert(self.layout, self.bar.icon_widgets) -- for the focusmanager
2014-03-13 13:52:43 +00:00
for c = 1, self.perpage do
-- calculate index in item_table
local i = (self.page - 1) * self.perpage + c
if i <= #self.item_table then
local item = self.item_table[i]
local item_tmp = TouchMenuItem:new{
item = item,
menu = self,
dimen = Geom:new{
w = self.item_width,
h = self.item_height,
},
show_parent = self.show_parent,
}
table.insert(self.item_group, item_tmp)
if item_tmp:isEnabled() then
table.insert(self.layout, {[self.cur_tab] = item_tmp}) -- for the focusmanager
end
if item.separator and c ~= self.perpage then
-- insert split line
table.insert(self.item_group, self.split_line)
2014-03-13 13:52:43 +00:00
end
else
-- item not enough to fill the whole page, break out of loop
2016-02-16 06:34:28 +00:00
break
2014-03-13 13:52:43 +00:00
end -- if i <= self.items
end -- for c=1, self.perpage
2016-02-28 21:32:25 +00:00
table.insert(self.item_group, self.footer_top_margin)
2014-03-13 13:52:43 +00:00
table.insert(self.item_group, self.footer)
self.page_info_text.text = util.template(_("Page %1 of %2"), self.page, self.page_num)
self.page_info_left_chev:showHide(self.page_num > 1)
self.page_info_right_chev:showHide(self.page_num > 1)
self.page_info_left_chev:enableDisable(self.page > 1)
self.page_info_right_chev:enableDisable(self.page < self.page_num)
2016-02-28 21:32:25 +00:00
local time_info_txt = os.date("%H:%M").." @ "
if Device:getPowerDevice():isCharging() then
time_info_txt = time_info_txt.."+"
end
time_info_txt = time_info_txt..Device:getPowerDevice():getCapacity().."%"
self.time_info:setText(time_info_txt)
-- 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
-- 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...
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)
2019-02-07 00:14:37 +00:00
-- NOTE: Also avoid repainting what's underneath us on initial popup.
UIManager:setDirty(self.is_fresh and self.show_parent or "all", function()
local refresh_dimen =
old_dimen and old_dimen:combine(self.dimen)
or self.dimen
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)
2013-03-14 05:06:42 +00:00
end
2013-03-14 05:06:42 +00:00
function TouchMenu:switchMenuTab(tab_num)
if self.tab_item_table[tab_num].remember ~= false then
self.last_index = tab_num
end
if self.touch_menu_callback then
self.touch_menu_callback()
end
2014-03-13 13:52:43 +00:00
if self.tab_item_table[tab_num].callback then
self.tab_item_table[tab_num].callback()
end
if self.cur_tab ~= tab_num then
-- it's like getting a new menu everytime we switch tab!
self.page = 1
-- clear item table stack
self.item_table_stack = {}
self.cur_tab = tab_num
self.item_table = self.tab_item_table[tab_num]
self:updateItems()
end
2013-03-15 09:18:34 +00:00
end
function TouchMenu:backToUpperMenu()
2014-03-13 13:52:43 +00:00
if #self.item_table_stack ~= 0 then
self.item_table = table.remove(self.item_table_stack)
self.page = 1
self:updateItems()
else
self:closeMenu()
2014-03-13 13:52:43 +00:00
end
2013-03-14 05:06:42 +00:00
end
function TouchMenu:closeMenu()
2014-03-13 13:52:43 +00:00
self.close_callback()
2013-03-14 05:06:42 +00:00
end
2013-03-16 00:30:42 +00:00
function TouchMenu:onNextPage()
2014-03-13 13:52:43 +00:00
if self.page < self.page_num then
self.page = self.page + 1
elseif self.page == self.page_num then
self.page = 1
2014-03-13 13:52:43 +00:00
end
self:updateItems()
2014-03-13 13:52:43 +00:00
return true
2013-03-16 00:30:42 +00:00
end
function TouchMenu:onPrevPage()
2014-03-13 13:52:43 +00:00
if self.page > 1 then
self.page = self.page - 1
elseif self.page == 1 then
self.page = self.page_num
2014-03-13 13:52:43 +00:00
end
self:updateItems()
2014-03-13 13:52:43 +00:00
return true
2013-03-16 00:30:42 +00:00
end
function TouchMenu:onSwipe(arg, ges_ev)
if ges_ev.direction == "west" then
2014-03-13 13:52:43 +00:00
self:onNextPage()
elseif ges_ev.direction == "east" then
2014-03-13 13:52:43 +00:00
self:onPrevPage()
elseif ges_ev.direction == "north" then
self:closeMenu()
2014-03-13 13:52:43 +00:00
end
2013-03-16 00:30:42 +00:00
end
2013-03-14 05:06:42 +00:00
function TouchMenu:onMenuSelect(item)
if self.touch_menu_callback then
self.touch_menu_callback()
end
if item.tap_input or type(item.tap_input_func) == "function" then
if not item.keep_menu_open then
self:closeMenu()
end
if item.tap_input then
self:onInput(item.tap_input)
else
self:onInput(item.tap_input_func())
end
else
local sub_item_table = item.sub_item_table
if item.sub_item_table_func then
sub_item_table = item.sub_item_table_func()
end
if sub_item_table == nil then
-- keep menu opened if this item is a check option
local callback, refresh = item.callback, item.checked or item.checked_func
if item.callback_func then
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)
end
else
table.insert(self.item_table_stack, self.item_table)
self.item_table = sub_item_table
self.page = 1
self:updateItems()
end
2014-05-01 10:37:12 +00:00
end
return true
end
function TouchMenu:onMenuHold(item)
if self.touch_menu_callback then
self.touch_menu_callback()
end
if item.hold_input or type(item.hold_input_func) == "function" then
if item.hold_keep_menu_open == false then
self:closeMenu()
end
if item.hold_input then
self:onInput(item.hold_input)
else
self:onInput(item.hold_input_func())
end
elseif item.hold_callback or type(item.hold_callback_func) == "function" then
local callback = item.hold_callback
if item.hold_callback_func then
callback = item.hold_callback_func()
2014-05-01 10:37:12 +00:00
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)
2014-03-13 13:52:43 +00:00
end)
end
elseif item.help_text or type(item.help_text_func) == "function" then
local help_text = item.help_text
if item.help_text_func then
help_text = item.help_text_func()
end
if help_text then
UIManager:show(InfoMessage:new{ text = help_text, })
end
2014-03-13 13:52:43 +00:00
end
return true
2013-03-14 05:06:42 +00:00
end
function TouchMenu:onTapCloseAllMenus(arg, ges_ev)
2014-03-13 13:52:43 +00:00
if ges_ev.pos:notIntersectWith(self.dimen) then
self:closeMenu()
end
2013-03-14 05:06:42 +00:00
end
function TouchMenu:onClose()
self:closeMenu()
end
function TouchMenu:onBack()
self:backToUpperMenu()
end
function TouchMenu:onPress()
self:getFocusItem():handleEvent(Event:new("TapSelect"))
end
2013-10-18 20:38:07 +00:00
return TouchMenu