diff --git a/lib/ui/element.lua b/lib/ui/element.lua new file mode 100644 index 0000000..3a46b12 --- /dev/null +++ b/lib/ui/element.lua @@ -0,0 +1,44 @@ +require "lib.utils.vec3" +--- Stateful UI element +--- @class Element +--- @field state table +--- @field update fun(self: Element, dt: number) called each logical frame, alters self.state +--- @field draw fun(self: Element) uses self.state to draw, should not alter anything +--- @field origin Vec3 +--- @field size Vec3 +--- @field parent Element | nil +--- @field child Element | nil +local Element = {} +Element.__index = Element + +Element.state = {} +function Element:update(dt) + local parent = self.parent + if not parent then return end + + self.origin = self.origin or parent.origin + self.size = self.size or parent.size +end + +function Element:draw() end + +--- Recursive depth-first traversal. +--- If `visit` returns false, traversal is stopped early. +--- @param visit fun(el: Element): boolean|nil +--- @return boolean +function Element:traverse(visit) + local cont = visit(self) + if not cont then return false end + if not self.child then return false end + self.child.parent = self + return not not self.child:traverse(visit) +end + +--- template constructor +--- @param data {state: table, update: fun(dt: number), draw: fun(), [any]: any} +--- @return Element +function Element.new(data) + return setmetatable(data, Element) +end + +return Element diff --git a/lib/ui/widgets.lua b/lib/ui/widgets.lua new file mode 100644 index 0000000..084e569 --- /dev/null +++ b/lib/ui/widgets.lua @@ -0,0 +1,72 @@ +require "lib.utils.vec3" +local baseElement = require "lib.ui.element" + +local W = {} +--- @alias Alignment "topLeft" | "topCenter" | "topRight" | "centerLeft" | "center" | "centerRight" | "bottomLeft" | "bottomCenter" | "bottomRight" +--- @type {[Alignment]: Vec3} +local alignments = { + topLeft = Vec3 { 0, 0 }, + topCenter = Vec3 { 0.5, 0 }, + topRight = Vec3 { 1, 0 }, + centerLeft = Vec3 { 0, 0.5 }, + center = Vec3 { 0.5, 0.5 }, + centerRight = Vec3 { 1, 0.5 }, + bottomLeft = Vec3 { 0, 1 }, + bottomCenter = Vec3 { 0.5, 1 }, + bottomRight = Vec3 { 1, 1 } +} + + +--- @class UIRoot : Element +local Root = {} +setmetatable(Root, { __index = baseElement }) + +function Root.new(data) + return setmetatable(data, { __index = Root }) +end + +function Root:update(dt) + self.size = Vec3 { love.graphics.getWidth(), love.graphics.getHeight() } +end + +W.Root = Root.new +-------------------------------------------------- + +--- @class Align : Element +--- @field alignment Alignment +local Align = {} +setmetatable(Align, { __index = baseElement }) + +function Align.new(data) + data.alignment = data.alignment or "center" + return setmetatable(data, { __index = Align }) +end + +function Align:update(dt) + local parent = self.parent --[[@as Element]] + local shift = alignments[self.alignment] + self.origin = Vec3 { parent.size.x * shift.x, parent.size.y * shift.y } +end + +W.Align = Align.new +-------------------------------------------------- + +--- @class Rectangle : Element +--- @field color number[] +local Rectangle = {} +setmetatable(Rectangle, { __index = baseElement }) + +function Rectangle.new(data) + return setmetatable(data, { __index = Rectangle }) +end + +function Rectangle:draw() + love.graphics.setColor(self.color or { 1, 1, 1 }) + love.graphics.rectangle("fill", self.origin.x - self.size.x / 2, self.origin.y - self.size.y / 2, self.size.x, + self.size.y) +end + +W.Rectangle = Rectangle.new +--------------------------------------------------- + +return W diff --git a/main.lua b/main.lua index 8d29dc6..53dcbcb 100644 --- a/main.lua +++ b/main.lua @@ -1,10 +1,10 @@ -- CameraLoader = require 'lib/camera' +local ui = require "lib.ui.widgets" local character = require "lib/character/character" require "lib/tree" - function love.conf(t) t.console = true end @@ -24,6 +24,16 @@ function love.load() end end + Widgets = ui.Root { + child = ui.Align { + alignment = "bottomCenter", + child = ui.Rectangle { + size = Vec3 { 100, 100 }, + color = { 0, 0, 1 } + } + } + } + -- PlayerFaction.characters = { Hero1, Hero2 } love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true }) end @@ -35,6 +45,11 @@ function love.update(dt) Tree.panning:update(dt) Tree.level:update(dt) Tree.controls:cache() + + Widgets:traverse(function(el) + el:update(dt) + return true + end) local t2 = love.timer.getTime() lt = string.format("%.3f", (t2 - t1) * 1000) end @@ -81,11 +96,17 @@ function love.draw() love.graphics.setColor(1, 1, 1) end - - Tree.level:draw() Tree.level.camera:detach() + Widgets:traverse( + function(el) + el:draw() + return true + end + ) + love.graphics.setColor(1, 1, 1) + local stats = "fps: " .. love.timer.getFPS() .. " lt: " .. lt .. " dt: " .. dt love.graphics.print(stats, 10, 10)