mirror of
https://github.com/koreader/koreader
synced 2024-10-31 21:20:20 +00:00
3066c86e38
This is a major overhaul of the hardware abstraction layer. A few notes: General platform distinction happens in frontend/device.lua which will delegate everything else to frontend/device/<platform_name>/device.lua which should extend frontend/device/generic/device.lua Screen handling is implemented in frontend/device/screen.lua which includes the *functionality* to support device specifics. Actually setting up the device specific functionality, however, is done in the device specific setup code in the relevant device.lua file. The same goes for input handling.
617 lines
18 KiB
Lua
617 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 InputDialog = require("ui/widget/inputdialog")
|
|
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 DEBUG = require("dbg")
|
|
local _ = require("gettext")
|
|
local NetworkMgr = require("ui/networkmgr")
|
|
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
|
|
|
|
self.item_frame.invert = true
|
|
UIManager:setDirty(self.show_parent, "partial")
|
|
UIManager:scheduleIn(0.5, function()
|
|
self.item_frame.invert = false
|
|
UIManager:setDirty(self.show_parent, "partial")
|
|
end)
|
|
self.menu:onMenuSelect(self.item)
|
|
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
|
|
|
|
self.item_frame.invert = true
|
|
UIManager:setDirty(self.show_parent, "partial")
|
|
UIManager:scheduleIn(0.5, function()
|
|
self.item_frame.invert = false
|
|
UIManager:setDirty(self.show_parent, "partial")
|
|
end)
|
|
self.menu:onMenuHold(self.item)
|
|
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:scaleByDPI(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:scaleByDPI(20))
|
|
}
|
|
self.height = ib:getSize().h + Screen:scaleByDPI(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 = {}
|
|
-- 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,
|
|
})
|
|
|
|
-- 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:scaleByDPI(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:scaleByDPI(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
|
|
|
|
|
|
--[[
|
|
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:scaleByDPI(50),
|
|
bordersize = Screen:scaleByDPI(2),
|
|
padding = Screen:scaleByDPI(5),
|
|
footer_height = 48 + Screen:scaleByDPI(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()
|
|
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,
|
|
}
|
|
if Device:isKindle() or Device:isKobo() then
|
|
self.net_info = Button:new{
|
|
icon = "resources/icons/appbar.wifi.png",
|
|
callback = function() self:netToggle() end,
|
|
bordersize = 0,
|
|
show_parent = self,
|
|
}
|
|
self.net_info.label_widget.dim = not NetworkMgr:getWifiStatus()
|
|
self.device_info = HorizontalGroup:new{
|
|
self.time_info,
|
|
self.net_info,
|
|
}
|
|
else
|
|
self.device_info = HorizontalGroup:new{
|
|
self.time_info,
|
|
}
|
|
end
|
|
local footer_width = self.width - self.padding*2 - self.bordersize*2
|
|
self.footer = HorizontalGroup:new{
|
|
LeftContainer:new{
|
|
dimen = Geom:new{ w = footer_width*0.33, h = self.footer_height},
|
|
IconButton:new{
|
|
invert = true,
|
|
icon_file = "resources/icons/appbar.chevron.up.png",
|
|
show_parent = self.show_parent,
|
|
callback = function()
|
|
self:backToUpperMenu()
|
|
end,
|
|
},
|
|
},
|
|
CenterContainer:new{
|
|
dimen = Geom:new{ w = footer_width*0.33, h = self.footer_height},
|
|
self.page_info,
|
|
},
|
|
RightContainer:new{
|
|
dimen = Geom:new{ w = footer_width*0.33, h = self.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:switchMenuTab(1)
|
|
self:updateItems()
|
|
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()
|
|
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:scaleByDPI(2)})
|
|
table.insert(self.item_group, self.footer)
|
|
self.page_info_text.text = _("Page ")..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():getCapacity().."%"
|
|
-- FIXME: this is a dirty hack to clear previous menus
|
|
-- refert to issue #664
|
|
UIManager.repaint_all = true
|
|
end
|
|
|
|
function TouchMenu:netToggle()
|
|
if NetworkMgr:getWifiStatus() == true then
|
|
NetworkMgr:promptWifiOff()
|
|
else
|
|
NetworkMgr:promptWifiOn()
|
|
end
|
|
end
|
|
|
|
function TouchMenu:switchMenuTab(tab_num)
|
|
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()
|
|
end
|
|
end
|
|
|
|
function TouchMenu:closeMenu()
|
|
self.close_callback()
|
|
end
|
|
|
|
function TouchMenu:onNextPage()
|
|
if self.page < self.page_num then
|
|
self.page = self.page + 1
|
|
self:updateItems()
|
|
end
|
|
return true
|
|
end
|
|
|
|
function TouchMenu:onPrevPage()
|
|
if self.page > 1 then
|
|
self.page = self.page - 1
|
|
self:updateItems()
|
|
end
|
|
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 item.tap_input then
|
|
self:closeMenu()
|
|
self:onMenuInput(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
|
|
local callback = item.callback
|
|
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()
|
|
self:closeMenu()
|
|
callback()
|
|
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 item.hold_input then
|
|
self:closeMenu()
|
|
self:onMenuInput(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:onMenuInput(input)
|
|
self.input_dialog = InputDialog:new{
|
|
title = input.title or "",
|
|
input_hint = input.hint or "",
|
|
input_type = input.type or "number",
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
callback = function()
|
|
self:closeInputDialog()
|
|
end,
|
|
},
|
|
{
|
|
text = _("OK"),
|
|
callback = function()
|
|
input.callback(self.input_dialog:getInputText())
|
|
self:closeInputDialog()
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
enter_callback = function()
|
|
input.callback(self.input_dialog:getInputText())
|
|
self:closeInputDialog()
|
|
end,
|
|
width = Screen:getWidth() * 0.8,
|
|
height = Screen:getHeight() * 0.2,
|
|
}
|
|
self.input_dialog:onShowKeyboard()
|
|
UIManager:show(self.input_dialog)
|
|
end
|
|
|
|
function TouchMenu:closeInputDialog()
|
|
self.input_dialog:onClose()
|
|
UIManager:close(self.input_dialog)
|
|
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
|