2022-08-16 09:49:37 +02:00
|
|
|
local BASE = (...):gsub('ui$', '')
|
2022-08-15 23:41:17 +02:00
|
|
|
|
2022-08-16 00:23:52 +02:00
|
|
|
local Widget = require(BASE..'widget')
|
|
|
|
local Layout = require(BASE..'layout')
|
2022-08-15 23:41:17 +02:00
|
|
|
|
|
|
|
local gear = require 'lib.gear'
|
|
|
|
|
2022-08-16 11:24:41 +02:00
|
|
|
local Timer = gear.Timer
|
2022-08-15 23:41:17 +02:00
|
|
|
local isinstance = gear.meta.isinstance
|
|
|
|
local pointinrect = gear.rect.pointinside
|
|
|
|
|
|
|
|
local Ui = {}
|
|
|
|
Ui.__index = Ui
|
|
|
|
|
|
|
|
|
|
|
|
-- Scan UI for the LAST widgets with 'cancelfocus' or 'firstfocus' flags
|
|
|
|
local function resolveautofocus(widget)
|
|
|
|
local firstfocus, cancelfocus
|
|
|
|
|
|
|
|
if isinstance(widget, Layout) then
|
|
|
|
for _,w in ipairs(widget) do
|
|
|
|
local firstf, cancelf
|
|
|
|
|
|
|
|
if not w.nofocus then
|
|
|
|
if isinstance(w, Layout) then
|
|
|
|
firstf, cancelf = resolveautofocus(w)
|
|
|
|
else
|
|
|
|
if w.firstfocus then
|
|
|
|
firstf = w
|
|
|
|
end
|
|
|
|
if w.cancelfocus then
|
|
|
|
cancelf = w
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
firstfocus = firstf or firstfocus
|
|
|
|
cancelfocus = cancelf or cancelfocus
|
|
|
|
end
|
|
|
|
|
|
|
|
elseif not widget.nofocus then
|
|
|
|
if widget.firstfocus then
|
|
|
|
firstfocus = widget
|
|
|
|
end
|
|
|
|
if widget.cancelfocus then
|
|
|
|
cancelfocus = widget
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
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.")
|
|
|
|
|
2022-08-16 09:49:37 +02:00
|
|
|
self.device = self.device or require(BASE..'device.love').new()
|
2022-08-15 23:41:17 +02:00
|
|
|
self.x = self.x or 0
|
|
|
|
self.y = self.y or 0
|
|
|
|
self.pointerActive = true
|
|
|
|
self.timer = Timer.new()
|
|
|
|
|
|
|
|
local root = self[1]
|
|
|
|
if not isinstance(root, Widget) then
|
|
|
|
error("Ui.new() bad root Widget type: "..type(root)..".")
|
|
|
|
end
|
|
|
|
|
|
|
|
root.x,root.y = self.x,self.y
|
|
|
|
root.ui = self
|
|
|
|
if isinstance(root, Layout) then
|
|
|
|
root:layoutWidgets()
|
|
|
|
else
|
|
|
|
assert(type(root.w) == 'number', "Ui.new() root Widget must have a numeric width.")
|
|
|
|
assert(type(root.h) == 'number', "Ui.new() root Widget must have a numeric height.")
|
|
|
|
assert(not root.nofocus, "Ui.new() single root Widget can't be nofocus.")
|
|
|
|
end
|
|
|
|
|
|
|
|
self.w,self.h = root.w,root.h
|
|
|
|
|
|
|
|
local firstfocus, cancelfocus = resolveautofocus(root)
|
|
|
|
if firstfocus == nil then
|
|
|
|
firstfocus = isinstance(root, Layout) and
|
|
|
|
root:firstFocusableWidget() or
|
|
|
|
root
|
|
|
|
end
|
|
|
|
|
|
|
|
self.cancelfocus = cancelfocus
|
|
|
|
firstfocus:grabFocus()
|
|
|
|
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Event propagators for widgets listening to keyboard input
|
|
|
|
function Ui:keypressed(key, scancode, isrepeat)
|
|
|
|
local focused = self.focused
|
|
|
|
|
|
|
|
if focused ~= nil and focused.grabkeyboard then
|
|
|
|
focused:keypressed(key, scancode, isrepeat)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
function Ui:keyreleased(key, scancode)
|
|
|
|
local focused = self.focused
|
|
|
|
|
|
|
|
if focused ~= nil and focused.grabkeyboard then
|
|
|
|
focused:keyreleased(key, scancode)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
function Ui:textinput(text)
|
|
|
|
local focused = self.focused
|
|
|
|
|
|
|
|
if focused ~= nil and focused.grabkeyboard then
|
|
|
|
focused:textinput(text)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
function Ui:textedited(text, start, length)
|
|
|
|
local focused = self.focused
|
|
|
|
|
|
|
|
if focused ~= nil and focused.grabkeyboard then
|
|
|
|
focused: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()
|
|
|
|
|
|
|
|
self.timer:update(dt)
|
|
|
|
|
|
|
|
-- 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()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Perform regular lifetime updates
|
|
|
|
root:update(dt)
|
|
|
|
end
|
|
|
|
|
|
|
|
function Ui:draw()
|
|
|
|
local root = self[1]
|
|
|
|
|
|
|
|
love.graphics.push('all')
|
|
|
|
root:draw()
|
|
|
|
love.graphics.pop()
|
|
|
|
end
|
|
|
|
|
|
|
|
return Ui
|