@@ -666,6 +666,8 @@ local function pushHistory(field, group)
666666
667667 field .editHistoryIndex = i
668668 field .editHistoryGroup = group
669+
670+ field .navigationTargetX = nil
669671end
670672
671673
@@ -685,6 +687,8 @@ local function applyHistoryState(field, offset)
685687 field .cursorPosition = state .cursorPosition
686688 field .selectionStart = state .selectionStart
687689 field .selectionEnd = state .selectionEnd
690+
691+ field .navigationTargetX = nil
688692end
689693
690694-- @UX: Improve how the cursor and selection are restored on undo.
@@ -772,6 +776,8 @@ local function newInputField(text, fieldType)
772776
773777 alignment = " left" ,
774778
779+ navigationTargetX = nil , -- Used when navigating vertically. Nil means need recalculation.
780+
775781 -- These are updated by updateWrap():
776782 lastWrappedText = " " ,
777783 wrappedText = {" " }, -- []line
@@ -832,10 +838,17 @@ function InputField.setCursor(field, pos, selSideAnchor)
832838 field :scrollToCursor ()
833839end
834840
841+ local function setCursorPosition (field , pos , selSideAnchor , eraseNavTargetX )
842+ field :setCursor (pos , selSideAnchor )
843+ if eraseNavTargetX then
844+ field .navigationTargetX = nil
845+ end
846+ end
847+
835848-- field:moveCursor( steps [, selectionSideToAnchor:SelectionSide=none ] )
836849-- 'steps' may be positive or negative.
837850function InputField .moveCursor (field , steps , selSideAnchor )
838- field : setCursor (field .cursorPosition + steps , selSideAnchor )
851+ setCursorPosition (field , field .cursorPosition + steps , selSideAnchor , true )
839852end
840853
841854-- side:SelectionSide = field:getCursorSelectionSide( )
@@ -1255,6 +1268,11 @@ function InputField.reset(field, text)
12551268 field .selectionStart = 0
12561269 field .selectionEnd = 0
12571270
1271+ field .clickCount = 1
1272+ field .multiClickExpirationTime = 0
1273+
1274+ field .navigationTargetX = nil
1275+
12581276 field :setText (text == nil and " " or text )
12591277 field :clearHistory ()
12601278end
@@ -1685,7 +1703,7 @@ function InputField.mousepressed(field, mx, my, mbutton, pressCount)
16851703 field .dragLastY = my
16861704
16871705 else
1688- field : setCursor ( pos )
1706+ setCursorPosition ( field , pos , nil , true )
16891707
16901708 field .dragMode = " character"
16911709 field .dragStartPosition1 = pos
@@ -1786,7 +1804,7 @@ end
17861804
17871805local function action_moveCursorCharacterLeft (field , isRepeat )
17881806 if field .selectionStart ~= field .selectionEnd then
1789- field : setCursor (field .selectionStart )
1807+ setCursorPosition (field , field .selectionStart , nil , true )
17901808 else
17911809 field :moveCursor (- 1 )
17921810 end
@@ -1797,17 +1815,17 @@ local function action_moveCursorCharacterLeftAnchored(field, isRepeat)
17971815 return true , false
17981816end
17991817local function action_moveCursorWordLeft (field , isRepeat )
1800- field : setCursor ( getNextWordBound (field :getVisibleText (), field .cursorPosition , - 1 ))
1818+ setCursorPosition ( field , getNextWordBound (field :getVisibleText (), field .cursorPosition , - 1 ), nil , true )
18011819 return true , false
18021820end
18031821local function action_moveCursorWordLeftAnchored (field , isRepeat )
1804- field : setCursor ( getNextWordBound (field :getVisibleText (), field .cursorPosition , - 1 ), field :getAnchorSelectionSide ())
1822+ setCursorPosition ( field , getNextWordBound (field :getVisibleText (), field .cursorPosition , - 1 ), field :getAnchorSelectionSide (), true )
18051823 return true , false
18061824end
18071825
18081826local function action_moveCursorCharacterRight (field , isRepeat )
18091827 if field .selectionStart ~= field .selectionEnd then
1810- field : setCursor (field .selectionEnd )
1828+ setCursorPosition (field , field .selectionEnd , nil , true )
18111829 else
18121830 field :moveCursor (1 )
18131831 end
@@ -1818,11 +1836,11 @@ local function action_moveCursorCharacterRightAnchored(field, isRepeat)
18181836 return true , false
18191837end
18201838local function action_moveCursorWordRight (field , isRepeat )
1821- field : setCursor ( getNextWordBound (field :getVisibleText (), field .cursorPosition , 1 ))
1839+ setCursorPosition ( field , getNextWordBound (field :getVisibleText (), field .cursorPosition , 1 ), nil , true )
18221840 return true , false
18231841end
18241842local function action_moveCursorWordRightAnchored (field , isRepeat )
1825- field : setCursor ( getNextWordBound (field :getVisibleText (), field .cursorPosition , 1 ), field :getAnchorSelectionSide ())
1843+ setCursorPosition ( field , getNextWordBound (field :getVisibleText (), field .cursorPosition , 1 ), field :getAnchorSelectionSide (), true )
18261844 return true , false
18271845end
18281846
@@ -1831,61 +1849,66 @@ local function action_moveCursorLineStart(field, isRepeat)
18311849 if field .cursorPosition == linePos1 - 1 and field .softBreak [lineI - 1 ] then
18321850 line , posOnLine , lineI , linePos1 , linePos2 = getLineInfoAtPosition (field , field .cursorPosition - 1 )
18331851 end
1834- field : setCursor ( linePos1 - 1 )
1852+ setCursorPosition ( field , linePos1 - 1 , nil , true )
18351853 return true , false
18361854end
18371855local function action_moveCursorLineStartAnchored (field , isRepeat )
18381856 local line , posOnLine , lineI , linePos1 , linePos2 = getLineInfoAtPosition (field , field .cursorPosition )
18391857 if field .cursorPosition == linePos1 - 1 and field .softBreak [lineI - 1 ] then
18401858 line , posOnLine , lineI , linePos1 , linePos2 = getLineInfoAtPosition (field , field .cursorPosition - 1 )
18411859 end
1842- field : setCursor ( linePos1 - 1 , field :getAnchorSelectionSide ())
1860+ setCursorPosition ( field , linePos1 - 1 , field :getAnchorSelectionSide (), true )
18431861 return true , false
18441862end
18451863local function action_moveCursorDocumentStart (field , isRepeat )
1846- field : setCursor ( 0 )
1864+ setCursorPosition ( field , 0 , nil , true )
18471865 return true , false
18481866end
18491867local function action_moveCursorDocumentStartAnchored (field , isRepeat )
1850- field : setCursor ( 0 , field :getAnchorSelectionSide ())
1868+ setCursorPosition ( field , 0 , field :getAnchorSelectionSide (), true )
18511869 return true , false
18521870end
18531871
18541872local function action_moveCursorLineEnd (field , isRepeat )
18551873 local line , posOnLine , lineI , linePos1 , linePos2 = getLineInfoAtPosition (field , field .cursorPosition )
1856- field : setCursor ( linePos2 )
1874+ setCursorPosition ( field , linePos2 , nil , true )
18571875 return true , false
18581876end
18591877local function action_moveCursorLineEndAnchored (field , isRepeat )
18601878 local line , posOnLine , lineI , linePos1 , linePos2 = getLineInfoAtPosition (field , field .cursorPosition )
1861- field : setCursor ( linePos2 , field :getAnchorSelectionSide ())
1879+ setCursorPosition ( field , linePos2 , field :getAnchorSelectionSide (), true )
18621880 return true , false
18631881end
18641882local function action_moveCursorDocumentEnd (field , isRepeat )
1865- field : setCursor (field :getTextLength ())
1883+ setCursorPosition (field , field :getTextLength (), nil , true )
18661884 return true , false
18671885end
18681886local function action_moveCursorDocumentEndAnchored (field , isRepeat )
1869- field : setCursor (field :getTextLength (), field :getAnchorSelectionSide ())
1887+ setCursorPosition (field , field :getTextLength (), field :getAnchorSelectionSide (), true )
18701888 return true , false
18711889end
18721890
1873- local function navigateByLine (field , dirY , anchor )
1891+ local function navigateByLine (field , dirY , doAnchor )
18741892 if not field :isMultiline () then return false end
18751893
1876- local anchorSide = (anchor and field :getAnchorSelectionSide () or nil )
1894+ local anchorSide = (doAnchor and field :getAnchorSelectionSide () or nil )
18771895
18781896 -- Get info about the current line.
18791897 local oldLine , oldPosOnLine , oldLineI , oldLinePos1 , oldLinePos2 = getLineInfoAtPosition (field , field .cursorPosition )
18801898
18811899 if dirY < 0 and oldLineI == 1 then
1882- field : setCursor ( 0 , anchorSide )
1900+ -- setCursorPosition(field, 0, anchorSide, false) -- Eh, not sure it's good to navigate horizontally during vertical navigation.
18831901 return true
18841902 elseif dirY > 0 and oldLineI >= # field .wrappedText then
1885- field : setCursor ( utf8.len (field .text ), anchorSide )
1903+ -- setCursorPosition(field, utf8.len(field.text), anchorSide, false )
18861904 return true
18871905 end
18881906
1907+ if not field .navigationTargetX then
1908+ local linePart = oldLine :sub (1 , utf8GetEndOffset (oldLine , oldPosOnLine ))
1909+ field .navigationTargetX = alignOnLine (field , oldLine , field .font :getWidth (linePart ))
1910+ end
1911+
18891912 -- Get info about the target line.
18901913 local newLine , newPosOnLine , newLineI , newLinePos1 , newLinePos2
18911914 if dirY < 0 then
@@ -1894,43 +1917,30 @@ local function navigateByLine(field, dirY, anchor)
18941917 newLine , newPosOnLine , newLineI , newLinePos1 , newLinePos2 = getLineInfoAtPosition (field , oldLinePos2 + 1 )
18951918 end
18961919
1897- -- Avoid some work if we're at the start of a line.
1898- -- @Incomplete: Handle alignment~="left".
1899- if field .alignment == " left" and (oldPosOnLine == 0 or newLine == " " ) then
1900- field :setCursor (newLinePos1 - 1 , anchorSide )
1901- return true
1902- end
1903-
1904- local linePart = oldLine :sub (1 , utf8GetEndOffset (oldLine , oldPosOnLine ))
1905- local targetX = alignOnLine (field , oldLine , field .font :getWidth (linePart ))
1906-
1907- local posOnLine = getCursorPositionAtX (field , newLine , targetX )
1920+ local posOnLine = getCursorPositionAtX (field , newLine , field .navigationTargetX )
19081921
19091922 -- Going from the end of a long line to the end of a short soft-wrapped line would put
19101923 -- the cursor at the start of the previous long line or two lines down. No good!
1911- if posOnLine == utf8.len (newLine ) and field . softBreak [ newLineI ] then
1924+ if field . softBreak [ newLineI ] and posOnLine == utf8.len (newLine ) then
19121925 posOnLine = posOnLine - 1
19131926 end
19141927
1915- field : setCursor ( newLinePos1 + posOnLine - 1 , anchorSide )
1928+ setCursorPosition ( field , newLinePos1 + posOnLine - 1 , anchorSide , false )
19161929 return true
19171930end
19181931
1919- local function navigateByPage (field , dirY , anchor )
1932+ local function navigateByPage (field , dirY , doAnchor )
19201933 local fontH = field .font :getHeight ()
19211934 local lineDist = math.ceil (fontH * field .font :getLineHeight ())
19221935 local walkCount = math.max (math.floor (field .height / lineDist ), 1 )
19231936 local anyHandled = false
19241937
19251938 for i = 1 , walkCount do
1926- local handled , _targetX = navigateByLine (field , dirY , anchor ) -- @Speed @Memory
1939+ local handled , _targetX = navigateByLine (field , dirY , doAnchor ) -- @Speed @Memory
19271940 if not handled then break end
19281941 anyHandled = true
19291942 end
19301943
1931- -- @Incomplete @UX: Preserve x better (including when navigating by line).
1932- if anyHandled then field :keypressed (" home" , false ) end -- @Temp so the behavior is a bit more consistent.
1933-
19341944 return anyHandled
19351945end
19361946
0 commit comments