2
0
mirror of https://github.com/koreader/koreader synced 2024-10-31 21:20:20 +00:00
koreader/frontend/ui/widget/focusmanager.lua
2015-09-13 01:10:09 -07:00

109 lines
3.5 KiB
Lua

local InputContainer = require("ui/widget/container/inputcontainer")
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
--[[
Wrapper Widget that manages focus for a whole dialog
supports a 2D model of active elements
e.g.:
layout = {
{ textinput, textinput },
{ okbutton, cancelbutton }
}
this is a dialog with 2 rows. in the top row, there is the
single (!) widget <textinput>. when the focus is in this
group, left/right movement seems (!) to be doing nothing.
in the second row, there are two widgets and you can move
left/right. also, you can go up from both to reach <textinput>,
and from that go down and (depending on internat coordinates)
reach either <okbutton> or <cancelbutton>.
but notice that this does _not_ do the layout for you,
it rather defines an abstract layout.
]]
local FocusManager = InputContainer:new{
selected = nil, -- defaults to x=1, y=1
layout = nil, -- mandatory
movement_allowed = { x = true, y = true }
}
function FocusManager:init()
if not self.selected then
self.selected = { x = 1, y = 1 }
end
self.key_events = {
-- these will all generate the same event, just with different arguments
FocusUp = { {"Up"}, doc = "move focus up", event = "FocusMove", args = {0, -1} },
FocusDown = { {"Down"}, doc = "move focus down", event = "FocusMove", args = {0, 1} },
FocusLeft = { {"Left"}, doc = "move focus left", event = "FocusMove", args = {-1, 0} },
FocusRight = { {"Right"}, doc = "move focus right", event = "FocusMove", args = {1, 0} },
}
end
function FocusManager:onFocusMove(args)
local dx, dy = unpack(args)
if (dx ~= 0 and not self.movement_allowed.x)
or (dy ~= 0 and not self.movement_allowed.y) then
return true
end
if not self.layout or not self.layout[self.selected.y] or not self.layout[self.selected.y][self.selected.x] then
return true
end
local current_item = self.layout[self.selected.y][self.selected.x]
while true do
if self.selected.x + dx > #self.layout[self.selected.y]
or self.selected.x + dx < 1 then
break -- abort when we run into horizontal borders
end
-- call widget wrap callbacks in vertical direction
if self.selected.y + dy > #self.layout then
if not self:onWrapLast() then
break
end
elseif self.selected.y + dy < 1 then
if not self:onWrapFirst() then
break
end
else
self.selected.y = self.selected.y + dy
end
self.selected.x = self.selected.x + dx
if self.layout[self.selected.y][self.selected.x] ~= current_item
or not self.layout[self.selected.y][self.selected.x].is_inactive then
-- we found a different object to focus
current_item:handleEvent(Event:new("Unfocus"))
self.layout[self.selected.y][self.selected.x]:handleEvent(Event:new("Focus"))
-- trigger a repaint (we need to be the registered widget!)
-- TODO: is this really needed?
UIManager:setDirty(self.show_parent or self, "partial")
break
end
end
return true
end
function FocusManager:onWrapFirst()
self.selected.y = #self.layout
return true
end
function FocusManager:onWrapLast()
self.selected.y = 1
return true
end
function FocusManager:getFocusItem()
return self.layout[self.selected.y][self.selected.x]
end
return FocusManager