115 lines
3.3 KiB
Lua
115 lines
3.3 KiB
Lua
--- Объект, который отвечает за работу с элементами интерфейса одного экрана
|
||
--- @class UIBuilder
|
||
--- @field builder fun(): UIElement
|
||
--- @field debugDraw boolean
|
||
--- @field private states {string: table}
|
||
--- @field private elementTree UIElement
|
||
local builder = {}
|
||
builder.__index = builder
|
||
|
||
--- @param cfg {debugDraw: boolean?, builder: (fun(): UIElement)}
|
||
--- @return UIBuilder
|
||
function builder.new(cfg)
|
||
local t = setmetatable(cfg, builder)
|
||
t.states = {}
|
||
t.builder = cfg.builder
|
||
t.debugDraw = cfg.debugDraw or false
|
||
return cfg
|
||
end
|
||
|
||
--- @param newNode UIElement?
|
||
--- @param oldNode UIElement?
|
||
--- @private
|
||
function builder:didChange(newNode, oldNode)
|
||
if not oldNode or not newNode then
|
||
return true
|
||
end
|
||
if oldNode.type ~= newNode.type then return true end
|
||
if oldNode.key ~= newNode.key then return true end
|
||
return false
|
||
end
|
||
|
||
--- @param element UIElement
|
||
--- @private
|
||
function builder:makeKey(element)
|
||
if not element.key then return nil end
|
||
return element.type .. "<" .. tostring(element.key) .. ">"
|
||
end
|
||
|
||
--- @generic T
|
||
--- @param element StatefulElement<T>
|
||
--- @return T
|
||
function builder:initStateOf(element)
|
||
return element.initState and element:initState() or {}
|
||
end
|
||
|
||
--- @generic T
|
||
--- @param element StatefulElement<T>
|
||
--- @return T
|
||
function builder:getStateOf(element)
|
||
return element.getState and element:getState() or self.states[self:makeKey(element)]
|
||
end
|
||
|
||
--- @param cur UIElement
|
||
--- @private
|
||
function builder:build_step(cur)
|
||
-- Самоприсваивания должны вырезаться компилятором, я в это верю
|
||
cur = cur --[[@as SingleChildElement | StatefulElement | MultiChildElement]]
|
||
|
||
local key = self:makeKey(cur)
|
||
if key then
|
||
cur = cur --[[@as StatefulElement]]
|
||
local storedState = self:getStateOf(cur)
|
||
if not storedState then
|
||
self.states[key] = self:initStateOf(cur)
|
||
cur.state = self.states[key]
|
||
else
|
||
cur.state = storedState
|
||
end
|
||
end
|
||
|
||
local buildRes = cur:build()
|
||
if not buildRes then return end
|
||
|
||
if buildRes.type then
|
||
cur = cur --[[@as SingleChildElement]]
|
||
|
||
cur._child_ = buildRes
|
||
buildRes._parent_ = cur
|
||
|
||
self:build_step(cur._child_)
|
||
else
|
||
cur = cur --[[@as MultiChildElement]]
|
||
cur._children_ = buildRes
|
||
for _, child in ipairs(cur._children_) do
|
||
child._parent_ = cur
|
||
self:build_step(child)
|
||
end
|
||
end
|
||
end
|
||
|
||
--- Этот метод раскрывает всех отложенных (через build) детей в дереве и хитро их кэширует, чтобы не перестраивались постоянно
|
||
---
|
||
--- Благодаря этому можно каждый раз создавать новые элементы в верстке, а получать старые :)
|
||
function builder:build()
|
||
self.elementTree = self:builder()
|
||
self:build_step(self.elementTree)
|
||
end
|
||
|
||
function builder:layout()
|
||
self.elementTree:layout()
|
||
end
|
||
|
||
function builder:update(dt)
|
||
self.elementTree:update(dt)
|
||
end
|
||
|
||
function builder:draw()
|
||
self.elementTree:draw()
|
||
if self.debugDraw then
|
||
self.elementTree:debugDraw()
|
||
end
|
||
end
|
||
|
||
return builder.new
|