Files
koreader/frontend/ui/widget/touchmenu.lua
NiLuJe bfb2ee703a Force poll the battery when showing the menu.
Making sure to always show up to date info.

Also fix the battery polling cache invalidation logic to actually
work?
Should make it behave properly on devices where we handle resuming
ourselves.
2015-10-16 02:49:39 +02:00

614 lines
18 KiB
Lua

local InputContainer = require("ui/widget/container/inputcontainer")
local FrameContainer = require("ui/widget/container/framecontainer")
local LeftContainer = require("ui/widget/container/leftcontainer")
local RightContainer = require("ui/widget/container/rightcontainer")
local CenterContainer = require("ui/widget/container/centercontainer")
local HorizontalGroup = require("ui/widget/horizontalgroup")
local VerticalGroup = require("ui/widget/verticalgroup")
local HorizontalSpan = require("ui/widget/horizontalspan")
local VerticalSpan = require("ui/widget/verticalspan")
local TextWidget = require("ui/widget/textwidget")
local LineWidget = require("ui/widget/linewidget")
local IconButton = require("ui/widget/iconbutton")
local GestureRange = require("ui/gesturerange")
local Button = require("ui/widget/button")
local UIManager = require("ui/uimanager")
local Device = require("device")
local Screen = require("device").screen
local Geom = require("ui/geometry")
local Font = require("ui/font")
local util = require("ffi/util")
local DEBUG = require("dbg")
local _ = require("gettext")
local Blitbuffer = require("ffi/blitbuffer")
--[[
TouchMenuItem widget
--]]
local TouchMenuItem = InputContainer:new{
menu = nil,
vertical_align = "center",
item = nil,
dimen = nil,
face = Font:getFace("cfont", 22),
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_checked = self.item.checked
if self.item.checked_func then
item_checked = self.item.checked_func()
end
local checked_widget = TextWidget:new{
text = "",
face = self.face,
}
local unchecked_widget = TextWidget:new{
text = "",
face = self.face,
}
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 },
item_checked and checked_widget or unchecked_widget
},
TextWidget:new{
text = self.item.text or self.item.text_func(),
fgcolor = Blitbuffer.gray(item_enabled ~= false and 1.0 or 0.5),
face = self.face,
},
},
}
self[1] = self.item_frame
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
UIManager:scheduleIn(0.0, function()
self.item_frame.invert = true
UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen
end)
end)
UIManager:scheduleIn(0.1, function()
self.menu:onMenuSelect(self.item)
end)
UIManager:scheduleIn(0.5, function()
self.item_frame.invert = false
UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen
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
UIManager:scheduleIn(0.0, function()
self.item_frame.invert = true
UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen
end)
end)
UIManager:scheduleIn(0.1, function()
self.menu:onMenuHold(self.item)
end)
UIManager:scheduleIn(0.5, function()
self.item_frame.invert = false
UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen
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 = Screen:scaleBySize(2)
local icons_sep_width = icon_sep_width * (#self.icons + 1)
-- we assume all icons are of the same width
local ib = IconButton:new{icon_file = self.icons[1]}
local content_width = ib:getSize().w * #self.icons + icons_sep_width
local spacing_width = (self.width - content_width)/(#self.icons*2)
local spacing = HorizontalSpan:new{
width = math.min(spacing_width, Screen:scaleBySize(20))
}
self.height = ib:getSize().h + Screen:scaleBySize(10)
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 = {}
-- hold all icon buttons
self.icon_buttons = {}
-- 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
for k, v in ipairs(self.icons) do
local ib = IconButton:new{
show_parent = self.show_parent,
icon_file = v,
callback = nil,
}
table.insert(self.icon_widgets, HorizontalGroup:new{
spacing, ib, spacing,
})
table.insert(self.icon_buttons, ib)
-- 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
if k == 1 then
self.bar_sep = LineWidget:new{
dimen = Geom:new{
w = self.width,
h = Screen:scaleBySize(2),
},
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 = Screen:scaleBySize(2),
h = self.height,
}
}
table.insert(self.icon_seps, icon_sep)
-- 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 = i == k - 1 or i == k
self.icon_seps[i].style = current_icon and "solid" or "none"
end
self.menu:switchMenuTab(k)
end
table.insert(self.bar_icon_group, self.icon_widgets[k])
table.insert(self.bar_icon_group, icon_sep)
start_seg = _start_seg
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
self.bar_sep
},
}
self.dimen = Geom:new{ w = self.width, h = self.height }
end
function TouchMenuBar:switchToTab(index)
self.icon_buttons[index].callback()
end
--[[
TouchMenu widget for hierarchical menus
--]]
local TouchMenu = InputContainer:new{
tab_item_table = {},
-- for returnning in multi-level menus
item_table_stack = nil,
item_table = nil,
item_height = Screen:scaleBySize(50),
bordersize = Screen:scaleBySize(2),
padding = Screen:scaleBySize(5),
fface = Font:getFace("ffont", 20),
width = nil,
height = nil,
page = 1,
max_per_page = 10,
-- for UIManager:setDirty
show_parent = nil,
cur_tab = -1,
close_callback = nil,
}
function TouchMenu:init()
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.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.Close = { {"Back"}, doc = "close touch menu" }
local icons = {}
for _,v in ipairs(self.tab_item_table) do
table.insert(icons, v.icon)
end
self.bar = TouchMenuBar:new{
width = self.width - self.padding * 2 - self.bordersize * 2,
icons = icons,
show_parent = self.show_parent,
menu = self,
}
self.item_group = VerticalGroup:new{
align = "left",
}
-- 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,
show_parent = self,
}
self.page_info_right_chev = Button:new{
icon = "resources/icons/appbar.chevron.right.png",
callback = function() self:onNextPage() end,
bordersize = 0,
show_parent = self,
}
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,
}
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 - self.bordersize*2
local footer_height = up_button:getSize().h + Screen:scaleBySize(2)
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[1] = 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,
}
self.bar:switchToTab(self.last_index or 1)
-- Make sure we always show an up to date battery status when first opening the menu...
Device:getPowerDevice():refreshCapacity()
self:updateItems()
end
function TouchMenu:onCloseWidget()
UIManager:setDirty(nil, "partial", self.dimen)
end
function TouchMenu:_recalculateDimen()
self.dimen.w = self.width
-- if height not given, dynamically calculate it
if not self.height then
self.dimen.h = (#self.item_table + 2) * self.item_height
+ self.bar:getSize().h
else
self.dimen.h = self.height
end
-- make sure self.dimen.h does not overflow screen height
if self.dimen.h > Screen:getHeight() then
self.dimen.h = Screen:getHeight() - self.bar:getSize().h
end
self.perpage = math.floor(self.dimen.h / self.item_height) - 2
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)
end
function TouchMenu:updateItems()
local old_dimen = self.dimen and self.dimen:copy()
self:_recalculateDimen()
self.item_group:clear()
table.insert(self.item_group, self.bar)
local item_width = self.dimen.w - self.padding*2 - self.bordersize*2
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_tmp = TouchMenuItem:new{
item = self.item_table[i],
menu = self,
dimen = Geom:new{
w = item_width,
h = self.item_height,
},
show_parent = self.show_parent,
}
table.insert(self.item_group, item_tmp)
-- insert split line
if c ~= self.perpage then
table.insert(self.item_group, HorizontalGroup:new{
-- pad with 10 pixel to align with the up arrow in footer
HorizontalSpan:new{width = 10},
LineWidget:new{
style = "dashed",
dimen = Geom:new{
w = item_width - 20,
h = 1,
}
}
})
end
else
-- item not enough to fill the whole page, break out of loop
--table.insert(self.item_group,
--VerticalSpan:new{
--width = self.item_height
--})
--break
end -- if i <= self.items
end -- for c=1, self.perpage
table.insert(self.item_group, VerticalSpan:new{width = Screen:scaleBySize(2)})
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)
self.time_info.text = os.date("%H:%M").." @ "..(Device:getPowerDevice():isCharging() and "+" or "")..Device:getPowerDevice():getCapacity().."%"
UIManager:setDirty("all", function()
local refresh_dimen =
old_dimen and old_dimen:combine(self.dimen)
or self.dimen
return "ui", 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)
if ges_ev.direction == "west" or ges_ev.direction == "north" then
self:onNextPage()
elseif ges_ev.direction == "east" or ges_ev.direction == "south" then
self:onPrevPage()
end
end
function TouchMenu:onMenuSelect(item)
if self.touch_menu_callback then
self.touch_menu_callback()
end
if item.tap_input then
self:closeMenu()
self:onInput(item.tap_input)
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:scheduleIn(0.1, function()
callback()
if refresh then
self:updateItems()
else
self:closeMenu()
end
end)
end
else
table.insert(self.item_table_stack, self.item_table)
self.item_table = sub_item_table
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 then
self:closeMenu()
self:onInput(item.hold_input)
else
local callback = item.hold_callback
if item.hold_callback_func then
callback = item.hold_callback_func()
end
if callback then
UIManager:scheduleIn(0.1, function()
self:closeMenu()
callback()
end)
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
return TouchMenu