--- Объект, который отвечает за работу с элементами интерфейса одного экрана --- @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 --- @return T function builder:initStateOf(element) return element.initState and element:initState() or {} end --- @generic T --- @param element StatefulElement --- @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) 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