mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
233 lines
7.4 KiB
Lua
233 lines
7.4 KiB
Lua
local packageName = ... or "FocusIndicator"
|
|
local modulePath = packageName:match("(.-)[^%.]+$")
|
|
|
|
local function req(name)
|
|
return require(modulePath .. name)
|
|
end
|
|
|
|
local FocusIndicator = {}
|
|
|
|
--- Configuration
|
|
---@type KeyboardNavigationFocusIndicatorConfig
|
|
FocusIndicator.config = {
|
|
enabled = true,
|
|
|
|
--- Custom draw function to override default rendering
|
|
---@type function|nil
|
|
--- Called with: element, bounds, style - return true to skip default drawing
|
|
draw = nil,
|
|
|
|
-- Appearance
|
|
color = { 0.2, 0.6, 1.0, 0.8 }, -- Blue with 80% opacity
|
|
lineWidth = 2,
|
|
inset = -3, -- Negative value extends beyond element
|
|
borderRadius = 4,
|
|
|
|
-- Animation
|
|
animationDuration = 0.15, -- Seconds for focus animation
|
|
pulseEnabled = false, -- Enable pulsing animation
|
|
pulseDuration = 1.0, -- Seconds per pulse cycle
|
|
pulseScaleMin = 0.95, -- Minimum scale during pulse
|
|
pulseScaleMax = 1.05, -- Maximum scale during pulse
|
|
}
|
|
|
|
--- State
|
|
FocusIndicator._focusedElement = nil
|
|
FocusIndicator._animationProgress = 0
|
|
FocusIndicator._pulsePhase = 0
|
|
FocusIndicator._hidden = true
|
|
FocusIndicator._deps = nil
|
|
|
|
--- Initialize FocusIndicator module
|
|
---@param deps table Dependencies table containing Context and Color modules
|
|
---@field deps.Context table Context module for getting focused element
|
|
---@field deps.Color table Color module for color manipulation
|
|
function FocusIndicator.init(deps)
|
|
FocusIndicator._deps = deps
|
|
FocusIndicator._Context = deps.Context
|
|
FocusIndicator._Color = deps.Color
|
|
end
|
|
|
|
--- Update animation state for entrance and pulse effects
|
|
---@param dt number Delta time in seconds since last frame
|
|
function FocusIndicator:update(dt)
|
|
if not FocusIndicator.config.enabled then
|
|
return
|
|
end
|
|
|
|
-- Update focus entrance animation
|
|
if FocusIndicator._animationProgress < 1 then
|
|
FocusIndicator._animationProgress =
|
|
math.min(1, FocusIndicator._animationProgress + (dt / FocusIndicator.config.animationDuration))
|
|
end
|
|
|
|
-- Update pulse animation
|
|
if FocusIndicator.config.pulseEnabled then
|
|
FocusIndicator._pulsePhase = (FocusIndicator._pulsePhase + dt) % FocusIndicator.config.pulseDuration
|
|
end
|
|
end
|
|
|
|
--- Set the focused element to render indicator around
|
|
---@param element Element? The element to show focus indicator around, or nil to hide
|
|
function FocusIndicator.setFocused(element)
|
|
FocusIndicator._focusedElement = element
|
|
FocusIndicator._hidden = element == nil
|
|
-- Reset animation when focus changes
|
|
if element then
|
|
FocusIndicator._animationProgress = 0
|
|
end
|
|
end
|
|
|
|
--- Get the current scale factor for animations
|
|
--- Combines entrance scale (0.8 to 1.0) with optional pulse scale
|
|
---@return number Scale factor (typically 0.8-1.05 range)
|
|
function FocusIndicator:getScale()
|
|
local scale = 1
|
|
|
|
-- Apply entrance animation (scale up from 0.8)
|
|
local entranceScale = 0.8 + (0.2 * FocusIndicator._animationProgress)
|
|
scale = scale * entranceScale
|
|
|
|
-- Apply pulse animation
|
|
if FocusIndicator.config.pulseEnabled then
|
|
local pulseProgress = FocusIndicator._pulsePhase / FocusIndicator.config.pulseDuration
|
|
-- Smooth sine wave pulse
|
|
local pulseScale = FocusIndicator.config.pulseScaleMin
|
|
+ (FocusIndicator.config.pulseScaleMax - FocusIndicator.config.pulseScaleMin)
|
|
* (0.5 + 0.5 * math.sin(2 * math.pi * pulseProgress))
|
|
scale = scale * pulseScale
|
|
end
|
|
|
|
return scale
|
|
end
|
|
|
|
--- Get the current opacity for the indicator
|
|
--- Applies entrance animation fade-in to the configured alpha
|
|
---@return number Alpha value (0-1 range)
|
|
function FocusIndicator:getOpacity()
|
|
-- Fade in on focus
|
|
return FocusIndicator.config.color[4] * FocusIndicator._animationProgress
|
|
end
|
|
|
|
--- Draw the focus indicator around the focused element
|
|
--- Renders a rounded rectangle border, or calls custom draw function if configured
|
|
--- Should be called from within love.draw() after all elements are drawn
|
|
function FocusIndicator:draw()
|
|
if not FocusIndicator.config.enabled then
|
|
return
|
|
end
|
|
|
|
if FocusIndicator._hidden then
|
|
return
|
|
end
|
|
|
|
-- In immediate mode the stored element reference is stale (recreated every frame).
|
|
-- Always resolve through Context so we get the live object with up-to-date positions.
|
|
local element
|
|
if FocusIndicator._Context then
|
|
element = FocusIndicator._Context.getFocused()
|
|
else
|
|
element = FocusIndicator._focusedElement
|
|
end
|
|
|
|
if not element then
|
|
return
|
|
end
|
|
|
|
-- Get element dimensions (use border-box size which includes padding)
|
|
local x = element.x or 0
|
|
local y = element.y or 0
|
|
local w = element._borderBoxWidth
|
|
or (element.width + (element.padding and (element.padding.left + element.padding.right) or 0))
|
|
local h = element._borderBoxHeight
|
|
or (element.height + (element.padding and (element.padding.top + element.padding.bottom) or 0))
|
|
|
|
if w == 0 or h == 0 then
|
|
return
|
|
end
|
|
|
|
-- Calculate indicator dimensions with inset and scale
|
|
local inset = FocusIndicator.config.inset
|
|
local scale = self:getScale()
|
|
|
|
local indicatorX = x + inset
|
|
local indicatorY = y + inset
|
|
local indicatorW = w - 2 * inset
|
|
local indicatorH = h - 2 * inset
|
|
|
|
-- Center the scale around the element
|
|
local offsetX = (indicatorW * (1 - scale)) / 2
|
|
local offsetY = (indicatorH * (1 - scale)) / 2
|
|
|
|
indicatorX = indicatorX + offsetX
|
|
indicatorY = indicatorY + offsetY
|
|
indicatorW = indicatorW * scale
|
|
indicatorH = indicatorH * scale
|
|
|
|
-- Get color with animated opacity
|
|
local r, g, b = FocusIndicator.config.color[1], FocusIndicator.config.color[2], FocusIndicator.config.color[3]
|
|
local a = self:getOpacity()
|
|
|
|
-- Build style table for custom draw callback
|
|
local bounds = {
|
|
x = indicatorX,
|
|
y = indicatorY,
|
|
width = indicatorW,
|
|
height = indicatorH,
|
|
}
|
|
|
|
local style = {
|
|
color = { r = r, g = g, b = b, a = a },
|
|
lineWidth = FocusIndicator.config.lineWidth,
|
|
borderRadius = FocusIndicator.config.borderRadius,
|
|
scale = scale,
|
|
opacity = a,
|
|
}
|
|
|
|
-- Check for custom draw callback
|
|
if FocusIndicator.config.draw then
|
|
local skipDefault = FocusIndicator.config.draw(element, bounds, style)
|
|
if skipDefault then
|
|
return
|
|
end
|
|
end
|
|
|
|
-- Save current love.graphics state
|
|
local prevBlend, prevAlphaMode = love.graphics.getBlendMode()
|
|
local prevR, prevG, prevB, prevA = love.graphics.getColor()
|
|
local prevLineWidth = love.graphics.getLineWidth()
|
|
|
|
-- Set blend mode for transparency
|
|
love.graphics.setBlendMode("alpha")
|
|
|
|
-- Draw rounded rectangle border
|
|
love.graphics.setColor(r, g, b, a)
|
|
love.graphics.setLineWidth(FocusIndicator.config.lineWidth)
|
|
|
|
-- Draw the rounded rectangle border
|
|
local borderRadius = FocusIndicator.config.borderRadius
|
|
love.graphics.rectangle("line", indicatorX, indicatorY, indicatorW, indicatorH, borderRadius)
|
|
|
|
-- Restore love.graphics state
|
|
love.graphics.setBlendMode(prevBlend, prevAlphaMode)
|
|
love.graphics.setColor(prevR, prevG, prevB, prevA)
|
|
love.graphics.setLineWidth(prevLineWidth)
|
|
end
|
|
|
|
--- Set the indicator color
|
|
---@param r number Red component (0-1 range)
|
|
---@param g number Green component (0-1 range)
|
|
---@param b number Blue component (0-1 range)
|
|
---@param a number|nil Alpha component (0-1 range), defaults to current alpha if omitted
|
|
function FocusIndicator.setColor(r, g, b, a)
|
|
FocusIndicator.config.color = { r, g, b, a or FocusIndicator.config.color[4] }
|
|
end
|
|
|
|
--- Set the stroke width for the indicator border
|
|
---@param width number Line width in pixels
|
|
function FocusIndicator.setLineWidth(width)
|
|
FocusIndicator.config.lineWidth = width
|
|
end
|
|
|
|
return FocusIndicator
|