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.

129 lines
3.4 KiB
Lua

--- A user interface element
-- @classmod yui.Widget
-- @copyright 2022, The DoubleFourteen Code Forge
-- @author Lorenzo Cogotti, Andrea Pasquini
--- Functions accepted by all the Widget classes
-- @field onEnter function is called when the widget gets entered (e.g. hovered by pointer/mouse cursor)
-- @field onHit function is called when the widget is hit (e.g. clicked)
-- @field onChange function is called when the widget value is changed (e.g. checkbox is ticked)
-- @field onLeave function is called when the widget is left (e.g. another widget acquires focus, mouse leaves the widget area)
-- @table WidgetCallbacks
--- Attributes accepted by all the Widget classes
-- @field w (number) widget width
-- @field h (number) widget height
-- @field theme (@{yui.theme.Theme|Theme}) widget specific theme
-- @field color (@{yui.theme.ColorPalette|ColorPalette}) widget color
-- @table WidgetAttributes
local rectunion = require('lib.gear.rect').union
local Widget = {
__call = function(cls, args) return cls:new(args) end
}
Widget.__index = Widget
local function raise(widget)
local parent = widget.parent
-- A parent of a widget is necessarily a Layout
while parent ~= nil do
local stack = parent.stack
-- Move widget at the end of the stack, so it is rendered last.
for i,w in ipairs(stack) do
if w == widget then
table.remove(stack, i)
stack[#stack+1] = widget
break
end
end
-- Focus widget's container, if any
widget = parent
parent = widget.parent
end
end
function Widget:grabFocus()
local ui = self.ui
local focused = ui.focused
if focused == self then
return
end
if focused ~= nil then
-- Notify leave
focused.hovered = false
-- Widget specific focus loss
focused:loseFocus()
-- Event handler
focused:onLeave()
if focused.grabkeyboard then
-- If focused widget stole input,
-- then drop current input snapshot, since
-- those events should have been already
-- managed directly
ui.device:snapshot()
end
end
local wasHovered = self.hovered
self.hovered = true
if not wasHovered then
-- First time hovered, notify enter
self:gainFocus()
self:onEnter()
end
-- Raise widget
ui.focused = self
raise(self)
end
function Widget:isFocused()
return self.ui.focused == self
end
function Widget:recalculateBounds()
local widget = self.parent
while widget ~= nil do
local rx,ry,rw,rh = widget.x,widget.y,-1,-1
for _,w in ipairs(widget) do
rx,ry,rw,rh = rectunion(rx,ry,rw,rh, w.x,w.y,w.w,w.h)
end
widget.x = rx
widget.y = ry
widget.w = rw
widget.h = rh
widget = widget.parent
end
end
-- NOP hooks for UI internal use
function Widget:loseFocus() end
function Widget:gainFocus() end
-- NOP event handlers, publicly overridable
function Widget:onHit() end
function Widget:onEnter() end
function Widget:onLeave() end
function Widget:onChange() end
-- NOP input event handlers
function Widget:onActionInput(action) end
function Widget:onPointerInput(x,y, clicked) end
-- NOP UI event handlers
function Widget:update(dt) end
function Widget:draw() end
return Widget