cursor animation for immediate mode
This commit is contained in:
31
FlexLove.lua
31
FlexLove.lua
@@ -221,6 +221,11 @@ function Gui.endFrame()
|
|||||||
state._scrollbarDragging = element._scrollbarDragging
|
state._scrollbarDragging = element._scrollbarDragging
|
||||||
state._hoveredScrollbar = element._hoveredScrollbar
|
state._hoveredScrollbar = element._hoveredScrollbar
|
||||||
state._scrollbarDragOffset = element._scrollbarDragOffset
|
state._scrollbarDragOffset = element._scrollbarDragOffset
|
||||||
|
-- Save cursor blink state
|
||||||
|
state._cursorBlinkTimer = element._cursorBlinkTimer
|
||||||
|
state._cursorVisible = element._cursorVisible
|
||||||
|
state._cursorBlinkPaused = element._cursorBlinkPaused
|
||||||
|
state._cursorBlinkPauseTimer = element._cursorBlinkPauseTimer
|
||||||
|
|
||||||
StateManager.setState(element.id, state)
|
StateManager.setState(element.id, state)
|
||||||
end
|
end
|
||||||
@@ -464,6 +469,23 @@ function Gui.update(dt)
|
|||||||
end
|
end
|
||||||
|
|
||||||
Gui._activeEventElement = nil
|
Gui._activeEventElement = nil
|
||||||
|
|
||||||
|
-- In immediate mode, save state after update so that cursor blink timer changes persist
|
||||||
|
if Gui._immediateMode and Gui._currentFrameElements then
|
||||||
|
for _, element in ipairs(Gui._currentFrameElements) do
|
||||||
|
if element.id and element.id ~= "" and element.editable and element._focused then
|
||||||
|
local state = StateManager.getState(element.id, {})
|
||||||
|
|
||||||
|
-- Save cursor blink state (updated during element:update())
|
||||||
|
state._cursorBlinkTimer = element._cursorBlinkTimer
|
||||||
|
state._cursorVisible = element._cursorVisible
|
||||||
|
state._cursorBlinkPaused = element._cursorBlinkPaused
|
||||||
|
state._cursorBlinkPauseTimer = element._cursorBlinkPauseTimer
|
||||||
|
|
||||||
|
StateManager.setState(element.id, state)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Forward text input to focused element
|
--- Forward text input to focused element
|
||||||
@@ -621,6 +643,15 @@ function Gui.new(props)
|
|||||||
element._scrollbarDragging = state._scrollbarDragging ~= nil and state._scrollbarDragging or false
|
element._scrollbarDragging = state._scrollbarDragging ~= nil and state._scrollbarDragging or false
|
||||||
element._hoveredScrollbar = state._hoveredScrollbar
|
element._hoveredScrollbar = state._hoveredScrollbar
|
||||||
element._scrollbarDragOffset = state._scrollbarDragOffset ~= nil and state._scrollbarDragOffset or 0
|
element._scrollbarDragOffset = state._scrollbarDragOffset ~= nil and state._scrollbarDragOffset or 0
|
||||||
|
-- Restore cursor blink state
|
||||||
|
element._cursorBlinkTimer = state._cursorBlinkTimer or element._cursorBlinkTimer or 0
|
||||||
|
if state._cursorVisible ~= nil then
|
||||||
|
element._cursorVisible = state._cursorVisible
|
||||||
|
elseif element._cursorVisible == nil then
|
||||||
|
element._cursorVisible = true
|
||||||
|
end
|
||||||
|
element._cursorBlinkPaused = state._cursorBlinkPaused or false
|
||||||
|
element._cursorBlinkPauseTimer = state._cursorBlinkPauseTimer or 0
|
||||||
|
|
||||||
-- Bind element to StateManager for interactive states
|
-- Bind element to StateManager for interactive states
|
||||||
-- Use the same ID for StateManager so state persists across frames
|
-- Use the same ID for StateManager so state persists across frames
|
||||||
|
|||||||
@@ -163,6 +163,8 @@ Public API methods to access internal state:
|
|||||||
---@field _cursorColumn number? -- Internal: cursor column within line
|
---@field _cursorColumn number? -- Internal: cursor column within line
|
||||||
---@field _cursorBlinkTimer number? -- Internal: cursor blink timer
|
---@field _cursorBlinkTimer number? -- Internal: cursor blink timer
|
||||||
---@field _cursorVisible boolean? -- Internal: cursor visibility state
|
---@field _cursorVisible boolean? -- Internal: cursor visibility state
|
||||||
|
---@field _cursorBlinkPaused boolean? -- Internal: whether cursor blink is paused (e.g., while typing)
|
||||||
|
---@field _cursorBlinkPauseTimer number? -- Internal: timer for how long cursor blink has been paused
|
||||||
---@field _selectionStart number? -- Internal: selection start position
|
---@field _selectionStart number? -- Internal: selection start position
|
||||||
---@field _selectionEnd number? -- Internal: selection end position
|
---@field _selectionEnd number? -- Internal: selection end position
|
||||||
---@field _selectionAnchor number? -- Internal: selection anchor point
|
---@field _selectionAnchor number? -- Internal: selection anchor point
|
||||||
@@ -329,6 +331,8 @@ function Element.new(props)
|
|||||||
self._cursorColumn = 0 -- Column within current line
|
self._cursorColumn = 0 -- Column within current line
|
||||||
self._cursorBlinkTimer = 0
|
self._cursorBlinkTimer = 0
|
||||||
self._cursorVisible = true
|
self._cursorVisible = true
|
||||||
|
self._cursorBlinkPaused = false
|
||||||
|
self._cursorBlinkPauseTimer = 0
|
||||||
|
|
||||||
-- Selection state
|
-- Selection state
|
||||||
self._selectionStart = nil -- nil = no selection
|
self._selectionStart = nil -- nil = no selection
|
||||||
@@ -3026,10 +3030,21 @@ function Element:update(dt)
|
|||||||
|
|
||||||
-- Update cursor blink timer (only if editable and focused)
|
-- Update cursor blink timer (only if editable and focused)
|
||||||
if self.editable and self._focused then
|
if self.editable and self._focused then
|
||||||
self._cursorBlinkTimer = self._cursorBlinkTimer + dt
|
-- If blink is paused, increment pause timer
|
||||||
if self._cursorBlinkTimer >= self.cursorBlinkRate then
|
if self._cursorBlinkPaused then
|
||||||
self._cursorBlinkTimer = 0
|
self._cursorBlinkPauseTimer = (self._cursorBlinkPauseTimer or 0) + dt
|
||||||
self._cursorVisible = not self._cursorVisible
|
-- Unpause after 0.5 seconds of no typing
|
||||||
|
if self._cursorBlinkPauseTimer >= 0.5 then
|
||||||
|
self._cursorBlinkPaused = false
|
||||||
|
self._cursorBlinkPauseTimer = 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Normal blinking
|
||||||
|
self._cursorBlinkTimer = self._cursorBlinkTimer + dt
|
||||||
|
if self._cursorBlinkTimer >= self.cursorBlinkRate then
|
||||||
|
self._cursorBlinkTimer = 0
|
||||||
|
self._cursorVisible = not self._cursorVisible
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4145,13 +4160,19 @@ function Element:_validateCursorPosition()
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Reset cursor blink (show cursor immediately)
|
--- Reset cursor blink (show cursor immediately)
|
||||||
function Element:_resetCursorBlink()
|
---@param pauseBlink boolean|nil -- Whether to pause blinking (for typing)
|
||||||
|
function Element:_resetCursorBlink(pauseBlink)
|
||||||
if not self.editable then
|
if not self.editable then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self._cursorBlinkTimer = 0
|
self._cursorBlinkTimer = 0
|
||||||
self._cursorVisible = true
|
self._cursorVisible = true
|
||||||
|
|
||||||
|
if pauseBlink then
|
||||||
|
self._cursorBlinkPaused = true -- Pause blinking while typing
|
||||||
|
self._cursorBlinkPauseTimer = 0 -- Reset pause timer
|
||||||
|
end
|
||||||
|
|
||||||
-- Update scroll to keep cursor visible
|
-- Update scroll to keep cursor visible
|
||||||
self:_updateTextScroll()
|
self:_updateTextScroll()
|
||||||
end
|
end
|
||||||
@@ -4396,6 +4417,10 @@ function Element:_saveEditableState()
|
|||||||
_cursorPosition = self._cursorPosition,
|
_cursorPosition = self._cursorPosition,
|
||||||
_selectionStart = self._selectionStart,
|
_selectionStart = self._selectionStart,
|
||||||
_selectionEnd = self._selectionEnd,
|
_selectionEnd = self._selectionEnd,
|
||||||
|
_cursorBlinkTimer = self._cursorBlinkTimer,
|
||||||
|
_cursorVisible = self._cursorVisible,
|
||||||
|
_cursorBlinkPaused = self._cursorBlinkPaused,
|
||||||
|
_cursorBlinkPauseTimer = self._cursorBlinkPauseTimer,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4470,6 +4495,9 @@ function Element:insertText(text, position)
|
|||||||
self:_updateAutoGrowHeight() -- Then update height based on new content
|
self:_updateAutoGrowHeight() -- Then update height based on new content
|
||||||
self:_validateCursorPosition()
|
self:_validateCursorPosition()
|
||||||
|
|
||||||
|
-- Reset cursor blink to show cursor and pause blinking while typing
|
||||||
|
self:_resetCursorBlink(true)
|
||||||
|
|
||||||
-- Save state to StateManager in immediate mode
|
-- Save state to StateManager in immediate mode
|
||||||
self:_saveEditableState()
|
self:_saveEditableState()
|
||||||
end
|
end
|
||||||
@@ -4506,6 +4534,9 @@ function Element:deleteText(startPos, endPos)
|
|||||||
self:_updateTextIfDirty() -- Update immediately to recalculate lines/wrapping
|
self:_updateTextIfDirty() -- Update immediately to recalculate lines/wrapping
|
||||||
self:_updateAutoGrowHeight() -- Then update height based on new content
|
self:_updateAutoGrowHeight() -- Then update height based on new content
|
||||||
|
|
||||||
|
-- Reset cursor blink to show cursor and pause blinking while deleting
|
||||||
|
self:_resetCursorBlink(true)
|
||||||
|
|
||||||
-- Save state to StateManager in immediate mode
|
-- Save state to StateManager in immediate mode
|
||||||
self:_saveEditableState()
|
self:_saveEditableState()
|
||||||
end
|
end
|
||||||
@@ -5553,7 +5584,7 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
if self.onTextChange and self._textBuffer ~= oldText then
|
if self.onTextChange and self._textBuffer ~= oldText then
|
||||||
self.onTextChange(self, self._textBuffer, oldText)
|
self.onTextChange(self, self._textBuffer, oldText)
|
||||||
end
|
end
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink(true)
|
||||||
elseif key == "delete" then
|
elseif key == "delete" then
|
||||||
local oldText = self._textBuffer
|
local oldText = self._textBuffer
|
||||||
if self:hasSelection() then
|
if self:hasSelection() then
|
||||||
@@ -5571,7 +5602,7 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
if self.onTextChange and self._textBuffer ~= oldText then
|
if self.onTextChange and self._textBuffer ~= oldText then
|
||||||
self.onTextChange(self, self._textBuffer, oldText)
|
self.onTextChange(self, self._textBuffer, oldText)
|
||||||
end
|
end
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink(true)
|
||||||
|
|
||||||
-- Handle return/enter
|
-- Handle return/enter
|
||||||
elseif key == "return" or key == "kpenter" then
|
elseif key == "return" or key == "kpenter" then
|
||||||
@@ -5593,7 +5624,7 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
self.onEnter(self)
|
self.onEnter(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink(true)
|
||||||
|
|
||||||
-- Handle Ctrl/Cmd+A (select all)
|
-- Handle Ctrl/Cmd+A (select all)
|
||||||
elseif ctrl and key == "a" then
|
elseif ctrl and key == "a" then
|
||||||
@@ -5627,7 +5658,7 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink(true)
|
||||||
|
|
||||||
-- Handle Ctrl/Cmd+V (paste)
|
-- Handle Ctrl/Cmd+V (paste)
|
||||||
elseif ctrl and key == "v" then
|
elseif ctrl and key == "v" then
|
||||||
@@ -5648,7 +5679,7 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
self.onTextChange(self, self._textBuffer, oldText)
|
self.onTextChange(self, self._textBuffer, oldText)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink(true)
|
||||||
|
|
||||||
-- Handle Escape
|
-- Handle Escape
|
||||||
elseif key == "escape" then
|
elseif key == "escape" then
|
||||||
|
|||||||
Reference in New Issue
Block a user