From 2802570a50442abcd2df37952cc10aebfb12d062 Mon Sep 17 00:00:00 2001 From: PeaAshMeter Date: Tue, 4 Nov 2025 01:29:45 +0300 Subject: [PATCH] Remove deprecated UI system --- lib/ui/core.lua | 569 ---------------------------------------------- lib/ui/layout.lua | 58 ----- main.lua | 6 +- 3 files changed, 1 insertion(+), 632 deletions(-) delete mode 100644 lib/ui/core.lua delete mode 100644 lib/ui/layout.lua diff --git a/lib/ui/core.lua b/lib/ui/core.lua deleted file mode 100644 index 153f3e2..0000000 --- a/lib/ui/core.lua +++ /dev/null @@ -1,569 +0,0 @@ -local controls = require "lib.controls" - ----@class UIConstraints ----@field min_w number ----@field min_h number ----@field max_w number ----@field max_h number - -local ui = {} - --- =============== Constraints helpers =============== - -local function make_constraints(min_w, min_h, max_w, max_h) - return { - min_w = min_w or 0, - min_h = min_h or 0, - max_w = max_w or math.huge, - max_h = max_h or math.huge, - } -end - -local function clamp(v, lo, hi) - if v < lo then return lo end - if v > hi then return hi end - return v -end - -local function clamp_size(w, h, c) - return clamp(w, c.min_w, c.max_w), clamp(h, c.min_h, c.max_h) -end - -local function loosen(c) - -- "loose" constraints (0..max), удобно для wrap-контейнеров/Align - return make_constraints(0, 0, c.max_w, c.max_h) -end - -local function tighten(c, w, h) - -- exact (tight) constraints — зажимают размер ребёнка - w, h = clamp_size(w, h, c) - return make_constraints(w, h, w, h) -end - --- =============== Base elements =============== - ---- @class Element ---- @field parent Element|nil ---- @field children Element[] ---- @field child Element|nil ---- @field origin Vec3 ---- @field size Vec3 ---- @field state table -local Element = {} -Element.__index = Element - -function Element:new(props) - local o = setmetatable({}, self) - o.parent = nil - o.children = {} - o.origin = Vec3 { 0, 0, 0 } - o.size = Vec3 { 0, 0, 0 } - o.state = {} - if props then - -- Копируем "публичные" поля; Таблицы клонируем, если можно - for k, v in pairs(props) do - if type(v) == "table" then - o[k] = v.copy and v:copy() or v - else - o[k] = v - end - end - end - return o -end - ----@param dt number -function Element:update(dt) - -- По умолчанию спускаем update вниз - for _, ch in ipairs(self.children or {}) do - ch:update(dt) - end -end - ----@param constraints UIConstraints -function Element:layout(constraints) - -- Базовое поведение: размер = объединённый bbox детей (wrap-content) - local max_w, max_h = 0, 0 - for _, ch in ipairs(self.children) do - ch:layout(loosen(constraints)) -- по умолчанию не заставляем детей растягиваться - max_w = math.max(max_w, ch.size.x) - max_h = math.max(max_h, ch.size.y) - end - local w, h = clamp_size(max_w, max_h, constraints) - self.size.x, self.size.y = w, h -end - ----@param origin Vec3 -function Element:arrange(origin) - -- Базово: ставим себя, а детей кладём в (0,0) внутри нас - self.origin = Vec3 { origin.x, origin.y, origin.z or 0 } - for _, ch in ipairs(self.children) do - ch:arrange(Vec3 { self.origin.x, self.origin.y, self.origin.z }) - end -end - -function Element:draw() - -- По умолчанию — только рисуем детей - for _, ch in ipairs(self.children) do - ch:draw() - end -end - ---- calls the callback if clicked inside own bounding box (rectangular) ---- @param callback function -function Element:onTap(callback) - local mx, my = love.mouse.getPosition() - if mx > self.origin.x and mx < self.origin.x + self.size.x - and my > self.origin.y and my < self.origin.y + self.size.y - then - if controls:isJustPressed("select") then - controls:consume("select") - callback() - end - end -end - --- =============== SingleChild / MultiChild базовые =============== - ---- @class SingleChildElement: Element -local SingleChildElement = setmetatable({}, { __index = Element }) -SingleChildElement.__index = SingleChildElement - -function SingleChildElement:new(props) - local o = Element.new(self, props) - if o.child then - o.child.parent = o - o.children = { o.child } - end - return o -end - ----@class MultiChildElement: Element -local MultiChildElement = setmetatable({}, { __index = Element }) -MultiChildElement.__index = MultiChildElement - -function MultiChildElement:new(props) - local o = Element.new(self, props) - o.children = o.children or o.children or {} - o.children = o.children -- ensure array - if props and props.children then - for _, ch in ipairs(props.children) do - ch.parent = o - end - end - return o -end - --- =============== Root =============== - ----@class Root: SingleChildElement -local Root = setmetatable({}, { __index = SingleChildElement }) -Root.__index = Root - -function Root:new(props) - return SingleChildElement.new(self, props) -end - -function Root:update(dt) - -- Root может делать глобальные обновления, но главное — спустить вниз - if self.child then self.child:update(dt) end - - -- На каждом кадре делаем полный проход раскладки: - local w, h = love.graphics.getWidth(), love.graphics.getHeight() - self.size = Vec3 { w, h, 0 } - local constraints = make_constraints(w, h, w, h) -- tight: размер окна - if self.child then - -- layout с точными ограничениями родителя - self.child:layout(loosen(constraints)) -- разрешаем контенту быть меньше окна - -- arrange кладём ребёнка в начало (0,0), а Align/др. уже выровняет внутри себя - self.child:arrange(Vec3 { 0, 0, 0 }) - end -end - -function Root:draw() - if self.child then self.child:draw() end -end - --- =============== Rectangle (leaf) =============== - ----@class Rectangle: Element ----@field color number[] @ {r,g,b} 0..1 ----@field size Vec3 -local Rectangle = setmetatable({}, { __index = Element }) -Rectangle.__index = Rectangle - -function Rectangle:new(props) - local o = Element.new(self, props) - --- @cast o Rectangle - o.color = o.color or { 1, 1, 1 } - -- size должен быть задан; если нет — size = 0,0 - o.size = o.size or Vec3 { 0, 0, 0 } - return o -end - -function Rectangle:layout(constraints) - local w = self.size.x or 0 - local h = self.size.y or 0 - w, h = clamp_size(w, h, constraints) - self.size.x, self.size.y = w, h -end - -function Rectangle:draw() - love.graphics.setColor(self.color[1], self.color[2], self.color[3], 1) - love.graphics.rectangle("fill", self.origin.x, self.origin.y, self.size.x, self.size.y) - -- Сброс цвета — по вкусу -end - --- =============== Align (single child) =============== - ----@class Align: SingleChildElement ----@field alignment string "center_left" | "center" | "center_right" | "top_left" | "top_center" | "top_right" | "bottom_left" | "bottom_center" | "bottom_right" ----@field expand boolean если true — занимает все доступное от родителя -local Align = setmetatable({}, { __index = SingleChildElement }) -Align.__index = Align - -function Align:new(props) - local o = SingleChildElement.new(self, props) - ---@cast o Align - o.alignment = o.alignment or "center" - o.expand = (o.expand ~= nil) and o.expand or true -- по умолчанию растягиваемся под родителя - return o -end - -function Align:layout(constraints) - if self.child then - -- Ребёнка считаем "loose" — пусть занимает сколько хочет - self.child:layout(loosen(constraints)) - if self.expand then - -- Сам Align займет максимум - self.size.x, self.size.y = clamp_size(constraints.max_w, constraints.max_h, constraints) - else - -- Или же wrap по ребёнку - local w, h = self.child.size.x, self.child.size.y - self.size.x, self.size.y = clamp_size(w, h, constraints) - end - else - self.size.x, self.size.y = clamp_size(0, 0, constraints) - end -end - -function Align:arrange(origin) - self.origin = Vec3 { origin.x, origin.y, origin.z or 0 } - if not self.child then return end - - local pw, ph = self.size.x, self.size.y - local cw, ch = self.child.size.x, self.child.size.y - local x, y = self.origin.x, self.origin.y - - --- @todo сделать красиво - if self.alignment == "center" then - x = x + (pw - cw) / 2 - y = y + (ph - ch) / 2 - elseif self.alignment == "center_left" then - y = y + (ph - ch) / 2 - elseif self.alignment == "center_right" then - x = x + (pw - cw) - y = y + (ph - ch) / 2 - elseif self.alignment == "top_left" then - -- x,y остаются - elseif self.alignment == "top_center" then - x = x + (pw - cw) / 2 - elseif self.alignment == "top_right" then - x = x + (pw - cw) - elseif self.alignment == "bottom_left" then - y = y + (ph - ch) - elseif self.alignment == "bottom_center" then - y = y + (ph - ch) - x = x + (pw - cw) / 2 - elseif self.alignment == "bottom_right" then - x = x + (pw - cw) - y = y + (ph - ch) - else - -- неизвестное — по центру - x = x + (pw - cw) / 2 - y = y + (ph - ch) / 2 - end - - self.child:arrange(Vec3 { x, y, self.origin.z }) -end - --- =============== Row / Column (multi) =============== - --- helper: прочитать flex у ребёнка (если есть) -local function get_flex_of(ch) - return (ch.get_flex and ch:get_flex()) or 0 -end - ----@class Row: MultiChildElement ----@field gap number расстояние между детьми -local Row = setmetatable({}, { __index = MultiChildElement }) -Row.__index = Row - -function Row:new(props) - local o = MultiChildElement.new(self, props) - ---@cast o Row - o.gap = o.gap or 0 - return o -end - -function Row:layout(constraints) - local total_gap = (#self.children > 1) and (self.gap * (#self.children - 1)) or 0 - - local fixed_w, max_h = 0, 0 - local flex_children, total_flex = {}, 0 - local loose = loosen(constraints) - - -- проход 1: меряем нефлекс-детей, собираем флекс-список - for _, ch in ipairs(self.children) do - local f = get_flex_of(ch) - if f > 0 then - total_flex = total_flex + f - flex_children[#flex_children + 1] = { node = ch, flex = f } - else - ch:layout(loose) - fixed_w = fixed_w + ch.size.x - if ch.size.y > max_h then max_h = ch.size.y end - end - end - - local min_row_w = fixed_w + total_gap - local wants_expand = total_flex > 0 - - -- КЛЮЧ: если есть флекс → растягиваемся до доступного max_w - local candidate = wants_expand and constraints.max_w or min_row_w - local target_w = clamp(candidate, math.max(min_row_w, constraints.min_w), constraints.max_w) - local remaining = math.max(0, target_w - min_row_w) - - -- проход 2: раздаём остаток флекс-детям (tight по ширине) - if total_flex > 0 and remaining > 0 then - local acc = 0 - for i, item in ipairs(flex_children) do - local alloc = (i == #flex_children) - and (remaining - acc) -- последний добирает остаток для суммирования в точности в target_w - or (remaining * item.flex / total_flex) - acc = acc + alloc - local c = make_constraints(alloc, 0, alloc, loose.max_h) - item.node:layout(c) - if item.node.size.y > max_h then max_h = item.node.size.y end - end - else - -- даже если remaining==0, флекс-детей всё равно надо промерить (tight нулевой шириной) - for _, item in ipairs(flex_children) do - local c = make_constraints(0, 0, 0, loose.max_h) - item.node:layout(c) - if item.node.size.y > max_h then max_h = item.node.size.y end - end - end - - self.size.x, self.size.y = target_w, clamp(max_h, constraints.min_h, constraints.max_h) -end - -function Row:arrange(origin) - self.origin = Vec3 { origin.x, origin.y, origin.z or 0 } - local cursor_x = self.origin.x - local y_base = self.origin.y - - for i, ch in ipairs(self.children) do - -- Вертикальное выравнивание: по базовой линии (top). Можно прокачать до "center" / "bottom". - ch:arrange(Vec3 { cursor_x, y_base, self.origin.z }) - cursor_x = cursor_x + ch.size.x + (i < #self.children and self.gap or 0) - end -end - ----@class Column: MultiChildElement ----@field gap number -local Column = setmetatable({}, { __index = MultiChildElement }) -Column.__index = Column - -function Column:new(props) - local o = MultiChildElement.new(self, props) - --- @cast o Column - o.gap = o.gap or 0 - return o -end - -function Column:layout(constraints) - local total_gap = (#self.children > 1) and (self.gap * (#self.children - 1)) or 0 - - local fixed_h, max_w = 0, 0 - local flex_children, total_flex = {}, 0 - local loose = loosen(constraints) - - for _, ch in ipairs(self.children) do - local f = get_flex_of(ch) - if f > 0 then - total_flex = total_flex + f - flex_children[#flex_children + 1] = { node = ch, flex = f } - else - ch:layout(loose) - fixed_h = fixed_h + ch.size.y - if ch.size.x > max_w then max_w = ch.size.x end - end - end - - local min_col_h = fixed_h + total_gap - local wants_expand = (total_flex > 0) - local candidate = wants_expand and constraints.max_h or min_col_h - local target_h = clamp(candidate, math.max(min_col_h, constraints.min_h), constraints.max_h) - local remaining = math.max(0, target_h - min_col_h) - - if total_flex > 0 and remaining > 0 then - local acc = 0 - for i, item in ipairs(flex_children) do - local alloc = (i == #flex_children) - and (remaining - acc) - or (remaining * item.flex / total_flex) - acc = acc + alloc - local c = make_constraints(0, alloc, loose.max_w, alloc) - item.node:layout(c) - if item.node.size.x > max_w then max_w = item.node.size.x end - end - else - for _, item in ipairs(flex_children) do - local c = make_constraints(0, 0, loose.max_w, 0) - item.node:layout(c) - if item.node.size.x > max_w then max_w = item.node.size.x end - end - end - - self.size.x, self.size.y = clamp(max_w, constraints.min_w, constraints.max_w), target_h -end - -function Column:arrange(origin) - self.origin = Vec3 { origin.x, origin.y, origin.z or 0 } - local x_base = self.origin.x - local cursor_y = self.origin.y - - for i, ch in ipairs(self.children) do - ch:arrange(Vec3 { x_base, cursor_y, self.origin.z }) - cursor_y = cursor_y + ch.size.y + (i < #self.children and self.gap or 0) - end -end - ------------------------------------------------------------------ - ----@class Expanded: SingleChildElement ----@field flex integer -- коэффициент флекса (>=1) -local Expanded = setmetatable({}, { __index = SingleChildElement }) -Expanded.__index = Expanded - ----@param props { flex?: integer, child?: Element }|nil ----@return Expanded -function Expanded:new(props) - local o = SingleChildElement.new(self, props) - ---@cast o Expanded - o.flex = (o.flex and math.max(1, math.floor(o.flex))) or 1 - return o -end - -function Expanded:get_flex() - return self.flex -end - -function Expanded:layout(constraints) - if self.child then - self.child:layout(constraints) - -- Становимся размером с ребёнка с учётом ограничений - local w, h = self.child.size.x, self.child.size.y - w, h = clamp_size(w, h, constraints) - self.size.x, self.size.y = w, h - else - -- Пустой Expanded — просто занимает выделенное родителем место (можно для Spacer) - local w, h = clamp_size(0, 0, constraints) - self.size.x, self.size.y = w, h - end -end - -function Expanded:arrange(origin) - self.origin = Vec3 { origin.x, origin.y, origin.z or 0 } - if self.child then - self.child:arrange(self.origin) - end -end - --- =============== Padding (single child) =============== - ----@class Padding: SingleChildElement ----@field padding {left?: number, right?: number, top?: number, bottom?: number} -local Padding = setmetatable({}, { __index = SingleChildElement }) -Padding.__index = Padding - ----@param props { padding?: {left?: number, right?: number, top?: number, bottom?: number}, child?: Element }|nil ----@return Padding -function Padding:new(props) - local o = SingleChildElement.new(self, props) - ---@cast o Padding - local p = o.padding or {} - o.padding = { - left = tonumber(p.left) or 0, - right = tonumber(p.right) or 0, - top = tonumber(p.top) or 0, - bottom = tonumber(p.bottom) or 0, - } - return o -end - -function Padding:layout(constraints) - local L = self.padding.left - local R = self.padding.right - local T = self.padding.top - local B = self.padding.bottom - local hp = L + R - local vp = T + B - - -- Вычитаем паддинги из ограничений для ребёнка - local child_min_w = math.max(0, constraints.min_w - hp) - local child_min_h = math.max(0, constraints.min_h - vp) - local child_max_w = math.max(0, constraints.max_w - hp) - local child_max_h = math.max(0, constraints.max_h - vp) - local child_c = make_constraints(child_min_w, child_min_h, child_max_w, child_max_h) - - if self.child then - self.child:layout(child_c) - local w = self.child.size.x + hp - local h = self.child.size.y + vp - self.size.x, self.size.y = clamp_size(w, h, constraints) - else - -- Нет ребёнка: размер равен сумме паддингов (с учётом ограничений) - self.size.x, self.size.y = clamp_size(hp, vp, constraints) - end -end - -function Padding:arrange(origin) - self.origin = Vec3 { origin.x, origin.y, origin.z or 0 } - if not self.child then return end - local L = self.padding.left - local T = self.padding.top - self.child:arrange(Vec3 { self.origin.x + L, self.origin.y + T, self.origin.z }) -end - --- =============== Public constructors (callable) =============== - -local function mk_ctor(class) - -- Сохраняем существующую метатаблицу (в ней уже есть __index на родителя!) - local mt = getmetatable(class) or {} - mt.__call = function(_, props) - return class:new(props or {}) - end - setmetatable(class, mt) - return class -end - -ui.Element = Element -ui.Root = mk_ctor(Root) -ui.Align = mk_ctor(Align) -ui.Row = mk_ctor(Row) -ui.Column = mk_ctor(Column) -ui.Rectangle = mk_ctor(Rectangle) -ui.Expanded = mk_ctor(Expanded) -ui.Padding = mk_ctor(Padding) - --- Экспорт вспомогательных, если пригодится -ui.constraints = { - make = make_constraints, - loosen = loosen, - tighten = tighten, - clamp_size = clamp_size, -} - -return ui diff --git a/lib/ui/layout.lua b/lib/ui/layout.lua deleted file mode 100644 index 3a3b544..0000000 --- a/lib/ui/layout.lua +++ /dev/null @@ -1,58 +0,0 @@ -local Vec3 = require "lib.utils.vec3" -local ui = require "lib.ui.core" - - ---- @class SkillButton : Rectangle ---- @field owner Character ---- @field spellId number -local SkillButton = ui.Rectangle { - size = Vec3 { 100, 100 }, - color = { 1, 0, 0 }, -} -function SkillButton:update(dt) - ui.Rectangle.update(self, dt) - self.owner:try(Tree.behaviors.spellcaster, function(spellcaster) - self.color = spellcaster.state == "casting" and { 0, 1, 0 } or { 1, 0, 0 } - self:onTap(function() - if not spellcaster.cast then - spellcaster.cast = spellcaster.spellbook - [self.spellId] - spellcaster.state = "casting" - else - spellcaster.state = "idle" - spellcaster.cast = nil - end - end) - end) -end - -local skillRows = {} - -local layout = {} -function layout:build() - return ui.Root { - child = ui.Align { - alignment = "bottom_center", - child = - --- для каждого персонажа строим свой ряд скиллов, сохраняем его на потом и возвращаем - --- если персонаж не выделен, не возвращаем ничего - (function() - local id = Tree.level.selector.id - if not id then return nil end - if skillRows[id] then return skillRows[id] end - local r = - ui.Row { - children = { - ui.Padding { padding = { left = 4, right = 4 }, child = setmetatable({ owner = Tree.level.characters[id], spellId = 1 }, { __index = SkillButton }) }, - ui.Padding { padding = { left = 4, right = 4 }, child = setmetatable({ owner = Tree.level.characters[id], spellId = 2 }, { __index = SkillButton }) }, - ui.Padding { padding = { left = 4, right = 4 }, child = setmetatable({ owner = Tree.level.characters[id], spellId = 3 }, { __index = SkillButton }) }, - } - } - skillRows[id] = r - return r - end)() - } - } -end - -return layout diff --git a/main.lua b/main.lua index 4ec8c8c..ede2b2d 100644 --- a/main.lua +++ b/main.lua @@ -2,7 +2,6 @@ local character = require "lib/character/character" require "lib/tree" -local layout = require "lib.ui.layout" local testLayout = require "lib.simple_ui.level_layout" function love.conf(t) @@ -19,9 +18,7 @@ local lt = "0" function love.update(dt) local t1 = love.timer.getTime() Tree.controls:poll() - testLayout:update(dt) - --Widgets = layout:build() - --Widgets:update(dt) -- логика UI-слоя должна отработать раньше всех, потому что нужно перехватить жесты и не пустить их дальше + testLayout:update(dt) -- логика UI-слоя должна отработать раньше всех, потому что нужно перехватить жесты и не пустить их дальше Tree.panning:update(dt) Tree.level:update(dt) Tree.controls:cache() @@ -56,7 +53,6 @@ function love.draw() Tree.level.camera:detach() testLayout:draw() - --Widgets:draw() love.graphics.setColor(1, 1, 1) local stats = "fps: " .. love.timer.getFPS() .. " lt: " .. lt .. " dt: " .. dt