cleaned up rendering mode swapping
This commit is contained in:
@@ -38,7 +38,18 @@ function Context.registerElement(element)
|
||||
end
|
||||
|
||||
function Context.clearFrameElements()
|
||||
Context._zIndexOrderedElements = {}
|
||||
-- Preserve retained-mode elements
|
||||
if Context._immediateMode then
|
||||
local retainedElements = {}
|
||||
for _, element in ipairs(Context._zIndexOrderedElements) do
|
||||
if element._elementMode == "retained" then
|
||||
table.insert(retainedElements, element)
|
||||
end
|
||||
end
|
||||
Context._zIndexOrderedElements = retainedElements
|
||||
else
|
||||
Context._zIndexOrderedElements = {}
|
||||
end
|
||||
end
|
||||
|
||||
--- Sort elements by z-index (called after all elements are registered)
|
||||
|
||||
@@ -188,6 +188,24 @@ end
|
||||
---@param props ElementProps
|
||||
---@return Element
|
||||
function Element.new(props)
|
||||
-- Early check: If this is a retained-mode element in an immediate-mode context,
|
||||
-- check if it already exists (was restored to parent) to prevent duplicates
|
||||
local elementMode = props.mode
|
||||
if elementMode == nil then
|
||||
elementMode = Element._Context._immediateMode and "immediate" or "retained"
|
||||
end
|
||||
|
||||
-- If retained mode and has an ID, check if element already exists in parent's children
|
||||
if elementMode == "retained" and props.id and props.id ~= "" and props.parent then
|
||||
-- Check if this element already exists in parent's restored children
|
||||
for _, child in ipairs(props.parent.children) do
|
||||
if child.id == props.id and child._elementMode == "retained" then
|
||||
-- Element already exists (was restored), return existing instance
|
||||
return child
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local self = setmetatable({}, Element)
|
||||
|
||||
-- Create dependency subsets for sub-modules (defined once, used throughout)
|
||||
@@ -272,11 +290,56 @@ function Element.new(props)
|
||||
self.children = {}
|
||||
self.onEvent = props.onEvent
|
||||
|
||||
-- Auto-generate ID in immediate mode if not provided
|
||||
if self._elementMode == "immediate" and (not props.id or props.id == "") then
|
||||
-- Track whether ID was auto-generated (before ID assignment)
|
||||
local idWasAutoGenerated = not props.id or props.id == ""
|
||||
|
||||
-- Auto-generate ID if not provided (for all elements)
|
||||
if idWasAutoGenerated then
|
||||
self.id = Element._StateManager.generateID(props, props.parent)
|
||||
else
|
||||
self.id = props.id or ""
|
||||
self.id = props.id
|
||||
end
|
||||
|
||||
-- AFTER ID is determined, check for duplicate top-level OR child retained elements
|
||||
-- ONLY for auto-generated IDs (same call site recreating same element)
|
||||
-- If user provides explicit ID, they control uniqueness
|
||||
if self._elementMode == "retained" and idWasAutoGenerated and self.id and self.id ~= "" then
|
||||
if not props.parent then
|
||||
-- Top-level element: check in topElements
|
||||
for _, existingElement in ipairs(Element._Context.topElements) do
|
||||
if existingElement.id == self.id and existingElement._elementMode == "retained" then
|
||||
-- Element already exists (was preserved from previous frame), return existing instance
|
||||
-- CRITICAL: Clear children array to prevent accumulation
|
||||
-- Children will be re-declared this frame (retained children will be found via duplicate check)
|
||||
existingElement.children = {}
|
||||
return existingElement
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Child element: check in parent's children
|
||||
for _, existingChild in ipairs(props.parent.children) do
|
||||
if existingChild.id == self.id and existingChild._elementMode == "retained" then
|
||||
-- Element already exists (was restored to parent), return existing instance
|
||||
-- CRITICAL: Clear children array to prevent accumulation
|
||||
-- Children will be re-declared this frame (retained children will be found via duplicate check)
|
||||
existingChild.children = {}
|
||||
return existingChild
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- In immediate mode, restore retained children from StateManager
|
||||
-- This allows retained-mode children to persist when immediate-mode parents recreate
|
||||
if self._elementMode == "immediate" and self.id and self.id ~= "" then
|
||||
local retainedChildren = Element._StateManager.getRetainedChildren(self.id)
|
||||
if retainedChildren and #retainedChildren > 0 then
|
||||
-- Restore retained children and update their parent references
|
||||
for _, child in ipairs(retainedChildren) do
|
||||
child.parent = self
|
||||
table.insert(self.children, child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.userdata = props.userdata
|
||||
@@ -2237,6 +2300,11 @@ function Element:destroy()
|
||||
-- Clear children table
|
||||
self.children = {}
|
||||
|
||||
-- Clear retained children from StateManager (if this is an immediate-mode element)
|
||||
if self._elementMode == "immediate" and self.id and self.id ~= "" then
|
||||
Element._StateManager.clearRetainedChildren(self.id)
|
||||
end
|
||||
|
||||
-- Clear parent reference
|
||||
if self.parent then
|
||||
self.parent = nil
|
||||
@@ -3503,6 +3571,12 @@ function Element:saveState()
|
||||
state._textDragOccurred = self._textDragOccurred
|
||||
end
|
||||
|
||||
-- Save retained children references (for mixed-mode trees)
|
||||
-- Only save if this is an immediate-mode element with retained children
|
||||
if self._elementMode == "immediate" and #self.children > 0 then
|
||||
Element._StateManager.saveRetainedChildren(self.id, self.children)
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ local stateDefaults = {
|
||||
_cursorVisible = true,
|
||||
_cursorBlinkPaused = false,
|
||||
_cursorBlinkPauseTimer = 0,
|
||||
|
||||
-- Retained children references (for mixed-mode trees)
|
||||
retainedChildren = nil,
|
||||
}
|
||||
|
||||
--- Check if a value equals the default for a key
|
||||
@@ -208,16 +211,24 @@ function StateManager.generateID(props, parent)
|
||||
|
||||
-- If we have a parent, use tree-based ID generation for stability
|
||||
if parent and parent.id and parent.id ~= "" then
|
||||
-- Count how many children the parent currently has
|
||||
-- This gives us a stable sibling index
|
||||
local siblingIndex = #(parent.children or {})
|
||||
-- For child elements, use call-site (file + line) like top-level elements
|
||||
-- This ensures the same call site always generates the same ID, even when
|
||||
-- retained children persist in parent.children array
|
||||
local baseID = parent.id .. "_" .. locationKey
|
||||
|
||||
-- Count how many children have been created at THIS call site
|
||||
local callSiteKey = parent.id .. "_" .. locationKey
|
||||
callSiteCounters[callSiteKey] = (callSiteCounters[callSiteKey] or 0) + 1
|
||||
local instanceNum = callSiteCounters[callSiteKey]
|
||||
|
||||
if instanceNum > 1 then
|
||||
baseID = baseID .. "_" .. instanceNum
|
||||
end
|
||||
|
||||
-- Generate ID based on parent ID + sibling position (NO line number for stability)
|
||||
-- This ensures the same position in the tree always gets the same ID
|
||||
local baseID = parent.id .. "_child" .. siblingIndex
|
||||
|
||||
-- Add property hash if provided (for additional differentiation at same position)
|
||||
if props then
|
||||
-- Add property hash if provided (for additional differentiation)
|
||||
-- IMPORTANT: Skip property hash for retained-mode elements to ensure ID stability
|
||||
-- Retained elements should persist across frames even if props change slightly
|
||||
if props and props.mode ~= "retained" then
|
||||
local propHash = hashProps(props)
|
||||
if propHash ~= "" then
|
||||
-- Use first 8 chars of a simple hash
|
||||
@@ -245,7 +256,9 @@ function StateManager.generateID(props, parent)
|
||||
end
|
||||
|
||||
-- Add property hash if provided (for additional differentiation)
|
||||
if props then
|
||||
-- IMPORTANT: Skip property hash for retained-mode elements to ensure ID stability
|
||||
-- Retained elements should persist across frames even if props change slightly
|
||||
if props and props.mode ~= "retained" then
|
||||
local propHash = hashProps(props)
|
||||
if propHash ~= "" then
|
||||
-- Use first 8 chars of a simple hash
|
||||
@@ -655,4 +668,72 @@ function StateManager.isActive(id)
|
||||
return state.active or false
|
||||
end
|
||||
|
||||
-- ====================
|
||||
-- Retained Children Management (for mixed-mode trees)
|
||||
-- ====================
|
||||
|
||||
--- Save retained children for an element
|
||||
--- Only stores children that are in retained mode
|
||||
---@param id string Parent element ID
|
||||
---@param children table Array of child elements
|
||||
function StateManager.saveRetainedChildren(id, children)
|
||||
if not id or not children then
|
||||
return
|
||||
end
|
||||
|
||||
-- Filter to only retained-mode children
|
||||
local retainedChildren = {}
|
||||
for _, child in ipairs(children) do
|
||||
if child._elementMode == "retained" then
|
||||
table.insert(retainedChildren, child)
|
||||
end
|
||||
end
|
||||
|
||||
-- Only save if we have retained children
|
||||
if #retainedChildren > 0 then
|
||||
local state = StateManager.getState(id)
|
||||
state.retainedChildren = retainedChildren
|
||||
end
|
||||
end
|
||||
|
||||
--- Get retained children for an element
|
||||
--- Returns an array of retained-mode child elements
|
||||
---@param id string Parent element ID
|
||||
---@return table children Array of retained child elements (empty if none)
|
||||
function StateManager.getRetainedChildren(id)
|
||||
if not id then
|
||||
return {}
|
||||
end
|
||||
|
||||
local state = StateManager.getCurrentState(id)
|
||||
if state.retainedChildren then
|
||||
-- Verify children still exist (weren't destroyed)
|
||||
local validChildren = {}
|
||||
for _, child in ipairs(state.retainedChildren) do
|
||||
-- Children are element objects, check if they're still valid
|
||||
-- A destroyed element would have nil references or be garbage collected
|
||||
if child and type(child) == "table" and child.id then
|
||||
table.insert(validChildren, child)
|
||||
end
|
||||
end
|
||||
return validChildren
|
||||
end
|
||||
|
||||
return {}
|
||||
end
|
||||
|
||||
--- Clear retained children for an element
|
||||
--- Used when parent is destroyed or children are manually removed
|
||||
---@param id string Parent element ID
|
||||
function StateManager.clearRetainedChildren(id)
|
||||
if not id then
|
||||
return
|
||||
end
|
||||
|
||||
local state = StateManager.getCurrentState(id)
|
||||
if state.retainedChildren then
|
||||
state.retainedChildren = nil
|
||||
end
|
||||
end
|
||||
|
||||
return StateManager
|
||||
|
||||
Reference in New Issue
Block a user