mirror of
https://github.com/koreader/koreader
synced 2024-11-10 01:10:34 +00:00
4a65cc666b
This is a quick exploration into how the new Device:hasFewKeys() method could work to make things more usable on very limited devices. In the reader, the right is repurposed to open the menu. Left in turn closes it. The same principle is applied to ButtonDialog. This means you can select anything in principle, but once you go right you can't go back. References <https://github.com/koreader/koreader/issues/4029>.
874 lines
30 KiB
Lua
874 lines
30 KiB
Lua
--[[--
|
|
TouchMenu widget for hierarchical menus.
|
|
]]
|
|
local BD = require("ui/bidi")
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
local Button = require("ui/widget/button")
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
local CheckMark = require("ui/widget/checkmark")
|
|
local Device = require("device")
|
|
local Event = require("ui/event")
|
|
local FocusManager = require("ui/widget/focusmanager")
|
|
local Font = require("ui/font")
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
|
local Geom = require("ui/geometry")
|
|
local GestureRange = require("ui/gesturerange")
|
|
local HorizontalGroup = require("ui/widget/horizontalgroup")
|
|
local HorizontalSpan = require("ui/widget/horizontalspan")
|
|
local IconButton = require("ui/widget/iconbutton")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local LeftContainer = require("ui/widget/container/leftcontainer")
|
|
local LineWidget = require("ui/widget/linewidget")
|
|
local RightContainer = require("ui/widget/container/rightcontainer")
|
|
local Size = require("ui/size")
|
|
local TextWidget = require("ui/widget/textwidget")
|
|
local UIManager = require("ui/uimanager")
|
|
local UnderlineContainer = require("ui/widget/container/underlinecontainer")
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
|
local getMenuText = require("ui/widget/menu").getMenuText
|
|
local _ = require("gettext")
|
|
local T = require("ffi/util").template
|
|
local Input = Device.input
|
|
local Screen = Device.screen
|
|
|
|
--[[
|
|
TouchMenuItem widget
|
|
--]]
|
|
local TouchMenuItem = InputContainer:new{
|
|
menu = nil,
|
|
vertical_align = "center",
|
|
item = nil,
|
|
dimen = nil,
|
|
face = Font:getFace("smallinfofont"),
|
|
show_parent = nil,
|
|
}
|
|
|
|
function TouchMenuItem:init()
|
|
self.ges_events = {
|
|
TapSelect = {
|
|
GestureRange:new{
|
|
ges = "tap",
|
|
range = self.dimen,
|
|
},
|
|
doc = "Select Menu Item",
|
|
},
|
|
HoldSelect = {
|
|
GestureRange:new{
|
|
ges = "hold",
|
|
range = self.dimen,
|
|
},
|
|
doc = "Hold Menu Item",
|
|
},
|
|
}
|
|
|
|
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)
|
|
self.item_frame = FrameContainer:new{
|
|
width = self.dimen.w,
|
|
bordersize = 0,
|
|
color = Blitbuffer.COLOR_BLACK,
|
|
HorizontalGroup:new {
|
|
align = "center",
|
|
CenterContainer:new{
|
|
dimen = Geom:new{ w = checked_widget:getSize().w },
|
|
checkmark_widget,
|
|
},
|
|
TextWidget:new{
|
|
text = text,
|
|
max_width = text_max_width,
|
|
fgcolor = item_enabled ~= false and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY,
|
|
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
|
|
end
|
|
|
|
function TouchMenuItem:onTapSelect(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:onMenuSelect(self.item)
|
|
else
|
|
self.item_frame.invert = true
|
|
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
|
|
--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
|
|
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
|
|
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
|
|
-- 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
|
|
|
|
--[[
|
|
TouchMenuBar widget
|
|
--]]
|
|
local TouchMenuBar = InputContainer:new{
|
|
width = Screen:getWidth(),
|
|
icons = {},
|
|
-- touch menu that holds the bar, used for trigger repaint on icons
|
|
show_parent = nil,
|
|
menu = nil,
|
|
}
|
|
|
|
function TouchMenuBar:init()
|
|
local icon_sep_width = Size.span.vertical_default
|
|
local icons_sep_width = icon_sep_width * (#self.icons + 1)
|
|
-- we assume all icons are of the same width
|
|
local icon_width = Screen:scaleBySize(40)
|
|
local icon_height = icon_width
|
|
-- content_width is the width of all the icon images
|
|
local content_width = icon_width * #self.icons + icons_sep_width
|
|
local spacing_width = (self.width - content_width)/(#self.icons*2)
|
|
local icon_padding = math.min(spacing_width, Screen:scaleBySize(16))
|
|
self.height = icon_height + Size.span.vertical_large
|
|
self.show_parent = self.show_parent or self
|
|
self.bar_icon_group = HorizontalGroup:new{}
|
|
-- build up image widget for menu icon bar
|
|
self.icon_widgets = {}
|
|
-- hold icon seperators
|
|
self.icon_seps = {}
|
|
-- 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
|
|
|
|
for k, v in ipairs(self.icons) do
|
|
local ib = IconButton:new{
|
|
show_parent = self.show_parent,
|
|
icon_file = v,
|
|
width = icon_width,
|
|
height = icon_height,
|
|
scale_for_dpi = false,
|
|
callback = nil,
|
|
padding_left = icon_padding,
|
|
padding_right = icon_padding,
|
|
menu = self.menu,
|
|
}
|
|
|
|
table.insert(self.icon_widgets, ib)
|
|
table.insert(self.menu.layout, ib) -- for the focusmanager
|
|
|
|
-- we have to use local variable here for closure callback
|
|
local _start_seg = end_seg + icon_sep_width
|
|
local _end_seg = _start_seg + self.icon_widgets[k]:getSize().w
|
|
end_seg = _end_seg -- for next loop _start_seg
|
|
|
|
if BD.mirroredUILayout() then
|
|
_start_seg, _end_seg = self.width - _end_seg, self.width - _start_seg
|
|
end
|
|
|
|
if k == 1 then
|
|
self.bar_sep = LineWidget:new{
|
|
dimen = Geom:new{
|
|
w = self.width,
|
|
h = Size.line.thick,
|
|
},
|
|
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
|
|
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
|
|
local _start_last_seg = icon_sep_width + stretch_width + _start_seg
|
|
local _end_last_seg = icon_sep_width + stretch_width + _end_seg
|
|
if BD.mirroredUILayout() then
|
|
_start_last_seg = _start_seg - icon_sep_width - stretch_width
|
|
_end_last_seg = _end_seg - icon_sep_width - stretch_width
|
|
end
|
|
self.bar_sep.empty_segments = {
|
|
{
|
|
s = _start_last_seg, e = _end_last_seg
|
|
}
|
|
}
|
|
sep.style = "solid"
|
|
-- regular behavior
|
|
else
|
|
sep.style = current_icon and "solid" or "none"
|
|
end
|
|
end
|
|
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
|
|
end
|
|
|
|
self[1] = FrameContainer:new{
|
|
bordersize = 0,
|
|
padding = 0,
|
|
VerticalGroup:new{
|
|
align = "left",
|
|
-- bar icons
|
|
self.bar_icon_group,
|
|
-- horizontal separate line
|
|
self.bar_sep
|
|
},
|
|
}
|
|
self.dimen = Geom:new{ w = self.width, h = self.height }
|
|
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
|
|
|
|
--[[
|
|
TouchMenu widget for hierarchical menus
|
|
--]]
|
|
local TouchMenu = FocusManager:new{
|
|
tab_item_table = {},
|
|
-- for returning in multi-level menus
|
|
item_table_stack = nil,
|
|
item_table = nil,
|
|
item_height = Size.item.height_large,
|
|
bordersize = Size.border.window,
|
|
padding = Size.padding.default,
|
|
fface = Font:getFace("ffont"),
|
|
width = nil,
|
|
height = nil,
|
|
page = 1,
|
|
max_per_page_default = 10,
|
|
-- for UIManager:setDirty
|
|
show_parent = nil,
|
|
cur_tab = -1,
|
|
close_callback = nil,
|
|
is_fresh = true,
|
|
}
|
|
|
|
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
|
|
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 = {}
|
|
|
|
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" }
|
|
if Device:hasFewKeys() then
|
|
self.key_events.Back = { {"Left"}, doc = "back to upper menu or close touchmenu" }
|
|
end
|
|
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" }
|
|
|
|
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
|
|
icons = icons,
|
|
show_parent = self.show_parent,
|
|
menu = self,
|
|
}
|
|
|
|
self.item_group = VerticalGroup:new{
|
|
align = "center",
|
|
}
|
|
-- group for page info
|
|
local chevron_left = "resources/icons/appbar.chevron.left.png"
|
|
local chevron_right = "resources/icons/appbar.chevron.right.png"
|
|
if BD.mirroredUILayout() then
|
|
chevron_left, chevron_right = chevron_right, chevron_left
|
|
end
|
|
self.page_info_left_chev = Button:new{
|
|
icon = chevron_left,
|
|
callback = function() self:onPrevPage() end,
|
|
bordersize = 0,
|
|
show_parent = self.show_parent,
|
|
}
|
|
self.page_info_right_chev = Button:new{
|
|
icon = chevron_right,
|
|
callback = function() self:onNextPage() end,
|
|
bordersize = 0,
|
|
show_parent = self.show_parent,
|
|
}
|
|
self.page_info_left_chev:hide()
|
|
self.page_info_right_chev:hide()
|
|
self.page_info_text = TextWidget:new{
|
|
text = "",
|
|
face = self.fface,
|
|
}
|
|
self.page_info = HorizontalGroup:new{
|
|
self.page_info_left_chev,
|
|
self.page_info_text,
|
|
self.page_info_right_chev
|
|
}
|
|
-- group for device info
|
|
self.time_info = TextWidget:new{
|
|
text = "",
|
|
face = self.fface,
|
|
}
|
|
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 footer_width = self.width - self.padding*2
|
|
local up_button = IconButton:new{
|
|
icon_file = "resources/icons/appbar.chevron.up.png",
|
|
show_parent = self.show_parent,
|
|
padding_left = footer_width*0.33*0.1,
|
|
padding_right = footer_width*0.33*0.1,
|
|
callback = function()
|
|
self:backToUpperMenu()
|
|
end,
|
|
}
|
|
local footer_height = up_button:getSize().h + Size.line.thick
|
|
self.footer = HorizontalGroup:new{
|
|
LeftContainer:new{
|
|
dimen = Geom:new{ w = footer_width*0.33, h = footer_height},
|
|
up_button,
|
|
},
|
|
CenterContainer:new{
|
|
dimen = Geom:new{ w = footer_width*0.33, h = footer_height},
|
|
self.page_info,
|
|
},
|
|
RightContainer:new{
|
|
dimen = Geom:new{ w = footer_width*0.33, h = footer_height},
|
|
self.device_info,
|
|
}
|
|
}
|
|
|
|
self.menu_frame = FrameContainer:new{
|
|
padding = self.padding,
|
|
bordersize = self.bordersize,
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
-- 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
|
|
}
|
|
|
|
self.item_width = self.width - self.padding*2
|
|
self.split_line = HorizontalGroup:new{
|
|
-- pad with 10 pixel to align with the up arrow in footer
|
|
HorizontalSpan:new{width = Size.span.horizontal_default},
|
|
LineWidget:new{
|
|
background = Blitbuffer.COLOR_GRAY,
|
|
dimen = Geom:new{
|
|
w = self.item_width - 2*Size.span.horizontal_default,
|
|
h = Size.line.medium,
|
|
}
|
|
},
|
|
HorizontalSpan:new{width = Size.span.horizontal_default},
|
|
}
|
|
self.footer_top_margin = VerticalSpan:new{width = Size.span.vertical_default}
|
|
self.bar:switchToTab(self.last_index or 1)
|
|
end
|
|
|
|
function TouchMenu:onCloseWidget()
|
|
-- NOTE: We don't pass a region in order to ensure a full-screen flash to avoid ghosting
|
|
UIManager:setDirty(nil, "flashui")
|
|
end
|
|
|
|
function TouchMenu:_recalculatePageLayout()
|
|
local content_height -- content == item_list + footer
|
|
|
|
local bar_height = self.bar:getSize().h
|
|
local footer_height = self.footer:getSize().h
|
|
if self.height then
|
|
content_height = self.height - bar_height
|
|
else
|
|
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
|
|
end
|
|
if content_height + bar_height > Screen:getHeight() then
|
|
content_height = Screen:getHeight() - bar_height
|
|
end
|
|
|
|
local item_list_content_height = content_height - footer_height
|
|
self.perpage = math.floor(item_list_content_height / self.item_height)
|
|
local max_per_page = self.item_table.max_per_page or self.max_per_page_default
|
|
if self.perpage > max_per_page then
|
|
self.perpage = max_per_page
|
|
end
|
|
|
|
self.page_num = math.ceil(#self.item_table / self.perpage)
|
|
end
|
|
|
|
function TouchMenu:updateItems()
|
|
local old_dimen = self.dimen and self.dimen:copy()
|
|
self:_recalculatePageLayout()
|
|
self.item_group:clear()
|
|
self.layout = {}
|
|
table.insert(self.item_group, self.bar)
|
|
table.insert(self.layout, self.bar.icon_widgets) -- for the focusmanager
|
|
|
|
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)
|
|
end
|
|
else
|
|
-- item not enough to fill the whole page, break out of loop
|
|
break
|
|
end -- if i <= self.items
|
|
end -- for c=1, self.perpage
|
|
|
|
table.insert(self.item_group, self.footer_top_margin)
|
|
table.insert(self.item_group, self.footer)
|
|
if self.page_num > 1 then
|
|
-- @translators %1 is the current page. %2 is the total number of pages. In some languages a good translation might need to reverse this order, for instance: "Total %2, page %1".
|
|
self.page_info_text:setText(T(_("Page %1 of %2"), self.page, self.page_num))
|
|
else
|
|
self.page_info_text:setText("")
|
|
end
|
|
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)
|
|
|
|
local time_info_txt
|
|
if G_reader_settings:nilOrTrue("twelve_hour_clock") then
|
|
if os.date("%p") == "AM" then
|
|
-- @translators This is the time in the morning in the 12-hour clock (%I is the hour, %M the minute).
|
|
time_info_txt = os.date(_("%I:%M AM"))
|
|
else
|
|
-- @translators This is the time in the afternoon in the 12-hour clock (%I is the hour, %M the minute).
|
|
time_info_txt = os.date(_("%I:%M PM"))
|
|
end
|
|
else
|
|
-- @translators This is the time in the 24-hour clock (%H is the hour, %M the minute).
|
|
time_info_txt = os.date(_("%H:%M"))
|
|
end
|
|
local powerd = Device:getPowerDevice()
|
|
local batt_lvl = powerd:getCapacity()
|
|
local batt_symbol
|
|
if powerd:isCharging() then
|
|
batt_symbol = ""
|
|
else
|
|
if batt_lvl >= 100 then
|
|
batt_symbol = ""
|
|
elseif batt_lvl >= 90 then
|
|
batt_symbol = ""
|
|
elseif batt_lvl >= 80 then
|
|
batt_symbol = ""
|
|
elseif batt_lvl >= 70 then
|
|
batt_symbol = ""
|
|
elseif batt_lvl >= 60 then
|
|
batt_symbol = ""
|
|
elseif batt_lvl >= 50 then
|
|
batt_symbol = ""
|
|
elseif batt_lvl >= 40 then
|
|
batt_symbol = ""
|
|
elseif batt_lvl >= 30 then
|
|
batt_symbol = ""
|
|
elseif batt_lvl >= 20 then
|
|
batt_symbol = ""
|
|
elseif batt_lvl >= 10 then
|
|
batt_symbol = ""
|
|
else
|
|
batt_symbol = ""
|
|
end
|
|
end
|
|
if not Device:isDesktop() then
|
|
time_info_txt = BD.wrap(time_info_txt) .. " " .. BD.wrap("⌁") .. BD.wrap(batt_symbol) .. BD.wrap(batt_lvl .. "%")
|
|
end
|
|
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...
|
|
-- NOTE: Also avoid repainting what's underneath us on initial popup.
|
|
-- NOTE: And we also only need to repaint what's behind us when switching to a smaller menu...
|
|
local keep_bg = old_dimen and self.dimen.h >= old_dimen.h
|
|
UIManager:setDirty((self.is_fresh or keep_bg) 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)
|
|
end
|
|
|
|
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
|
|
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
|
|
end
|
|
|
|
function TouchMenu:backToUpperMenu()
|
|
if #self.item_table_stack ~= 0 then
|
|
self.item_table = table.remove(self.item_table_stack)
|
|
self.page = 1
|
|
self:updateItems()
|
|
else
|
|
self:closeMenu()
|
|
end
|
|
end
|
|
|
|
function TouchMenu:closeMenu()
|
|
self.close_callback()
|
|
end
|
|
|
|
function TouchMenu:onNextPage()
|
|
if self.page < self.page_num then
|
|
self.page = self.page + 1
|
|
elseif self.page == self.page_num then
|
|
self.page = 1
|
|
end
|
|
self:updateItems()
|
|
return true
|
|
end
|
|
|
|
function TouchMenu:onPrevPage()
|
|
if self.page > 1 then
|
|
self.page = self.page - 1
|
|
elseif self.page == 1 then
|
|
self.page = self.page_num
|
|
end
|
|
self:updateItems()
|
|
return true
|
|
end
|
|
|
|
function TouchMenu:onSwipe(arg, ges_ev)
|
|
local direction = BD.flipDirectionIfMirroredUILayout(ges_ev.direction)
|
|
if direction == "west" then
|
|
self:onNextPage()
|
|
elseif direction == "east" then
|
|
self:onPrevPage()
|
|
elseif direction == "north" then
|
|
self:closeMenu()
|
|
end
|
|
end
|
|
|
|
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
|
|
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()
|
|
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)
|
|
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
|
|
end
|
|
return true
|
|
end
|
|
|
|
function TouchMenu:onTapCloseAllMenus(arg, ges_ev)
|
|
if ges_ev.pos:notIntersectWith(self.dimen) then
|
|
self:closeMenu()
|
|
end
|
|
end
|
|
|
|
function TouchMenu:onClose()
|
|
self:closeMenu()
|
|
end
|
|
|
|
function TouchMenu:onBack()
|
|
self:backToUpperMenu()
|
|
end
|
|
|
|
function TouchMenu:onPress()
|
|
self:getFocusItem():handleEvent(Event:new("TapSelect"))
|
|
end
|
|
|
|
return TouchMenu
|