You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
4.5 KiB
Lua

local BASE = (...):gsub('ui$', '')
local Widget = require(BASE..'widget')
local Layout = require(BASE..'layout')
local gear = require 'lib.gear'
local Timer = gear.Timer
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.")
self.device = self.device or require(BASE..'device.love').new()
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