diff --git a/FlexLove.lua b/FlexLove.lua index 8ef014b..1fe5922 100644 --- a/FlexLove.lua +++ b/FlexLove.lua @@ -469,11 +469,8 @@ end --- Forward text input to focused element ---@param text string function Gui.textinput(text) - print("[Gui.textinput] Received text:", text, "Focused element:", Gui._focusedElement) if Gui._focusedElement then Gui._focusedElement:textinput(text) - else - print("[Gui.textinput] No focused element!") end end diff --git a/modules/Element.lua b/modules/Element.lua index ec8b0d8..14f3747 100644 --- a/modules/Element.lua +++ b/modules/Element.lua @@ -3223,6 +3223,12 @@ function Element:update(dt) self.callback(self, pressEvent) end self._pressed[button] = true + + -- Set mouse down position for text selection on left click + if button == 1 and self.editable then + self._mouseDownPosition = self:_mouseToTextPosition(mx, my) + self._textDragOccurred = false -- Reset drag flag on press + end end -- Record drag start position per button @@ -3320,7 +3326,13 @@ function Element:update(dt) self:focus() -- Handle text click for cursor positioning and word selection - self:_handleTextClick(mx, my, clickCount) + -- Only process click if no text drag occurred (to preserve drag selection) + if not self._textDragOccurred then + self:_handleTextClick(mx, my, clickCount) + end + + -- Reset drag flag after release + self._textDragOccurred = false elseif button == 1 then end @@ -4907,6 +4919,7 @@ function Element:_handleTextDrag(mouseX, mouseY) if currentPos ~= self._mouseDownPosition then self:setSelection(self._mouseDownPosition, currentPos) self._cursorPosition = currentPos + self._textDragOccurred = true -- Mark that a text drag occurred else self:clearSelection() end @@ -5009,7 +5022,7 @@ function Element:keypressed(key, scancode, isrepeat) end local modifiers = getModifiers() - local ctrl = modifiers.ctrl or modifiers.super -- Support both Ctrl and Cmd + local ctrl = modifiers.ctrl or modifiers.super -- Handle cursor movement with selection if key == "left" or key == "right" or key == "home" or key == "end" or key == "up" or key == "down" then diff --git a/testing/__tests__/33_input_field_tests.lua b/testing/__tests__/33_input_field_tests.lua index c87ba0b..af742fb 100644 --- a/testing/__tests__/33_input_field_tests.lua +++ b/testing/__tests__/33_input_field_tests.lua @@ -296,6 +296,154 @@ function TestInputField:testDeleteSelection() lu.assertEquals(testElement:getCursorPosition(), 0) end +function TestInputField:testMouseDownPositionSetOnUnfocusedElement() + -- Test that _mouseDownPosition is set on mouse press even when element is not focused + -- This is critical for text selection to work on the first click + + -- Ensure element is not focused + lu.assertFalse(testElement:isFocused()) + + -- Simulate mouse press at position (150, 120) inside the element + love.mouse.setPosition(150, 120) + love.mouse.setDown(1, true) + testElement:update(0.016) + + -- Verify that _mouseDownPosition was set + lu.assertNotNil(testElement._mouseDownPosition, + "_mouseDownPosition should be set on press even when unfocused") +end + +function TestInputField:testMouseDownPositionSetOnFocusedElement() + -- Test that _mouseDownPosition is set on mouse press when element is focused + + testElement:focus() + lu.assertTrue(testElement:isFocused()) + + -- Simulate mouse press at position (150, 120) + love.mouse.setPosition(150, 120) + love.mouse.setDown(1, true) + testElement:update(0.016) + + -- Verify that _mouseDownPosition was set + lu.assertNotNil(testElement._mouseDownPosition, + "_mouseDownPosition should be set on press when focused") +end + +function TestInputField:testMouseDownPositionNotSetOnNonEditableElement() + -- Test that _mouseDownPosition is NOT set for non-editable elements + + local nonEditableElement = FlexLove.Element.new({ + x = 100, + y = 100, + width = 200, + height = 40, + editable = false, + text = "Not Editable", + }) + + -- Simulate mouse press at position (150, 120) + love.mouse.setPosition(150, 120) + love.mouse.setDown(1, true) + nonEditableElement:update(0.016) + + -- Verify that _mouseDownPosition was NOT set + lu.assertNil(nonEditableElement._mouseDownPosition, + "_mouseDownPosition should not be set for non-editable elements") +end + +function TestInputField:testDragSelectionPreservedOnRelease() + -- Test that text selection created by dragging is preserved when releasing over the element + -- This is the fix for the bug where selections were dropped on mouse release + + testElement:focus() + + -- Simulate mouse press at position (110, 120) + love.mouse.setPosition(110, 120) + love.mouse.setDown(1, true) + testElement:update(0.016) + + -- Drag to another position to create a selection + love.mouse.setPosition(150, 120) + testElement:update(0.016) + + -- Verify selection exists after drag + lu.assertTrue(testElement:hasSelection(), "Selection should exist after drag") + + -- Release mouse while still over the element + love.mouse.setDown(1, false) + testElement:update(0.016) + + -- The key test: selection should STILL be there after release + lu.assertTrue(testElement:hasSelection(), + "Selection should be preserved after releasing mouse over element") +end + +function TestInputField:testClickWithoutDragClearsSelection() + -- Test that a click (press and release without drag) still clears selection as expected + + testElement:focus() + testElement:setSelection(0, 5) -- Select "Hello" + lu.assertTrue(testElement:hasSelection()) + + -- Click at a position (press and release without moving) + love.mouse.setPosition(120, 120) + love.mouse.setDown(1, true) + testElement:update(0.016) + + -- Release at same position (no drag occurred) + love.mouse.setDown(1, false) + testElement:update(0.016) + + -- Selection should be cleared since it was a click, not a drag + lu.assertFalse(testElement:hasSelection(), + "Selection should be cleared by click without drag") +end + +function TestInputField:testDragSelectionOnInitialClick() + -- Test that drag selection works even on the first interaction with an unfocused element + + local newElement = FlexLove.Element.new({ + x = 100, + y = 100, + width = 200, + height = 40, + editable = true, + text = "Test Text", + }) + + lu.assertFalse(newElement:isFocused(), "Element should start unfocused") + + -- First interaction: press (will focus on release) + love.mouse.setPosition(110, 120) + love.mouse.setDown(1, true) + newElement:update(0.016) + + -- Release to complete the focus + love.mouse.setDown(1, false) + newElement:update(0.016) + + lu.assertTrue(newElement:isFocused(), "Element should be focused after click") + + -- Now perform a drag selection on the focused element + love.mouse.setPosition(110, 120) + love.mouse.setDown(1, true) + newElement:update(0.016) + + -- Drag to create selection + love.mouse.setPosition(150, 120) + newElement:update(0.016) + + lu.assertTrue(newElement:hasSelection(), "Selection should exist after drag") + + -- Release over element + love.mouse.setDown(1, false) + newElement:update(0.016) + + -- Selection should be preserved + lu.assertTrue(newElement:hasSelection(), + "Selection should be preserved after drag release") +end + -- ==================== -- Text Input Tests -- ====================