remove debug prints, delete prev behavior
This commit is contained in:
@@ -343,7 +343,7 @@ function Element.new(props)
|
|||||||
self._lines = nil -- Split lines (for multiline)
|
self._lines = nil -- Split lines (for multiline)
|
||||||
self._wrappedLines = nil -- Wrapped line data
|
self._wrappedLines = nil -- Wrapped line data
|
||||||
self._textDirty = true -- Flag to recalculate lines/wrapping
|
self._textDirty = true -- Flag to recalculate lines/wrapping
|
||||||
|
|
||||||
-- Scroll state for text overflow
|
-- Scroll state for text overflow
|
||||||
self._textScrollX = 0 -- Horizontal scroll offset in pixels
|
self._textScrollX = 0 -- Horizontal scroll offset in pixels
|
||||||
end
|
end
|
||||||
@@ -2551,10 +2551,10 @@ function Element:draw(backdropCanvas)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
print("[FlexLove] Component not found: " .. self.themeComponent .. " in theme: " .. themeToUse.name)
|
-- Component not found in theme
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
print("[FlexLove] No theme available for themeComponent: " .. self.themeComponent)
|
-- No theme available for themeComponent
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2591,20 +2591,19 @@ function Element:draw(backdropCanvas)
|
|||||||
self:_updateTextIfDirty()
|
self:_updateTextIfDirty()
|
||||||
self:_updateAutoGrowHeight()
|
self:_updateAutoGrowHeight()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- For editable elements, use _textBuffer; for non-editable, use text
|
-- For editable elements, use _textBuffer; for non-editable, use text
|
||||||
local displayText = self.editable and self._textBuffer or self.text
|
local displayText = self.editable and self._textBuffer or self.text
|
||||||
local isPlaceholder = false
|
local isPlaceholder = false
|
||||||
|
|
||||||
-- Show placeholder if editable, empty, and not focused
|
-- Show placeholder if editable, empty, and not focused
|
||||||
if self.editable and (not displayText or displayText == "") and self.placeholder and not self._focused then
|
if self.editable and (not displayText or displayText == "") and self.placeholder and not self._focused then
|
||||||
displayText = self.placeholder
|
displayText = self.placeholder
|
||||||
isPlaceholder = true
|
isPlaceholder = true
|
||||||
end
|
end
|
||||||
|
|
||||||
if displayText and displayText ~= "" then
|
if displayText and displayText ~= "" then
|
||||||
local textColor = isPlaceholder
|
local textColor = isPlaceholder and Color.new(self.textColor.r * 0.5, self.textColor.g * 0.5, self.textColor.b * 0.5, self.textColor.a * 0.5)
|
||||||
and Color.new(self.textColor.r * 0.5, self.textColor.g * 0.5, self.textColor.b * 0.5, self.textColor.a * 0.5)
|
|
||||||
or self.textColor
|
or self.textColor
|
||||||
local textColorWithOpacity = Color.new(textColor.r, textColor.g, textColor.b, textColor.a * self.opacity)
|
local textColorWithOpacity = Color.new(textColor.r, textColor.g, textColor.b, textColor.a * self.opacity)
|
||||||
love.graphics.setColor(textColorWithOpacity:toRGBA())
|
love.graphics.setColor(textColorWithOpacity:toRGBA())
|
||||||
@@ -2694,103 +2693,103 @@ function Element:draw(backdropCanvas)
|
|||||||
tx = contentX
|
tx = contentX
|
||||||
ty = contentY
|
ty = contentY
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply scroll offset for editable single-line inputs
|
-- Apply scroll offset for editable single-line inputs
|
||||||
if self.editable and not self.multiline and self._textScrollX then
|
if self.editable and not self.multiline and self._textScrollX then
|
||||||
tx = tx - self._textScrollX
|
tx = tx - self._textScrollX
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Use scissor to clip text to content area for editable inputs
|
-- Use scissor to clip text to content area for editable inputs
|
||||||
if self.editable and not self.multiline then
|
if self.editable and not self.multiline then
|
||||||
love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight)
|
love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight)
|
||||||
end
|
end
|
||||||
|
|
||||||
love.graphics.print(displayText, tx, ty)
|
love.graphics.print(displayText, tx, ty)
|
||||||
|
|
||||||
-- Reset scissor
|
-- Reset scissor
|
||||||
if self.editable and not self.multiline then
|
if self.editable and not self.multiline then
|
||||||
love.graphics.setScissor()
|
love.graphics.setScissor()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw cursor for focused editable elements (even if text is empty)
|
-- Draw cursor for focused editable elements (even if text is empty)
|
||||||
if self.editable and self._focused and self._cursorVisible then
|
if self.editable and self._focused and self._cursorVisible then
|
||||||
local cursorColor = self.cursorColor or self.textColor
|
local cursorColor = self.cursorColor or self.textColor
|
||||||
local cursorWithOpacity = Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * self.opacity)
|
local cursorWithOpacity = Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * self.opacity)
|
||||||
love.graphics.setColor(cursorWithOpacity:toRGBA())
|
love.graphics.setColor(cursorWithOpacity:toRGBA())
|
||||||
|
|
||||||
-- Calculate cursor position using new method that handles multiline
|
-- Calculate cursor position using new method that handles multiline
|
||||||
local cursorRelX, cursorRelY = self:_getCursorScreenPosition()
|
local cursorRelX, cursorRelY = self:_getCursorScreenPosition()
|
||||||
local cursorX = contentX + cursorRelX
|
local cursorX = contentX + cursorRelX
|
||||||
local cursorY = contentY + cursorRelY
|
local cursorY = contentY + cursorRelY
|
||||||
local cursorHeight = textHeight
|
local cursorHeight = textHeight
|
||||||
|
|
||||||
-- Apply scroll offset for single-line inputs
|
-- Apply scroll offset for single-line inputs
|
||||||
if not self.multiline and self._textScrollX then
|
if not self.multiline and self._textScrollX then
|
||||||
cursorX = cursorX - self._textScrollX
|
cursorX = cursorX - self._textScrollX
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply scissor for single-line editable inputs
|
-- Apply scissor for single-line editable inputs
|
||||||
if not self.multiline then
|
if not self.multiline then
|
||||||
love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight)
|
love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw cursor line
|
-- Draw cursor line
|
||||||
love.graphics.rectangle("fill", cursorX, cursorY, 2, cursorHeight)
|
love.graphics.rectangle("fill", cursorX, cursorY, 2, cursorHeight)
|
||||||
|
|
||||||
-- Reset scissor
|
-- Reset scissor
|
||||||
if not self.multiline then
|
if not self.multiline then
|
||||||
love.graphics.setScissor()
|
love.graphics.setScissor()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw selection highlight for editable elements
|
-- Draw selection highlight for editable elements
|
||||||
if self.editable and self._focused and self:hasSelection() and self.text and self.text ~= "" then
|
if self.editable and self._focused and self:hasSelection() and self.text and self.text ~= "" then
|
||||||
local selStart, selEnd = self:getSelection()
|
local selStart, selEnd = self:getSelection()
|
||||||
local selectionColor = self.selectionColor or Color.new(0.3, 0.5, 0.8, 0.5)
|
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)
|
local selectionWithOpacity = Color.new(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * self.opacity)
|
||||||
|
|
||||||
-- Calculate selection bounds safely
|
-- Calculate selection bounds safely
|
||||||
local beforeSelection = ""
|
local beforeSelection = ""
|
||||||
local selectedText = ""
|
local selectedText = ""
|
||||||
|
|
||||||
local startByte = utf8.offset(self.text, selStart + 1)
|
local startByte = utf8.offset(self.text, selStart + 1)
|
||||||
local endByte = utf8.offset(self.text, selEnd + 1)
|
local endByte = utf8.offset(self.text, selEnd + 1)
|
||||||
|
|
||||||
if startByte and endByte then
|
if startByte and endByte then
|
||||||
beforeSelection = self.text:sub(1, startByte - 1)
|
beforeSelection = self.text:sub(1, startByte - 1)
|
||||||
selectedText = self.text:sub(startByte, endByte - 1)
|
selectedText = self.text:sub(startByte, endByte - 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local selX = (tx or contentX) + font:getWidth(beforeSelection)
|
local selX = (tx or contentX) + font:getWidth(beforeSelection)
|
||||||
local selWidth = font:getWidth(selectedText)
|
local selWidth = font:getWidth(selectedText)
|
||||||
local selY = ty or contentY
|
local selY = ty or contentY
|
||||||
local selHeight = textHeight
|
local selHeight = textHeight
|
||||||
|
|
||||||
-- Apply scissor for single-line editable inputs
|
-- Apply scissor for single-line editable inputs
|
||||||
if not self.multiline then
|
if not self.multiline then
|
||||||
love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight)
|
love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw selection background
|
-- Draw selection background
|
||||||
love.graphics.setColor(selectionWithOpacity:toRGBA())
|
love.graphics.setColor(selectionWithOpacity:toRGBA())
|
||||||
love.graphics.rectangle("fill", selX, selY, selWidth, selHeight)
|
love.graphics.rectangle("fill", selX, selY, selWidth, selHeight)
|
||||||
|
|
||||||
-- Redraw selected text on top
|
-- Redraw selected text on top
|
||||||
love.graphics.setColor(textColorWithOpacity:toRGBA())
|
love.graphics.setColor(textColorWithOpacity:toRGBA())
|
||||||
love.graphics.print(selectedText, selX, selY)
|
love.graphics.print(selectedText, selX, selY)
|
||||||
|
|
||||||
-- Reset scissor
|
-- Reset scissor
|
||||||
if not self.multiline then
|
if not self.multiline then
|
||||||
love.graphics.setScissor()
|
love.graphics.setScissor()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.textSize then
|
if self.textSize then
|
||||||
love.graphics.setFont(origFont)
|
love.graphics.setFont(origFont)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw cursor for focused editable elements even when empty
|
-- Draw cursor for focused editable elements even when empty
|
||||||
if self.editable and self._focused and self._cursorVisible and (not displayText or displayText == "") then
|
if self.editable and self._focused and self._cursorVisible and (not displayText or displayText == "") then
|
||||||
-- Set up font for cursor rendering
|
-- Set up font for cursor rendering
|
||||||
@@ -2808,10 +2807,10 @@ function Element:draw(backdropCanvas)
|
|||||||
local font = FONT_CACHE.get(self.textSize, fontPath)
|
local font = FONT_CACHE.get(self.textSize, fontPath)
|
||||||
love.graphics.setFont(font)
|
love.graphics.setFont(font)
|
||||||
end
|
end
|
||||||
|
|
||||||
local font = love.graphics.getFont()
|
local font = love.graphics.getFont()
|
||||||
local textHeight = font:getHeight()
|
local textHeight = font:getHeight()
|
||||||
|
|
||||||
-- Calculate text area position
|
-- Calculate text area position
|
||||||
local textPaddingLeft = self.padding.left
|
local textPaddingLeft = self.padding.left
|
||||||
local textPaddingTop = self.padding.top
|
local textPaddingTop = self.padding.top
|
||||||
@@ -2820,16 +2819,16 @@ function Element:draw(backdropCanvas)
|
|||||||
textPaddingLeft = scaledContentPadding.left
|
textPaddingLeft = scaledContentPadding.left
|
||||||
textPaddingTop = scaledContentPadding.top
|
textPaddingTop = scaledContentPadding.top
|
||||||
end
|
end
|
||||||
|
|
||||||
local contentX = self.x + textPaddingLeft
|
local contentX = self.x + textPaddingLeft
|
||||||
local contentY = self.y + textPaddingTop
|
local contentY = self.y + textPaddingTop
|
||||||
|
|
||||||
-- Draw cursor
|
-- Draw cursor
|
||||||
local cursorColor = self.cursorColor or self.textColor
|
local cursorColor = self.cursorColor or self.textColor
|
||||||
local cursorWithOpacity = Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * self.opacity)
|
local cursorWithOpacity = Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * self.opacity)
|
||||||
love.graphics.setColor(cursorWithOpacity:toRGBA())
|
love.graphics.setColor(cursorWithOpacity:toRGBA())
|
||||||
love.graphics.rectangle("fill", contentX, contentY, 2, textHeight)
|
love.graphics.rectangle("fill", contentX, contentY, 2, textHeight)
|
||||||
|
|
||||||
if self.textSize then
|
if self.textSize then
|
||||||
love.graphics.setFont(origFont)
|
love.graphics.setFont(origFont)
|
||||||
end
|
end
|
||||||
@@ -2892,7 +2891,7 @@ function Element:draw(backdropCanvas)
|
|||||||
love.graphics.setCanvas()
|
love.graphics.setCanvas()
|
||||||
love.graphics.stencil(stencilFunc, "replace", 1)
|
love.graphics.stencil(stencilFunc, "replace", 1)
|
||||||
love.graphics.setCanvas(currentCanvas)
|
love.graphics.setCanvas(currentCanvas)
|
||||||
|
|
||||||
love.graphics.setStencilTest("greater", 0)
|
love.graphics.setStencilTest("greater", 0)
|
||||||
|
|
||||||
-- Apply scroll offset AFTER clipping is set
|
-- Apply scroll offset AFTER clipping is set
|
||||||
@@ -3255,7 +3254,7 @@ function Element:update(dt)
|
|||||||
})
|
})
|
||||||
self.callback(self, dragEvent)
|
self.callback(self, dragEvent)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle text selection drag for editable elements
|
-- Handle text selection drag for editable elements
|
||||||
if button == 1 and self.editable and self._focused then
|
if button == 1 and self.editable and self._focused then
|
||||||
self:_handleTextDrag(mx, my)
|
self:_handleTextDrag(mx, my)
|
||||||
@@ -3310,7 +3309,7 @@ function Element:update(dt)
|
|||||||
-- Clean up drag tracking
|
-- Clean up drag tracking
|
||||||
self._dragStartX[button] = nil
|
self._dragStartX[button] = nil
|
||||||
self._dragStartY[button] = nil
|
self._dragStartY[button] = nil
|
||||||
|
|
||||||
-- Clean up text selection drag tracking
|
-- Clean up text selection drag tracking
|
||||||
if button == 1 then
|
if button == 1 then
|
||||||
self._mouseDownPosition = nil
|
self._mouseDownPosition = nil
|
||||||
@@ -3318,13 +3317,11 @@ function Element:update(dt)
|
|||||||
|
|
||||||
-- 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")
|
|
||||||
self:focus()
|
self:focus()
|
||||||
|
|
||||||
-- Handle text click for cursor positioning and word selection
|
-- Handle text click for cursor positioning and word selection
|
||||||
self:_handleTextClick(mx, my, clickCount)
|
self:_handleTextClick(mx, my, clickCount)
|
||||||
elseif button == 1 then
|
elseif button == 1 then
|
||||||
print("[Element:update] Button 1 clicked but editable:", self.editable)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Fire release event
|
-- Fire release event
|
||||||
@@ -4069,7 +4066,7 @@ function Element:_resetCursorBlink()
|
|||||||
end
|
end
|
||||||
self._cursorBlinkTimer = 0
|
self._cursorBlinkTimer = 0
|
||||||
self._cursorVisible = true
|
self._cursorVisible = true
|
||||||
|
|
||||||
-- Update scroll to keep cursor visible
|
-- Update scroll to keep cursor visible
|
||||||
self:_updateTextScroll()
|
self:_updateTextScroll()
|
||||||
end
|
end
|
||||||
@@ -4201,16 +4198,16 @@ function Element:getSelectedText()
|
|||||||
local text = self._textBuffer or ""
|
local text = self._textBuffer or ""
|
||||||
local startByte = utf8.offset(text, startPos + 1)
|
local startByte = utf8.offset(text, startPos + 1)
|
||||||
local endByte = utf8.offset(text, endPos + 1)
|
local endByte = utf8.offset(text, endPos + 1)
|
||||||
|
|
||||||
if not startByte then
|
if not startByte then
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If endByte is nil, it means we want to the end of the string
|
-- If endByte is nil, it means we want to the end of the string
|
||||||
if endByte then
|
if endByte then
|
||||||
endByte = endByte - 1 -- Adjust to get the last byte of the character
|
endByte = endByte - 1 -- Adjust to get the last byte of the character
|
||||||
end
|
end
|
||||||
|
|
||||||
return string.sub(text, startByte, endByte)
|
return string.sub(text, startByte, endByte)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4239,13 +4236,9 @@ end
|
|||||||
--- Focus this element for keyboard input
|
--- Focus this element for keyboard input
|
||||||
function Element:focus()
|
function Element:focus()
|
||||||
if not self.editable then
|
if not self.editable then
|
||||||
print("[Element:focus] Not editable, skipping focus")
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
print("[Element:focus] Focusing element, editable:", self.editable)
|
|
||||||
|
|
||||||
-- Blur previously focused element
|
|
||||||
if Gui._focusedElement and Gui._focusedElement ~= self then
|
if Gui._focusedElement and Gui._focusedElement ~= self then
|
||||||
Gui._focusedElement:blur()
|
Gui._focusedElement:blur()
|
||||||
end
|
end
|
||||||
@@ -4254,16 +4247,11 @@ function Element:focus()
|
|||||||
self._focused = true
|
self._focused = true
|
||||||
Gui._focusedElement = self
|
Gui._focusedElement = self
|
||||||
|
|
||||||
print("[Element:focus] Focus set, _focused:", self._focused)
|
|
||||||
|
|
||||||
-- Reset cursor blink
|
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink()
|
||||||
|
|
||||||
-- Select all text if selectOnFocus is enabled
|
|
||||||
if self.selectOnFocus then
|
if self.selectOnFocus then
|
||||||
self:selectAll()
|
self:selectAll()
|
||||||
else
|
else
|
||||||
-- Move cursor to end of text
|
|
||||||
self:moveCursorToEnd()
|
self:moveCursorToEnd()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4362,19 +4350,14 @@ function Element:insertText(text, position)
|
|||||||
self._textBuffer = before .. text .. after
|
self._textBuffer = before .. text .. after
|
||||||
self.text = self._textBuffer -- Sync display text
|
self.text = self._textBuffer -- Sync display text
|
||||||
|
|
||||||
-- Update cursor position
|
|
||||||
self._cursorPosition = position + utf8.len(text)
|
self._cursorPosition = position + utf8.len(text)
|
||||||
|
|
||||||
print(string.format("[InsertText] Text: '%s', multiline: %s, autoGrow: %s",
|
|
||||||
self._textBuffer:gsub("\n", "\\n"), tostring(self.multiline), tostring(self.autoGrow)))
|
|
||||||
|
|
||||||
self:_markTextDirty()
|
self:_markTextDirty()
|
||||||
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
|
||||||
self:_validateCursorPosition()
|
self:_validateCursorPosition()
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Delete text in range
|
|
||||||
---@param startPos number -- Start position (inclusive)
|
---@param startPos number -- Start position (inclusive)
|
||||||
---@param endPos number -- End position (inclusive)
|
---@param endPos number -- End position (inclusive)
|
||||||
function Element:deleteText(startPos, endPos)
|
function Element:deleteText(startPos, endPos)
|
||||||
@@ -4507,11 +4490,13 @@ function Element:_wrapLine(line, maxWidth)
|
|||||||
local wrappedParts = {}
|
local wrappedParts = {}
|
||||||
local currentLine = ""
|
local currentLine = ""
|
||||||
local startIdx = 0
|
local startIdx = 0
|
||||||
|
|
||||||
-- Helper function to extract a UTF-8 character by character index
|
-- Helper function to extract a UTF-8 character by character index
|
||||||
local function getUtf8Char(str, charIndex)
|
local function getUtf8Char(str, charIndex)
|
||||||
local byteStart = utf8.offset(str, charIndex)
|
local byteStart = utf8.offset(str, charIndex)
|
||||||
if not byteStart then return "" end
|
if not byteStart then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
local byteEnd = utf8.offset(str, charIndex + 1)
|
local byteEnd = utf8.offset(str, charIndex + 1)
|
||||||
if byteEnd then
|
if byteEnd then
|
||||||
return str:sub(byteStart, byteEnd - 1)
|
return str:sub(byteStart, byteEnd - 1)
|
||||||
@@ -4519,12 +4504,8 @@ function Element:_wrapLine(line, maxWidth)
|
|||||||
return str:sub(byteStart)
|
return str:sub(byteStart)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
print(string.format("[WrapLine] line length: %d, maxWidth: %.1f, textWrap: %s",
|
|
||||||
utf8.len(line) or 0, maxWidth, tostring(self.textWrap)))
|
|
||||||
|
|
||||||
if self.textWrap == "word" then
|
if self.textWrap == "word" then
|
||||||
-- Word wrapping
|
|
||||||
local words = {}
|
local words = {}
|
||||||
for word in line:gmatch("%S+") do
|
for word in line:gmatch("%S+") do
|
||||||
table.insert(words, word)
|
table.insert(words, word)
|
||||||
@@ -4535,7 +4516,6 @@ function Element:_wrapLine(line, maxWidth)
|
|||||||
local width = font:getWidth(testLine)
|
local width = font:getWidth(testLine)
|
||||||
|
|
||||||
if width > maxWidth and currentLine ~= "" then
|
if width > maxWidth and currentLine ~= "" then
|
||||||
-- Current line is full, start new line
|
|
||||||
local currentLineLen = utf8.len(currentLine)
|
local currentLineLen = utf8.len(currentLine)
|
||||||
table.insert(wrappedParts, {
|
table.insert(wrappedParts, {
|
||||||
text = currentLine,
|
text = currentLine,
|
||||||
@@ -4544,19 +4524,19 @@ function Element:_wrapLine(line, maxWidth)
|
|||||||
})
|
})
|
||||||
startIdx = startIdx + currentLineLen + 1 -- +1 for the space
|
startIdx = startIdx + currentLineLen + 1 -- +1 for the space
|
||||||
currentLine = word
|
currentLine = word
|
||||||
|
|
||||||
-- Check if the word itself is too long - if so, break it with character wrapping
|
-- Check if the word itself is too long - if so, break it with character wrapping
|
||||||
if font:getWidth(word) > maxWidth then
|
if font:getWidth(word) > maxWidth then
|
||||||
local wordLen = utf8.len(word)
|
local wordLen = utf8.len(word)
|
||||||
local charLine = ""
|
local charLine = ""
|
||||||
local charStartIdx = startIdx
|
local charStartIdx = startIdx
|
||||||
|
|
||||||
for j = 1, wordLen do
|
for j = 1, wordLen do
|
||||||
local char = getUtf8Char(word, j)
|
local char = getUtf8Char(word, j)
|
||||||
local testCharLine = charLine .. char
|
local testCharLine = charLine .. char
|
||||||
local charWidth = font:getWidth(testCharLine)
|
local charWidth = font:getWidth(testCharLine)
|
||||||
|
|
||||||
if charWidth > maxWidth and charLine ~= "" then
|
if charWidth > maxWidth and charLine ~= "" then
|
||||||
table.insert(wrappedParts, {
|
table.insert(wrappedParts, {
|
||||||
text = charLine,
|
text = charLine,
|
||||||
startIdx = charStartIdx,
|
startIdx = charStartIdx,
|
||||||
@@ -4568,7 +4548,7 @@ function Element:_wrapLine(line, maxWidth)
|
|||||||
charLine = testCharLine
|
charLine = testCharLine
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
currentLine = charLine
|
currentLine = charLine
|
||||||
startIdx = charStartIdx
|
startIdx = charStartIdx
|
||||||
end
|
end
|
||||||
@@ -4577,12 +4557,12 @@ function Element:_wrapLine(line, maxWidth)
|
|||||||
local wordLen = utf8.len(word)
|
local wordLen = utf8.len(word)
|
||||||
local charLine = ""
|
local charLine = ""
|
||||||
local charStartIdx = startIdx
|
local charStartIdx = startIdx
|
||||||
|
|
||||||
for j = 1, wordLen do
|
for j = 1, wordLen do
|
||||||
local char = getUtf8Char(word, j)
|
local char = getUtf8Char(word, j)
|
||||||
local testCharLine = charLine .. char
|
local testCharLine = charLine .. char
|
||||||
local charWidth = font:getWidth(testCharLine)
|
local charWidth = font:getWidth(testCharLine)
|
||||||
|
|
||||||
if charWidth > maxWidth and charLine ~= "" then
|
if charWidth > maxWidth and charLine ~= "" then
|
||||||
table.insert(wrappedParts, {
|
table.insert(wrappedParts, {
|
||||||
text = charLine,
|
text = charLine,
|
||||||
@@ -4595,7 +4575,7 @@ function Element:_wrapLine(line, maxWidth)
|
|||||||
charLine = testCharLine
|
charLine = testCharLine
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
currentLine = charLine
|
currentLine = charLine
|
||||||
startIdx = charStartIdx
|
startIdx = charStartIdx
|
||||||
else
|
else
|
||||||
@@ -4641,16 +4621,10 @@ function Element:_wrapLine(line, maxWidth)
|
|||||||
endIdx = 0,
|
endIdx = 0,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
if #wrappedParts > 1 then
|
|
||||||
print(string.format("[WrapLine] Returning %d segments for line length %d",
|
|
||||||
#wrappedParts, utf8.len(line) or 0))
|
|
||||||
end
|
|
||||||
|
|
||||||
return wrappedParts
|
return wrappedParts
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get font for text rendering
|
|
||||||
---@return love.Font
|
---@return love.Font
|
||||||
function Element:_getFont()
|
function Element:_getFont()
|
||||||
-- Get font path from theme or element
|
-- Get font path from theme or element
|
||||||
@@ -4660,7 +4634,6 @@ function Element:_getFont()
|
|||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then
|
if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then
|
||||||
fontPath = themeToUse.fonts[self.fontFamily]
|
fontPath = themeToUse.fonts[self.fontFamily]
|
||||||
else
|
else
|
||||||
-- Assume fontFamily is a direct path
|
|
||||||
fontPath = self.fontFamily
|
fontPath = self.fontFamily
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4724,16 +4697,16 @@ function Element:_getCursorScreenPosition()
|
|||||||
|
|
||||||
for lineNum, line in ipairs(lines) do
|
for lineNum, line in ipairs(lines) do
|
||||||
local lineLength = utf8.len(line) or 0
|
local lineLength = utf8.len(line) or 0
|
||||||
|
|
||||||
-- Check if cursor is on this line (before the newline)
|
-- Check if cursor is on this line (before the newline)
|
||||||
if cursorPos <= charCount + lineLength then
|
if cursorPos <= charCount + lineLength then
|
||||||
-- Cursor is on this line
|
-- Cursor is on this line
|
||||||
local posInLine = cursorPos - charCount
|
local posInLine = cursorPos - charCount
|
||||||
|
|
||||||
-- If text wrapping is enabled, find which wrapped segment
|
-- If text wrapping is enabled, find which wrapped segment
|
||||||
if self.textWrap and textAreaWidth > 0 then
|
if self.textWrap and textAreaWidth > 0 then
|
||||||
local wrappedSegments = self:_wrapLine(line, textAreaWidth)
|
local wrappedSegments = self:_wrapLine(line, textAreaWidth)
|
||||||
|
|
||||||
for segmentIdx, segment in ipairs(wrappedSegments) do
|
for segmentIdx, segment in ipairs(wrappedSegments) do
|
||||||
-- Check if cursor is within this segment's character range
|
-- Check if cursor is within this segment's character range
|
||||||
if posInLine >= segment.startIdx and posInLine <= segment.endIdx then
|
if posInLine >= segment.startIdx and posInLine <= segment.endIdx then
|
||||||
@@ -4750,10 +4723,8 @@ function Element:_getCursorScreenPosition()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
cursorX = font:getWidth(segmentText)
|
cursorX = font:getWidth(segmentText)
|
||||||
-- Add line offset for wrapped segments
|
|
||||||
cursorY = (lineNum - 1) * lineHeight + (segmentIdx - 1) * lineHeight
|
cursorY = (lineNum - 1) * lineHeight + (segmentIdx - 1) * lineHeight
|
||||||
print(string.format("[CursorCalc] Line %d, Segment %d, posInLine: %d, startIdx: %d, endIdx: %d, cursorY: %.1f",
|
|
||||||
lineNum, segmentIdx, posInLine, segment.startIdx, segment.endIdx, cursorY))
|
|
||||||
return cursorX, cursorY
|
return cursorX, cursorY
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4774,13 +4745,12 @@ function Element:_getCursorScreenPosition()
|
|||||||
return cursorX, cursorY
|
return cursorX, cursorY
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Move to next line (add 1 for the newline character)
|
|
||||||
charCount = charCount + lineLength + 1
|
charCount = charCount + lineLength + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Cursor is at the very end
|
-- Cursor is at the very end
|
||||||
return 0, (#lines) * lineHeight
|
return 0, #lines * lineHeight
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Update element height based on text content (for autoGrow multiline fields)
|
--- Update element height based on text content (for autoGrow multiline fields)
|
||||||
@@ -4796,7 +4766,7 @@ function Element:_updateAutoGrowHeight()
|
|||||||
|
|
||||||
local text = self._textBuffer or ""
|
local text = self._textBuffer or ""
|
||||||
local lineHeight = font:getHeight()
|
local lineHeight = font:getHeight()
|
||||||
|
|
||||||
-- Get text area width for wrapping
|
-- Get text area width for wrapping
|
||||||
local textAreaWidth = self.width
|
local textAreaWidth = self.width
|
||||||
local scaledContentPadding = self:getScaledContentPadding()
|
local scaledContentPadding = self:getScaledContentPadding()
|
||||||
@@ -4829,22 +4799,14 @@ function Element:_updateAutoGrowHeight()
|
|||||||
totalWrappedLines = #lines
|
totalWrappedLines = #lines
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Ensure at least one line
|
|
||||||
totalWrappedLines = math.max(1, totalWrappedLines)
|
totalWrappedLines = math.max(1, totalWrappedLines)
|
||||||
|
|
||||||
-- Calculate new content height
|
|
||||||
local newContentHeight = totalWrappedLines * lineHeight
|
local newContentHeight = totalWrappedLines * lineHeight
|
||||||
|
|
||||||
-- Update height if it changed
|
|
||||||
if self.height ~= newContentHeight then
|
if self.height ~= newContentHeight then
|
||||||
print(string.format("[AutoGrow] Height changing from %s to %s (lines: %d)",
|
|
||||||
tostring(self.height), tostring(newContentHeight), totalWrappedLines))
|
|
||||||
self.height = newContentHeight
|
self.height = newContentHeight
|
||||||
self._borderBoxHeight = self.height + self.padding.top + self.padding.bottom
|
self._borderBoxHeight = self.height + self.padding.top + self.padding.bottom
|
||||||
|
|
||||||
-- Re-layout parent if this element participates in parent layout
|
|
||||||
if self.parent and not self._explicitlyAbsolute then
|
if self.parent and not self._explicitlyAbsolute then
|
||||||
print("[AutoGrow] Re-layouting parent")
|
|
||||||
self.parent:layoutChildren()
|
self.parent:layoutChildren()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4866,35 +4828,40 @@ function Element:_mouseToTextPosition(mouseX, mouseY)
|
|||||||
-- Get content area bounds
|
-- Get content area bounds
|
||||||
local contentX = (self._absoluteX or self.x) + self.padding.left
|
local contentX = (self._absoluteX or self.x) + self.padding.left
|
||||||
local contentY = (self._absoluteY or self.y) + self.padding.top
|
local contentY = (self._absoluteY or self.y) + self.padding.top
|
||||||
|
|
||||||
-- Calculate relative X position within text area
|
-- Calculate relative X position within text area
|
||||||
local relativeX = mouseX - contentX
|
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
|
||||||
|
|
||||||
-- Get font for measuring text
|
-- Get font for measuring text
|
||||||
local font = self:_getFont()
|
local font = self:_getFont()
|
||||||
|
|
||||||
-- Find the character position closest to the click
|
-- Find the character position closest to the click
|
||||||
local text = self._textBuffer
|
local text = self._textBuffer
|
||||||
local textLength = utf8.len(text) or 0
|
local textLength = utf8.len(text) or 0
|
||||||
local closestPos = 0
|
local closestPos = 0
|
||||||
local closestDist = math.huge
|
local closestDist = math.huge
|
||||||
|
|
||||||
-- Check each position in the text
|
-- Check each position in the text
|
||||||
for i = 0, textLength do
|
for i = 0, textLength do
|
||||||
-- Get text up to this position
|
-- Get text up to this position
|
||||||
local offset = utf8.offset(text, i + 1)
|
local offset = utf8.offset(text, i + 1)
|
||||||
local beforeText = offset and text:sub(1, offset - 1) or text
|
local beforeText = offset and text:sub(1, offset - 1) or text
|
||||||
local textWidth = font:getWidth(beforeText)
|
local textWidth = font:getWidth(beforeText)
|
||||||
|
|
||||||
-- Calculate distance from click to this position
|
-- Calculate distance from click to this position
|
||||||
local dist = math.abs(relativeX - textWidth)
|
local dist = math.abs(relativeX - textWidth)
|
||||||
|
|
||||||
if dist < closestDist then
|
if dist < closestDist then
|
||||||
closestDist = dist
|
closestDist = dist
|
||||||
closestPos = i
|
closestPos = i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return closestPos
|
return closestPos
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4912,19 +4879,17 @@ function Element:_handleTextClick(mouseX, mouseY, clickCount)
|
|||||||
local pos = self:_mouseToTextPosition(mouseX, mouseY)
|
local pos = self:_mouseToTextPosition(mouseX, mouseY)
|
||||||
self:setCursorPosition(pos)
|
self:setCursorPosition(pos)
|
||||||
self:clearSelection()
|
self:clearSelection()
|
||||||
|
|
||||||
-- Store position for potential drag selection
|
-- Store position for potential drag selection
|
||||||
self._mouseDownPosition = pos
|
self._mouseDownPosition = pos
|
||||||
|
|
||||||
elseif clickCount == 2 then
|
elseif clickCount == 2 then
|
||||||
-- Double click: Select word
|
-- Double click: Select word
|
||||||
self:_selectWordAtPosition(self:_mouseToTextPosition(mouseX, mouseY))
|
self:_selectWordAtPosition(self:_mouseToTextPosition(mouseX, mouseY))
|
||||||
|
|
||||||
elseif clickCount >= 3 then
|
elseif clickCount >= 3 then
|
||||||
-- Triple click: Select all (or line in multi-line mode)
|
-- Triple click: Select all (or line in multi-line mode)
|
||||||
self:selectAll()
|
self:selectAll()
|
||||||
end
|
end
|
||||||
|
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4937,7 +4902,7 @@ function Element:_handleTextDrag(mouseX, mouseY)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local currentPos = self:_mouseToTextPosition(mouseX, mouseY)
|
local currentPos = self:_mouseToTextPosition(mouseX, mouseY)
|
||||||
|
|
||||||
-- Create selection from mouse down position to current position
|
-- Create selection from mouse down position to current position
|
||||||
if currentPos ~= self._mouseDownPosition then
|
if currentPos ~= self._mouseDownPosition then
|
||||||
self:setSelection(self._mouseDownPosition, currentPos)
|
self:setSelection(self._mouseDownPosition, currentPos)
|
||||||
@@ -4945,7 +4910,7 @@ function Element:_handleTextDrag(mouseX, mouseY)
|
|||||||
else
|
else
|
||||||
self:clearSelection()
|
self:clearSelection()
|
||||||
end
|
end
|
||||||
|
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4958,7 +4923,7 @@ function Element:_selectWordAtPosition(position)
|
|||||||
|
|
||||||
local text = self._textBuffer
|
local text = self._textBuffer
|
||||||
local textLength = utf8.len(text) or 0
|
local textLength = utf8.len(text) or 0
|
||||||
|
|
||||||
if position < 0 or position > textLength then
|
if position < 0 or position > textLength then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -4966,7 +4931,7 @@ function Element:_selectWordAtPosition(position)
|
|||||||
-- Find word boundaries
|
-- Find word boundaries
|
||||||
local wordStart = position
|
local wordStart = position
|
||||||
local wordEnd = position
|
local wordEnd = position
|
||||||
|
|
||||||
-- Find start of word (move left while alphanumeric)
|
-- Find start of word (move left while alphanumeric)
|
||||||
while wordStart > 0 do
|
while wordStart > 0 do
|
||||||
local offset = utf8.offset(text, wordStart)
|
local offset = utf8.offset(text, wordStart)
|
||||||
@@ -4977,7 +4942,7 @@ function Element:_selectWordAtPosition(position)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Find end of word (move right while alphanumeric)
|
-- Find end of word (move right while alphanumeric)
|
||||||
while wordEnd < textLength do
|
while wordEnd < textLength do
|
||||||
local offset = utf8.offset(text, wordEnd + 1)
|
local offset = utf8.offset(text, wordEnd + 1)
|
||||||
@@ -4988,7 +4953,7 @@ function Element:_selectWordAtPosition(position)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Select the word
|
-- Select the word
|
||||||
if wordEnd > wordStart then
|
if wordEnd > wordStart then
|
||||||
self:setSelection(wordStart, wordEnd)
|
self:setSelection(wordStart, wordEnd)
|
||||||
@@ -5129,6 +5094,13 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
if self:hasSelection() then
|
if self:hasSelection() then
|
||||||
-- Delete selection
|
-- Delete selection
|
||||||
self:deleteSelection()
|
self:deleteSelection()
|
||||||
|
elseif ctrl then
|
||||||
|
-- Ctrl/Cmd+Backspace: Delete all text from start to cursor
|
||||||
|
if self._cursorPosition > 0 then
|
||||||
|
self:deleteText(0, self._cursorPosition)
|
||||||
|
self._cursorPosition = 0
|
||||||
|
self:_validateCursorPosition()
|
||||||
|
end
|
||||||
elseif self._cursorPosition > 0 then
|
elseif self._cursorPosition > 0 then
|
||||||
-- Delete character before cursor
|
-- Delete character before cursor
|
||||||
-- Update cursor position BEFORE deleteText so updates use correct position
|
-- Update cursor position BEFORE deleteText so updates use correct position
|
||||||
@@ -5206,11 +5178,11 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
local selectedText = self:getSelectedText()
|
local selectedText = self:getSelectedText()
|
||||||
if selectedText then
|
if selectedText then
|
||||||
love.system.setClipboardText(selectedText)
|
love.system.setClipboardText(selectedText)
|
||||||
|
|
||||||
-- Delete the selected text
|
-- Delete the selected text
|
||||||
local oldText = self._textBuffer
|
local oldText = self._textBuffer
|
||||||
self:deleteSelection()
|
self:deleteSelection()
|
||||||
|
|
||||||
-- Trigger onTextChange callback
|
-- Trigger onTextChange callback
|
||||||
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)
|
||||||
@@ -5224,15 +5196,15 @@ function Element:keypressed(key, scancode, isrepeat)
|
|||||||
local clipboardText = love.system.getClipboardText()
|
local clipboardText = love.system.getClipboardText()
|
||||||
if clipboardText and clipboardText ~= "" then
|
if clipboardText and clipboardText ~= "" then
|
||||||
local oldText = self._textBuffer
|
local oldText = self._textBuffer
|
||||||
|
|
||||||
-- Delete selection if exists
|
-- Delete selection if exists
|
||||||
if self:hasSelection() then
|
if self:hasSelection() then
|
||||||
self:deleteSelection()
|
self:deleteSelection()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Insert clipboard text
|
-- Insert clipboard text
|
||||||
self:insertText(clipboardText)
|
self:insertText(clipboardText)
|
||||||
|
|
||||||
-- Trigger onTextChange callback
|
-- Trigger onTextChange callback
|
||||||
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user