cleaned up rendering mode swapping
This commit is contained in:
19
FlexLove.lua
19
FlexLove.lua
@@ -399,6 +399,7 @@ function flexlove.beginFrame()
|
|||||||
-- Cleanup elements from PREVIOUS frame (after they've been drawn)
|
-- Cleanup elements from PREVIOUS frame (after they've been drawn)
|
||||||
-- This breaks circular references and allows GC to collect memory
|
-- This breaks circular references and allows GC to collect memory
|
||||||
-- Note: Cleanup is minimal to preserve functionality
|
-- Note: Cleanup is minimal to preserve functionality
|
||||||
|
-- IMPORTANT: Only cleanup immediate-mode elements, preserve retained-mode elements
|
||||||
if flexlove._currentFrameElements then
|
if flexlove._currentFrameElements then
|
||||||
local function cleanupChildren(elem)
|
local function cleanupChildren(elem)
|
||||||
for _, child in ipairs(elem.children) do
|
for _, child in ipairs(elem.children) do
|
||||||
@@ -408,17 +409,31 @@ function flexlove.beginFrame()
|
|||||||
end
|
end
|
||||||
|
|
||||||
for _, element in ipairs(flexlove._currentFrameElements) do
|
for _, element in ipairs(flexlove._currentFrameElements) do
|
||||||
if not element.parent then
|
-- Only cleanup immediate-mode top-level elements
|
||||||
|
-- Retained-mode elements persist across frames
|
||||||
|
if not element.parent and element._elementMode == "immediate" then
|
||||||
cleanupChildren(element)
|
cleanupChildren(element)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Preserve top-level retained elements before resetting
|
||||||
|
local retainedTopElements = {}
|
||||||
|
if flexlove.topElements then
|
||||||
|
for _, element in ipairs(flexlove.topElements) do
|
||||||
|
if element._elementMode == "retained" then
|
||||||
|
table.insert(retainedTopElements, element)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
flexlove._frameNumber = flexlove._frameNumber + 1
|
flexlove._frameNumber = flexlove._frameNumber + 1
|
||||||
StateManager.incrementFrame()
|
StateManager.incrementFrame()
|
||||||
flexlove._currentFrameElements = {}
|
flexlove._currentFrameElements = {}
|
||||||
flexlove._frameStarted = true
|
flexlove._frameStarted = true
|
||||||
flexlove.topElements = {}
|
|
||||||
|
-- Restore retained top-level elements
|
||||||
|
flexlove.topElements = retainedTopElements
|
||||||
|
|
||||||
Context.clearFrameElements()
|
Context.clearFrameElements()
|
||||||
end
|
end
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -221,7 +221,7 @@ function love.draw()
|
|||||||
width = "30vw",
|
width = "30vw",
|
||||||
height = "40vh",
|
height = "40vh",
|
||||||
backgroundColor = Color.new(0.2, 0.2, 0.2, 1),
|
backgroundColor = Color.new(0.2, 0.2, 0.2, 1),
|
||||||
-- No ID auto-generation since it's in retained mode
|
-- ID is auto-generated if not provided (works in both modes)
|
||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.endFrame()
|
FlexLove.endFrame()
|
||||||
@@ -229,15 +229,51 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Key behaviors:**
|
**Key behaviors:**
|
||||||
- `mode = "immediate"` - Element uses immediate-mode lifecycle (recreated each frame, auto-generates ID, uses StateManager)
|
- `mode = "immediate"` - Element uses immediate-mode lifecycle (recreated each frame, uses StateManager)
|
||||||
- `mode = "retained"` - Element uses retained-mode lifecycle (persists across frames, no auto-ID, no StateManager)
|
- `mode = "retained"` - Element uses retained-mode lifecycle (persists across frames, uses StateManager for mixed-mode children)
|
||||||
- `mode = nil` - Element inherits from global mode setting (default behavior)
|
- `mode = nil` - Element inherits from global mode setting (default behavior)
|
||||||
|
- **ID auto-generation** - All elements receive an auto-generated ID if not explicitly provided, enabling state management features
|
||||||
- Mode does NOT inherit from parent to child - each element independently controls its own lifecycle
|
- Mode does NOT inherit from parent to child - each element independently controls its own lifecycle
|
||||||
- Common use cases:
|
- Common use cases:
|
||||||
- Performance-critical static UI in retained mode while using immediate mode globally
|
- Performance-critical static UI in retained mode while using immediate mode globally
|
||||||
- Reactive debug overlays in immediate mode within a retained-mode application
|
- Reactive debug overlays in immediate mode within a retained-mode application
|
||||||
- Mixed UI where some components are static (menus) and others are dynamic (HUD)
|
- Mixed UI where some components are static (menus) and others are dynamic (HUD)
|
||||||
|
|
||||||
|
#### Automatic ID Generation
|
||||||
|
|
||||||
|
All elements automatically receive a unique ID if not explicitly provided. This enables powerful state management features:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- ID is automatically generated based on call site
|
||||||
|
local button = FlexLove.new({
|
||||||
|
text = "Click Me",
|
||||||
|
-- id is auto-generated (e.g., "main_L42_child0_123456")
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Or provide a custom ID for explicit control
|
||||||
|
local namedButton = FlexLove.new({
|
||||||
|
id = "submit_button",
|
||||||
|
text = "Submit",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**ID Generation Strategy:**
|
||||||
|
- **Top-level elements**: ID based on source file and line number (e.g., `main_L42`)
|
||||||
|
- **Child elements**: ID based on parent ID + sibling index (e.g., `parent_child0`, `parent_child1`)
|
||||||
|
- **Multiple elements at same location**: Automatically numbered (e.g., `main_L42_1`, `main_L42_2`)
|
||||||
|
- **Property differentiation**: Similar elements get unique hash suffixes based on their properties
|
||||||
|
|
||||||
|
**Benefits of Auto-Generated IDs:**
|
||||||
|
- Enables state persistence in immediate mode (scroll position, input text, animations)
|
||||||
|
- Allows retained children in immediate parents (mixed-mode trees)
|
||||||
|
- Supports state management features without manual ID tracking
|
||||||
|
- Stable across frames when element structure is consistent
|
||||||
|
|
||||||
|
**When to Use Custom IDs:**
|
||||||
|
- When you need to reference elements programmatically
|
||||||
|
- For debugging and logging (readable names)
|
||||||
|
- When element creation order might change but identity should remain stable
|
||||||
|
|
||||||
### Element Properties
|
### Element Properties
|
||||||
|
|
||||||
Common properties for all elements:
|
Common properties for all elements:
|
||||||
@@ -303,6 +339,9 @@ Common properties for all elements:
|
|||||||
-- Rendering Mode
|
-- Rendering Mode
|
||||||
mode = nil, -- "immediate", "retained", or nil (uses global setting)
|
mode = nil, -- "immediate", "retained", or nil (uses global setting)
|
||||||
|
|
||||||
|
-- Element Identity
|
||||||
|
id = nil, -- Element ID (auto-generated if not provided)
|
||||||
|
|
||||||
-- Hierarchy
|
-- Hierarchy
|
||||||
parent = nil, -- Parent element
|
parent = nil, -- Parent element
|
||||||
}
|
}
|
||||||
|
|||||||
219
examples/mixed_mode_demo.lua
Normal file
219
examples/mixed_mode_demo.lua
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
-- Demo: Retained children with immediate parents
|
||||||
|
-- Shows how retained-mode children persist when immediate-mode parents recreate each frame
|
||||||
|
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
|
||||||
|
-- Track frame count for demo
|
||||||
|
local frameCount = 0
|
||||||
|
|
||||||
|
-- Retained button state (will persist across frames)
|
||||||
|
local buttonClicks = 0
|
||||||
|
local topLevelButtonClicks = 0
|
||||||
|
|
||||||
|
function love.load()
|
||||||
|
love.window.setTitle("Mixed-Mode Demo: Retained Children + Immediate Parents")
|
||||||
|
love.window.setMode(800, 600)
|
||||||
|
|
||||||
|
FlexLove.init({
|
||||||
|
immediateMode = true,
|
||||||
|
performanceMonitoring = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function love.update(dt)
|
||||||
|
FlexLove.update(dt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function love.draw()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
-- Frame counter (immediate mode - recreates each frame)
|
||||||
|
local header = FlexLove.new({
|
||||||
|
width = 800,
|
||||||
|
height = 60,
|
||||||
|
backgroundColor = { 0.1, 0.1, 0.15, 1 },
|
||||||
|
padding = 20,
|
||||||
|
flexDirection = "horizontal",
|
||||||
|
justifyContent = "space-between",
|
||||||
|
alignItems = "center",
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.new({
|
||||||
|
parent = header,
|
||||||
|
text = "Frame: " .. frameCount,
|
||||||
|
textColor = { 1, 1, 1, 1 },
|
||||||
|
textSize = 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.new({
|
||||||
|
parent = header,
|
||||||
|
text = "Mixed-Mode Element Tree Demo",
|
||||||
|
textColor = { 0.8, 0.9, 1, 1 },
|
||||||
|
textSize = 24,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Main content area (immediate parent)
|
||||||
|
local container = FlexLove.new({
|
||||||
|
id = "main_container",
|
||||||
|
y = 60,
|
||||||
|
width = 800,
|
||||||
|
height = 540,
|
||||||
|
padding = 30,
|
||||||
|
gap = 20,
|
||||||
|
backgroundColor = { 0.05, 0.05, 0.08, 1 },
|
||||||
|
flexDirection = "vertical",
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Section 1: Retained button in immediate parent
|
||||||
|
FlexLove.new({
|
||||||
|
parent = container,
|
||||||
|
text = "1. Retained Button (persists across frames)",
|
||||||
|
textColor = { 0.9, 0.9, 0.9, 1 },
|
||||||
|
textSize = 18,
|
||||||
|
})
|
||||||
|
|
||||||
|
local retainedButton = FlexLove.new({
|
||||||
|
id = "retained_button",
|
||||||
|
mode = "retained", -- This button will persist!
|
||||||
|
parent = container,
|
||||||
|
width = 300,
|
||||||
|
height = 50,
|
||||||
|
backgroundColor = { 0.2, 0.6, 0.9, 1 },
|
||||||
|
cornerRadius = 8,
|
||||||
|
text = "Clicks: " .. buttonClicks,
|
||||||
|
textColor = { 1, 1, 1, 1 },
|
||||||
|
textSize = 16,
|
||||||
|
textAlign = "center",
|
||||||
|
onEvent = function(element, event)
|
||||||
|
if event.type == "click" then
|
||||||
|
buttonClicks = buttonClicks + 1
|
||||||
|
-- Update button text
|
||||||
|
element.text = "Clicks: " .. buttonClicks
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.new({
|
||||||
|
parent = container,
|
||||||
|
text = "Note: Button state persists even though parent recreates every frame",
|
||||||
|
textColor = { 0.6, 0.6, 0.6, 1 },
|
||||||
|
textSize = 12,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Section 2: Top-level retained element
|
||||||
|
FlexLove.new({
|
||||||
|
parent = container,
|
||||||
|
text = "2. Top-Level Retained Element (also persists)",
|
||||||
|
textColor = { 0.9, 0.9, 0.9, 1 },
|
||||||
|
textSize = 18,
|
||||||
|
margin = { top = 20 },
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.new({
|
||||||
|
parent = container,
|
||||||
|
text = "Look at the bottom-left corner for a persistent panel",
|
||||||
|
textColor = { 0.6, 0.6, 0.6, 1 },
|
||||||
|
textSize = 12,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Section 3: Comparison with immediate button
|
||||||
|
FlexLove.new({
|
||||||
|
parent = container,
|
||||||
|
text = "3. Immediate Button (recreates every frame)",
|
||||||
|
textColor = { 0.9, 0.9, 0.9, 1 },
|
||||||
|
textSize = 18,
|
||||||
|
margin = { top = 20 },
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.new({
|
||||||
|
parent = container,
|
||||||
|
width = 300,
|
||||||
|
height = 50,
|
||||||
|
backgroundColor = { 0.9, 0.3, 0.3, 1 },
|
||||||
|
cornerRadius = 8,
|
||||||
|
text = "Can't track clicks (recreated)",
|
||||||
|
textColor = { 1, 1, 1, 1 },
|
||||||
|
textSize = 16,
|
||||||
|
textAlign = "center",
|
||||||
|
onEvent = function(element, event)
|
||||||
|
if event.type == "click" then
|
||||||
|
print("Immediate button clicked (but counter can't persist)")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.new({
|
||||||
|
parent = container,
|
||||||
|
text = "Note: This button is recreated every frame, so it can't maintain state",
|
||||||
|
textColor = { 0.6, 0.6, 0.6, 1 },
|
||||||
|
textSize = 12,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Top-level retained element (persists in immediate mode!)
|
||||||
|
local topLevelPanel = FlexLove.new({
|
||||||
|
id = "top_level_panel",
|
||||||
|
mode = "retained",
|
||||||
|
x = 10,
|
||||||
|
y = 500,
|
||||||
|
width = 250,
|
||||||
|
height = 90,
|
||||||
|
backgroundColor = { 0.15, 0.5, 0.3, 1 },
|
||||||
|
cornerRadius = 10,
|
||||||
|
padding = 15,
|
||||||
|
gap = 10,
|
||||||
|
flexDirection = "vertical",
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.new({
|
||||||
|
id = "panel_title",
|
||||||
|
mode = "retained",
|
||||||
|
parent = topLevelPanel,
|
||||||
|
text = "Persistent Panel",
|
||||||
|
textColor = { 1, 1, 1, 1 },
|
||||||
|
textSize = 16,
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.new({
|
||||||
|
id = "panel_button",
|
||||||
|
mode = "retained",
|
||||||
|
parent = topLevelPanel,
|
||||||
|
width = 220,
|
||||||
|
height = 35,
|
||||||
|
backgroundColor = { 1, 1, 1, 0.9 },
|
||||||
|
cornerRadius = 5,
|
||||||
|
text = "Panel Clicks: " .. topLevelButtonClicks,
|
||||||
|
textColor = { 0.15, 0.5, 0.3, 1 },
|
||||||
|
textSize = 14,
|
||||||
|
textAlign = "center",
|
||||||
|
onEvent = function(element, event)
|
||||||
|
if event.type == "click" then
|
||||||
|
topLevelButtonClicks = topLevelButtonClicks + 1
|
||||||
|
element.text = "Panel Clicks: " .. topLevelButtonClicks
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Increment frame counter AFTER drawing
|
||||||
|
frameCount = frameCount + 1
|
||||||
|
|
||||||
|
-- Draw all UI elements
|
||||||
|
FlexLove.draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
function love.mousepressed(x, y, button)
|
||||||
|
FlexLove.mousepressed(x, y, button)
|
||||||
|
end
|
||||||
|
|
||||||
|
function love.mousereleased(x, y, button)
|
||||||
|
FlexLove.mousereleased(x, y, button)
|
||||||
|
end
|
||||||
|
|
||||||
|
function love.mousemoved(x, y, dx, dy)
|
||||||
|
FlexLove.mousemoved(x, y, dx, dy)
|
||||||
|
end
|
||||||
|
|
||||||
|
function love.wheelmoved(x, y)
|
||||||
|
FlexLove.wheelmoved(x, y)
|
||||||
|
end
|
||||||
@@ -38,7 +38,18 @@ function Context.registerElement(element)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Context.clearFrameElements()
|
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
|
end
|
||||||
|
|
||||||
--- Sort elements by z-index (called after all elements are registered)
|
--- Sort elements by z-index (called after all elements are registered)
|
||||||
|
|||||||
@@ -188,6 +188,24 @@ end
|
|||||||
---@param props ElementProps
|
---@param props ElementProps
|
||||||
---@return Element
|
---@return Element
|
||||||
function Element.new(props)
|
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)
|
local self = setmetatable({}, Element)
|
||||||
|
|
||||||
-- Create dependency subsets for sub-modules (defined once, used throughout)
|
-- Create dependency subsets for sub-modules (defined once, used throughout)
|
||||||
@@ -272,11 +290,56 @@ function Element.new(props)
|
|||||||
self.children = {}
|
self.children = {}
|
||||||
self.onEvent = props.onEvent
|
self.onEvent = props.onEvent
|
||||||
|
|
||||||
-- Auto-generate ID in immediate mode if not provided
|
-- Track whether ID was auto-generated (before ID assignment)
|
||||||
if self._elementMode == "immediate" and (not props.id or props.id == "") then
|
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)
|
self.id = Element._StateManager.generateID(props, props.parent)
|
||||||
else
|
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
|
end
|
||||||
|
|
||||||
self.userdata = props.userdata
|
self.userdata = props.userdata
|
||||||
@@ -2237,6 +2300,11 @@ function Element:destroy()
|
|||||||
-- Clear children table
|
-- Clear children table
|
||||||
self.children = {}
|
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
|
-- Clear parent reference
|
||||||
if self.parent then
|
if self.parent then
|
||||||
self.parent = nil
|
self.parent = nil
|
||||||
@@ -3503,6 +3571,12 @@ function Element:saveState()
|
|||||||
state._textDragOccurred = self._textDragOccurred
|
state._textDragOccurred = self._textDragOccurred
|
||||||
end
|
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
|
return state
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ local stateDefaults = {
|
|||||||
_cursorVisible = true,
|
_cursorVisible = true,
|
||||||
_cursorBlinkPaused = false,
|
_cursorBlinkPaused = false,
|
||||||
_cursorBlinkPauseTimer = 0,
|
_cursorBlinkPauseTimer = 0,
|
||||||
|
|
||||||
|
-- Retained children references (for mixed-mode trees)
|
||||||
|
retainedChildren = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Check if a value equals the default for a key
|
--- 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 we have a parent, use tree-based ID generation for stability
|
||||||
if parent and parent.id and parent.id ~= "" then
|
if parent and parent.id and parent.id ~= "" then
|
||||||
-- Count how many children the parent currently has
|
-- For child elements, use call-site (file + line) like top-level elements
|
||||||
-- This gives us a stable sibling index
|
-- This ensures the same call site always generates the same ID, even when
|
||||||
local siblingIndex = #(parent.children or {})
|
-- 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)
|
-- Add property hash if provided (for additional differentiation)
|
||||||
-- This ensures the same position in the tree always gets the same ID
|
-- IMPORTANT: Skip property hash for retained-mode elements to ensure ID stability
|
||||||
local baseID = parent.id .. "_child" .. siblingIndex
|
-- Retained elements should persist across frames even if props change slightly
|
||||||
|
if props and props.mode ~= "retained" then
|
||||||
-- Add property hash if provided (for additional differentiation at same position)
|
|
||||||
if props then
|
|
||||||
local propHash = hashProps(props)
|
local propHash = hashProps(props)
|
||||||
if propHash ~= "" then
|
if propHash ~= "" then
|
||||||
-- Use first 8 chars of a simple hash
|
-- Use first 8 chars of a simple hash
|
||||||
@@ -245,7 +256,9 @@ function StateManager.generateID(props, parent)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Add property hash if provided (for additional differentiation)
|
-- 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)
|
local propHash = hashProps(props)
|
||||||
if propHash ~= "" then
|
if propHash ~= "" then
|
||||||
-- Use first 8 chars of a simple hash
|
-- Use first 8 chars of a simple hash
|
||||||
@@ -655,4 +668,72 @@ function StateManager.isActive(id)
|
|||||||
return state.active or false
|
return state.active or false
|
||||||
end
|
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
|
return StateManager
|
||||||
|
|||||||
400
testing/__tests__/mixed_mode_children_test.lua
Normal file
400
testing/__tests__/mixed_mode_children_test.lua
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
-- Test retained children persisting when immediate parents recreate
|
||||||
|
package.path = package.path .. ";./?.lua;./modules/?.lua"
|
||||||
|
|
||||||
|
-- Load love stub before anything else
|
||||||
|
require("testing.loveStub")
|
||||||
|
|
||||||
|
local luaunit = require("testing.luaunit")
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
|
||||||
|
TestMixedModeChildren = {}
|
||||||
|
|
||||||
|
function TestMixedModeChildren:setUp()
|
||||||
|
FlexLove.init({ immediateMode = true })
|
||||||
|
FlexLove.setMode("immediate")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestMixedModeChildren:tearDown()
|
||||||
|
FlexLove._defaultDependencies.StateManager.reset()
|
||||||
|
FlexLove.topElements = {}
|
||||||
|
FlexLove._currentFrameElements = {}
|
||||||
|
FlexLove._frameStarted = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 1: Retained child persists when immediate parent recreates
|
||||||
|
function TestMixedModeChildren:testRetainedChildPersistsWithImmediateParent()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
-- Frame 1: Create immediate parent with retained child
|
||||||
|
local parent1 = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
local retainedChild = FlexLove.new({
|
||||||
|
id = "retained_child",
|
||||||
|
mode = "retained",
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
parent1:addChild(retainedChild)
|
||||||
|
|
||||||
|
luaunit.assertEquals(#parent1.children, 1, "Parent should have 1 child")
|
||||||
|
luaunit.assertEquals(parent1.children[1], retainedChild, "Child should be the retained element")
|
||||||
|
luaunit.assertEquals(retainedChild.parent, parent1, "Child's parent should be set")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2: Recreate immediate parent, retained child should persist
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent2 = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- The retained child should be automatically restored
|
||||||
|
luaunit.assertEquals(#parent2.children, 1, "Parent should still have 1 child after recreation")
|
||||||
|
luaunit.assertEquals(parent2.children[1], retainedChild, "Child should be the same retained element")
|
||||||
|
luaunit.assertEquals(retainedChild.parent, parent2, "Child's parent reference should be updated")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 2: Multiple retained children persist
|
||||||
|
function TestMixedModeChildren:testMultipleRetainedChildrenPersist()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent1 = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
local child1 = FlexLove.new({
|
||||||
|
id = "child1",
|
||||||
|
mode = "retained",
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
local child2 = FlexLove.new({
|
||||||
|
id = "child2",
|
||||||
|
mode = "retained",
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
local child3 = FlexLove.new({
|
||||||
|
id = "child3",
|
||||||
|
mode = "retained",
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
parent1:addChild(child1)
|
||||||
|
parent1:addChild(child2)
|
||||||
|
parent1:addChild(child3)
|
||||||
|
|
||||||
|
luaunit.assertEquals(#parent1.children, 3, "Parent should have 3 children")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent2 = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
luaunit.assertEquals(#parent2.children, 3, "Parent should still have 3 children")
|
||||||
|
luaunit.assertEquals(parent2.children[1], child1, "First child should persist")
|
||||||
|
luaunit.assertEquals(parent2.children[2], child2, "Second child should persist")
|
||||||
|
luaunit.assertEquals(parent2.children[3], child3, "Third child should persist")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 3: Immediate children do NOT persist (only retained children)
|
||||||
|
function TestMixedModeChildren:testImmediateChildrenDoNotPersist()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent1 = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
local immediateChild = FlexLove.new({
|
||||||
|
id = "immediate_child",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
local retainedChild = FlexLove.new({
|
||||||
|
id = "retained_child",
|
||||||
|
mode = "retained",
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
parent1:addChild(immediateChild)
|
||||||
|
parent1:addChild(retainedChild)
|
||||||
|
|
||||||
|
luaunit.assertEquals(#parent1.children, 2, "Parent should have 2 children")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent2 = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Only retained child should persist
|
||||||
|
luaunit.assertEquals(#parent2.children, 1, "Parent should only have 1 child (retained)")
|
||||||
|
luaunit.assertEquals(parent2.children[1], retainedChild, "Only retained child should persist")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 4: Top-level retained element persists in immediate mode
|
||||||
|
function TestMixedModeChildren:testTopLevelRetainedElementPersists()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local retainedElement = FlexLove.new({
|
||||||
|
id = "top_retained",
|
||||||
|
mode = "retained",
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
luaunit.assertEquals(#FlexLove.topElements, 1, "Should have 1 top-level element")
|
||||||
|
luaunit.assertEquals(FlexLove.topElements[1], retainedElement, "Top element should be retained element")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
-- Retained element should still be in topElements
|
||||||
|
luaunit.assertEquals(#FlexLove.topElements, 1, "Retained element should persist in topElements")
|
||||||
|
luaunit.assertEquals(FlexLove.topElements[1], retainedElement, "Should be same retained element")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 5: Mixed top-level elements (immediate and retained)
|
||||||
|
function TestMixedModeChildren:testMixedTopLevelElements()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local immediateElement1 = FlexLove.new({
|
||||||
|
id = "immediate1",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
local retainedElement = FlexLove.new({
|
||||||
|
id = "retained",
|
||||||
|
mode = "retained",
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
luaunit.assertEquals(#FlexLove.topElements, 2, "Should have 2 top-level elements")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local immediateElement2 = FlexLove.new({
|
||||||
|
id = "immediate2",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Should have retained element + new immediate element
|
||||||
|
luaunit.assertEquals(#FlexLove.topElements, 2, "Should have 2 top-level elements")
|
||||||
|
|
||||||
|
-- Find retained element in topElements
|
||||||
|
local foundRetained = false
|
||||||
|
for _, elem in ipairs(FlexLove.topElements) do
|
||||||
|
if elem == retainedElement then
|
||||||
|
foundRetained = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
luaunit.assertTrue(foundRetained, "Retained element should still be in topElements")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 6: Retained child cleanup on parent destroy
|
||||||
|
function TestMixedModeChildren:testRetainedChildCleanupOnDestroy()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
local retainedChild = FlexLove.new({
|
||||||
|
id = "retained_child",
|
||||||
|
mode = "retained",
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
parent:addChild(retainedChild)
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Destroy parent (simulating explicit cleanup)
|
||||||
|
parent:destroy()
|
||||||
|
|
||||||
|
-- Frame 2: Create new parent with same ID
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent2 = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Since parent was destroyed, retained children should be cleared
|
||||||
|
luaunit.assertEquals(#parent2.children, 0, "Parent should have no children after destroy")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 7: Nested mixed-mode tree (immediate -> retained -> immediate)
|
||||||
|
function TestMixedModeChildren:testNestedMixedModeTree()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local immediateParent = FlexLove.new({
|
||||||
|
id = "immediate_parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 300,
|
||||||
|
height = 300,
|
||||||
|
})
|
||||||
|
|
||||||
|
local retainedMiddle = FlexLove.new({
|
||||||
|
id = "retained_middle",
|
||||||
|
mode = "retained",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
local immediateGrandchild = FlexLove.new({
|
||||||
|
id = "immediate_grandchild",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
immediateParent:addChild(retainedMiddle)
|
||||||
|
retainedMiddle:addChild(immediateGrandchild)
|
||||||
|
|
||||||
|
luaunit.assertEquals(#immediateParent.children, 1, "Immediate parent should have 1 child")
|
||||||
|
luaunit.assertEquals(#retainedMiddle.children, 1, "Retained middle should have 1 child")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2: Recreate immediate parent
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local immediateParent2 = FlexLove.new({
|
||||||
|
id = "immediate_parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 300,
|
||||||
|
height = 300,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Retained middle should persist
|
||||||
|
luaunit.assertEquals(#immediateParent2.children, 1, "Immediate parent should still have retained child")
|
||||||
|
luaunit.assertEquals(immediateParent2.children[1], retainedMiddle, "Retained middle should persist")
|
||||||
|
|
||||||
|
-- Immediate grandchild should also persist (as child of retained middle)
|
||||||
|
luaunit.assertEquals(#retainedMiddle.children, 1, "Retained middle should still have its child")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 8: Prevent duplicate creation of retained children
|
||||||
|
function TestMixedModeChildren:testPreventDuplicateRetainedChildren()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
-- Frame 1: Create immediate parent with retained child
|
||||||
|
local parent1 = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
local retainedChild = FlexLove.new({
|
||||||
|
id = "unique_child",
|
||||||
|
mode = "retained",
|
||||||
|
parent = parent1,
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
luaunit.assertEquals(#parent1.children, 1, "Parent should have 1 child")
|
||||||
|
local originalChild = parent1.children[1]
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2: Recreate parent and try to create child again
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent2 = FlexLove.new({
|
||||||
|
id = "parent",
|
||||||
|
mode = "immediate",
|
||||||
|
width = 200,
|
||||||
|
height = 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Try to create the same retained child again
|
||||||
|
local duplicateAttempt = FlexLove.new({
|
||||||
|
id = "unique_child",
|
||||||
|
mode = "retained",
|
||||||
|
parent = parent2,
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Should return the existing child, not create a new one
|
||||||
|
luaunit.assertEquals(duplicateAttempt, originalChild, "Should return existing child instead of creating duplicate")
|
||||||
|
luaunit.assertEquals(duplicateAttempt, retainedChild, "Should be the same retained child instance")
|
||||||
|
luaunit.assertEquals(#parent2.children, 1, "Parent should still have only 1 child")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run tests
|
||||||
|
if not _G.RUNNING_ALL_TESTS then
|
||||||
|
os.exit(luaunit.LuaUnit.run())
|
||||||
|
end
|
||||||
@@ -16,7 +16,9 @@ local originalSearchers = package.searchers or package.loaders
|
|||||||
table.insert(originalSearchers, 2, function(modname)
|
table.insert(originalSearchers, 2, function(modname)
|
||||||
if modname:match("^FlexLove%.modules%.") then
|
if modname:match("^FlexLove%.modules%.") then
|
||||||
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
|
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
|
||||||
return function() return require("modules." .. moduleName) end
|
return function()
|
||||||
|
return require("modules." .. moduleName)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -41,17 +43,17 @@ end
|
|||||||
function TestMixedModeEvents:test_immediateChildOfRetainedParentHandlesEvents()
|
function TestMixedModeEvents:test_immediateChildOfRetainedParentHandlesEvents()
|
||||||
local eventFired = false
|
local eventFired = false
|
||||||
local eventType = nil
|
local eventType = nil
|
||||||
|
|
||||||
-- Create retained parent
|
-- Create retained parent
|
||||||
local parent = FlexLove.new({
|
local parent = FlexLove.new({
|
||||||
mode = "retained",
|
mode = "retained",
|
||||||
width = 800,
|
width = 800,
|
||||||
height = 600,
|
height = 600,
|
||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.setMode("immediate")
|
FlexLove.setMode("immediate")
|
||||||
FlexLove.beginFrame()
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
-- Create immediate child with event handler
|
-- Create immediate child with event handler
|
||||||
local child = FlexLove.new({
|
local child = FlexLove.new({
|
||||||
mode = "immediate",
|
mode = "immediate",
|
||||||
@@ -66,19 +68,19 @@ function TestMixedModeEvents:test_immediateChildOfRetainedParentHandlesEvents()
|
|||||||
eventType = event.type
|
eventType = event.type
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.endFrame()
|
FlexLove.endFrame()
|
||||||
|
|
||||||
-- Verify child is positioned correctly
|
-- Verify child is positioned correctly
|
||||||
luaunit.assertEquals(child.x, 0)
|
luaunit.assertEquals(child.x, 0)
|
||||||
luaunit.assertEquals(child.y, 0)
|
luaunit.assertEquals(child.y, 0)
|
||||||
|
|
||||||
-- Manually call the event handler (simulating event processing)
|
-- Manually call the event handler (simulating event processing)
|
||||||
-- In the real app, this would be triggered by mousepressed/released
|
-- In the real app, this would be triggered by mousepressed/released
|
||||||
if child.onEvent then
|
if child.onEvent then
|
||||||
child.onEvent(child, { type = "release", x = 50, y = 25, button = 1 })
|
child.onEvent(child, { type = "release", x = 50, y = 25, button = 1 })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Verify event was handled
|
-- Verify event was handled
|
||||||
luaunit.assertTrue(eventFired)
|
luaunit.assertTrue(eventFired)
|
||||||
luaunit.assertEquals(eventType, "release")
|
luaunit.assertEquals(eventType, "release")
|
||||||
@@ -87,17 +89,17 @@ end
|
|||||||
-- Test that hover state is tracked for immediate children
|
-- Test that hover state is tracked for immediate children
|
||||||
function TestMixedModeEvents:test_immediateChildOfRetainedParentTracksHover()
|
function TestMixedModeEvents:test_immediateChildOfRetainedParentTracksHover()
|
||||||
FlexLove.setMode("retained")
|
FlexLove.setMode("retained")
|
||||||
|
|
||||||
-- Create retained parent
|
-- Create retained parent
|
||||||
local parent = FlexLove.new({
|
local parent = FlexLove.new({
|
||||||
mode = "retained",
|
mode = "retained",
|
||||||
width = 800,
|
width = 800,
|
||||||
height = 600,
|
height = 600,
|
||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.setMode("immediate")
|
FlexLove.setMode("immediate")
|
||||||
FlexLove.beginFrame()
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
-- Create immediate child
|
-- Create immediate child
|
||||||
local child = FlexLove.new({
|
local child = FlexLove.new({
|
||||||
mode = "immediate",
|
mode = "immediate",
|
||||||
@@ -108,12 +110,12 @@ function TestMixedModeEvents:test_immediateChildOfRetainedParentTracksHover()
|
|||||||
width = 100,
|
width = 100,
|
||||||
height = 50,
|
height = 50,
|
||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.endFrame()
|
FlexLove.endFrame()
|
||||||
|
|
||||||
-- Child should have event handler module
|
-- Child should have event handler module
|
||||||
luaunit.assertNotNil(child._eventHandler)
|
luaunit.assertNotNil(child._eventHandler)
|
||||||
|
|
||||||
-- Verify child can track hover state (stored in StateManager for immediate mode)
|
-- Verify child can track hover state (stored in StateManager for immediate mode)
|
||||||
-- The actual hover detection happens in Element's event processing
|
-- The actual hover detection happens in Element's event processing
|
||||||
luaunit.assertEquals(child._elementMode, "immediate")
|
luaunit.assertEquals(child._elementMode, "immediate")
|
||||||
@@ -123,9 +125,9 @@ end
|
|||||||
function TestMixedModeEvents:test_multipleImmediateChildrenHandleEventsIndependently()
|
function TestMixedModeEvents:test_multipleImmediateChildrenHandleEventsIndependently()
|
||||||
local button1Clicked = false
|
local button1Clicked = false
|
||||||
local button2Clicked = false
|
local button2Clicked = false
|
||||||
|
|
||||||
FlexLove.setMode("retained")
|
FlexLove.setMode("retained")
|
||||||
|
|
||||||
-- Create retained parent
|
-- Create retained parent
|
||||||
local parent = FlexLove.new({
|
local parent = FlexLove.new({
|
||||||
mode = "retained",
|
mode = "retained",
|
||||||
@@ -135,10 +137,10 @@ function TestMixedModeEvents:test_multipleImmediateChildrenHandleEventsIndepende
|
|||||||
flexDirection = "horizontal",
|
flexDirection = "horizontal",
|
||||||
gap = 10,
|
gap = 10,
|
||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.setMode("immediate")
|
FlexLove.setMode("immediate")
|
||||||
FlexLove.beginFrame()
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
-- Create two immediate button children
|
-- Create two immediate button children
|
||||||
local button1 = FlexLove.new({
|
local button1 = FlexLove.new({
|
||||||
mode = "immediate",
|
mode = "immediate",
|
||||||
@@ -152,7 +154,7 @@ function TestMixedModeEvents:test_multipleImmediateChildrenHandleEventsIndepende
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
local button2 = FlexLove.new({
|
local button2 = FlexLove.new({
|
||||||
mode = "immediate",
|
mode = "immediate",
|
||||||
id = "button2",
|
id = "button2",
|
||||||
@@ -165,28 +167,31 @@ function TestMixedModeEvents:test_multipleImmediateChildrenHandleEventsIndepende
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
FlexLove.endFrame()
|
FlexLove.endFrame()
|
||||||
|
|
||||||
-- Verify buttons are positioned correctly
|
-- Verify buttons are positioned correctly
|
||||||
luaunit.assertEquals(button1.x, 0)
|
luaunit.assertEquals(button1.x, 0)
|
||||||
luaunit.assertEquals(button2.x, 110) -- 100 + 10 gap
|
luaunit.assertEquals(button2.x, 110) -- 100 + 10 gap
|
||||||
|
|
||||||
-- Simulate clicking button1
|
-- Simulate clicking button1
|
||||||
if button1.onEvent then
|
if button1.onEvent then
|
||||||
button1.onEvent(button1, { type = "release", x = 50, y = 25, button = 1 })
|
button1.onEvent(button1, { type = "release", x = 50, y = 25, button = 1 })
|
||||||
end
|
end
|
||||||
|
|
||||||
luaunit.assertTrue(button1Clicked)
|
luaunit.assertTrue(button1Clicked)
|
||||||
luaunit.assertFalse(button2Clicked)
|
luaunit.assertFalse(button2Clicked)
|
||||||
|
|
||||||
-- Simulate clicking button2
|
-- Simulate clicking button2
|
||||||
if button2.onEvent then
|
if button2.onEvent then
|
||||||
button2.onEvent(button2, { type = "release", x = 150, y = 25, button = 1 })
|
button2.onEvent(button2, { type = "release", x = 150, y = 25, button = 1 })
|
||||||
end
|
end
|
||||||
|
|
||||||
luaunit.assertTrue(button1Clicked)
|
luaunit.assertTrue(button1Clicked)
|
||||||
luaunit.assertTrue(button2Clicked)
|
luaunit.assertTrue(button2Clicked)
|
||||||
end
|
end
|
||||||
|
|
||||||
os.exit(luaunit.LuaUnit.run())
|
-- Run tests
|
||||||
|
if not _G.RUNNING_ALL_TESTS then
|
||||||
|
os.exit(luaunit.LuaUnit.run())
|
||||||
|
end
|
||||||
|
|||||||
193
testing/__tests__/retained_in_immediate_test.lua
Normal file
193
testing/__tests__/retained_in_immediate_test.lua
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
--[[
|
||||||
|
Test: Retained Elements in Immediate Mode (No Duplication)
|
||||||
|
|
||||||
|
This test verifies that retained-mode elements don't get recreated
|
||||||
|
when the overall application is in immediate mode.
|
||||||
|
]]
|
||||||
|
|
||||||
|
package.path = package.path .. ";./?.lua;./modules/?.lua"
|
||||||
|
|
||||||
|
require("testing.loveStub")
|
||||||
|
|
||||||
|
local luaunit = require("testing.luaunit")
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
local Color = require("modules.Color")
|
||||||
|
|
||||||
|
TestRetainedInImmediateMode = {}
|
||||||
|
|
||||||
|
function TestRetainedInImmediateMode:setUp()
|
||||||
|
-- Initialize in IMMEDIATE mode
|
||||||
|
FlexLove.init({ immediateMode = true })
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestRetainedInImmediateMode:tearDown()
|
||||||
|
if FlexLove.getMode() == "immediate" then
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
FlexLove.init({ immediateMode = false })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test that top-level retained elements persist across frames
|
||||||
|
function TestRetainedInImmediateMode:test_topLevelRetainedElementPersists()
|
||||||
|
-- Helper function that creates elements (simulates user code called each frame)
|
||||||
|
local function createUI()
|
||||||
|
local backdrop = FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
width = "100%",
|
||||||
|
height = "100%",
|
||||||
|
backgroundColor = Color.new(0.1, 0.2, 0.3, 0.5),
|
||||||
|
})
|
||||||
|
return backdrop
|
||||||
|
end
|
||||||
|
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
-- Frame 1: Create a retained element (no explicit ID)
|
||||||
|
local backdrop = createUI()
|
||||||
|
|
||||||
|
local backdropId = backdrop.id
|
||||||
|
luaunit.assertNotNil(backdropId, "Backdrop should have auto-generated ID")
|
||||||
|
luaunit.assertEquals(backdrop._elementMode, "retained")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2: Call createUI() again (same function, same line numbers)
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local backdrop2 = createUI()
|
||||||
|
|
||||||
|
-- Should return the SAME element, not create a new one
|
||||||
|
luaunit.assertEquals(backdrop2.id, backdropId, "Should return existing element with same ID")
|
||||||
|
luaunit.assertEquals(backdrop2, backdrop, "Should return exact same element instance")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test that retained elements with explicit IDs can be recreated
|
||||||
|
function TestRetainedInImmediateMode:test_explicitIdAllowsNewElements()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
-- Create element with explicit ID
|
||||||
|
local element1 = FlexLove.new({
|
||||||
|
id = "my_custom_id",
|
||||||
|
mode = "retained",
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
backgroundColor = Color.new(1, 0, 0, 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
-- Create another element with SAME explicit ID but different properties
|
||||||
|
-- This should create a NEW element (user controls uniqueness)
|
||||||
|
local element2 = FlexLove.new({
|
||||||
|
id = "my_custom_id",
|
||||||
|
mode = "retained",
|
||||||
|
width = 200, -- Different properties
|
||||||
|
height = 200,
|
||||||
|
backgroundColor = Color.new(0, 1, 0, 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- With explicit IDs, we allow duplicates (user responsibility)
|
||||||
|
luaunit.assertEquals(element2.id, "my_custom_id")
|
||||||
|
-- Properties should match NEW element, not old
|
||||||
|
luaunit.assertEquals(element2.width, 200)
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test that multiple retained elements persist independently
|
||||||
|
function TestRetainedInImmediateMode:test_multipleRetainedElementsPersist()
|
||||||
|
local function createUI()
|
||||||
|
local backdrop = FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
width = "100%",
|
||||||
|
height = "100%",
|
||||||
|
})
|
||||||
|
|
||||||
|
local window = FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
width = "90%",
|
||||||
|
height = "90%",
|
||||||
|
})
|
||||||
|
|
||||||
|
return backdrop, window
|
||||||
|
end
|
||||||
|
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local backdrop, window = createUI()
|
||||||
|
|
||||||
|
local backdropId = backdrop.id
|
||||||
|
local windowId = window.id
|
||||||
|
|
||||||
|
luaunit.assertNotEquals(backdropId, windowId, "Different elements should have different IDs")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local backdrop2, window2 = createUI()
|
||||||
|
|
||||||
|
-- Both should return existing elements
|
||||||
|
luaunit.assertEquals(backdrop2.id, backdropId)
|
||||||
|
luaunit.assertEquals(window2.id, windowId)
|
||||||
|
luaunit.assertEquals(backdrop2, backdrop)
|
||||||
|
luaunit.assertEquals(window2, window)
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test that retained children of retained parents persist
|
||||||
|
function TestRetainedInImmediateMode:test_retainedChildOfRetainedParentPersists()
|
||||||
|
local function createUI()
|
||||||
|
local parent = FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
width = 400,
|
||||||
|
height = 400,
|
||||||
|
})
|
||||||
|
|
||||||
|
local child = FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
parent = parent,
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
return parent, child
|
||||||
|
end
|
||||||
|
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent, child = createUI()
|
||||||
|
|
||||||
|
local parentId = parent.id
|
||||||
|
local childId = child.id
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local parent2, child2 = createUI()
|
||||||
|
|
||||||
|
-- Parent should be the same
|
||||||
|
luaunit.assertEquals(parent2.id, parentId)
|
||||||
|
luaunit.assertEquals(parent2, parent)
|
||||||
|
|
||||||
|
-- Child should also be the same instance
|
||||||
|
luaunit.assertEquals(child2.id, childId, "Child ID should match")
|
||||||
|
luaunit.assertEquals(child2, child, "Child should be same instance")
|
||||||
|
|
||||||
|
-- Child should still exist in parent's children
|
||||||
|
luaunit.assertEquals(#parent2.children, 1, "Parent should have exactly 1 child")
|
||||||
|
luaunit.assertEquals(parent2.children[1].id, childId)
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run tests
|
||||||
|
os.exit(luaunit.LuaUnit.run())
|
||||||
184
testing/__tests__/retained_prop_stability_test.lua
Normal file
184
testing/__tests__/retained_prop_stability_test.lua
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
--[[
|
||||||
|
Test: Retained Elements with Varying Props (ID Stability)
|
||||||
|
|
||||||
|
This test verifies that retained-mode elements return the same instance
|
||||||
|
across frames even when props vary slightly (e.g., different Color instances).
|
||||||
|
]]
|
||||||
|
|
||||||
|
package.path = package.path .. ";./?.lua;./modules/?.lua"
|
||||||
|
|
||||||
|
require("testing.loveStub")
|
||||||
|
|
||||||
|
local luaunit = require("testing.luaunit")
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
local Color = require("modules.Color")
|
||||||
|
|
||||||
|
TestRetainedPropStability = {}
|
||||||
|
|
||||||
|
function TestRetainedPropStability:setUp()
|
||||||
|
-- Initialize in IMMEDIATE mode
|
||||||
|
FlexLove.init({ immediateMode = true })
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestRetainedPropStability:tearDown()
|
||||||
|
if FlexLove.getMode() == "immediate" then
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
FlexLove.init({ immediateMode = false })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test that retained elements persist despite creating new Color instances
|
||||||
|
function TestRetainedPropStability:test_retainedElementIgnoresColorInstanceChanges()
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
-- Frame 1: Create retained element with Color instance
|
||||||
|
local backdrop1 = FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
width = "100%",
|
||||||
|
height = "100%",
|
||||||
|
backgroundColor = Color.new(1, 1, 1, 0.1), -- NEW Color instance
|
||||||
|
})
|
||||||
|
|
||||||
|
local id1 = backdrop1.id
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2: Same props but NEW Color instance (common pattern in user code)
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local backdrop2 = FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
width = "100%",
|
||||||
|
height = "100%",
|
||||||
|
backgroundColor = Color.new(1, 1, 1, 0.1), -- NEW Color instance (different table)
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Should return SAME element despite different Color instance
|
||||||
|
luaunit.assertEquals(backdrop2.id, id1, "ID should be stable across frames")
|
||||||
|
luaunit.assertEquals(backdrop2, backdrop1, "Should return same element instance")
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test that retained elements with complex props persist
|
||||||
|
function TestRetainedPropStability:test_retainedElementWithComplexProps()
|
||||||
|
local function createWindow()
|
||||||
|
return FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
z = 100,
|
||||||
|
x = "5%",
|
||||||
|
y = "5%",
|
||||||
|
width = "90%",
|
||||||
|
height = "90%",
|
||||||
|
themeComponent = "framev3",
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "vertical",
|
||||||
|
justifySelf = "center",
|
||||||
|
justifyContent = "flex-start",
|
||||||
|
alignItems = "center",
|
||||||
|
scaleCorners = 3,
|
||||||
|
padding = { horizontal = "5%", vertical = "3%" },
|
||||||
|
gap = 10,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local window1 = createWindow()
|
||||||
|
local id1 = window1.id
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2: Same function, same props
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local window2 = createWindow()
|
||||||
|
|
||||||
|
-- Should return same element
|
||||||
|
luaunit.assertEquals(window2.id, id1)
|
||||||
|
luaunit.assertEquals(window2, window1)
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test that retained elements with backdrop blur persist
|
||||||
|
function TestRetainedPropStability:test_retainedElementWithBackdropBlur()
|
||||||
|
local function createBackdrop()
|
||||||
|
return FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
width = "100%",
|
||||||
|
height = "100%",
|
||||||
|
backdropBlur = { radius = 10 }, -- Table prop
|
||||||
|
backgroundColor = Color.new(1, 1, 1, 0.1),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local backdrop1 = createBackdrop()
|
||||||
|
local id1 = backdrop1.id
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local backdrop2 = createBackdrop()
|
||||||
|
|
||||||
|
-- Should return same element
|
||||||
|
luaunit.assertEquals(backdrop2.id, id1)
|
||||||
|
luaunit.assertEquals(backdrop2, backdrop1)
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test that multiple retained elements persist independently
|
||||||
|
function TestRetainedPropStability:test_multipleRetainedElementsWithVaryingProps()
|
||||||
|
local function createUI()
|
||||||
|
local backdrop = FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
z = 50,
|
||||||
|
width = "100%",
|
||||||
|
height = "100%",
|
||||||
|
backdropBlur = { radius = 10 },
|
||||||
|
backgroundColor = Color.new(1, 1, 1, 0.1),
|
||||||
|
})
|
||||||
|
|
||||||
|
local window = FlexLove.new({
|
||||||
|
mode = "retained",
|
||||||
|
z = 100,
|
||||||
|
x = "5%",
|
||||||
|
y = "5%",
|
||||||
|
width = "90%",
|
||||||
|
height = "90%",
|
||||||
|
themeComponent = "framev3",
|
||||||
|
padding = { horizontal = "5%", vertical = "3%" },
|
||||||
|
})
|
||||||
|
|
||||||
|
return backdrop, window
|
||||||
|
end
|
||||||
|
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local backdrop1, window1 = createUI()
|
||||||
|
local backdropId = backdrop1.id
|
||||||
|
local windowId = window1.id
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
-- Frame 2: New Color instances, new table instances for props
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
|
||||||
|
local backdrop2, window2 = createUI()
|
||||||
|
|
||||||
|
-- Both should return existing elements
|
||||||
|
luaunit.assertEquals(backdrop2.id, backdropId)
|
||||||
|
luaunit.assertEquals(window2.id, windowId)
|
||||||
|
luaunit.assertEquals(backdrop2, backdrop1)
|
||||||
|
luaunit.assertEquals(window2, window1)
|
||||||
|
|
||||||
|
FlexLove.endFrame()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run tests
|
||||||
|
os.exit(luaunit.LuaUnit.run())
|
||||||
@@ -46,6 +46,9 @@ local testFiles = {
|
|||||||
"testing/__tests__/image_scaler_test.lua",
|
"testing/__tests__/image_scaler_test.lua",
|
||||||
"testing/__tests__/input_event_test.lua",
|
"testing/__tests__/input_event_test.lua",
|
||||||
"testing/__tests__/layout_engine_test.lua",
|
"testing/__tests__/layout_engine_test.lua",
|
||||||
|
"testing/__tests__/mixed_mode_events_test.lua",
|
||||||
|
"testing/__tests__/mixed_mode_children_test.lua",
|
||||||
|
"testing/__tests__/retained_in_immediate_test.lua",
|
||||||
"testing/__tests__/module_loader_test.lua",
|
"testing/__tests__/module_loader_test.lua",
|
||||||
"testing/__tests__/ninepatch_test.lua",
|
"testing/__tests__/ninepatch_test.lua",
|
||||||
"testing/__tests__/performance_test.lua",
|
"testing/__tests__/performance_test.lua",
|
||||||
@@ -58,7 +61,7 @@ local testFiles = {
|
|||||||
"testing/__tests__/utils_test.lua",
|
"testing/__tests__/utils_test.lua",
|
||||||
"testing/__tests__/calc_test.lua",
|
"testing/__tests__/calc_test.lua",
|
||||||
-- Feature/Integration tests
|
-- Feature/Integration tests
|
||||||
--"testing/__tests__/critical_failures_test.lua",
|
"testing/__tests__/critical_failures_test.lua",
|
||||||
"testing/__tests__/flexlove_test.lua",
|
"testing/__tests__/flexlove_test.lua",
|
||||||
"testing/__tests__/touch_events_test.lua",
|
"testing/__tests__/touch_events_test.lua",
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user