mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
749 lines
20 KiB
Lua
749 lines
20 KiB
Lua
local packageName = ... or "KeyboardNavigation"
|
|
local modulePath = packageName:match("(.-)[^%.]+$")
|
|
|
|
local function req(name)
|
|
return require(modulePath .. name)
|
|
end
|
|
|
|
---@class KeyboardNavigation
|
|
---@field config KeyboardNavigationConfig
|
|
local KeyboardNavigation = {
|
|
|
|
config = {
|
|
-- Global settings
|
|
enabled = true,
|
|
debugMode = false,
|
|
|
|
-- Key bindings
|
|
keys = {
|
|
next = "tab",
|
|
previous = "shifttab",
|
|
up = "up",
|
|
down = "down",
|
|
left = "left",
|
|
right = "right",
|
|
activate = { "return", "space" },
|
|
dismiss = "escape",
|
|
toggleDebug = "f12",
|
|
inspect = "i",
|
|
},
|
|
|
|
-- Navigation behavior
|
|
wrapAround = true,
|
|
directionalNavigation = true,
|
|
focusVisible = true,
|
|
autofocusOnCreate = false,
|
|
|
|
--- Drop focus after pressing Enter/Space to activate an element
|
|
--- When false, focus remains on the element after activation
|
|
dropFocusOnSelection = true,
|
|
|
|
-- Developer tools
|
|
developerTools = {
|
|
enabled = true,
|
|
showProperties = true,
|
|
highlightColor = { 1, 0.8, 0, 0.5 },
|
|
},
|
|
|
|
-- Focus indicator style
|
|
focusIndicator = {
|
|
color = { 0.2, 0.6, 1.0, 0.8 },
|
|
lineWidth = 2,
|
|
inset = -3,
|
|
borderRadius = 4,
|
|
animationDuration = 0.15,
|
|
},
|
|
},
|
|
|
|
-- State
|
|
_navigationStack = {},
|
|
_lastNavigationTime = 0,
|
|
_inspectMode = false,
|
|
_deps = nil,
|
|
|
|
-- Spatial index for directional navigation (performance optimization)
|
|
_spatialIndex = {
|
|
enabled = false,
|
|
cellSize = 100, -- Grid cell size in pixels
|
|
grid = {}, -- Grid storing element references
|
|
elementPositions = {}, -- Cache of element positions {element = {x, y, w, h}}
|
|
lastUpdateFrame = 0,
|
|
},
|
|
}
|
|
|
|
--- Initialize KeyboardNavigation module
|
|
---@param deps table {Context, Element, ErrorHandler, utils, InputEvent}
|
|
function KeyboardNavigation.init(deps)
|
|
-- Validate required dependencies
|
|
local required = { Context = true, Element = true, ErrorHandler = true, utils = true, InputEvent = true }
|
|
for depName, _ in pairs(required) do
|
|
if not deps[depName] then
|
|
error(string.format("KeyboardNavigation.init: Missing required dependency: %s", depName))
|
|
end
|
|
end
|
|
|
|
KeyboardNavigation._deps = deps
|
|
KeyboardNavigation._ErrorHandler = deps.ErrorHandler
|
|
KeyboardNavigation._InputEvent = deps.InputEvent
|
|
KeyboardNavigation._Context = deps.Context
|
|
KeyboardNavigation._Element = deps.Element
|
|
KeyboardNavigation._utils = deps.utils
|
|
end
|
|
|
|
--- Handle keyboard press for navigation
|
|
---@param key string
|
|
---@param scancode string
|
|
---@param isrepeat boolean
|
|
---@return boolean handled
|
|
function KeyboardNavigation:handleKeyPress(key, scancode, isrepeat)
|
|
if not KeyboardNavigation._Context then
|
|
return false
|
|
end
|
|
|
|
-- Debug logging
|
|
if KeyboardNavigation.config.debugMode then
|
|
print(
|
|
string.format(
|
|
"[KeyboardNavigation] Key pressed: %s (scancode: %s, repeat: %s)",
|
|
key,
|
|
scancode,
|
|
tostring(isrepeat)
|
|
)
|
|
)
|
|
print(string.format("[KeyboardNavigation] Enabled: %s", tostring(KeyboardNavigation.config.enabled)))
|
|
end
|
|
|
|
local config = KeyboardNavigation.config
|
|
local keys = config.keys
|
|
|
|
-- Check for activation keys
|
|
for _, activateKey in ipairs(keys.activate) do
|
|
if key == activateKey then
|
|
return self:activateElement()
|
|
end
|
|
end
|
|
|
|
-- Check for dismiss key
|
|
if key == keys.dismiss then
|
|
return self:dismissElement()
|
|
end
|
|
|
|
-- Check for next/previous navigation
|
|
-- Tab with shift held = previous; Tab without shift = next
|
|
if key == keys.next then
|
|
if love.keyboard.isDown("lshift") or love.keyboard.isDown("rshift") then
|
|
return self:previousFocusable()
|
|
end
|
|
return self:nextFocusable()
|
|
end
|
|
|
|
if key == keys.previous then
|
|
return self:previousFocusable()
|
|
end
|
|
|
|
-- Check for directional navigation
|
|
if config.directionalNavigation then
|
|
if key == keys.up then
|
|
return self:navigateDirectional("up")
|
|
elseif key == keys.down then
|
|
return self:navigateDirectional("down")
|
|
elseif key == keys.left then
|
|
return self:navigateDirectional("left")
|
|
elseif key == keys.right then
|
|
return self:navigateDirectional("right")
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Find next focusable element in the focusable list
|
|
---@param focusableList table<Element> List of focusable elements in tab order
|
|
---@param current Element? Currently focused element
|
|
---@return Element?
|
|
function KeyboardNavigation:_findNextInList(focusableList, current)
|
|
local currentIndex = 0
|
|
if current then
|
|
for i, elem in ipairs(focusableList) do
|
|
if elem.id == current.id then
|
|
currentIndex = i
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Search forward
|
|
if currentIndex < #focusableList then
|
|
return focusableList[currentIndex + 1]
|
|
end
|
|
|
|
-- Wrap around if enabled
|
|
if KeyboardNavigation.config.wrapAround and #focusableList > 0 then
|
|
return focusableList[1]
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- Get the focusable element list scoped to the navigation container
|
|
---@return Element[]
|
|
function KeyboardNavigation:_getScopedFocusableList()
|
|
local Context = KeyboardNavigation._Context
|
|
local container = Context.getNavigationContainer()
|
|
if container then
|
|
return container:getFocusableChildren()
|
|
end
|
|
return Context.getFocusableElements()
|
|
end
|
|
|
|
--- Navigate to next focusable element (Tab)
|
|
---@return boolean success
|
|
function KeyboardNavigation:nextFocusable()
|
|
local Context = KeyboardNavigation._Context
|
|
|
|
local current = Context.getFocused()
|
|
if KeyboardNavigation.config.debugMode then
|
|
print(
|
|
string.format("[KeyboardNavigation] Tab pressed - Current focus: %s", tostring(current and current.id or "nil"))
|
|
)
|
|
end
|
|
|
|
local focusableList = self:_getScopedFocusableList()
|
|
local nextElem = self:_findNextInList(focusableList, current)
|
|
|
|
if nextElem then
|
|
self:_focusElement(nextElem)
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Find previous focusable element in the focusable list
|
|
---@param focusableList table<Element> List of focusable elements in tab order
|
|
---@param current Element? Currently focused element
|
|
---@return Element?
|
|
function KeyboardNavigation:_findPreviousInList(focusableList, current)
|
|
local currentIndex = #focusableList + 1
|
|
if current then
|
|
for i, elem in ipairs(focusableList) do
|
|
if elem.id == current.id then
|
|
currentIndex = i
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Search backward
|
|
if currentIndex - 1 >= 1 then
|
|
return focusableList[currentIndex - 1]
|
|
end
|
|
|
|
-- Wrap around if enabled
|
|
if KeyboardNavigation.config.wrapAround and #focusableList > 0 then
|
|
return focusableList[#focusableList]
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- Navigate to previous focusable element (Shift+Tab)
|
|
---@return boolean success
|
|
function KeyboardNavigation:previousFocusable()
|
|
local Context = KeyboardNavigation._Context
|
|
|
|
local current = Context.getFocused()
|
|
|
|
local focusableList = self:_getScopedFocusableList()
|
|
local prevElem = self:_findPreviousInList(focusableList, current)
|
|
|
|
if prevElem then
|
|
self:_focusElement(prevElem)
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Navigate using arrow keys
|
|
---@param direction "up"|"down"|"left"|"right"
|
|
---@return boolean success
|
|
function KeyboardNavigation:navigateDirectional(direction)
|
|
local Context = KeyboardNavigation._Context
|
|
local current = Context.getFocused()
|
|
|
|
if not current then
|
|
return false
|
|
end
|
|
|
|
local nextElem = KeyboardNavigation:_findDirectionalNeighbor(current, direction)
|
|
|
|
if nextElem then
|
|
self:_focusElement(nextElem)
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Find closest focusable element in the given direction
|
|
---@param current Element
|
|
---@param direction "up"|"down"|"left"|"right"
|
|
---@return Element?
|
|
function KeyboardNavigation:_findDirectionalNeighbor(current, direction)
|
|
-- Try spatial index first if enabled
|
|
if KeyboardNavigation._spatialIndex.enabled then
|
|
local spatialResult = self:_findDirectionalNeighborSpatial(current, direction)
|
|
if spatialResult then
|
|
return spatialResult
|
|
end
|
|
end
|
|
|
|
-- Collect all focusable elements visible this frame
|
|
local Context = KeyboardNavigation._Context
|
|
local focusable = {}
|
|
|
|
local function collectFocusable(elem)
|
|
if elem:isFocusable() and elem ~= current then
|
|
table.insert(focusable, elem)
|
|
end
|
|
for _, child in ipairs(elem.children) do
|
|
collectFocusable(child)
|
|
end
|
|
end
|
|
|
|
-- Mode-agnostic: collect from Context's focusable list
|
|
local allFocusable = Context.getFocusableElements()
|
|
for _, elem in ipairs(allFocusable) do
|
|
if elem ~= current then
|
|
table.insert(focusable, elem)
|
|
end
|
|
end
|
|
|
|
if #focusable == 0 then
|
|
return nil
|
|
end
|
|
|
|
local currentRect = {
|
|
x = current.x,
|
|
y = current.y,
|
|
width = current.width or 0,
|
|
height = current.height or 0,
|
|
}
|
|
|
|
local closest = nil
|
|
local closestDistance = math.huge
|
|
|
|
for _, elem in ipairs(focusable) do
|
|
local elemRect = {
|
|
x = elem.x,
|
|
y = elem.y,
|
|
width = elem.width or 0,
|
|
height = elem.height or 0,
|
|
}
|
|
|
|
local distance, isInDirection = self:_calculateDirectionalDistance(currentRect, elemRect, direction)
|
|
|
|
if isInDirection and distance < closestDistance then
|
|
closest = elem
|
|
closestDistance = distance
|
|
end
|
|
end
|
|
|
|
-- If no element found in exact direction, try with looser criteria
|
|
if not closest then
|
|
closest = self:_findClosestInDirection(current, focusable, direction)
|
|
end
|
|
|
|
return closest
|
|
end
|
|
|
|
--- Calculate distance and direction between elements
|
|
---@param from table {x, y, width, height}
|
|
---@param to table {x, y, width, height}
|
|
---@param direction string
|
|
---@return number distance, boolean isInDirection
|
|
function KeyboardNavigation:_calculateDirectionalDistance(from, to, direction)
|
|
-- Calculate bounding box edges
|
|
local fromLeft = from.x
|
|
local fromRight = from.x + from.width
|
|
local fromTop = from.y
|
|
local fromBottom = from.y + from.height
|
|
|
|
local toLeft = to.x
|
|
local toRight = to.x + to.width
|
|
local toTop = to.y
|
|
local toBottom = to.y + to.height
|
|
|
|
local distance = math.huge
|
|
local isInDirection = false
|
|
|
|
if direction == "up" then
|
|
if toBottom < fromTop then
|
|
isInDirection = true
|
|
distance = fromTop - toBottom
|
|
end
|
|
elseif direction == "down" then
|
|
if toTop > fromBottom then
|
|
isInDirection = true
|
|
distance = toTop - fromBottom
|
|
end
|
|
elseif direction == "left" then
|
|
if toRight < fromLeft then
|
|
isInDirection = true
|
|
distance = fromLeft - toRight
|
|
end
|
|
elseif direction == "right" then
|
|
if toLeft > fromRight then
|
|
isInDirection = true
|
|
distance = toLeft - fromRight
|
|
end
|
|
end
|
|
|
|
return distance, isInDirection
|
|
end
|
|
|
|
--- Find closest element in direction using center-to-center distance
|
|
---@param current Element
|
|
---@param focusable Element[]
|
|
---@param direction string
|
|
---@return Element?
|
|
function KeyboardNavigation:_findClosestInDirection(current, focusable, direction)
|
|
local currentCenterX = current.x + (current.width or 0) / 2
|
|
local currentCenterY = current.y + (current.height or 0) / 2
|
|
|
|
local closest = nil
|
|
local closestDistance = math.huge
|
|
|
|
for _, elem in ipairs(focusable) do
|
|
if elem ~= current then
|
|
local elemCenterX = elem.x + (elem.width or 0) / 2
|
|
local elemCenterY = elem.y + (elem.height or 0) / 2
|
|
|
|
local dx = elemCenterX - currentCenterX
|
|
local dy = elemCenterY - currentCenterY
|
|
|
|
-- Check if element is generally in the right direction
|
|
local isInDirection = false
|
|
|
|
if direction == "up" and dy < 0 then
|
|
isInDirection = true
|
|
elseif direction == "down" and dy > 0 then
|
|
isInDirection = true
|
|
elseif direction == "left" and dx < 0 then
|
|
isInDirection = true
|
|
elseif direction == "right" and dx > 0 then
|
|
isInDirection = true
|
|
end
|
|
|
|
if isInDirection then
|
|
local distance = math.sqrt(dx * dx + dy * dy)
|
|
if distance < closestDistance then
|
|
closest = elem
|
|
closestDistance = distance
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return closest
|
|
end
|
|
|
|
--- Focus an element
|
|
---@param element Element
|
|
function KeyboardNavigation:_focusElement(element)
|
|
local Context = KeyboardNavigation._Context
|
|
|
|
if element and element:isFocusable() then
|
|
if KeyboardNavigation.config.debugMode then
|
|
print(
|
|
string.format(
|
|
"[KeyboardNavigation] Focusing element: %s (id: %s)",
|
|
element.themeComponent or "unknown",
|
|
tostring(element.id)
|
|
)
|
|
)
|
|
end
|
|
Context.setFocused(element)
|
|
|
|
-- Update focus indicator
|
|
if KeyboardNavigation.FocusIndicator then
|
|
KeyboardNavigation.FocusIndicator.setFocused(element)
|
|
end
|
|
|
|
-- Call onFocus callback if it exists
|
|
if element.onFocus then
|
|
local success, err = pcall(function()
|
|
if element.onFocusDeferred then
|
|
table.insert(Context._deferredCallbacks or {}, function()
|
|
element:onFocus(element)
|
|
end)
|
|
else
|
|
element:onFocus(element)
|
|
end
|
|
end)
|
|
|
|
if not success then
|
|
KeyboardNavigation._ErrorHandler:warn("KeyboardNavigation", "NAV_001", {
|
|
elementId = element.id or "unknown",
|
|
error = tostring(err),
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param element Element
|
|
---@return boolean
|
|
function KeyboardNavigation:_shouldDropFocusOnSelection(element)
|
|
if element and element.dropFocusOnSelection ~= nil then
|
|
return element.dropFocusOnSelection == true
|
|
end
|
|
|
|
return KeyboardNavigation.config.dropFocusOnSelection == true
|
|
end
|
|
|
|
--- Activate currently focused element
|
|
---@return boolean success
|
|
function KeyboardNavigation:activateElement()
|
|
local Context = KeyboardNavigation._Context
|
|
local focused = Context.getFocused()
|
|
|
|
if not focused then
|
|
return false
|
|
end
|
|
|
|
if focused.disabled then
|
|
return false
|
|
end
|
|
|
|
-- Fire press and release events
|
|
if focused.onEvent then
|
|
local modifiers = KeyboardNavigation._utils.getModifiers()
|
|
local pressEvent = KeyboardNavigation._InputEvent.new({
|
|
type = "press",
|
|
button = 1,
|
|
x = focused.x,
|
|
y = focused.y,
|
|
modifiers = modifiers,
|
|
clickCount = 1,
|
|
})
|
|
|
|
local releaseEvent = KeyboardNavigation._InputEvent.new({
|
|
type = "release",
|
|
button = 1,
|
|
x = focused.x,
|
|
y = focused.y,
|
|
modifiers = modifiers,
|
|
clickCount = 1,
|
|
})
|
|
|
|
local success, err = pcall(function()
|
|
focused.onEvent(focused, pressEvent)
|
|
focused.onEvent(focused, releaseEvent)
|
|
end)
|
|
|
|
if not success then
|
|
KeyboardNavigation._ErrorHandler:warn("KeyboardNavigation", "NAV_002", {
|
|
elementId = focused.id or "unknown",
|
|
error = tostring(err),
|
|
})
|
|
end
|
|
|
|
-- Drop focus after selection based on per-element override or global config.
|
|
if KeyboardNavigation:_shouldDropFocusOnSelection(focused) then
|
|
Context.clearFocus()
|
|
if KeyboardNavigation.FocusIndicator then
|
|
KeyboardNavigation.FocusIndicator.setFocused(nil)
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Dismiss currently focused element
|
|
---@return boolean success
|
|
function KeyboardNavigation:dismissElement()
|
|
local Context = KeyboardNavigation._Context
|
|
local focused = Context.getFocused()
|
|
|
|
if not focused then
|
|
return false
|
|
end
|
|
|
|
-- Check if element has a dismiss handler
|
|
if focused.onDismiss then
|
|
local success, err = pcall(function()
|
|
if focused.onDismissDeferred then
|
|
table.insert(Context._deferredCallbacks or {}, function()
|
|
focused:onDismiss(focused)
|
|
end)
|
|
else
|
|
focused:onDismiss(focused)
|
|
end
|
|
end)
|
|
|
|
if not success then
|
|
KeyboardNavigation._ErrorHandler:warn("KeyboardNavigation", "NAV_003", {
|
|
elementId = focused.id or "unknown",
|
|
error = tostring(err),
|
|
})
|
|
end
|
|
|
|
return true -- Handler took care of dismissal
|
|
end
|
|
|
|
-- Default behavior: blur the element (only if no onDismiss handler)
|
|
Context.clearFocus()
|
|
return true
|
|
end
|
|
|
|
--- Update keyboard navigation (for animations, etc.)
|
|
---@param dt number
|
|
function KeyboardNavigation:update(dt)
|
|
-- Update focus indicator if it exists
|
|
if KeyboardNavigation.FocusIndicator then
|
|
KeyboardNavigation.FocusIndicator:update(dt)
|
|
end
|
|
end
|
|
|
|
--- Push current focus onto stack (for modals/dialogs)
|
|
--- Saves current focus and sets new focus to the given element
|
|
---@param element Element? The element to focus (e.g., modal dialog)
|
|
function KeyboardNavigation:pushFocus(element)
|
|
local Context = KeyboardNavigation._Context
|
|
|
|
table.insert(KeyboardNavigation._navigationStack, Context.getFocused())
|
|
Context.pushFocusStack(element)
|
|
end
|
|
|
|
--- Pop focus from stack (return from modal)
|
|
--- Restores previously focused element from the stack
|
|
---@return Element? The previously focused element, or nil if stack was empty
|
|
function KeyboardNavigation:popFocus()
|
|
local Context = KeyboardNavigation._Context
|
|
|
|
local previous = Context.popFocusStack()
|
|
if #KeyboardNavigation._navigationStack > 0 then
|
|
previous = table.remove(KeyboardNavigation._navigationStack)
|
|
end
|
|
|
|
return previous
|
|
end
|
|
|
|
-- ====================
|
|
-- Spatial Index (Performance Optimization)
|
|
-- ====================
|
|
|
|
--- Enable spatial index for faster directional navigation
|
|
---@param enabled boolean
|
|
function KeyboardNavigation.enableSpatialIndex(enabled)
|
|
KeyboardNavigation._spatialIndex.enabled = enabled
|
|
if not enabled then
|
|
KeyboardNavigation:_clearSpatialIndex()
|
|
end
|
|
end
|
|
|
|
--- Clear spatial index
|
|
function KeyboardNavigation:_clearSpatialIndex()
|
|
KeyboardNavigation._spatialIndex.grid = {}
|
|
KeyboardNavigation._spatialIndex.elementPositions = {}
|
|
end
|
|
|
|
--- Find directional neighbor using spatial index
|
|
---@param current Element
|
|
---@param direction "up"|"down"|"left"|"right"
|
|
---@return Element?
|
|
function KeyboardNavigation:_findDirectionalNeighborSpatial(current, direction)
|
|
local index = KeyboardNavigation._spatialIndex
|
|
local cellSize = index.cellSize
|
|
|
|
-- Get current element's grid position
|
|
local currentPos = index.elementPositions[current]
|
|
if not currentPos then
|
|
return nil
|
|
end
|
|
|
|
local centerX = currentPos.x + currentPos.w / 2
|
|
local centerY = currentPos.y + currentPos.h / 2
|
|
local currentCellX = math.floor(centerX / cellSize)
|
|
local currentCellY = math.floor(centerY / cellSize)
|
|
|
|
-- Search in direction, expanding outward
|
|
local maxSearchRadius = 20 -- Maximum cells to search
|
|
local visited = {}
|
|
|
|
for radius = 1, maxSearchRadius do
|
|
local candidates = {}
|
|
|
|
-- Get cells in the search ring
|
|
if direction == "up" then
|
|
table.insert(candidates, { currentCellX, currentCellY - radius })
|
|
if radius > 1 then
|
|
table.insert(candidates, { currentCellX - 1, currentCellY - radius })
|
|
table.insert(candidates, { currentCellX + 1, currentCellY - radius })
|
|
end
|
|
elseif direction == "down" then
|
|
table.insert(candidates, { currentCellX, currentCellY + radius })
|
|
if radius > 1 then
|
|
table.insert(candidates, { currentCellX - 1, currentCellY + radius })
|
|
table.insert(candidates, { currentCellX + 1, currentCellY + radius })
|
|
end
|
|
elseif direction == "left" then
|
|
table.insert(candidates, { currentCellX - radius, currentCellY })
|
|
if radius > 1 then
|
|
table.insert(candidates, { currentCellX - radius, currentCellY - 1 })
|
|
table.insert(candidates, { currentCellX - radius, currentCellY + 1 })
|
|
end
|
|
elseif direction == "right" then
|
|
table.insert(candidates, { currentCellX + radius, currentCellY })
|
|
if radius > 1 then
|
|
table.insert(candidates, { currentCellX + radius, currentCellY - 1 })
|
|
table.insert(candidates, { currentCellX + radius, currentCellY + 1 })
|
|
end
|
|
end
|
|
|
|
-- Check each candidate cell
|
|
for _, cell in ipairs(candidates) do
|
|
local cellKey = string.format("%d,%d", cell[1], cell[2])
|
|
local cellElements = index.grid[cellKey]
|
|
|
|
if cellElements then
|
|
for _, elem in ipairs(cellElements) do
|
|
if elem ~= current and not visited[elem] then
|
|
visited[elem] = true
|
|
local elemPos = index.elementPositions[elem]
|
|
if elemPos then
|
|
local elemCenterX = elemPos.x + elemPos.w / 2
|
|
local elemCenterY = elemPos.y + elemPos.h / 2
|
|
|
|
-- Check if element is in the correct direction
|
|
local isInDirection = false
|
|
if direction == "up" and elemCenterY < centerY then
|
|
isInDirection = true
|
|
elseif direction == "down" and elemCenterY > centerY then
|
|
isInDirection = true
|
|
elseif direction == "left" and elemCenterX < centerX then
|
|
isInDirection = true
|
|
elseif direction == "right" and elemCenterX > centerX then
|
|
isInDirection = true
|
|
end
|
|
|
|
if isInDirection then
|
|
return elem
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
return KeyboardNavigation
|