2
0
mirror of https://github.com/koreader/koreader synced 2024-11-13 19:11:25 +00:00
koreader/frontend/ui/widget/focusmanager.lua
NiLuJe 8189945be9
A few graphics fixes after #4541 (#4554)
* Various FocusManager related tweaks to limit its usage to devices with a DPad, and prevent initial button highlights in Dialogs on devices where it makes no sense (i.e., those without a DPad. And even on DPad devices, I'm not even sure how we'd go about making one of those pop up anyway, because no Touch ;)!).
* One mysterious fix to text-only Buttons so that the flash_ui highlight always works, and always honors `FrameContainer`'s pill shape. (Before that, an unhighlight on a text button with a callback that didn't repaint anything [say, the find first/find last buttons in the Reader's search bar when you're already on the first/last match] would do a square black highlight, and a white pill-shaped unhighlight (leaving the black corners visible)).
The workaround makes *absolutely* no sense to me (as `self[1] -> self.frame`, AFAICT), but it works, and ensures all highlights/unhighlights are pill-shaped, so at least we're not doing maths for rounded corners for nothing ;).
2019-02-08 00:56:32 +01:00

142 lines
5.0 KiB
Lua

local Device = require("device")
local Event = require("ui/event")
local InputContainer = require("ui/widget/container/inputcontainer")
local logger = require("logger")
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, item },
{ okbutton, cancelbutton, item },
{ nil, item, nil },
{ nil, item, nil },
{ nil, item, nil },
}
Navigate the layout by trying to avoid not set or nil value.
Provide a simple wrap around in the vertical direction.
The first element of the first table must be valid to ensure
to not get stuck in an invalid position.
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
if Device:hasDPad() then
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
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 not self.layout[self.selected.y + dy] then
--horizontal border, try to wraparound
if not self:_wrapAround(dy) then
break
end
elseif not self.layout[self.selected.y + dy][self.selected.x] then
--inner horizontal border, trying to be clever and step down
if not self:_verticalStep(dy) then
break
end
elseif not self.layout[self.selected.y + dy][self.selected.x + dx] then
--vertical border, no wraparound
break
else
self.selected.y = self.selected.y + dy
self.selected.x = self.selected.x + dx
end
logger.dbg("Cursor position : ".. self.selected.y .." : "..self.selected.x)
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 fast repaint, this does not count toward a flashing eink refresh
-- NOTE: Ideally, we'd only have to repaint the specific subwidget we're highlighting,
-- but we may not know its exact coordinates, so, redraw the parent widget instead.
UIManager:setDirty(self.show_parent or self, "fast")
break
end
end
return true
end
function FocusManager:_wrapAround(dy)
--go to the last valid item directly above or below the current item
--return false if none could be found
local y = self.selected.y
while self.layout[y - dy] do
y = y - dy
end
if y ~= self.selected.y then
self.selected.y = y
if not self.layout[self.selected.y][self.selected.x] then
--call verticalStep on the current line to perform the search
return self:_verticalStep(0)
end
return true
else
return false
end
end
function FocusManager:_verticalStep(dy)
local x = self.selected.x
if type(self.layout[self.selected.y + dy]) ~= "table" or self.layout[self.selected.y + dy] == {} then
logger.err("[FocusManager] : Malformed layout")
return false
end
--looking for the item on the line below, the closest on the left side
while not self.layout[self.selected.y + dy][x] do
x = x - 1
if x == 0 then
--if he is not on the left, must be on the right
x = self.selected.x
while not self.layout[self.selected.y + dy][x] do
x = x + 1
end
end
end
self.selected.x = x
self.selected.y = self.selected.y + dy
return true
end
function FocusManager:getFocusItem()
return self.layout[self.selected.y][self.selected.x]
end
return FocusManager