fix immediate mode state update/draw ordering
This commit is contained in:
11
FlexLove.lua
11
FlexLove.lua
@@ -176,8 +176,8 @@ function flexlove.endFrame()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Auto-update all top-level elements (triggers additional state updates)
|
-- Auto-update all top-level elements created this frame
|
||||||
-- This must happen BEFORE saving state so that scroll positions and overflow are calculated
|
-- This happens AFTER layout so positions are correct
|
||||||
for _, element in ipairs(flexlove._currentFrameElements) do
|
for _, element in ipairs(flexlove._currentFrameElements) do
|
||||||
if not element.parent then
|
if not element.parent then
|
||||||
element:update(0) -- dt=0 since we're not doing animation updates here
|
element:update(0) -- dt=0 since we're not doing animation updates here
|
||||||
@@ -442,8 +442,11 @@ function flexlove.update(dt)
|
|||||||
|
|
||||||
flexlove._activeEventElement = topElement
|
flexlove._activeEventElement = topElement
|
||||||
|
|
||||||
for _, win in ipairs(flexlove.topElements) do
|
-- In immediate mode, skip updating here - elements will be updated in endFrame after layout
|
||||||
win:update(dt)
|
if not flexlove._immediateMode then
|
||||||
|
for _, win in ipairs(flexlove.topElements) do
|
||||||
|
win:update(dt)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
flexlove._activeEventElement = nil
|
flexlove._activeEventElement = nil
|
||||||
|
|||||||
@@ -253,17 +253,33 @@ function Element.new(props)
|
|||||||
self.onTextChange = props.onTextChange
|
self.onTextChange = props.onTextChange
|
||||||
self.onEnter = props.onEnter
|
self.onEnter = props.onEnter
|
||||||
|
|
||||||
self._eventHandler = EventHandler.new({
|
-- Initialize state manager ID for immediate mode (use self.id which may be auto-generated)
|
||||||
onEvent = self.onEvent,
|
self._stateId = self.id
|
||||||
}, {
|
|
||||||
|
-- In immediate mode, restore EventHandler state from StateManager
|
||||||
|
local eventHandlerConfig = { onEvent = self.onEvent }
|
||||||
|
if Context._immediateMode and self._stateId and self._stateId ~= "" then
|
||||||
|
local state = StateManager.getState(self._stateId)
|
||||||
|
if state then
|
||||||
|
-- Restore EventHandler state from StateManager
|
||||||
|
eventHandlerConfig._pressed = state._pressed
|
||||||
|
eventHandlerConfig._lastClickTime = state._lastClickTime
|
||||||
|
eventHandlerConfig._lastClickButton = state._lastClickButton
|
||||||
|
eventHandlerConfig._clickCount = state._clickCount
|
||||||
|
eventHandlerConfig._dragStartX = state._dragStartX
|
||||||
|
eventHandlerConfig._dragStartY = state._dragStartY
|
||||||
|
eventHandlerConfig._lastMouseX = state._lastMouseX
|
||||||
|
eventHandlerConfig._lastMouseY = state._lastMouseY
|
||||||
|
eventHandlerConfig._hovered = state._hovered
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self._eventHandler = EventHandler.new(eventHandlerConfig, {
|
||||||
InputEvent = InputEvent,
|
InputEvent = InputEvent,
|
||||||
Context = Context,
|
Context = Context,
|
||||||
})
|
})
|
||||||
self._eventHandler:initialize(self)
|
self._eventHandler:initialize(self)
|
||||||
|
|
||||||
-- Initialize state manager ID for immediate mode (use self.id which may be auto-generated)
|
|
||||||
self._stateId = self.id
|
|
||||||
|
|
||||||
self._themeManager = Theme.Manager.new({
|
self._themeManager = Theme.Manager.new({
|
||||||
theme = props.theme or Context.defaultTheme,
|
theme = props.theme or Context.defaultTheme,
|
||||||
themeComponent = props.themeComponent or nil,
|
themeComponent = props.themeComponent or nil,
|
||||||
@@ -2096,6 +2112,29 @@ function Element:update(dt)
|
|||||||
isActiveElement = (Context._activeEventElement == nil or Context._activeEventElement == self)
|
isActiveElement = (Context._activeEventElement == nil or Context._activeEventElement == self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Reset scrollbar press flag at start of each frame
|
||||||
|
self._eventHandler:resetScrollbarPressFlag()
|
||||||
|
|
||||||
|
-- Process mouse events through EventHandler FIRST
|
||||||
|
-- This ensures pressed states are updated before theme state is calculated
|
||||||
|
self._eventHandler:processMouseEvents(mx, my, isHovering, isActiveElement)
|
||||||
|
|
||||||
|
-- In immediate mode, save EventHandler state to StateManager after processing events
|
||||||
|
if self._stateId and Context._immediateMode and self._stateId ~= "" then
|
||||||
|
local eventHandlerState = self._eventHandler:getState()
|
||||||
|
StateManager.updateState(self._stateId, {
|
||||||
|
_pressed = eventHandlerState._pressed,
|
||||||
|
_lastClickTime = eventHandlerState._lastClickTime,
|
||||||
|
_lastClickButton = eventHandlerState._lastClickButton,
|
||||||
|
_clickCount = eventHandlerState._clickCount,
|
||||||
|
_dragStartX = eventHandlerState._dragStartX,
|
||||||
|
_dragStartY = eventHandlerState._dragStartY,
|
||||||
|
_lastMouseX = eventHandlerState._lastMouseX,
|
||||||
|
_lastMouseY = eventHandlerState._lastMouseY,
|
||||||
|
_hovered = eventHandlerState._hovered,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
-- Update theme state based on interaction
|
-- Update theme state based on interaction
|
||||||
if self.themeComponent then
|
if self.themeComponent then
|
||||||
-- Check if any button is pressed via EventHandler
|
-- Check if any button is pressed via EventHandler
|
||||||
@@ -2128,12 +2167,6 @@ function Element:update(dt)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Reset scrollbar press flag at start of each frame
|
|
||||||
self._eventHandler:resetScrollbarPressFlag()
|
|
||||||
|
|
||||||
-- Process mouse events through EventHandler
|
|
||||||
self._eventHandler:processMouseEvents(mx, my, isHovering, isActiveElement)
|
|
||||||
|
|
||||||
-- Process touch events through EventHandler
|
-- Process touch events through EventHandler
|
||||||
self._eventHandler:processTouchEvents()
|
self._eventHandler:processTouchEvents()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -123,10 +123,30 @@ function EventHandler:processMouseEvents(mx, my, isHovering, isActiveElement)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Can only process events if we have handler, element is enabled, and is active or dragging
|
-- Check if any button is currently pressed (tracked state)
|
||||||
local canProcessEvents = (self.onEvent or element.editable) and not element.disabled and (isActiveElement or isDragging)
|
local hasTrackedPress = false
|
||||||
|
for _, button in ipairs({ 1, 2, 3 }) do
|
||||||
|
if self._pressed[button] then
|
||||||
|
hasTrackedPress = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Can only process events if we have handler, element is enabled, and is active or dragging or has tracked press
|
||||||
|
local canProcessEvents = (self.onEvent or element.editable) and not element.disabled and (isActiveElement or isDragging or hasTrackedPress)
|
||||||
|
|
||||||
if not canProcessEvents then
|
if not canProcessEvents then
|
||||||
|
-- If not hovering and no buttons are physically pressed, reset all pressed states
|
||||||
|
-- This ensures the pressed state is cleared when mouse leaves without button held
|
||||||
|
if not isHovering and not isDragging then
|
||||||
|
for _, button in ipairs({ 1, 2, 3 }) do
|
||||||
|
if self._pressed[button] and not love.mouse.isDown(button) then
|
||||||
|
self._pressed[button] = false
|
||||||
|
self._dragStartX[button] = nil
|
||||||
|
self._dragStartY[button] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -134,23 +154,43 @@ function EventHandler:processMouseEvents(mx, my, isHovering, isActiveElement)
|
|||||||
local buttons = { 1, 2, 3 } -- left, right, middle
|
local buttons = { 1, 2, 3 } -- left, right, middle
|
||||||
|
|
||||||
for _, button in ipairs(buttons) do
|
for _, button in ipairs(buttons) do
|
||||||
if isHovering or isDragging then
|
-- Check if this button was tracked as pressed
|
||||||
if love.mouse.isDown(button) then
|
local wasPressed = self._pressed[button]
|
||||||
|
local isPhysicallyPressed = love.mouse.isDown(button)
|
||||||
|
|
||||||
|
if isHovering or isDragging or wasPressed then
|
||||||
|
if isPhysicallyPressed then
|
||||||
-- Button is pressed down
|
-- Button is pressed down
|
||||||
if not self._pressed[button] then
|
if not wasPressed then
|
||||||
-- Just pressed - fire press event
|
-- Just pressed - fire press event (only if hovering)
|
||||||
self:_handleMousePress(mx, my, button)
|
if isHovering then
|
||||||
|
self:_handleMousePress(mx, my, button)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- Button is still pressed - check for drag
|
-- Button is still pressed - check for drag
|
||||||
self:_handleMouseDrag(mx, my, button, isHovering)
|
self:_handleMouseDrag(mx, my, button, isHovering)
|
||||||
end
|
end
|
||||||
elseif self._pressed[button] then
|
elseif wasPressed then
|
||||||
-- Button was just released - fire click and release events
|
-- Button was just released
|
||||||
self:_handleMouseRelease(mx, my, button)
|
-- Only fire click and release events if mouse is still hovering AND element is active
|
||||||
|
-- (not occluded by another element)
|
||||||
|
if isHovering and isActiveElement then
|
||||||
|
self:_handleMouseRelease(mx, my, button)
|
||||||
|
else
|
||||||
|
-- Mouse left before release OR element is occluded - just clear the pressed state without firing events
|
||||||
|
self._pressed[button] = false
|
||||||
|
self._dragStartX[button] = nil
|
||||||
|
self._dragStartY[button] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
end
|
||||||
-- Mouse left the element - reset pressed state and drag tracking
|
end
|
||||||
if self._pressed[button] then
|
|
||||||
|
-- After processing events, reset pressed states for buttons that are no longer held
|
||||||
|
-- This handles the case where mouse leaves while button is held, then released
|
||||||
|
if not isHovering and not isDragging then
|
||||||
|
for _, button in ipairs({ 1, 2, 3 }) do
|
||||||
|
if self._pressed[button] and not love.mouse.isDown(button) then
|
||||||
self._pressed[button] = false
|
self._pressed[button] = false
|
||||||
self._dragStartX[button] = nil
|
self._dragStartX[button] = nil
|
||||||
self._dragStartY[button] = nil
|
self._dragStartY[button] = nil
|
||||||
|
|||||||
Reference in New Issue
Block a user