implement drawing in local coordinates

these damn trees are still WIP but I'm confident I'm doing everything
right since I've recently embraced Stoicism
This commit is contained in:
PeaAshMeter 2026-05-14 19:27:40 +03:00
parent 460e8b78b7
commit 987ce25474
8 changed files with 118 additions and 51 deletions

View File

@ -1,37 +1,51 @@
local Element = require "lib.simple_ui.core.element"
--- Объект, который отвечает за работу с элементами интерфейса одного экрана --- Объект, который отвечает за работу с элементами интерфейса одного экрана
--- @class UIBuilder --- @class UIBuilder
--- @field private _cache UIElement[] --- @field private _cache UIElement[]
--- @field elementTree UIElement --- @field elementTree UIElement
--- @field private shadowTree UIElement
local builder = {} local builder = {}
builder.__index = builder builder.__index = builder
--- @return UIBuilder --- @return UIBuilder
local function new(from) local function new(from)
from._cache = {} from._cache = {}
from.shadowTree = Element:new {}
setmetatable(from, builder) setmetatable(from, builder)
return from return from
end end
--- @param element? UIElement -- --- @param element? UIElement
-- --- @private
-- function builder:_get(element)
-- if not element then return nil end
-- local key = builder:_makeKey(element)
-- if not key then return element end
-- local cached = self._cache[key]
-- if cached then return cached end
-- self._cache[key] = element
-- return element
-- end
--- @param newNode UIElement?
--- @param oldNode UIElement?
--- @private --- @private
function builder:_get(element) function builder:didChange(newNode, oldNode)
if not element then return nil end if not oldNode or not newNode then
return true
local key = builder:_makeKey(element) end
if not key then return element end if oldNode.type ~= newNode.type then return true end
if oldNode.key ~= newNode.key then return true end
local cached = self._cache[key] return false
if cached then return cached end
self._cache[key] = element
return element
end end
--- @param element UIElement --- @param element UIElement
--- @private --- @private
function builder:_makeKey(element) function builder:_makeKey(element)
if not element.key then return nil end --if not element.key then return nil end
if type(element.key) == "string" then return element.key end if type(element.key) == "string" then return element.key end
element.key = element.type .. "<" .. tostring(element.key) .. ">" element.key = element.type .. "<" .. tostring(element.key) .. ">"
return element.key return element.key
@ -39,18 +53,22 @@ end
--- @private --- @private
function builder:build_step(cur) function builder:build_step(cur)
if not cur then return end
if cur.build then if cur.build then
cur.child = self:_get(cur:build()) local orphan = cur:build()
cur.child.parent = cur local child = cur.child
child = orphan
if not child then return end
child.parent = cur
cur.child = child
self:build_step(cur.child) self:build_step(cur.child)
elseif cur.children then elseif cur.children then
for _, child in ipairs(cur.children) do for _, child in ipairs(cur.children) do
self:build_step(self:_get(child)) child.parent = cur
self:build_step(child)
end end
else
cur.child = self:_get(cur.child)
self:build_step(cur.child)
end end
end end
@ -58,7 +76,7 @@ end
--- ---
--- Благодаря этому можно каждый раз создавать новые элементы в верстке, а получать старые :) --- Благодаря этому можно каждый раз создавать новые элементы в верстке, а получать старые :)
function builder:build() function builder:build()
local root = self:_get(self.elementTree) local root = self.elementTree
self:build_step(root) self:build_step(root)
end end

View File

@ -8,7 +8,7 @@ local Vec3 = require "lib.utils.vec3"
--- @field constraints Constraints --- @field constraints Constraints
--- @field offset Vec3 Положение левого верхнего угла элемента в экранных координатах {x, y}. Устанавливается родительским элементом. --- @field offset Vec3 Положение левого верхнего угла элемента в экранных координатах {x, y}. Устанавливается родительским элементом.
--- @field size Vec3 Размеры элемента в экранных координатах {x, y} --- @field size Vec3 Размеры элемента в экранных координатах {x, y}
--- @field build? fun(self): UIElement? --- @field build? fun(self, ctx: UIElement): UIElement
local element = {} local element = {}
element.__index = element element.__index = element
element.type = "Element" element.type = "Element"
@ -23,7 +23,11 @@ function element:layout() end
function element:update(dt) end function element:update(dt) end
function element:draw() end function element:draw()
if self.type == "SizedBox" then
print(self.offset)
end
end
--- @param values {[string]: any} --- @param values {[string]: any}
--- @return UIElement --- @return UIElement

View File

@ -13,28 +13,30 @@ function element:update(dt)
end end
function element:draw() function element:draw()
love.graphics.push("transform")
love.graphics.translate(self.offset.x, self.offset.y)
for _, child in ipairs(self.children) do for _, child in ipairs(self.children) do
child:draw() child:draw()
end end
--- @TODO: сделать дебажный метод для отрисовки границ --- @TODO: сделать дебажный метод для отрисовки границ
love.graphics.setColor(1, 0, 0) love.graphics.setColor(1, 0, 0)
love.graphics.line(self.offset.x, self.offset.y, self.offset.x + self.size.x, self.offset.y) love.graphics.line(0, 0, 0 + self.size.x, 0)
love.graphics.line(self.offset.x, self.offset.y, self.offset.x, self.offset.y + self.size.y) love.graphics.line(0, 0, 0, self.offset.y + 0)
love.graphics.line(self.offset.x + self.size.x, self.offset.y, self.offset.x + self.size.x, love.graphics.line(0 + self.size.x, self.offset.y, 0 + self.size.x,
self.offset.y + self.size.y) 0 + self.size.y)
love.graphics.line(self.offset.x, self.offset.y + self.size.y, self.offset.x + self.size.x, love.graphics.line(0, 0 + self.size.y, 0 + self.size.x,
self.offset.y + self.size.y) 0 + self.size.y)
love.graphics.setColor(1, 1, 1) love.graphics.setColor(1, 1, 1)
love.graphics.pop()
end end
--- @generic T : MultiChildElement --- @generic T : MultiChildElement
--- @param values {children: UIElement[]?, [string]: any} --- @param values {children: UIElement[]?, [string]: any}
--- @return T --- @return T
function element:new(values) function element:new(values)
for _, child in ipairs(values.children or {}) do
child.parent = values
end
return Element.new(self, values) return Element.new(self, values)
end end

View File

@ -1,15 +1,24 @@
local Element = require "lib.simple_ui.core.element" local Element = require "lib.simple_ui.core.element"
local Constraints = require "lib.simple_ui.core.constraints"
--- @class SingleChildElement : UIElement --- @class SingleChildElement : UIElement
--- @field child? UIElement --- @field child? UIElement
local element = setmetatable({}, require "lib.simple_ui.core.element") local element = setmetatable({}, require "lib.simple_ui.core.element")
element.__index = element element.__index = element
--- дефолтное поведение -- просто возвращать своего ребенка
function element:build()
return self.child
end
function element:layout() function element:layout()
--- передать ребенку ограничения
--- получить назад размеры
--- разместить ребенка
if not self.child then return end if not self.child then return end
self.child.constraints = self.constraints self.child.constraints = Constraints(self.constraints)
self.child:layout() self.child:layout()
self.child.offset = self.offset:copy() self.child.offset = Vec3 {}
end end
function element:update(dt) function element:update(dt)
@ -17,7 +26,10 @@ function element:update(dt)
end end
function element:draw() function element:draw()
love.graphics.push("transform")
love.graphics.translate(self.offset.x, self.offset.y)
if self.child then self.child:draw() end if self.child then self.child:draw() end
love.graphics.pop()
end end
--- @generic T : SingleChildElement --- @generic T : SingleChildElement
@ -25,7 +37,6 @@ end
--- @param values {child: UIElement?, [string]: any} --- @param values {child: UIElement?, [string]: any}
--- @return T --- @return T
function element:new(values) function element:new(values)
if values.child then values.child.parent = values end
return Element.new(self, values) return Element.new(self, values)
end end

View File

@ -12,7 +12,6 @@ function element:layout()
if not self.child then return end if not self.child then return end
self.child.constraints = Constraints(self.constraints) self.child.constraints = Constraints(self.constraints)
self.child:layout() self.child:layout()
self.child.offset = self.offset:copy()
end end
function element:draw() function element:draw()

View File

@ -15,10 +15,7 @@ function element:layout()
self.size = Vec3 { screenW, screenH } self.size = Vec3 { screenW, screenH }
if not self.child then return end if not self.child then return end
self.child.constraints = Constraints { self.child.constraints = Constraints(self.constraints)
maxWidth = screenW,
maxHeight = screenH,
}
self.child:layout() self.child:layout()
self.child.offset = Vec3 {} self.child.offset = Vec3 {}
end end

View File

@ -17,7 +17,7 @@ function element:layout()
maxHeight = self.height, maxHeight = self.height,
} }
self.child:layout() self.child:layout()
self.child.offset = self.offset:copy() self.child.offset = Vec3 {}
end end
--- @return SizedBox --- @return SizedBox

View File

@ -3,25 +3,30 @@ local Placeholder = require "lib.simple_ui.elements.placeholder"
local Padding = require "lib.simple_ui.elements.padding" local Padding = require "lib.simple_ui.elements.padding"
local Builder = require "lib.simple_ui.core.builder" local Builder = require "lib.simple_ui.core.builder"
local Flex = require "lib.simple_ui.elements.flex" local Flex = require "lib.simple_ui.elements.flex"
local Center = require "lib.simple_ui.elements.center"
local SizedBox = require "lib.simple_ui.elements.sized_box" local SizedBox = require "lib.simple_ui.elements.sized_box"
local SingleChildElement = require "lib.simple_ui.core.single_child_element" local SingleChildElement = require "lib.simple_ui.core.single_child_element"
local MyWidget = setmetatable({}, SingleChildElement) local MyWidget = setmetatable({}, SingleChildElement)
MyWidget.__index = MyWidget MyWidget.__index = MyWidget
MyWidget.type = "MyWidget"
local Canary = setmetatable({}, SingleChildElement) local Canary = setmetatable({}, SingleChildElement)
Canary.__index = Canary Canary.__index = Canary
Canary.type = "Canary"
local reported = false local reported = false
function Canary:build() function Canary:build()
if not reported then -- self.i = self.i and self.i + 1 or 0
self:traverseUp(function(element) -- print(self.i)
print(element.type) -- if not reported then
return true -- self:traverseUp(function(element)
end) -- print(element.type)
reported = true -- return true
end -- end)
-- reported = true
-- end
return Placeholder:new {} return Placeholder:new {}
end end
@ -37,7 +42,6 @@ function MyWidget:build()
Padding:new { Padding:new {
top = 8, top = 8,
child = Flex:new { child = Flex:new {
key = "inner_flex",
mainAxisAlignment = "start", mainAxisAlignment = "start",
mainAxisSize = "min", mainAxisSize = "min",
children = { children = {
@ -52,9 +56,12 @@ function MyWidget:build()
child = Placeholder:new {} child = Placeholder:new {}
}, },
SizedBox:new { SizedBox:new {
key = "mybox",
width = 100, width = 100,
height = 100, height = 100,
child = Canary:new {} child = Canary:new {
i = 10
}
}, },
}, },
} }
@ -81,6 +88,35 @@ function MyWidget:build()
} }
end end
--
-- --- comment
-- --- @return Flex
-- function MyWidget:build()
-- return Flex:new {
-- mainAxisAlignment = "start",
-- mainAxisSize = "min",
-- children = {
-- SizedBox:new {
-- width = 100,
-- height = 100,
-- child = Placeholder:new {}
-- },
-- SizedBox:new {
-- width = 150,
-- height = 200,
-- child = Placeholder:new {}
-- },
-- SizedBox:new {
-- width = 100,
-- height = 100,
-- child = Canary:new {
-- i = 10
-- }
-- },
-- },
-- }
-- end
return Builder { return Builder {
elementTree = ScreenArea:new { elementTree = ScreenArea:new {
child = MyWidget:new {} child = MyWidget:new {}