mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
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.
614 lines
18 KiB
Lua
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
|