multi-line selection fixed
This commit is contained in:
@@ -2749,35 +2749,24 @@ function Element:draw(backdropCanvas)
|
||||
local selectionColor = self.selectionColor or Color.new(0.3, 0.5, 0.8, 0.5)
|
||||
local selectionWithOpacity = Color.new(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * self.opacity)
|
||||
|
||||
-- Calculate selection bounds safely
|
||||
local beforeSelection = ""
|
||||
local selectedText = ""
|
||||
|
||||
local startByte = utf8.offset(self.text, selStart + 1)
|
||||
local endByte = utf8.offset(self.text, selEnd + 1)
|
||||
|
||||
if startByte and endByte then
|
||||
beforeSelection = self.text:sub(1, startByte - 1)
|
||||
selectedText = self.text:sub(startByte, endByte - 1)
|
||||
end
|
||||
|
||||
local selX = (tx or contentX) + font:getWidth(beforeSelection)
|
||||
local selWidth = font:getWidth(selectedText)
|
||||
local selY = ty or contentY
|
||||
local selHeight = textHeight
|
||||
-- Get selection rectangles (handles multiline and wrapping)
|
||||
local selectionRects = self:_getSelectionRects(selStart, selEnd)
|
||||
|
||||
-- Apply scissor for single-line editable inputs
|
||||
if not self.multiline then
|
||||
love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight)
|
||||
end
|
||||
|
||||
-- Draw selection background
|
||||
-- Draw selection background rectangles
|
||||
love.graphics.setColor(selectionWithOpacity:toRGBA())
|
||||
love.graphics.rectangle("fill", selX, selY, selWidth, selHeight)
|
||||
|
||||
-- Redraw selected text on top
|
||||
love.graphics.setColor(textColorWithOpacity:toRGBA())
|
||||
love.graphics.print(selectedText, selX, selY)
|
||||
for _, rect in ipairs(selectionRects) do
|
||||
local rectX = contentX + rect.x
|
||||
local rectY = contentY + rect.y
|
||||
if not self.multiline and self._textScrollX then
|
||||
rectX = rectX - self._textScrollX
|
||||
end
|
||||
love.graphics.rectangle("fill", rectX, rectY, rect.width, rect.height)
|
||||
end
|
||||
|
||||
-- Reset scissor
|
||||
if not self.multiline then
|
||||
@@ -4769,6 +4758,158 @@ function Element:_getCursorScreenPosition()
|
||||
return 0, #lines * lineHeight
|
||||
end
|
||||
|
||||
--- Get selection rectangles for rendering (handles multiline and wrapped text)
|
||||
---@param selStart number -- Selection start position (character index)
|
||||
---@param selEnd number -- Selection end position (character index)
|
||||
---@return table -- Array of rectangles {x, y, width, height} relative to content area
|
||||
function Element:_getSelectionRects(selStart, selEnd)
|
||||
if not self.editable then
|
||||
return {}
|
||||
end
|
||||
|
||||
local font = self:_getFont()
|
||||
if not font then
|
||||
return {}
|
||||
end
|
||||
|
||||
local text = self._textBuffer or ""
|
||||
local rects = {}
|
||||
|
||||
-- For single-line text, calculate simple rectangle
|
||||
if not self.multiline then
|
||||
local startByte = utf8.offset(text, selStart + 1)
|
||||
local endByte = utf8.offset(text, selEnd + 1)
|
||||
|
||||
if startByte and endByte then
|
||||
local beforeSelection = text:sub(1, startByte - 1)
|
||||
local selectedText = text:sub(startByte, endByte - 1)
|
||||
local selX = font:getWidth(beforeSelection)
|
||||
local selWidth = font:getWidth(selectedText)
|
||||
local selY = 0
|
||||
local selHeight = font:getHeight()
|
||||
|
||||
table.insert(rects, {x = selX, y = selY, width = selWidth, height = selHeight})
|
||||
end
|
||||
|
||||
return rects
|
||||
end
|
||||
|
||||
-- For multiline text, we need to handle line wrapping
|
||||
self:_updateTextIfDirty()
|
||||
|
||||
-- Get text area width for wrapping
|
||||
local textAreaWidth = self.width
|
||||
local scaledContentPadding = self:getScaledContentPadding()
|
||||
if scaledContentPadding then
|
||||
local borderBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right)
|
||||
textAreaWidth = borderBoxWidth - scaledContentPadding.left - scaledContentPadding.right
|
||||
end
|
||||
|
||||
-- Split text by actual newlines first
|
||||
local lines = {}
|
||||
for line in (text .. "\n"):gmatch("([^\n]*)\n") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
if #lines == 0 then
|
||||
lines = { "" }
|
||||
end
|
||||
|
||||
local lineHeight = font:getHeight()
|
||||
local charCount = 0
|
||||
local visualLineNum = 0
|
||||
|
||||
for lineNum, line in ipairs(lines) do
|
||||
local lineLength = utf8.len(line) or 0
|
||||
|
||||
-- Check if selection intersects with this line
|
||||
local lineStartChar = charCount
|
||||
local lineEndChar = charCount + lineLength
|
||||
|
||||
if selEnd > lineStartChar and selStart <= lineEndChar then
|
||||
-- Selection intersects with this line
|
||||
local selStartInLine = math.max(0, selStart - charCount)
|
||||
local selEndInLine = math.min(lineLength, selEnd - charCount)
|
||||
|
||||
-- If text wrapping is enabled, handle wrapped segments
|
||||
if self.textWrap and textAreaWidth > 0 then
|
||||
local wrappedSegments = self:_wrapLine(line, textAreaWidth)
|
||||
|
||||
for segmentIdx, segment in ipairs(wrappedSegments) do
|
||||
-- Check if selection intersects with this segment
|
||||
if selEndInLine > segment.startIdx and selStartInLine <= segment.endIdx then
|
||||
-- Selection intersects with this segment
|
||||
local segSelStart = math.max(segment.startIdx, selStartInLine)
|
||||
local segSelEnd = math.min(segment.endIdx, selEndInLine)
|
||||
|
||||
-- Calculate X position and width
|
||||
local beforeText = ""
|
||||
local selectedText = ""
|
||||
|
||||
if segSelStart > segment.startIdx then
|
||||
local startByte = utf8.offset(segment.text, segSelStart - segment.startIdx + 1)
|
||||
if startByte then
|
||||
beforeText = segment.text:sub(1, startByte - 1)
|
||||
end
|
||||
end
|
||||
|
||||
local selStartByte = utf8.offset(segment.text, segSelStart - segment.startIdx + 1)
|
||||
local selEndByte = utf8.offset(segment.text, segSelEnd - segment.startIdx + 1)
|
||||
if selStartByte and selEndByte then
|
||||
selectedText = segment.text:sub(selStartByte, selEndByte - 1)
|
||||
end
|
||||
|
||||
local selX = font:getWidth(beforeText)
|
||||
local selWidth = font:getWidth(selectedText)
|
||||
local selY = visualLineNum * lineHeight
|
||||
local selHeight = lineHeight
|
||||
|
||||
table.insert(rects, {x = selX, y = selY, width = selWidth, height = selHeight})
|
||||
end
|
||||
|
||||
visualLineNum = visualLineNum + 1
|
||||
end
|
||||
else
|
||||
-- No wrapping, simple calculation
|
||||
local beforeText = ""
|
||||
local selectedText = ""
|
||||
|
||||
if selStartInLine > 0 then
|
||||
local startByte = utf8.offset(line, selStartInLine + 1)
|
||||
if startByte then
|
||||
beforeText = line:sub(1, startByte - 1)
|
||||
end
|
||||
end
|
||||
|
||||
local selStartByte = utf8.offset(line, selStartInLine + 1)
|
||||
local selEndByte = utf8.offset(line, selEndInLine + 1)
|
||||
if selStartByte and selEndByte then
|
||||
selectedText = line:sub(selStartByte, selEndByte - 1)
|
||||
end
|
||||
|
||||
local selX = font:getWidth(beforeText)
|
||||
local selWidth = font:getWidth(selectedText)
|
||||
local selY = visualLineNum * lineHeight
|
||||
local selHeight = lineHeight
|
||||
|
||||
table.insert(rects, {x = selX, y = selY, width = selWidth, height = selHeight})
|
||||
visualLineNum = visualLineNum + 1
|
||||
end
|
||||
else
|
||||
-- Selection doesn't intersect, but we still need to count visual lines
|
||||
if self.textWrap and textAreaWidth > 0 then
|
||||
local wrappedSegments = self:_wrapLine(line, textAreaWidth)
|
||||
visualLineNum = visualLineNum + #wrappedSegments
|
||||
else
|
||||
visualLineNum = visualLineNum + 1
|
||||
end
|
||||
end
|
||||
|
||||
charCount = charCount + lineLength + 1
|
||||
end
|
||||
|
||||
return rects
|
||||
end
|
||||
|
||||
--- Update element height based on text content (for autoGrow multiline fields)
|
||||
function Element:_updateAutoGrowHeight()
|
||||
if not self.editable or not self.multiline or not self.autoGrow then
|
||||
@@ -4845,20 +4986,27 @@ function Element:_mouseToTextPosition(mouseX, mouseY)
|
||||
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
|
||||
-- Calculate relative position within text area
|
||||
local relativeX = mouseX - contentX
|
||||
|
||||
-- Account for horizontal scroll offset in single-line inputs
|
||||
if not self.multiline and self._textScrollX then
|
||||
relativeX = relativeX + self._textScrollX
|
||||
end
|
||||
local relativeY = mouseY - contentY
|
||||
|
||||
-- Get font for measuring text
|
||||
local font = self:_getFont()
|
||||
if not font then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- Find the character position closest to the click
|
||||
local text = self._textBuffer
|
||||
local textLength = utf8.len(text) or 0
|
||||
|
||||
-- === SINGLE-LINE TEXT HANDLING ===
|
||||
if not self.multiline then
|
||||
-- Account for horizontal scroll offset in single-line inputs
|
||||
if self._textScrollX then
|
||||
relativeX = relativeX + self._textScrollX
|
||||
end
|
||||
|
||||
-- Find the character position closest to the click
|
||||
local closestPos = 0
|
||||
local closestDist = math.huge
|
||||
|
||||
@@ -4879,6 +5027,96 @@ function Element:_mouseToTextPosition(mouseX, mouseY)
|
||||
end
|
||||
|
||||
return closestPos
|
||||
end
|
||||
|
||||
-- === MULTILINE TEXT HANDLING ===
|
||||
|
||||
-- Update text wrapping if dirty
|
||||
self:_updateTextIfDirty()
|
||||
|
||||
-- Split text into lines
|
||||
local lines = {}
|
||||
for line in (text .. "\n"):gmatch("([^\n]*)\n") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
if #lines == 0 then
|
||||
lines = { "" }
|
||||
end
|
||||
|
||||
local lineHeight = font:getHeight()
|
||||
|
||||
-- Get text area width for wrapping calculations
|
||||
local textAreaWidth = self.width
|
||||
local scaledContentPadding = self:getScaledContentPadding()
|
||||
if scaledContentPadding then
|
||||
local borderBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right)
|
||||
textAreaWidth = borderBoxWidth - scaledContentPadding.left - scaledContentPadding.right
|
||||
end
|
||||
|
||||
-- Determine which line the click is on based on Y coordinate
|
||||
local clickedLineNum = math.floor(relativeY / lineHeight) + 1
|
||||
clickedLineNum = math.max(1, math.min(clickedLineNum, #lines))
|
||||
|
||||
-- Calculate character offset for lines before the clicked line
|
||||
local charOffset = 0
|
||||
for i = 1, clickedLineNum - 1 do
|
||||
local lineLen = utf8.len(lines[i]) or 0
|
||||
charOffset = charOffset + lineLen + 1 -- +1 for newline character
|
||||
end
|
||||
|
||||
-- Get the clicked line
|
||||
local clickedLine = lines[clickedLineNum]
|
||||
local lineLen = utf8.len(clickedLine) or 0
|
||||
|
||||
-- If text wrapping is enabled, handle wrapped segments
|
||||
if self.textWrap and textAreaWidth > 0 then
|
||||
local wrappedSegments = self:_wrapLine(clickedLine, textAreaWidth)
|
||||
|
||||
-- Determine which wrapped segment was clicked
|
||||
local lineYOffset = (clickedLineNum - 1) * lineHeight
|
||||
local segmentNum = math.floor((relativeY - lineYOffset) / lineHeight) + 1
|
||||
segmentNum = math.max(1, math.min(segmentNum, #wrappedSegments))
|
||||
|
||||
local segment = wrappedSegments[segmentNum]
|
||||
|
||||
-- Find closest position within the segment
|
||||
local segmentText = segment.text
|
||||
local segmentLen = utf8.len(segmentText) or 0
|
||||
local closestPos = segment.startIdx
|
||||
local closestDist = math.huge
|
||||
|
||||
for i = 0, segmentLen do
|
||||
local offset = utf8.offset(segmentText, i + 1)
|
||||
local beforeText = offset and segmentText:sub(1, offset - 1) or segmentText
|
||||
local textWidth = font:getWidth(beforeText)
|
||||
local dist = math.abs(relativeX - textWidth)
|
||||
|
||||
if dist < closestDist then
|
||||
closestDist = dist
|
||||
closestPos = segment.startIdx + i
|
||||
end
|
||||
end
|
||||
|
||||
return charOffset + closestPos
|
||||
end
|
||||
|
||||
-- No wrapping - find closest position in the clicked line
|
||||
local closestPos = 0
|
||||
local closestDist = math.huge
|
||||
|
||||
for i = 0, lineLen do
|
||||
local offset = utf8.offset(clickedLine, i + 1)
|
||||
local beforeText = offset and clickedLine:sub(1, offset - 1) or clickedLine
|
||||
local textWidth = font:getWidth(beforeText)
|
||||
local dist = math.abs(relativeX - textWidth)
|
||||
|
||||
if dist < closestDist then
|
||||
closestDist = dist
|
||||
closestPos = i
|
||||
end
|
||||
end
|
||||
|
||||
return charOffset + closestPos
|
||||
end
|
||||
|
||||
--- Handle mouse click on text (set cursor position or start selection)
|
||||
|
||||
@@ -1776,5 +1776,313 @@ function TestInputField:testTextScrollWithBackspace()
|
||||
lu.assertTrue(element._textScrollX <= initialScroll)
|
||||
end
|
||||
|
||||
-- ====================
|
||||
-- Multiline Text Selection Tests
|
||||
-- ====================
|
||||
|
||||
function TestInputField:testMultilineMouseToTextPositionBasic()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 100,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "Line 1\nLine 2\nLine 3",
|
||||
})
|
||||
|
||||
element:focus()
|
||||
|
||||
-- Get font to calculate positions
|
||||
local font = element:_getFont()
|
||||
local lineHeight = font:getHeight()
|
||||
|
||||
-- Click at start (should be position 0)
|
||||
local pos = element:_mouseToTextPosition(10, 10)
|
||||
lu.assertEquals(pos, 0)
|
||||
|
||||
-- Click on second line start (should be after "Line 1\n" = position 7)
|
||||
pos = element:_mouseToTextPosition(10, 10 + lineHeight)
|
||||
lu.assertEquals(pos, 7)
|
||||
|
||||
-- Click on third line start (should be after "Line 1\nLine 2\n" = position 14)
|
||||
pos = element:_mouseToTextPosition(10, 10 + lineHeight * 2)
|
||||
lu.assertEquals(pos, 14)
|
||||
end
|
||||
|
||||
function TestInputField:testMultilineMouseToTextPositionXCoordinate()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 100,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "ABC\nDEF\nGHI",
|
||||
})
|
||||
|
||||
element:focus()
|
||||
|
||||
local font = element:_getFont()
|
||||
local lineHeight = font:getHeight()
|
||||
local charWidth = font:getWidth("A")
|
||||
|
||||
-- Click in middle of first line (should be around position 1-2)
|
||||
local pos = element:_mouseToTextPosition(10 + charWidth * 1.5, 10)
|
||||
lu.assertTrue(pos >= 1 and pos <= 2)
|
||||
|
||||
-- Click at end of second line (should be around position 6-7)
|
||||
-- Text is "ABC\nDEF\nGHI", so second line "DEF" ends at position 6 or 7 (newline)
|
||||
pos = element:_mouseToTextPosition(10 + charWidth * 3, 10 + lineHeight)
|
||||
lu.assertTrue(pos >= 6 and pos <= 7)
|
||||
end
|
||||
|
||||
function TestInputField:testMultilineMouseDragSelection()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 100,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "Line 1\nLine 2\nLine 3",
|
||||
})
|
||||
|
||||
element:focus()
|
||||
|
||||
local font = element:_getFont()
|
||||
local lineHeight = font:getHeight()
|
||||
|
||||
-- Simulate mouse click on first line (sets _mouseDownPosition)
|
||||
element:_handleTextClick(10, 10, 1)
|
||||
lu.assertEquals(element._cursorPosition, 0)
|
||||
lu.assertFalse(element:hasSelection())
|
||||
|
||||
-- Drag to second line
|
||||
element:_handleTextDrag(50, 10 + lineHeight)
|
||||
lu.assertTrue(element:hasSelection())
|
||||
|
||||
-- Selection should span from first line to second line
|
||||
local startPos, endPos = element:getSelection()
|
||||
lu.assertTrue(startPos == 0 or endPos == 0)
|
||||
lu.assertTrue(startPos > 6 or endPos > 6) -- Past first newline
|
||||
|
||||
-- After drag, selection should be preserved
|
||||
lu.assertTrue(element:hasSelection())
|
||||
end
|
||||
|
||||
function TestInputField:testMultilineMouseDragAcrossThreeLines()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 150,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "First\nSecond\nThird\nFourth",
|
||||
})
|
||||
|
||||
element:focus()
|
||||
|
||||
local font = element:_getFont()
|
||||
local lineHeight = font:getHeight()
|
||||
|
||||
-- Click on first line, then drag to third line
|
||||
element:_handleTextClick(10, 10, 1)
|
||||
element:_handleTextDrag(50, 10 + lineHeight * 2.5)
|
||||
|
||||
lu.assertTrue(element:hasSelection())
|
||||
local startPos, endPos = element:getSelection()
|
||||
|
||||
-- Should select across multiple lines
|
||||
local minPos = math.min(startPos, endPos)
|
||||
local maxPos = math.max(startPos, endPos)
|
||||
lu.assertEquals(minPos, 0) -- From start
|
||||
lu.assertTrue(maxPos > 12) -- Past "First\nSecond\n"
|
||||
|
||||
-- Selection should persist after drag
|
||||
lu.assertTrue(element:hasSelection())
|
||||
end
|
||||
|
||||
function TestInputField:testMultilineClickOnDifferentLines()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 100,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "AAA\nBBB\nCCC",
|
||||
})
|
||||
|
||||
element:focus()
|
||||
|
||||
local font = element:_getFont()
|
||||
local lineHeight = font:getHeight()
|
||||
|
||||
-- Click on first line
|
||||
element:_handleTextClick(10, 10, 1)
|
||||
local pos1 = element._cursorPosition
|
||||
lu.assertEquals(pos1, 0)
|
||||
|
||||
-- Click on second line
|
||||
element:_handleTextClick(10, 10 + lineHeight, 1)
|
||||
local pos2 = element._cursorPosition
|
||||
lu.assertEquals(pos2, 4) -- After "AAA\n"
|
||||
|
||||
-- Click on third line
|
||||
element:_handleTextClick(10, 10 + lineHeight * 2, 1)
|
||||
local pos3 = element._cursorPosition
|
||||
lu.assertEquals(pos3, 8) -- After "AAA\nBBB\n"
|
||||
end
|
||||
|
||||
function TestInputField:testMultilineSelectionWithKeyboard()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 100,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "Line 1\nLine 2\nLine 3",
|
||||
})
|
||||
|
||||
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
|
||||
|
||||
-- Test Shift+Right selection (horizontal movement works)
|
||||
element:keypressed("right", nil, false)
|
||||
lu.assertTrue(element:hasSelection())
|
||||
|
||||
local startPos, endPos = element:getSelection()
|
||||
lu.assertTrue(math.abs(endPos - startPos) > 0)
|
||||
|
||||
-- Reset mock
|
||||
_G.love.keyboard.isDown = oldIsDown
|
||||
end
|
||||
|
||||
function TestInputField:testMultilineMouseSelectionPreservedAfterRelease()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 100,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "First line\nSecond line\nThird line",
|
||||
})
|
||||
|
||||
element:focus()
|
||||
|
||||
local font = element:_getFont()
|
||||
local lineHeight = font:getHeight()
|
||||
|
||||
-- Create a selection by dragging
|
||||
element:_handleTextClick(10, 10, 1)
|
||||
element:_handleTextDrag(100, 10 + lineHeight * 1.5)
|
||||
|
||||
local startPos, endPos = element:getSelection()
|
||||
local hadSelection = element:hasSelection()
|
||||
|
||||
lu.assertTrue(hadSelection)
|
||||
|
||||
-- Note: There's no mouse release handler that affects selection
|
||||
-- The drag creates the selection and it persists
|
||||
|
||||
-- Selection should still exist
|
||||
lu.assertTrue(element:hasSelection())
|
||||
local startPos2, endPos2 = element:getSelection()
|
||||
lu.assertEquals(startPos, startPos2)
|
||||
lu.assertEquals(endPos, endPos2)
|
||||
end
|
||||
|
||||
function TestInputField:testMultilineClickDoesNotPreserveSelection()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 100,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "First line\nSecond line",
|
||||
})
|
||||
|
||||
element:focus()
|
||||
|
||||
local font = element:_getFont()
|
||||
local lineHeight = font:getHeight()
|
||||
|
||||
-- Create a selection
|
||||
element:setSelection(0, 5)
|
||||
lu.assertTrue(element:hasSelection())
|
||||
|
||||
-- Click somewhere else (should clear selection)
|
||||
element:_handleTextClick(10, 10 + lineHeight, 1)
|
||||
|
||||
-- Selection should be cleared
|
||||
lu.assertFalse(element:hasSelection())
|
||||
end
|
||||
|
||||
function TestInputField:testMultilineEmptyLinesHandling()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 150,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "Line 1\n\nLine 3",
|
||||
})
|
||||
|
||||
element:focus()
|
||||
|
||||
local font = element:_getFont()
|
||||
local lineHeight = font:getHeight()
|
||||
|
||||
-- Click on empty line (second line)
|
||||
local pos = element:_mouseToTextPosition(10, 10 + lineHeight)
|
||||
lu.assertEquals(pos, 7) -- After "Line 1\n"
|
||||
|
||||
-- Click on third line
|
||||
pos = element:_mouseToTextPosition(10, 10 + lineHeight * 2)
|
||||
lu.assertEquals(pos, 8) -- After "Line 1\n\n"
|
||||
end
|
||||
|
||||
function TestInputField:testMultilineYCoordinateBeyondText()
|
||||
local element = FlexLove.Element.new({
|
||||
x = 10,
|
||||
y = 10,
|
||||
width = 300,
|
||||
height = 200,
|
||||
editable = true,
|
||||
multiline = true,
|
||||
text = "Line 1\nLine 2",
|
||||
})
|
||||
|
||||
element:focus()
|
||||
|
||||
local font = element:_getFont()
|
||||
local lineHeight = font:getHeight()
|
||||
|
||||
-- Click way below the text (should clamp to last line)
|
||||
local pos = element:_mouseToTextPosition(10, 10 + lineHeight * 10)
|
||||
local textLen = utf8.len(element.text)
|
||||
|
||||
-- Should be at or near end of text
|
||||
lu.assertTrue(pos >= textLen - 6) -- Within last line
|
||||
end
|
||||
|
||||
-- Run tests
|
||||
lu.LuaUnit.run()
|
||||
|
||||
Reference in New Issue
Block a user