mirror of
https://codeberg.org/1414codeforge/yui.git
synced 2025-06-05 22:19:11 +02:00
[*] General code improvement.
* Rework navigation allowing direct management and triggering for grabkeyboard widgets. * Navigation code now behaves better with deeply nested layouts. * Allow widget hierarchies deeper than 2 (__call() is implemented for every Widget metatable). * Make BASE locals more secure (match '<filename>$' in regexp)
This commit is contained in:
156
ui.lua
156
ui.lua
@ -2,6 +2,8 @@ local BASE = (...):gsub('ui$', '')
|
||||
|
||||
local Widget = require(BASE..'widget')
|
||||
local Layout = require(BASE..'layout')
|
||||
local Columns = require(BASE..'columns')
|
||||
local Rows = require(BASE..'rows')
|
||||
|
||||
local gear = require 'lib.gear'
|
||||
|
||||
@ -50,24 +52,6 @@ local function resolveautofocus(widget)
|
||||
return firstfocus, cancelfocus
|
||||
end
|
||||
|
||||
local function propagateaction(ui, action)
|
||||
local focused = ui.focused
|
||||
if focused.grabkeyboard then
|
||||
-- A widget stealing input
|
||||
-- explicitly consumes any action.
|
||||
return true
|
||||
end
|
||||
while focused ~= nil do
|
||||
if focused:onActionInput(action) then
|
||||
return true -- action consumed
|
||||
end
|
||||
|
||||
focused = focused.parent
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function Ui.new(args)
|
||||
local self = setmetatable(args, Ui)
|
||||
assert(#self == 1, "Ui.new() must have exactly one root widget.")
|
||||
@ -98,7 +82,7 @@ function Ui.new(args)
|
||||
local firstfocus, cancelfocus = resolveautofocus(root)
|
||||
if firstfocus == nil then
|
||||
firstfocus = isinstance(root, Layout) and
|
||||
root:firstFocusableWidget() or
|
||||
root:first() or
|
||||
root
|
||||
end
|
||||
|
||||
@ -138,28 +122,136 @@ function Ui:textedited(text, start, length)
|
||||
end
|
||||
end
|
||||
|
||||
function Ui:update(dt)
|
||||
local root = self[1]
|
||||
local x,y,w,h = root.x,root.y,root.w,root.h
|
||||
local snap = self.device:snapshot()
|
||||
local function actionpropagate(widget, action)
|
||||
while widget ~= nil do
|
||||
if widget:onActionInput(action) then
|
||||
return true -- action consumed
|
||||
end
|
||||
|
||||
self.timer:update(dt)
|
||||
widget = widget.parent
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local navs = { 'cancel', 'up', 'down', 'left', 'right' }
|
||||
|
||||
local function globalactions(ui, snap)
|
||||
for _,nav in ipairs(navs) do
|
||||
if snap[nav] then
|
||||
ui:navigate(nav)
|
||||
break -- discard other directions, if any
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function eventpropagate(ui, snap)
|
||||
-- 1. Pointer events
|
||||
if snap.pointer and not ui.grabpointer then
|
||||
local root = ui[1]
|
||||
local x,y,w,h = root.x,root.y,root.w,root.h
|
||||
|
||||
-- Propagate pointer events in focus order
|
||||
if self.pointerActive then
|
||||
if snap.pointer and pointinrect(snap.px,snap.py, x,y,w,h) then
|
||||
root:onPointerInput(snap.px,snap.py, snap.clicked, snap.pointing)
|
||||
end
|
||||
end
|
||||
|
||||
-- Propagate actions from focused widget up
|
||||
if snap.action and not propagateaction(self, snap) then
|
||||
-- Take global actions if nobody consumed the event
|
||||
if snap.cancel and self.cancelfocus then
|
||||
-- Focus on the last widget with 'cancelfocus'
|
||||
self.cancelfocus:grabFocus()
|
||||
-- 2. Actions (keyboard/buttons)
|
||||
if snap.action and not ui.grabkeyboard then
|
||||
local consumed = actionpropagate(ui.focused, snap)
|
||||
|
||||
if not consumed then
|
||||
-- 3. If no widget consumed action,
|
||||
-- take global actions (e.g. navigation).
|
||||
globalactions(ui, snap)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- cls may be Rows, Columns or Layout in general
|
||||
local function containerof(widget, cls)
|
||||
repeat
|
||||
widget = widget.parent
|
||||
if isinstance(widget, cls) then
|
||||
return widget
|
||||
end
|
||||
until widget == nil
|
||||
end
|
||||
|
||||
local function findprev(ui, cls, widget)
|
||||
local child = widget
|
||||
|
||||
while true do
|
||||
local container = containerof(child, cls)
|
||||
if container == nil then
|
||||
-- All the way up, either return the original
|
||||
-- widget or wraparound to the bottom
|
||||
return isinstance(child, cls) and child:last() or widget
|
||||
end
|
||||
|
||||
local w = container:before(child)
|
||||
if w ~= nil then
|
||||
return w
|
||||
end
|
||||
|
||||
child = container -- move up into the hierarchy
|
||||
end
|
||||
end
|
||||
|
||||
-- Specular to findprev()
|
||||
local function findnext(ui, cls, widget)
|
||||
local child = widget
|
||||
|
||||
while true do
|
||||
local container = containerof(child, cls)
|
||||
if container == nil then
|
||||
return isinstance(child, cls) and child:first() or widget
|
||||
end
|
||||
|
||||
local w = container:after(child)
|
||||
if w ~= nil then
|
||||
return w
|
||||
end
|
||||
|
||||
child = container
|
||||
end
|
||||
end
|
||||
|
||||
--- Move focus to the given direction.
|
||||
--
|
||||
-- @param where (string) Direction to move to,
|
||||
-- one of: 'up', 'down', 'left', 'right', 'cancel'.
|
||||
function Ui:navigate(where)
|
||||
local nextfocus = nil
|
||||
|
||||
if where == 'cancel' then
|
||||
nextfocus = self.cancelfocus
|
||||
elseif where == 'up' then
|
||||
nextfocus = findprev(self, Rows, self.focused)
|
||||
elseif where == 'down' then
|
||||
nextfocus = findnext(self, Rows, self.focused)
|
||||
elseif where == 'left' then
|
||||
nextfocus = findprev(self, Columns, self.focused)
|
||||
elseif where == 'right' then
|
||||
nextfocus = findnext(self, Columns, self.focused)
|
||||
else
|
||||
error("Bad direction: "..tostring(where))
|
||||
end
|
||||
|
||||
if nextfocus ~= nil then
|
||||
nextfocus:grabFocus()
|
||||
end
|
||||
end
|
||||
|
||||
function Ui:update(dt)
|
||||
local root = self[1]
|
||||
local snap = self.device:snapshot()
|
||||
|
||||
-- Update timer related effects
|
||||
self.timer:update(dt)
|
||||
|
||||
-- Regular event propagation
|
||||
eventpropagate(self, snap)
|
||||
|
||||
-- Perform regular lifetime updates
|
||||
root:update(dt)
|
||||
|
Reference in New Issue
Block a user