Compare commits
2 Commits
main
...
feature/hp
| Author | SHA1 | Date | |
|---|---|---|---|
| 031da7fd0a | |||
| c7ee957c8c |
76
lib/simple_ui/builder.lua
Normal file
76
lib/simple_ui/builder.lua
Normal file
@ -0,0 +1,76 @@
|
||||
--- Объект, который отвечает за работу с элементами интерфейса одного экрана
|
||||
--- @class UIBuilder
|
||||
--- @field private _cache UIElement[]
|
||||
--- @field elementTree UIElement
|
||||
local builder = {}
|
||||
builder.__index = builder
|
||||
|
||||
--- @return UIBuilder
|
||||
local function new(from)
|
||||
from._cache = {}
|
||||
|
||||
setmetatable(from, builder)
|
||||
return from
|
||||
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 Constraints = require "lib.simple_ui.constraints"
|
||||
local Vec3 = require "lib.utils.vec3"
|
||||
|
||||
--- @class UIElement
|
||||
--- @field bounds Rect Прямоугольник, в границах которого размещается элемент. Размеры и положение в *локальных* координатах
|
||||
--- @field transform love.Transform Преобразование из локальных координат элемента (bounds) в экранные координаты
|
||||
local uiElement = {}
|
||||
uiElement.__index = uiElement
|
||||
--- @field type string
|
||||
--- @field key? any Must be convertible to string
|
||||
--- @field parent? 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)
|
||||
local lx, ly = self.transform:inverseTransformPoint(screenX, screenY)
|
||||
return self.bounds:hasPoint(lx, ly)
|
||||
end
|
||||
function element:draw() end
|
||||
|
||||
--- @generic T : UIElement
|
||||
--- @param values table
|
||||
--- @param self T
|
||||
--- @return T
|
||||
function uiElement.new(self, values)
|
||||
function element.new(self, values)
|
||||
values.bounds = values.bounds or Rect {}
|
||||
values.transform = values.transform or love.math.newTransform()
|
||||
if values.child then values.child.parent = values end
|
||||
return setmetatable(values, self)
|
||||
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
|
||||
27
lib/simple_ui/level/test.lua
Normal file
27
lib/simple_ui/level/test.lua
Normal file
@ -0,0 +1,27 @@
|
||||
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"
|
||||
local Builder = require "lib.simple_ui.builder"
|
||||
|
||||
|
||||
return Builder {
|
||||
elementTree = 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
|
||||
--- @param vec number[]
|
||||
--- @return Vec3
|
||||
function Vec3(vec)
|
||||
return setmetatable({
|
||||
x = vec[1] or 0,
|
||||
|
||||
4
main.lua
4
main.lua
@ -2,7 +2,7 @@
|
||||
|
||||
local character = require "lib/character/character"
|
||||
require "lib/tree"
|
||||
local testLayout = require "lib.simple_ui.level.layout"
|
||||
local testLayout = require("lib.simple_ui.level.test")
|
||||
|
||||
function love.conf(t)
|
||||
t.console = true
|
||||
@ -25,6 +25,8 @@ local lt = "0"
|
||||
function love.update(dt)
|
||||
local t1 = love.timer.getTime()
|
||||
Tree.controls:poll()
|
||||
testLayout:build()
|
||||
testLayout:layout()
|
||||
testLayout:update(dt) -- логика UI-слоя должна отработать раньше всех, потому что нужно перехватить жесты и не пустить их дальше
|
||||
Tree.panning:update(dt)
|
||||
Tree.level:update(dt)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user