local icons = require("lib.utils.sprite_atlas").load(Tree.assets.files.dev_icons) local easing = require "lib.utils.easing" local AnimationNode = require "lib.animation_node" local Element = require "lib.simple_ui.element" local Rect = require "lib.simple_ui.rect" local UI_SCALE = require "lib.simple_ui.level.scale" --- @class SkillButton : UIElement --- @field hovered boolean --- @field selected boolean --- @field onClick function? --- @field icon? string local skillButton = setmetatable({}, Element) skillButton.__index = skillButton function skillButton:update(dt) if not self.icon then return end local mx, my = love.mouse.getPosition() if self:hitTest(mx, my) then self.hovered = true if Tree.controls:isJustPressed("select") then if self.onClick then self.onClick() end Tree.controls:consume("select") end else self.hovered = false end end function skillButton:draw() love.graphics.setLineWidth(2) if not self.icon then love.graphics.setColor(0.05, 0.05, 0.05) love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) self:drawBorder("inner") return end if self.selected then love.graphics.setColor(0.3, 1, 0.3) elseif self.hovered then love.graphics.setColor(0.7, 1, 0.7) else love.graphics.setColor(1, 1, 1) end local quad = icons:pickQuad(self.icon) love.graphics.push() love.graphics.translate(self.bounds.x, self.bounds.y) love.graphics.scale(self.bounds.width / icons.tileSize, self.bounds.height / icons.tileSize) love.graphics.draw(icons.atlas, quad) love.graphics.pop() self:drawBorder("inner") end -------------------------------------------------------------------------------- --- @class SkillRow : UIElement --- @field characterId Id --- @field selected SkillButton? --- @field animationNode AnimationNode --- @field state "show" | "idle" | "hide" --- @field children SkillButton[] local skillRow = setmetatable({}, Element) skillRow.__index = skillRow --- @param characterId Id --- @return SkillRow function skillRow.new(characterId) local t = { characterId = characterId, state = "show", children = {} } setmetatable(t, skillRow) local char = Tree.level.characters[characterId] char:try(Tree.behaviors.spellcaster, function(behavior) for i, spell in ipairs(behavior.spellbook) do local skb = skillButton:new { icon = spell.tag } skb.onClick = function() if t.state ~= "idle" then return end skb.selected = not skb.selected if t.selected then t.selected.selected = false end t.selected = skb if not behavior.cast then behavior.cast = behavior.spellbook[i] behavior.state = "casting" else behavior.state = "idle" behavior.cast = nil end end t.children[i] = skb end end) for i = #t.children + 1, 7, 1 do t.children[i] = skillButton:new {} end return t end function skillRow:show() AnimationNode { function(animationNode) if self.animationNode then self.animationNode:finish() end self.animationNode = animationNode self.state = "show" end, duration = 300, onEnd = function() self.state = "idle" end, easing = easing.easeOutCubic }:run() end function skillRow:hide() AnimationNode { function(animationNode) if self.animationNode then self.animationNode:finish() end self.animationNode = animationNode self.state = "hide" end, duration = 300, easing = easing.easeOutCubic }:run() end function skillRow:update(dt) if self.animationNode then self.animationNode:update(dt) end local iconSize = 64 * UI_SCALE local screenW, screenH = love.graphics.getDimensions() local padding, margin = 8, 4 local count = #self.children -- слоты под скиллы self.bounds = Rect { width = iconSize * count + (count + 1) * margin, height = iconSize + 2 * margin, } self.bounds.y = screenH - self.bounds.height - padding -- отступ снизу self.bounds.x = screenW / 2 - self.bounds.width / 2 for i, skb in ipairs(self.children) do skb.bounds = Rect { x = self.bounds.x + margin + (i - 1) * (iconSize + margin), -- друг за другом, включая первый отступ от границы y = self.bounds.y + margin, height = iconSize, width = iconSize } skb:update(dt) end end local c = love.graphics.newCanvas(1280, 720) --- @TODO: выставлять канвасу правильный размер в зависимости от окна function skillRow:draw() love.graphics.setCanvas({ c, stencil = true }) love.graphics.clear() love.graphics.setColor(1, 1, 1) -- сначала иконки скиллов for _, skb in ipairs(self.children) do skb:draw() end -- маска для вырезов под иконки love.graphics.setShader(Tree.assets.files.shaders.alpha_mask) love.graphics.stencil(function() local mask = Tree.assets.files.masks.rrect32 local maskSize = mask:getWidth() for _, skb in ipairs(self.children) do love.graphics.draw(mask, skb.bounds.x, skb.bounds.y, 0, skb.bounds.width / maskSize, skb.bounds.height / maskSize) end end, "replace", 1) love.graphics.setShader() -- дальше рисуем панель, перекрывая иконки love.graphics.setStencilTest("less", 1) -- шум love.graphics.setShader(Tree.assets.files.shaders.soft_uniform_noise) love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) love.graphics.setShader() -- фон love.graphics.setColor(38 / 255, 50 / 255, 56 / 255) love.graphics.setBlendMode("multiply", "premultiplied") love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) love.graphics.setBlendMode("alpha") --граница self:drawBorder("outer") love.graphics.setStencilTest() --затенение self:drawGradientOverlay() love.graphics.setColor(1, 1, 1) love.graphics.setCanvas() local alpha = 1 if self.state == "show" then alpha = self.animationNode:getValue() elseif self.state == "hide" then alpha = 1 - self.animationNode:getValue() end love.graphics.setColor(1, 1, 1, alpha) love.graphics.draw(c) end return skillRow.new