Skip to content

Commit a729522

Browse files
committed
Preserving cursor's x position when navigating vertically.
Navigating up on the first or down on the last line now does nothing.
1 parent 4c50e57 commit a729522

1 file changed

Lines changed: 48 additions & 38 deletions

File tree

InputField.lua

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,8 @@ local function pushHistory(field, group)
666666

667667
field.editHistoryIndex = i
668668
field.editHistoryGroup = group
669+
670+
field.navigationTargetX = nil
669671
end
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
688692
end
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()
833839
end
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.
837850
function InputField.moveCursor(field, steps, selSideAnchor)
838-
field:setCursor(field.cursorPosition+steps, selSideAnchor)
851+
setCursorPosition(field, field.cursorPosition+steps, selSideAnchor, true)
839852
end
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()
12601278
end
@@ -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

17871805
local 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
17981816
end
17991817
local 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
18021820
end
18031821
local 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
18061824
end
18071825

18081826
local 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
18191837
end
18201838
local 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
18231841
end
18241842
local 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
18271845
end
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
18361854
end
18371855
local 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
18441862
end
18451863
local function action_moveCursorDocumentStart(field, isRepeat)
1846-
field:setCursor(0)
1864+
setCursorPosition(field, 0, nil, true)
18471865
return true, false
18481866
end
18491867
local function action_moveCursorDocumentStartAnchored(field, isRepeat)
1850-
field:setCursor(0, field:getAnchorSelectionSide())
1868+
setCursorPosition(field, 0, field:getAnchorSelectionSide(), true)
18511869
return true, false
18521870
end
18531871

18541872
local 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
18581876
end
18591877
local 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
18631881
end
18641882
local function action_moveCursorDocumentEnd(field, isRepeat)
1865-
field:setCursor(field:getTextLength())
1883+
setCursorPosition(field, field:getTextLength(), nil, true)
18661884
return true, false
18671885
end
18681886
local function action_moveCursorDocumentEndAnchored(field, isRepeat)
1869-
field:setCursor(field:getTextLength(), field:getAnchorSelectionSide())
1887+
setCursorPosition(field, field:getTextLength(), field:getAnchorSelectionSide(), true)
18701888
return true, false
18711889
end
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
19171930
end
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
19351945
end
19361946

0 commit comments

Comments
 (0)