reinvent the wheel
This commit is contained in:
parent
411c435e7a
commit
c7ee957c8c
77
lib/simple_ui/builder.lua
Normal file
77
lib/simple_ui/builder.lua
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
--- Объект, который отвечает за работу с элементами интерфейса одного экрана
|
||||||
|
--- @class UIBuilder
|
||||||
|
--- @field private _cache UIElement[]
|
||||||
|
--- @field elementTree UIElement
|
||||||
|
local builder = {}
|
||||||
|
builder.__index = builder
|
||||||
|
|
||||||
|
local function new(elementTree)
|
||||||
|
local l = {}
|
||||||
|
l.elementTree = elementTree
|
||||||
|
l._cache = {}
|
||||||
|
|
||||||
|
setmetatable(l, builder)
|
||||||
|
return l
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @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 element UIElement
|
||||||
|
--- @private
|
||||||
|
function builder:_makeKey(element)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
function builder:build_step(cur)
|
||||||
|
if not cur then return end
|
||||||
|
if cur.build then
|
||||||
|
cur.child = self:_get(cur:build())
|
||||||
|
self:build_step(cur.child)
|
||||||
|
elseif cur.children then
|
||||||
|
for _, child in ipairs(cur.children) do
|
||||||
|
self:build_step(self:_get(child))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
cur.child = self:_get(cur.child)
|
||||||
|
self:build_step(cur.child)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Этот метод раскрывает всех отложенных (через build) детей в дереве и хитро их кэширует, чтобы не перестраивались постоянно
|
||||||
|
---
|
||||||
|
--- Благодаря этому можно каждый раз создавать новые элементы в верстке, а получать старые :)
|
||||||
|
function builder:build()
|
||||||
|
local root = self:_get(self.elementTree)
|
||||||
|
self:build_step(root)
|
||||||
|
end
|
||||||
|
|
||||||
|
function builder:layout()
|
||||||
|
self.elementTree:layout()
|
||||||
|
end
|
||||||
|
|
||||||
|
function builder:update(dt)
|
||||||
|
self.elementTree:update(dt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function builder:draw()
|
||||||
|
self.elementTree:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
return new
|
||||||
22
lib/simple_ui/center.lua
Normal file
22
lib/simple_ui/center.lua
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
local Constraints = require "lib.simple_ui.constraints"
|
||||||
|
local SingleChildElement = require "lib.simple_ui.single_child_element"
|
||||||
|
|
||||||
|
--- @class Center : SingleChildElement
|
||||||
|
local element = setmetatable({}, SingleChildElement)
|
||||||
|
element.__index = element
|
||||||
|
element.__type = "Center"
|
||||||
|
|
||||||
|
function element:layout()
|
||||||
|
self.size = Vec3 { self.constraints.maxWidth, self.constraints.maxHeight }
|
||||||
|
|
||||||
|
if not self.child then return end
|
||||||
|
self.child.constraints = Constraints(self.constraints)
|
||||||
|
self.child:layout()
|
||||||
|
|
||||||
|
self.child.offset = Vec3 {
|
||||||
|
self.offset.x + (self.size.x - self.child.size.x) / 2,
|
||||||
|
self.offset.y + (self.size.y - self.child.size.y) / 2,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return element
|
||||||
21
lib/simple_ui/constraints.lua
Normal file
21
lib/simple_ui/constraints.lua
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
--- @class Constraints
|
||||||
|
--- @field minWidth number
|
||||||
|
--- @field maxWidth number
|
||||||
|
--- @field minHeight number
|
||||||
|
--- @field maxHeight number
|
||||||
|
local constraints = {
|
||||||
|
minWidth = 0,
|
||||||
|
maxWidth = math.huge,
|
||||||
|
minHeight = 0,
|
||||||
|
maxHeight = math.huge
|
||||||
|
}
|
||||||
|
|
||||||
|
constraints.__index = constraints
|
||||||
|
|
||||||
|
--- @param from {minWidth?: number, maxWidth?: number, minHeight?: number, maxHeight?: number}
|
||||||
|
--- @return Constraints
|
||||||
|
local function new(from)
|
||||||
|
return setmetatable(from, constraints)
|
||||||
|
end
|
||||||
|
|
||||||
|
return new
|
||||||
@ -1,28 +1,40 @@
|
|||||||
local Rect = require "lib.simple_ui.rect"
|
local Rect = require "lib.simple_ui.rect"
|
||||||
|
local Constraints = require "lib.simple_ui.constraints"
|
||||||
|
local Vec3 = require "lib.utils.vec3"
|
||||||
|
|
||||||
--- @class UIElement
|
--- @class UIElement
|
||||||
--- @field bounds Rect Прямоугольник, в границах которого размещается элемент. Размеры и положение в *локальных* координатах
|
--- @field type string
|
||||||
--- @field transform love.Transform Преобразование из локальных координат элемента (bounds) в экранные координаты
|
--- @field key? any Must be convertible to string
|
||||||
local uiElement = {}
|
--- @field parent? UIElement
|
||||||
uiElement.__index = uiElement
|
--- @field constraints Constraints
|
||||||
|
--- @field offset Vec3 Положение левого верхнего угла элемента в экранных координатах {x, y}. Устанавливается родительским элементом.
|
||||||
|
--- @field size Vec3 Размеры элемента в экранных координатах {x, y}
|
||||||
|
--- @field build? fun(self)
|
||||||
|
local element = {}
|
||||||
|
element.__index = element
|
||||||
|
element.type = "Element"
|
||||||
|
element.constraints = Constraints {}
|
||||||
|
element.offset = Vec3 {}
|
||||||
|
element.size = Vec3 {}
|
||||||
|
|
||||||
function uiElement:update(dt) end
|
--- "Constraints go down. Sizes go up. Parent sets position."
|
||||||
|
---
|
||||||
|
--- Karl Marx, probably.
|
||||||
|
function element:layout() end
|
||||||
|
|
||||||
function uiElement:draw() end
|
function element:update(dt) end
|
||||||
|
|
||||||
function uiElement:hitTest(screenX, screenY)
|
function element:draw() end
|
||||||
local lx, ly = self.transform:inverseTransformPoint(screenX, screenY)
|
|
||||||
return self.bounds:hasPoint(lx, ly)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @generic T : UIElement
|
--- @generic T : UIElement
|
||||||
--- @param values table
|
--- @param values table
|
||||||
--- @param self T
|
--- @param self T
|
||||||
--- @return T
|
--- @return T
|
||||||
function uiElement.new(self, values)
|
function element.new(self, values)
|
||||||
values.bounds = values.bounds or Rect {}
|
values.bounds = values.bounds or Rect {}
|
||||||
values.transform = values.transform or love.math.newTransform()
|
values.transform = values.transform or love.math.newTransform()
|
||||||
|
if values.child then values.child.parent = values end
|
||||||
return setmetatable(values, self)
|
return setmetatable(values, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
return uiElement
|
return element
|
||||||
|
|||||||
14
lib/simple_ui/flex.lua
Normal file
14
lib/simple_ui/flex.lua
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
--- @class Flex : MultiChildElement
|
||||||
|
--- @field direction "horizontal" | "vertical"
|
||||||
|
local flex = setmetatable({}, require "lib.simple_ui.multi_child_element")
|
||||||
|
flex.__index = flex
|
||||||
|
flex.type = "Flex"
|
||||||
|
flex.direction = "horizontal"
|
||||||
|
|
||||||
|
function flex:layout()
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return flex
|
||||||
24
lib/simple_ui/level/test.lua
Normal file
24
lib/simple_ui/level/test.lua
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
local ScreenArea = require "lib.simple_ui.screen_area"
|
||||||
|
local Center = require "lib.simple_ui.center"
|
||||||
|
local Placeholder = require "lib.simple_ui.placeholder"
|
||||||
|
local Padding = require "lib.simple_ui.padding"
|
||||||
|
|
||||||
|
|
||||||
|
return ScreenArea:new {
|
||||||
|
build = function(self)
|
||||||
|
return
|
||||||
|
(love.timer.getTime() / 2) % 2 < 1 and
|
||||||
|
Center:new {
|
||||||
|
child = Padding:new {
|
||||||
|
left = 8,
|
||||||
|
right = 8,
|
||||||
|
top = 8,
|
||||||
|
bottom = 8,
|
||||||
|
child = Placeholder:new {
|
||||||
|
key = "const :)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
or nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
13
lib/simple_ui/multi_child_element.lua
Normal file
13
lib/simple_ui/multi_child_element.lua
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
--- @class MultiChildElement : UIElement
|
||||||
|
--- @field children UIElement[]
|
||||||
|
local element = setmetatable({}, require "lib.simple_ui.element")
|
||||||
|
element.__index = element
|
||||||
|
element.children = {}
|
||||||
|
|
||||||
|
function element:update(dt)
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
child:update(dt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return element
|
||||||
35
lib/simple_ui/padding.lua
Normal file
35
lib/simple_ui/padding.lua
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
local Constraints = require "lib.simple_ui.constraints"
|
||||||
|
local SingleChildElement = require "lib.simple_ui.single_child_element"
|
||||||
|
|
||||||
|
--- @class Padding : SingleChildElement
|
||||||
|
--- @field left number
|
||||||
|
--- @field right number
|
||||||
|
--- @field top number
|
||||||
|
--- @field bottom number
|
||||||
|
local element = setmetatable({}, SingleChildElement)
|
||||||
|
element.__index = element
|
||||||
|
element.type = "Placeholder"
|
||||||
|
element.left = 0
|
||||||
|
element.right = 0
|
||||||
|
element.top = 0
|
||||||
|
element.bottom = 0
|
||||||
|
|
||||||
|
--- "When passing layout constraints to its child, padding shrinks the constraints by the given padding, causing the child to layout at a smaller size.
|
||||||
|
--- Padding then sizes itself to its child's size, inflated by the padding, effectively creating empty space around the child."
|
||||||
|
---
|
||||||
|
--- as in https://api.flutter.dev/flutter/widgets/Padding-class.html
|
||||||
|
function element:layout()
|
||||||
|
if not self.child then return end
|
||||||
|
local c = Constraints(self.constraints)
|
||||||
|
c.maxWidth = c.maxWidth - self.left - self.right
|
||||||
|
c.maxHeight = c.maxHeight - self.top - self.bottom
|
||||||
|
c.maxWidth = c.maxWidth > 0 and c.maxWidth or 0
|
||||||
|
c.maxHeight = c.maxHeight > 0 and c.maxHeight or 0
|
||||||
|
self.child.constraints = c
|
||||||
|
|
||||||
|
self.child:layout()
|
||||||
|
self.size = Vec3 { self.child.size.x + self.left + self.right, self.constraints.maxHeight + self.top + self.bottom }
|
||||||
|
self.child.offset = self.offset + Vec3 { self.left, self.top }
|
||||||
|
end
|
||||||
|
|
||||||
|
return element
|
||||||
24
lib/simple_ui/placeholder.lua
Normal file
24
lib/simple_ui/placeholder.lua
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
local Constraints = require "lib.simple_ui.constraints"
|
||||||
|
local SingleChildElement = require "lib.simple_ui.single_child_element"
|
||||||
|
|
||||||
|
--- @class Placeholder : SingleChildElement
|
||||||
|
local element = setmetatable({}, SingleChildElement)
|
||||||
|
element.__index = element
|
||||||
|
element.type = "Placeholder"
|
||||||
|
|
||||||
|
function element:layout()
|
||||||
|
self.size = Vec3 { self.constraints.maxWidth, self.constraints.maxHeight }
|
||||||
|
|
||||||
|
if not self.child then return end
|
||||||
|
self.child.constraints = Constraints(self.constraints)
|
||||||
|
self.child:layout()
|
||||||
|
self.child.offset = Vec3 {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function element:draw()
|
||||||
|
love.graphics.rectangle("line", self.offset.x, self.offset.y, self.size.x, self.size.y)
|
||||||
|
love.graphics.line(self.offset.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)
|
||||||
|
end
|
||||||
|
|
||||||
|
return element
|
||||||
28
lib/simple_ui/screen_area.lua
Normal file
28
lib/simple_ui/screen_area.lua
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
local Constraints = require "lib.simple_ui.constraints"
|
||||||
|
local SingleChildElement = require "lib.simple_ui.single_child_element"
|
||||||
|
|
||||||
|
--- @class ScreenArea : SingleChildElement
|
||||||
|
local element = setmetatable({}, SingleChildElement)
|
||||||
|
element.__index = element
|
||||||
|
element.type = "ScreenArea"
|
||||||
|
|
||||||
|
function element:layout()
|
||||||
|
local screenW, screenH = love.graphics.getWidth(), love.graphics.getHeight()
|
||||||
|
self.constraints = Constraints {
|
||||||
|
maxWidth = screenW,
|
||||||
|
maxHeight = screenH
|
||||||
|
}
|
||||||
|
self.size = Vec3 { screenW, screenH }
|
||||||
|
|
||||||
|
if not self.child then return end
|
||||||
|
self.child.constraints = Constraints { -- force a child to be the same size as the screen
|
||||||
|
minWidth = screenW,
|
||||||
|
maxWidth = screenW,
|
||||||
|
minHeight = screenH,
|
||||||
|
maxHeight = screenH,
|
||||||
|
}
|
||||||
|
self.child:layout()
|
||||||
|
self.child.offset = Vec3 {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return element
|
||||||
20
lib/simple_ui/single_child_element.lua
Normal file
20
lib/simple_ui/single_child_element.lua
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
--- @class SingleChildElement : UIElement
|
||||||
|
--- @field child? UIElement
|
||||||
|
local element = setmetatable({}, require "lib.simple_ui.element")
|
||||||
|
element.__index = element
|
||||||
|
|
||||||
|
function element:layout()
|
||||||
|
if not self.child then return end
|
||||||
|
self.child.constraints = self.constraints
|
||||||
|
self.child:layout()
|
||||||
|
end
|
||||||
|
|
||||||
|
function element:update(dt)
|
||||||
|
if self.child then self.child:update(dt) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function element:draw()
|
||||||
|
if self.child then self.child:draw() end
|
||||||
|
end
|
||||||
|
|
||||||
|
return element
|
||||||
@ -80,6 +80,7 @@ end
|
|||||||
|
|
||||||
--- Vec3 constructor
|
--- Vec3 constructor
|
||||||
--- @param vec number[]
|
--- @param vec number[]
|
||||||
|
--- @return Vec3
|
||||||
function Vec3(vec)
|
function Vec3(vec)
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
x = vec[1] or 0,
|
x = vec[1] or 0,
|
||||||
|
|||||||
5
main.lua
5
main.lua
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
local character = require "lib/character/character"
|
local character = require "lib/character/character"
|
||||||
require "lib/tree"
|
require "lib/tree"
|
||||||
local testLayout = require "lib.simple_ui.level.layout"
|
local UIBuilder = require("lib.simple_ui.builder")
|
||||||
|
local testLayout = UIBuilder(require "lib.simple_ui.level.test")
|
||||||
|
|
||||||
function love.conf(t)
|
function love.conf(t)
|
||||||
t.console = true
|
t.console = true
|
||||||
@ -25,6 +26,8 @@ local lt = "0"
|
|||||||
function love.update(dt)
|
function love.update(dt)
|
||||||
local t1 = love.timer.getTime()
|
local t1 = love.timer.getTime()
|
||||||
Tree.controls:poll()
|
Tree.controls:poll()
|
||||||
|
testLayout:build()
|
||||||
|
testLayout:layout()
|
||||||
testLayout:update(dt) -- логика UI-слоя должна отработать раньше всех, потому что нужно перехватить жесты и не пустить их дальше
|
testLayout:update(dt) -- логика UI-слоя должна отработать раньше всех, потому что нужно перехватить жесты и не пустить их дальше
|
||||||
Tree.panning:update(dt)
|
Tree.panning:update(dt)
|
||||||
Tree.level:update(dt)
|
Tree.level:update(dt)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user