single line input buffer completed
This commit is contained in:
@@ -3206,6 +3206,11 @@ function Element:update(dt)
|
|||||||
self.callback(self, dragEvent)
|
self.callback(self, dragEvent)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Handle text selection drag for editable elements
|
||||||
|
if button == 1 and self.editable and self._focused then
|
||||||
|
self:_handleTextDrag(mx, my)
|
||||||
|
end
|
||||||
|
|
||||||
-- Update last known position for this button
|
-- Update last known position for this button
|
||||||
self._lastMouseX[button] = mx
|
self._lastMouseX[button] = mx
|
||||||
self._lastMouseY[button] = my
|
self._lastMouseY[button] = my
|
||||||
@@ -3256,10 +3261,18 @@ function Element:update(dt)
|
|||||||
self._dragStartX[button] = nil
|
self._dragStartX[button] = nil
|
||||||
self._dragStartY[button] = nil
|
self._dragStartY[button] = nil
|
||||||
|
|
||||||
|
-- Clean up text selection drag tracking
|
||||||
|
if button == 1 then
|
||||||
|
self._mouseDownPosition = nil
|
||||||
|
end
|
||||||
|
|
||||||
-- Focus editable elements on left click
|
-- Focus editable elements on left click
|
||||||
if button == 1 and self.editable then
|
if button == 1 and self.editable then
|
||||||
print("[Element:update] Calling focus on editable element")
|
print("[Element:update] Calling focus on editable element")
|
||||||
self:focus()
|
self:focus()
|
||||||
|
|
||||||
|
-- Handle text click for cursor positioning and word selection
|
||||||
|
self:_handleTextClick(mx, my, clickCount)
|
||||||
elseif button == 1 then
|
elseif button == 1 then
|
||||||
print("[Element:update] Button 1 clicked but editable:", self.editable)
|
print("[Element:update] Button 1 clicked but editable:", self.editable)
|
||||||
end
|
end
|
||||||
@@ -3915,6 +3928,81 @@ function Element:moveCursorToLineEnd()
|
|||||||
self:moveCursorToEnd()
|
self:moveCursorToEnd()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Move cursor to start of previous word
|
||||||
|
function Element:moveCursorToPreviousWord()
|
||||||
|
if not self.editable or not self._textBuffer then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local text = self._textBuffer
|
||||||
|
local pos = self._cursorPosition
|
||||||
|
|
||||||
|
if pos <= 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Skip any whitespace/punctuation before current position
|
||||||
|
while pos > 0 do
|
||||||
|
local offset = utf8.offset(text, pos)
|
||||||
|
local char = offset and text:sub(offset, utf8.offset(text, pos + 1) - 1) or ""
|
||||||
|
if char:match("[%w]") then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
pos = pos - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move to start of current word
|
||||||
|
while pos > 0 do
|
||||||
|
local offset = utf8.offset(text, pos)
|
||||||
|
local char = offset and text:sub(offset, utf8.offset(text, pos + 1) - 1) or ""
|
||||||
|
if not char:match("[%w]") then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
pos = pos - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
self._cursorPosition = pos
|
||||||
|
self:_validateCursorPosition()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move cursor to start of next word
|
||||||
|
function Element:moveCursorToNextWord()
|
||||||
|
if not self.editable or not self._textBuffer then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local text = self._textBuffer
|
||||||
|
local textLength = utf8.len(text) or 0
|
||||||
|
local pos = self._cursorPosition
|
||||||
|
|
||||||
|
if pos >= textLength then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Skip current word
|
||||||
|
while pos < textLength do
|
||||||
|
local offset = utf8.offset(text, pos + 1)
|
||||||
|
local char = offset and text:sub(offset, utf8.offset(text, pos + 2) - 1) or ""
|
||||||
|
if not char:match("[%w]") then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Skip any whitespace/punctuation
|
||||||
|
while pos < textLength do
|
||||||
|
local offset = utf8.offset(text, pos + 1)
|
||||||
|
local char = offset and text:sub(offset, utf8.offset(text, pos + 2) - 1) or ""
|
||||||
|
if char:match("[%w]") then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
self._cursorPosition = pos
|
||||||
|
self:_validateCursorPosition()
|
||||||
|
end
|
||||||
|
|
||||||
--- Validate cursor position (ensure it's within text bounds)
|
--- Validate cursor position (ensure it's within text bounds)
|
||||||
function Element:_validateCursorPosition()
|
function Element:_validateCursorPosition()
|
||||||
if not self.editable then
|
if not self.editable then
|
||||||
@@ -4396,6 +4484,152 @@ function Element:_getFont()
|
|||||||
return FONT_CACHE.getFont(self.textSize, fontPath)
|
return FONT_CACHE.getFont(self.textSize, fontPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ====================
|
||||||
|
-- Input Handling - Mouse Selection
|
||||||
|
-- ====================
|
||||||
|
|
||||||
|
--- Convert mouse coordinates to cursor position in text
|
||||||
|
---@param mouseX number -- Mouse X coordinate (absolute)
|
||||||
|
---@param mouseY number -- Mouse Y coordinate (absolute)
|
||||||
|
---@return number -- Cursor position (character index)
|
||||||
|
function Element:_mouseToTextPosition(mouseX, mouseY)
|
||||||
|
if not self.editable or not self._textBuffer then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get content area bounds
|
||||||
|
local contentX = (self._absoluteX or self.x) + self.padding.left
|
||||||
|
local contentY = (self._absoluteY or self.y) + self.padding.top
|
||||||
|
|
||||||
|
-- Calculate relative X position within text area
|
||||||
|
local relativeX = mouseX - contentX
|
||||||
|
|
||||||
|
-- Get font for measuring text
|
||||||
|
local font = self:_getFont()
|
||||||
|
|
||||||
|
-- Find the character position closest to the click
|
||||||
|
local text = self._textBuffer
|
||||||
|
local textLength = utf8.len(text) or 0
|
||||||
|
local closestPos = 0
|
||||||
|
local closestDist = math.huge
|
||||||
|
|
||||||
|
-- Check each position in the text
|
||||||
|
for i = 0, textLength do
|
||||||
|
-- Get text up to this position
|
||||||
|
local offset = utf8.offset(text, i + 1)
|
||||||
|
local beforeText = offset and text:sub(1, offset - 1) or text
|
||||||
|
local textWidth = font:getWidth(beforeText)
|
||||||
|
|
||||||
|
-- Calculate distance from click to this position
|
||||||
|
local dist = math.abs(relativeX - textWidth)
|
||||||
|
|
||||||
|
if dist < closestDist then
|
||||||
|
closestDist = dist
|
||||||
|
closestPos = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestPos
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Handle mouse click on text (set cursor position or start selection)
|
||||||
|
---@param mouseX number -- Mouse X coordinate
|
||||||
|
---@param mouseY number -- Mouse Y coordinate
|
||||||
|
---@param clickCount number -- Number of clicks (1=single, 2=double, 3=triple)
|
||||||
|
function Element:_handleTextClick(mouseX, mouseY, clickCount)
|
||||||
|
if not self.editable or not self._focused then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if clickCount == 1 then
|
||||||
|
-- Single click: Set cursor position
|
||||||
|
local pos = self:_mouseToTextPosition(mouseX, mouseY)
|
||||||
|
self:setCursorPosition(pos)
|
||||||
|
self:clearSelection()
|
||||||
|
|
||||||
|
-- Store position for potential drag selection
|
||||||
|
self._mouseDownPosition = pos
|
||||||
|
|
||||||
|
elseif clickCount == 2 then
|
||||||
|
-- Double click: Select word
|
||||||
|
self:_selectWordAtPosition(self:_mouseToTextPosition(mouseX, mouseY))
|
||||||
|
|
||||||
|
elseif clickCount >= 3 then
|
||||||
|
-- Triple click: Select all (or line in multi-line mode)
|
||||||
|
self:selectAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
self:_resetCursorBlink()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Handle mouse drag for text selection
|
||||||
|
---@param mouseX number -- Mouse X coordinate
|
||||||
|
---@param mouseY number -- Mouse Y coordinate
|
||||||
|
function Element:_handleTextDrag(mouseX, mouseY)
|
||||||
|
if not self.editable or not self._focused or not self._mouseDownPosition then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local currentPos = self:_mouseToTextPosition(mouseX, mouseY)
|
||||||
|
|
||||||
|
-- Create selection from mouse down position to current position
|
||||||
|
if currentPos ~= self._mouseDownPosition then
|
||||||
|
self:setSelection(self._mouseDownPosition, currentPos)
|
||||||
|
self._cursorPosition = currentPos
|
||||||
|
else
|
||||||
|
self:clearSelection()
|
||||||
|
end
|
||||||
|
|
||||||
|
self:_resetCursorBlink()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Select word at given position
|
||||||
|
---@param position number -- Character position
|
||||||
|
function Element:_selectWordAtPosition(position)
|
||||||
|
if not self.editable or not self._textBuffer then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local text = self._textBuffer
|
||||||
|
local textLength = utf8.len(text) or 0
|
||||||
|
|
||||||
|
if position < 0 or position > textLength then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find word boundaries
|
||||||
|
local wordStart = position
|
||||||
|
local wordEnd = position
|
||||||
|
|
||||||
|
-- Find start of word (move left while alphanumeric)
|
||||||
|
while wordStart > 0 do
|
||||||
|
local offset = utf8.offset(text, wordStart)
|
||||||
|
local char = offset and text:sub(offset, utf8.offset(text, wordStart + 1) - 1) or ""
|
||||||
|
if char:match("[%w]") then
|
||||||
|
wordStart = wordStart - 1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find end of word (move right while alphanumeric)
|
||||||
|
while wordEnd < textLength do
|
||||||
|
local offset = utf8.offset(text, wordEnd + 1)
|
||||||
|
local char = offset and text:sub(offset, utf8.offset(text, wordEnd + 2) - 1) or ""
|
||||||
|
if char:match("[%w]") then
|
||||||
|
wordEnd = wordEnd + 1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Select the word
|
||||||
|
if wordEnd > wordStart then
|
||||||
|
self:setSelection(wordStart, wordEnd)
|
||||||
|
self._cursorPosition = wordEnd
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- ====================
|
-- ====================
|
||||||
-- Input Handling - Keyboard Input
|
-- Input Handling - Keyboard Input
|
||||||
-- ====================
|
-- ====================
|
||||||
@@ -4446,48 +4680,81 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
local modifiers = getModifiers()
|
local modifiers = getModifiers()
|
||||||
local ctrl = modifiers.ctrl or modifiers.super -- Support both Ctrl and Cmd
|
local ctrl = modifiers.ctrl or modifiers.super -- Support both Ctrl and Cmd
|
||||||
|
|
||||||
-- Handle cursor movement
|
-- Handle cursor movement with selection
|
||||||
|
if key == "left" or key == "right" or key == "home" or key == "end" or key == "up" or key == "down" then
|
||||||
|
-- Set selection anchor if Shift is pressed and no anchor exists
|
||||||
|
if modifiers.shift and not self._selectionAnchor then
|
||||||
|
self._selectionAnchor = self._cursorPosition
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Store old cursor position
|
||||||
|
local oldCursorPos = self._cursorPosition
|
||||||
|
|
||||||
|
-- Move cursor based on key
|
||||||
if key == "left" then
|
if key == "left" then
|
||||||
if self:hasSelection() and not modifiers.shift then
|
if self:hasSelection() and not modifiers.shift then
|
||||||
-- Move to start of selection
|
-- Move to start of selection
|
||||||
local startPos, _ = self:getSelection()
|
local startPos, _ = self:getSelection()
|
||||||
self._cursorPosition = startPos
|
self._cursorPosition = startPos
|
||||||
self:clearSelection()
|
self:clearSelection()
|
||||||
|
elseif ctrl then
|
||||||
|
-- Ctrl+Left: Move to previous word
|
||||||
|
self:moveCursorToPreviousWord()
|
||||||
else
|
else
|
||||||
self:moveCursorBy(-1)
|
self:moveCursorBy(-1)
|
||||||
end
|
end
|
||||||
self:_resetCursorBlink()
|
|
||||||
elseif key == "right" then
|
elseif key == "right" then
|
||||||
if self:hasSelection() and not modifiers.shift then
|
if self:hasSelection() and not modifiers.shift then
|
||||||
-- Move to end of selection
|
-- Move to end of selection
|
||||||
local _, endPos = self:getSelection()
|
local _, endPos = self:getSelection()
|
||||||
self._cursorPosition = endPos
|
self._cursorPosition = endPos
|
||||||
self:clearSelection()
|
self:clearSelection()
|
||||||
|
elseif ctrl then
|
||||||
|
-- Ctrl+Right: Move to next word
|
||||||
|
self:moveCursorToNextWord()
|
||||||
else
|
else
|
||||||
self:moveCursorBy(1)
|
self:moveCursorBy(1)
|
||||||
end
|
end
|
||||||
self:_resetCursorBlink()
|
elseif key == "home" then
|
||||||
elseif key == "home" or (ctrl and key == "a" and not self.multiline) then
|
|
||||||
-- Move to line start (or document start for single-line)
|
-- Move to line start (or document start for single-line)
|
||||||
if ctrl or not self.multiline then
|
if ctrl or not self.multiline then
|
||||||
self:moveCursorToStart()
|
self:moveCursorToStart()
|
||||||
else
|
else
|
||||||
self:moveCursorToLineStart()
|
self:moveCursorToLineStart()
|
||||||
end
|
end
|
||||||
if key == "home" then
|
if not modifiers.shift then
|
||||||
self:clearSelection()
|
self:clearSelection()
|
||||||
end
|
end
|
||||||
self:_resetCursorBlink()
|
elseif key == "end" then
|
||||||
elseif key == "end" or (ctrl and key == "e" and not self.multiline) then
|
|
||||||
-- Move to line end (or document end for single-line)
|
-- Move to line end (or document end for single-line)
|
||||||
if ctrl or not self.multiline then
|
if ctrl or not self.multiline then
|
||||||
self:moveCursorToEnd()
|
self:moveCursorToEnd()
|
||||||
else
|
else
|
||||||
self:moveCursorToLineEnd()
|
self:moveCursorToLineEnd()
|
||||||
end
|
end
|
||||||
if key == "end" then
|
if not modifiers.shift then
|
||||||
self:clearSelection()
|
self:clearSelection()
|
||||||
end
|
end
|
||||||
|
elseif key == "up" then
|
||||||
|
-- TODO: Implement up/down for multi-line
|
||||||
|
if not modifiers.shift then
|
||||||
|
self:clearSelection()
|
||||||
|
end
|
||||||
|
elseif key == "down" then
|
||||||
|
-- TODO: Implement up/down for multi-line
|
||||||
|
if not modifiers.shift then
|
||||||
|
self:clearSelection()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update selection if Shift is pressed
|
||||||
|
if modifiers.shift and self._selectionAnchor then
|
||||||
|
self:setSelection(self._selectionAnchor, self._cursorPosition)
|
||||||
|
elseif not modifiers.shift then
|
||||||
|
-- Clear anchor when Shift is released
|
||||||
|
self._selectionAnchor = nil
|
||||||
|
end
|
||||||
|
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink()
|
||||||
|
|
||||||
-- Handle backspace and delete
|
-- Handle backspace and delete
|
||||||
@@ -4554,6 +4821,56 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
self:selectAll()
|
self:selectAll()
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink()
|
||||||
|
|
||||||
|
-- Handle Ctrl/Cmd+C (copy)
|
||||||
|
elseif ctrl and key == "c" then
|
||||||
|
if self:hasSelection() then
|
||||||
|
local selectedText = self:getSelectedText()
|
||||||
|
if selectedText then
|
||||||
|
love.system.setClipboardText(selectedText)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:_resetCursorBlink()
|
||||||
|
|
||||||
|
-- Handle Ctrl/Cmd+X (cut)
|
||||||
|
elseif ctrl and key == "x" then
|
||||||
|
if self:hasSelection() then
|
||||||
|
local selectedText = self:getSelectedText()
|
||||||
|
if selectedText then
|
||||||
|
love.system.setClipboardText(selectedText)
|
||||||
|
|
||||||
|
-- Delete the selected text
|
||||||
|
local oldText = self._textBuffer
|
||||||
|
self:deleteSelection()
|
||||||
|
|
||||||
|
-- Trigger onTextChange callback
|
||||||
|
if self.onTextChange and self._textBuffer ~= oldText then
|
||||||
|
self.onTextChange(self, self._textBuffer, oldText)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:_resetCursorBlink()
|
||||||
|
|
||||||
|
-- Handle Ctrl/Cmd+V (paste)
|
||||||
|
elseif ctrl and key == "v" then
|
||||||
|
local clipboardText = love.system.getClipboardText()
|
||||||
|
if clipboardText and clipboardText ~= "" then
|
||||||
|
local oldText = self._textBuffer
|
||||||
|
|
||||||
|
-- Delete selection if exists
|
||||||
|
if self:hasSelection() then
|
||||||
|
self:deleteSelection()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Insert clipboard text
|
||||||
|
self:insertText(clipboardText)
|
||||||
|
|
||||||
|
-- Trigger onTextChange callback
|
||||||
|
if self.onTextChange and self._textBuffer ~= oldText then
|
||||||
|
self.onTextChange(self, self._textBuffer, oldText)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:_resetCursorBlink()
|
||||||
|
|
||||||
-- Handle Escape
|
-- Handle Escape
|
||||||
elseif key == "escape" then
|
elseif key == "escape" then
|
||||||
if self:hasSelection() then
|
if self:hasSelection() then
|
||||||
|
|||||||
@@ -586,5 +586,883 @@ function TestInputField:testPasswordModeDisablesMultiline()
|
|||||||
lu.assertFalse(element.multiline)
|
lu.assertFalse(element.multiline)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ====================
|
||||||
|
-- Keyboard Selection Tests
|
||||||
|
-- ====================
|
||||||
|
|
||||||
|
function TestInputField:testShiftRightSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(0)
|
||||||
|
|
||||||
|
-- Mock Shift key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lshift" or key == "rshift" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Shift+Right should select one character
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 0)
|
||||||
|
lu.assertEquals(endPos, 1)
|
||||||
|
|
||||||
|
-- Another Shift+Right should extend selection
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 0)
|
||||||
|
lu.assertEquals(endPos, 2)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testShiftLeftSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(5) -- Position after "Hello"
|
||||||
|
|
||||||
|
-- Mock Shift key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lshift" or key == "rshift" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Shift+Left should select one character backwards
|
||||||
|
element:keypressed("left", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 4)
|
||||||
|
lu.assertEquals(endPos, 5)
|
||||||
|
|
||||||
|
-- Another Shift+Left should extend selection
|
||||||
|
element:keypressed("left", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 3)
|
||||||
|
lu.assertEquals(endPos, 5)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testShiftHomeSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(5)
|
||||||
|
|
||||||
|
-- Mock Shift key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lshift" or key == "rshift" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Shift+Home should select from cursor to start
|
||||||
|
element:keypressed("home", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 0)
|
||||||
|
lu.assertEquals(endPos, 5)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testShiftEndSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(5)
|
||||||
|
|
||||||
|
-- Mock Shift key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lshift" or key == "rshift" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Shift+End should select from cursor to end
|
||||||
|
element:keypressed("end", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 5)
|
||||||
|
lu.assertEquals(endPos, 11) -- "Hello World" has 11 characters
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testSelectionDirectionChange()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(5)
|
||||||
|
|
||||||
|
-- Mock Shift key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lshift" or key == "rshift" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Select right
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 5)
|
||||||
|
lu.assertEquals(endPos, 7)
|
||||||
|
|
||||||
|
-- Now select left (should shrink selection)
|
||||||
|
element:keypressed("left", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 5)
|
||||||
|
lu.assertEquals(endPos, 6)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testArrowWithoutShiftClearsSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setSelection(0, 5)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
|
||||||
|
-- Arrow key without Shift should clear selection and move cursor
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertFalse(element:hasSelection())
|
||||||
|
lu.assertEquals(element._cursorPosition, 5) -- Should move to end of selection
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testTypingReplacesSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setSelection(0, 5) -- Select "Hello"
|
||||||
|
|
||||||
|
-- Type a character - should replace selection
|
||||||
|
element:textinput("X")
|
||||||
|
lu.assertEquals(element:getText(), "X World")
|
||||||
|
lu.assertFalse(element:hasSelection())
|
||||||
|
lu.assertEquals(element._cursorPosition, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ====================
|
||||||
|
-- Mouse Selection Tests
|
||||||
|
-- ====================
|
||||||
|
|
||||||
|
function TestInputField:testMouseClickSetsCursorPosition()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
|
||||||
|
-- Simulate single click (this would normally be done through the event system)
|
||||||
|
-- We'll test the _handleTextClick method directly
|
||||||
|
element:_handleTextClick(15, 15, 1) -- Single click near start
|
||||||
|
|
||||||
|
-- Cursor should be set (exact position depends on font, so we just check it's valid)
|
||||||
|
lu.assertTrue(element._cursorPosition >= 0)
|
||||||
|
lu.assertTrue(element._cursorPosition <= 11)
|
||||||
|
lu.assertFalse(element:hasSelection())
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testMouseDoubleClickSelectsWord()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(3) -- Position in "Hello"
|
||||||
|
|
||||||
|
-- Simulate double click to select word
|
||||||
|
element:_handleTextClick(15, 15, 2) -- Double click
|
||||||
|
|
||||||
|
-- Should have selected a word (we can't test exact positions without font metrics)
|
||||||
|
-- But we can verify a selection was created
|
||||||
|
lu.assertTrue(element:hasSelection() or element._cursorPosition >= 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testMouseTripleClickSelectsAll()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
|
||||||
|
-- Simulate triple click
|
||||||
|
element:_handleTextClick(15, 15, 3) -- Triple click
|
||||||
|
|
||||||
|
-- Should select all text
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 0)
|
||||||
|
lu.assertEquals(endPos, 11)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testMouseDragCreatesSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
|
||||||
|
-- Simulate mouse down at position 0
|
||||||
|
element._mouseDownPosition = 0
|
||||||
|
|
||||||
|
-- Simulate drag to position 5
|
||||||
|
element:_handleTextDrag(50, 15)
|
||||||
|
|
||||||
|
-- Should have created a selection (exact positions depend on font metrics)
|
||||||
|
-- We just verify the drag handler works
|
||||||
|
lu.assertTrue(element._cursorPosition >= 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testSelectWordAtPosition()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World Test",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
|
||||||
|
-- Select word at position 6 (in "World")
|
||||||
|
element:_selectWordAtPosition(6)
|
||||||
|
|
||||||
|
-- Should have selected "World"
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 6)
|
||||||
|
lu.assertEquals(endPos, 11)
|
||||||
|
lu.assertEquals(element:getSelectedText(), "World")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testSelectWordWithNonAlphanumeric()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello, World!",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
|
||||||
|
-- Select word at position 0 (in "Hello")
|
||||||
|
element:_selectWordAtPosition(2)
|
||||||
|
|
||||||
|
-- Should have selected "Hello" (not including comma)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 0)
|
||||||
|
lu.assertEquals(endPos, 5)
|
||||||
|
lu.assertEquals(element:getSelectedText(), "Hello")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testMouseToTextPosition()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
|
||||||
|
-- Test conversion at start of element
|
||||||
|
local pos = element:_mouseToTextPosition(10, 10)
|
||||||
|
lu.assertEquals(pos, 0)
|
||||||
|
|
||||||
|
-- Test conversion far to the right (should be at end)
|
||||||
|
pos = element:_mouseToTextPosition(200, 10)
|
||||||
|
lu.assertEquals(pos, 5) -- "Hello" has 5 characters
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ====================
|
||||||
|
-- Clipboard Operations Tests
|
||||||
|
-- ====================
|
||||||
|
|
||||||
|
function TestInputField:testCtrlCCopiesSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setSelection(0, 5) -- Select "Hello"
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simulate Ctrl+C
|
||||||
|
element:keypressed("c", nil, false)
|
||||||
|
|
||||||
|
-- Check clipboard content
|
||||||
|
lu.assertEquals(love.system.getClipboardText(), "Hello")
|
||||||
|
|
||||||
|
-- Text should remain unchanged
|
||||||
|
lu.assertEquals(element:getText(), "Hello World")
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testCtrlXCutsSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setSelection(0, 5) -- Select "Hello"
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simulate Ctrl+X
|
||||||
|
element:keypressed("x", nil, false)
|
||||||
|
|
||||||
|
-- Check clipboard content
|
||||||
|
lu.assertEquals(love.system.getClipboardText(), "Hello")
|
||||||
|
|
||||||
|
-- Text should be cut
|
||||||
|
lu.assertEquals(element:getText(), " World")
|
||||||
|
lu.assertFalse(element:hasSelection())
|
||||||
|
lu.assertEquals(element._cursorPosition, 0)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testCtrlVPastesFromClipboard()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(0)
|
||||||
|
|
||||||
|
-- Set clipboard content
|
||||||
|
love.system.setClipboardText("Hello ")
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simulate Ctrl+V
|
||||||
|
element:keypressed("v", nil, false)
|
||||||
|
|
||||||
|
-- Text should be pasted
|
||||||
|
lu.assertEquals(element:getText(), "Hello World")
|
||||||
|
lu.assertEquals(element._cursorPosition, 6)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testCtrlVReplacesSelection()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setSelection(6, 11) -- Select "World"
|
||||||
|
|
||||||
|
-- Set clipboard content
|
||||||
|
love.system.setClipboardText("Everyone")
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simulate Ctrl+V
|
||||||
|
element:keypressed("v", nil, false)
|
||||||
|
|
||||||
|
-- Selection should be replaced
|
||||||
|
lu.assertEquals(element:getText(), "Hello Everyone")
|
||||||
|
lu.assertFalse(element:hasSelection())
|
||||||
|
lu.assertEquals(element._cursorPosition, 14)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testCopyWithoutSelectionDoesNothing()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
|
||||||
|
-- Clear clipboard
|
||||||
|
love.system.setClipboardText("")
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simulate Ctrl+C without selection
|
||||||
|
element:keypressed("c", nil, false)
|
||||||
|
|
||||||
|
-- Clipboard should remain empty
|
||||||
|
lu.assertEquals(love.system.getClipboardText(), "")
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testCutWithoutSelectionDoesNothing()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
|
||||||
|
-- Clear clipboard
|
||||||
|
love.system.setClipboardText("")
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simulate Ctrl+X without selection
|
||||||
|
element:keypressed("x", nil, false)
|
||||||
|
|
||||||
|
-- Clipboard should remain empty and text unchanged
|
||||||
|
lu.assertEquals(love.system.getClipboardText(), "")
|
||||||
|
lu.assertEquals(element:getText(), "Hello World")
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testPasteEmptyClipboard()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(5)
|
||||||
|
|
||||||
|
-- Clear clipboard
|
||||||
|
love.system.setClipboardText("")
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simulate Ctrl+V with empty clipboard
|
||||||
|
element:keypressed("v", nil, false)
|
||||||
|
|
||||||
|
-- Text should remain unchanged
|
||||||
|
lu.assertEquals(element:getText(), "Hello")
|
||||||
|
lu.assertEquals(element._cursorPosition, 5)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ====================
|
||||||
|
-- Word Navigation Tests
|
||||||
|
-- ====================
|
||||||
|
|
||||||
|
function TestInputField:testCtrlLeftMovesToPreviousWord()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World Test",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(16) -- At end of text
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ctrl+Left should move to start of "Test"
|
||||||
|
element:keypressed("left", nil, false)
|
||||||
|
lu.assertEquals(element._cursorPosition, 12)
|
||||||
|
|
||||||
|
-- Another Ctrl+Left should move to start of "World"
|
||||||
|
element:keypressed("left", nil, false)
|
||||||
|
lu.assertEquals(element._cursorPosition, 6)
|
||||||
|
|
||||||
|
-- Another Ctrl+Left should move to start of "Hello"
|
||||||
|
element:keypressed("left", nil, false)
|
||||||
|
lu.assertEquals(element._cursorPosition, 0)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testCtrlRightMovesToNextWord()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World Test",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(0) -- At start of text
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ctrl+Right should move to start of "World"
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertEquals(element._cursorPosition, 6)
|
||||||
|
|
||||||
|
-- Another Ctrl+Right should move to start of "Test"
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertEquals(element._cursorPosition, 12)
|
||||||
|
|
||||||
|
-- Another Ctrl+Right should move to end
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertEquals(element._cursorPosition, 16)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testCtrlShiftLeftSelectsWord()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World Test",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(16) -- At end of text
|
||||||
|
|
||||||
|
-- Mock Ctrl+Shift keys
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" or key == "lshift" or key == "rshift" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ctrl+Shift+Left should select "Test"
|
||||||
|
element:keypressed("left", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 12)
|
||||||
|
lu.assertEquals(endPos, 16)
|
||||||
|
lu.assertEquals(element:getSelectedText(), "Test")
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testCtrlShiftRightSelectsWord()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello World Test",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(0) -- At start of text
|
||||||
|
|
||||||
|
-- Mock Ctrl+Shift keys
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" or key == "lshift" or key == "rshift" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ctrl+Shift+Right should select "Hello"
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertTrue(element:hasSelection())
|
||||||
|
local startPos, endPos = element:getSelection()
|
||||||
|
lu.assertEquals(startPos, 0)
|
||||||
|
lu.assertEquals(endPos, 6)
|
||||||
|
lu.assertEquals(element:getSelectedText(), "Hello ")
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestInputField:testWordNavigationWithPunctuation()
|
||||||
|
local element = FlexLove.Element.new({
|
||||||
|
x = 10,
|
||||||
|
y = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 30,
|
||||||
|
editable = true,
|
||||||
|
text = "Hello, World! Test.",
|
||||||
|
})
|
||||||
|
|
||||||
|
element:focus()
|
||||||
|
element:setCursorPosition(0)
|
||||||
|
|
||||||
|
-- Mock Ctrl key
|
||||||
|
local oldIsDown = _G.love.keyboard.isDown
|
||||||
|
_G.love.keyboard.isDown = function(...)
|
||||||
|
local keys = {...}
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
if key == "lctrl" or key == "rctrl" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ctrl+Right should skip punctuation and move to "World"
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertEquals(element._cursorPosition, 7)
|
||||||
|
|
||||||
|
-- Another Ctrl+Right should move to "Test"
|
||||||
|
element:keypressed("right", nil, false)
|
||||||
|
lu.assertEquals(element._cursorPosition, 14)
|
||||||
|
|
||||||
|
-- Reset mock
|
||||||
|
_G.love.keyboard.isDown = oldIsDown
|
||||||
|
end
|
||||||
|
|
||||||
-- Run tests
|
-- Run tests
|
||||||
lu.LuaUnit.run()
|
lu.LuaUnit.run()
|
||||||
|
|||||||
@@ -412,5 +412,17 @@ function love_helper.filesystem.addMockFile(path, data)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Mock system clipboard
|
||||||
|
love_helper.system = {}
|
||||||
|
local mockClipboard = ""
|
||||||
|
|
||||||
|
function love_helper.system.getClipboardText()
|
||||||
|
return mockClipboard
|
||||||
|
end
|
||||||
|
|
||||||
|
function love_helper.system.setClipboardText(text)
|
||||||
|
mockClipboard = text or ""
|
||||||
|
end
|
||||||
|
|
||||||
_G.love = love_helper
|
_G.love = love_helper
|
||||||
return love_helper
|
return love_helper
|
||||||
|
|||||||
Reference in New Issue
Block a user