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
--- @field private _cache UIElement[]
--- @field elementTree UIElement
--- @field private shadowTree UIElement
local builder = {}
builder.__index = builder
--- @return UIBuilder
local function new(from)
from._cache = {}
from.shadowTree = Element:new {}
setmetatable(from, builder)
return from
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
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
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
--if not element.key then return nil end
if type(element.key) == "string" then return element.key end
element.key = element.type .. "<" .. tostring(element.key) .. ">"
return element.key
@ -39,18 +53,22 @@ end
--- @private
function builder:build_step(cur)
if not cur then return end
if cur.build then
cur.child = self:_get(cur:build())
cur.child.parent = cur
local orphan = cur:build()
local child = cur.child
child = orphan
if not child then return end
child.parent = cur
cur.child = child
self:build_step(cur.child)
elseif cur.children then
for _, child in ipairs(cur.children) do
self:build_step(self:_get(child))
child.parent = cur
self:build_step(child)
end
else
cur.child = self:_get(cur.child)
self:build_step(cur.child)
end
end
@ -58,7 +76,7 @@ end
---
--- Благодаря этому можно каждый раз создавать новые элементы в верстке, а получать старые :)
function builder:build()
local root = self:_get(self.elementTree)
local root = self.elementTree
self:build_step(root)
end

View File

@ -8,7 +8,7 @@ local Vec3 = require "lib.utils.vec3"
--- @field constraints Constraints
--- @field offset Vec3 Положение левого верхнего угла элемента в экранных координатах {x, y}. Устанавливается родительским элементом.
--- @field size Vec3 Размеры элемента в экранных координатах {x, y}
--- @field build? fun(self): UIElement?
--- @field build? fun(self, ctx: UIElement): UIElement
local element = {}
element.__index = element
element.type = "Element"
@ -23,7 +23,11 @@ function element:layout() 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}
--- @return UIElement

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ function element:layout()
maxHeight = self.height,
}
self.child:layout()
self.child.offset = self.offset:copy()
self.child.offset = Vec3 {}
end
--- @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 Builder = require "lib.simple_ui.core.builder"
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 SingleChildElement = require "lib.simple_ui.core.single_child_element"
local MyWidget = setmetatable({}, SingleChildElement)
MyWidget.__index = MyWidget
MyWidget.type = "MyWidget"
local Canary = setmetatable({}, SingleChildElement)
Canary.__index = Canary
Canary.type = "Canary"
local reported = false
function Canary:build()
if not reported then
self:traverseUp(function(element)
print(element.type)
return true
end)
reported = true
end
-- self.i = self.i and self.i + 1 or 0
-- print(self.i)
-- if not reported then
-- self:traverseUp(function(element)
-- print(element.type)
-- return true
-- end)
-- reported = true
-- end
return Placeholder:new {}
end
@ -37,7 +42,6 @@ function MyWidget:build()
Padding:new {
top = 8,
child = Flex:new {
key = "inner_flex",
mainAxisAlignment = "start",
mainAxisSize = "min",
children = {
@ -52,9 +56,12 @@ function MyWidget:build()
child = Placeholder:new {}
},
SizedBox:new {
key = "mybox",
width = 100,
height = 100,
child = Canary:new {}
child = Canary:new {
i = 10
}
},
},
}
@ -81,6 +88,35 @@ function MyWidget:build()
}
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 {
elementTree = ScreenArea:new {
child = MyWidget:new {}