showing tasks
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,4 @@ lurker.lua
|
|||||||
themes/metal/
|
themes/metal/
|
||||||
themes/space/
|
themes/space/
|
||||||
.DS_STORE
|
.DS_STORE
|
||||||
tasks
|
#tasks
|
||||||
|
|||||||
@@ -2,25 +2,23 @@
|
|||||||
-- Element Object
|
-- Element Object
|
||||||
-- ====================
|
-- ====================
|
||||||
|
|
||||||
-- Module dependencies (using relative paths)
|
-- Module dependencies
|
||||||
local modulePath = (...):match("(.-)[^%.]+$")
|
local GuiState = require("GuiState")
|
||||||
local function req(name)
|
local Theme = require("Theme")
|
||||||
return require(modulePath .. name)
|
local Color = require("Color")
|
||||||
end
|
local Units = require("Units")
|
||||||
|
local Blur = require("Blur")
|
||||||
local GuiState = req("GuiState")
|
local ImageRenderer = require("ImageRenderer")
|
||||||
local Theme = req("Theme")
|
local NineSlice = require("NineSlice")
|
||||||
local Color = req("Color")
|
local RoundedRect = require("RoundedRect")
|
||||||
local Units = req("Units")
|
--local Animation = require("Animation")
|
||||||
local Blur = req("Blur")
|
local ImageCache = require("ImageCache")
|
||||||
local ImageRenderer = req("ImageRenderer")
|
local utils = require("utils")
|
||||||
local NineSlice = req("NineSlice")
|
local Grid = require("Grid")
|
||||||
local RoundedRect = req("RoundedRect")
|
local InputEvent = require("InputEvent")
|
||||||
--local Animation = req("Animation")
|
local StateManager = require("StateManager")
|
||||||
local ImageCache = req("ImageCache")
|
local StateManager = req("StateManager")
|
||||||
local utils = req("utils")
|
local StateManager = req("StateManager")
|
||||||
local Grid = req("Grid")
|
|
||||||
local InputEvent = req("InputEvent")
|
|
||||||
|
|
||||||
-- Extract utilities
|
-- Extract utilities
|
||||||
local enums = utils.enums
|
local enums = utils.enums
|
||||||
@@ -128,6 +126,7 @@ Public API methods to access internal state:
|
|||||||
---@field theme string? -- Theme component to use for rendering
|
---@field theme string? -- Theme component to use for rendering
|
||||||
---@field themeComponent string?
|
---@field themeComponent string?
|
||||||
---@field _themeState string? -- Current theme state (normal, hover, pressed, active, disabled)
|
---@field _themeState string? -- Current theme state (normal, hover, pressed, active, disabled)
|
||||||
|
---@field _stateId string? -- State manager ID for this element
|
||||||
---@field disabled boolean? -- Whether the element is disabled (default: false)
|
---@field disabled boolean? -- Whether the element is disabled (default: false)
|
||||||
---@field active boolean? -- Whether the element is active/focused (for inputs, default: false)
|
---@field active boolean? -- Whether the element is active/focused (for inputs, default: false)
|
||||||
---@field disableHighlight boolean? -- Whether to disable the pressed state highlight overlay (default: false)
|
---@field disableHighlight boolean? -- Whether to disable the pressed state highlight overlay (default: false)
|
||||||
|
|||||||
306
modules/StateManager.lua
Normal file
306
modules/StateManager.lua
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
-- ====================
|
||||||
|
-- State Manager Module
|
||||||
|
-- ====================
|
||||||
|
-- Provides centralized state management for immediate mode GUI elements
|
||||||
|
-- Handles hover, pressed, disabled, and other interactive states properly
|
||||||
|
-- Manages state change events and integrates with theme components
|
||||||
|
|
||||||
|
---@class StateManager
|
||||||
|
local StateManager = {}
|
||||||
|
|
||||||
|
-- State storage: ID -> state table
|
||||||
|
local stateStore = {}
|
||||||
|
local frameNumber = 0
|
||||||
|
|
||||||
|
-- Configuration
|
||||||
|
local config = {
|
||||||
|
stateRetentionFrames = 60, -- Keep unused state for 60 frames (~1 second at 60fps)
|
||||||
|
maxStateEntries = 1000, -- Maximum state entries before forced GC
|
||||||
|
}
|
||||||
|
|
||||||
|
-- State change listeners
|
||||||
|
local stateChangeListeners = {}
|
||||||
|
|
||||||
|
--- Get or create a state object for an element ID
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return table state State object for the element
|
||||||
|
function StateManager.getState(id)
|
||||||
|
if not stateStore[id] then
|
||||||
|
stateStore[id] = {
|
||||||
|
hover = false,
|
||||||
|
pressed = false,
|
||||||
|
focused = false,
|
||||||
|
disabled = false,
|
||||||
|
active = false,
|
||||||
|
lastHoverFrame = 0,
|
||||||
|
lastPressedFrame = 0,
|
||||||
|
lastFocusFrame = 0,
|
||||||
|
lastUpdateFrame = 0,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return stateStore[id]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Update state for an element ID
|
||||||
|
---@param id string Element ID
|
||||||
|
---@param newState table New state values to merge
|
||||||
|
function StateManager.updateState(id, newState)
|
||||||
|
local state = StateManager.getState(id)
|
||||||
|
|
||||||
|
-- Track which properties are changing
|
||||||
|
local changedProperties = {}
|
||||||
|
for key, value in pairs(newState) do
|
||||||
|
if state[key] ~= value then
|
||||||
|
changedProperties[key] = true
|
||||||
|
end
|
||||||
|
state[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Track frame numbers for state changes
|
||||||
|
if newState.hover ~= nil then
|
||||||
|
state.lastHoverFrame = frameNumber
|
||||||
|
end
|
||||||
|
if newState.pressed ~= nil then
|
||||||
|
state.lastPressedFrame = frameNumber
|
||||||
|
end
|
||||||
|
if newState.focused ~= nil then
|
||||||
|
state.lastFocusFrame = frameNumber
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Track last update frame
|
||||||
|
state.lastUpdateFrame = frameNumber
|
||||||
|
|
||||||
|
-- Notify listeners of state changes (if any)
|
||||||
|
if next(changedProperties) then
|
||||||
|
StateManager.notifyStateChange(id, changedProperties, newState)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the current state for an element ID
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return table state State object for the element
|
||||||
|
function StateManager.getCurrentState(id)
|
||||||
|
return stateStore[id] or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clear state for a specific element ID
|
||||||
|
---@param id string Element ID
|
||||||
|
function StateManager.clearState(id)
|
||||||
|
stateStore[id] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Increment frame counter (called at frame start)
|
||||||
|
function StateManager.incrementFrame()
|
||||||
|
frameNumber = frameNumber + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get current frame number
|
||||||
|
---@return number
|
||||||
|
function StateManager.getFrameNumber()
|
||||||
|
return frameNumber
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clean up stale states (not accessed recently)
|
||||||
|
---@return number count Number of states cleaned up
|
||||||
|
function StateManager.cleanup()
|
||||||
|
local cleanedCount = 0
|
||||||
|
local retentionFrames = config.stateRetentionFrames
|
||||||
|
|
||||||
|
for id, state in pairs(stateStore) do
|
||||||
|
-- Check if state is old (no updates in last N frames)
|
||||||
|
local lastUpdateFrame = math.max(
|
||||||
|
state.lastHoverFrame,
|
||||||
|
state.lastPressedFrame,
|
||||||
|
state.lastFocusFrame,
|
||||||
|
state.lastUpdateFrame
|
||||||
|
)
|
||||||
|
|
||||||
|
if frameNumber - lastUpdateFrame > retentionFrames then
|
||||||
|
stateStore[id] = nil
|
||||||
|
cleanedCount = cleanedCount + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return cleanedCount
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Force cleanup if state count exceeds maximum
|
||||||
|
---@return number count Number of states cleaned up
|
||||||
|
function StateManager.forceCleanupIfNeeded()
|
||||||
|
local stateCount = 0
|
||||||
|
for _ in pairs(stateStore) do
|
||||||
|
stateCount = stateCount + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if stateCount > config.maxStateEntries then
|
||||||
|
-- Clean up states not accessed in last 10 frames (aggressive)
|
||||||
|
local cleanedCount = 0
|
||||||
|
|
||||||
|
for id, state in pairs(stateStore) do
|
||||||
|
local lastUpdateFrame = math.max(
|
||||||
|
state.lastHoverFrame,
|
||||||
|
state.lastPressedFrame,
|
||||||
|
state.lastFocusFrame,
|
||||||
|
state.lastUpdateFrame
|
||||||
|
)
|
||||||
|
|
||||||
|
if frameNumber - lastUpdateFrame > 10 then
|
||||||
|
stateStore[id] = nil
|
||||||
|
cleanedCount = cleanedCount + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return cleanedCount
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get total number of stored states
|
||||||
|
---@return number
|
||||||
|
function StateManager.getStateCount()
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(stateStore) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clear all states
|
||||||
|
function StateManager.clearAllStates()
|
||||||
|
stateStore = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Configure state management
|
||||||
|
---@param newConfig {stateRetentionFrames?: number, maxStateEntries?: number}
|
||||||
|
function StateManager.configure(newConfig)
|
||||||
|
if newConfig.stateRetentionFrames then
|
||||||
|
config.stateRetentionFrames = newConfig.stateRetentionFrames
|
||||||
|
end
|
||||||
|
if newConfig.maxStateEntries then
|
||||||
|
config.maxStateEntries = newConfig.maxStateEntries
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Subscribe to state change events for an element ID
|
||||||
|
---@param id string Element ID
|
||||||
|
---@param callback fun(id: string, property: string, oldValue: any, newValue: any)
|
||||||
|
function StateManager.subscribe(id, callback)
|
||||||
|
if not stateChangeListeners[id] then
|
||||||
|
stateChangeListeners[id] = {}
|
||||||
|
end
|
||||||
|
table.insert(stateChangeListeners[id], callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Notify listeners of a state change
|
||||||
|
---@param id string Element ID
|
||||||
|
---@param changedProperties table Properties that have changed
|
||||||
|
---@param newState table The new state values
|
||||||
|
function StateManager.notifyStateChange(id, changedProperties, newState)
|
||||||
|
if not stateChangeListeners[id] then return end
|
||||||
|
|
||||||
|
local prevState = StateManager.getCurrentState(id)
|
||||||
|
|
||||||
|
for property, _ in pairs(changedProperties) do
|
||||||
|
local oldValue = prevState[property]
|
||||||
|
local newValue = newState[property]
|
||||||
|
|
||||||
|
for _, callback in ipairs(stateChangeListeners[id]) do
|
||||||
|
callback(id, property, oldValue, newValue)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unsubscribe a listener for an element ID
|
||||||
|
---@param id string Element ID
|
||||||
|
---@param callback fun(id: string, property: string, oldValue: any, newValue: any)
|
||||||
|
function StateManager.unsubscribe(id, callback)
|
||||||
|
if not stateChangeListeners[id] then return end
|
||||||
|
|
||||||
|
for i = #stateChangeListeners[id], 1, -1 do
|
||||||
|
if stateChangeListeners[id][i] == callback then
|
||||||
|
table.remove(stateChangeListeners[id], i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get all listeners for debugging
|
||||||
|
---@return table
|
||||||
|
function StateManager.getListeners()
|
||||||
|
return stateChangeListeners
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the active state values for an element
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return table state Active state values
|
||||||
|
function StateManager.getActiveState(id)
|
||||||
|
local state = StateManager.getState(id)
|
||||||
|
|
||||||
|
-- Return only the active state properties (not tracking frames)
|
||||||
|
return {
|
||||||
|
hover = state.hover,
|
||||||
|
pressed = state.pressed,
|
||||||
|
focused = state.focused,
|
||||||
|
disabled = state.disabled,
|
||||||
|
active = state.active,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if an element is currently hovered
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return boolean
|
||||||
|
function StateManager.isHovered(id)
|
||||||
|
local state = StateManager.getState(id)
|
||||||
|
return state.hover or false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if an element is currently pressed
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return boolean
|
||||||
|
function StateManager.isPressed(id)
|
||||||
|
local state = StateManager.getState(id)
|
||||||
|
return state.pressed or false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if an element is currently focused
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return boolean
|
||||||
|
function StateManager.isFocused(id)
|
||||||
|
local state = StateManager.getState(id)
|
||||||
|
return state.focused or false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if an element is disabled
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return boolean
|
||||||
|
function StateManager.isDisabled(id)
|
||||||
|
local state = StateManager.getState(id)
|
||||||
|
return state.disabled or false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if an element is active (e.g., input focused)
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return boolean
|
||||||
|
function StateManager.isActive(id)
|
||||||
|
local state = StateManager.getState(id)
|
||||||
|
return state.active or false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the time since last hover event for an element
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return number secondsSinceLastHover
|
||||||
|
function StateManager.getSecondsSinceLastHover(id)
|
||||||
|
local state = StateManager.getState(id)
|
||||||
|
return (frameNumber - state.lastHoverFrame) / 60 -- Assuming 60fps
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the time since last press event for an element
|
||||||
|
---@param id string Element ID
|
||||||
|
---@return number secondsSinceLastPress
|
||||||
|
function StateManager.getSecondsSinceLastPress(id)
|
||||||
|
local state = StateManager.getState(id)
|
||||||
|
return (frameNumber - state.lastPressedFrame) / 60 -- Assuming 60fps
|
||||||
|
end
|
||||||
|
|
||||||
|
return StateManager
|
||||||
Reference in New Issue
Block a user