Simplify TextInput and fix few small bugs

This commit is contained in:
UnicornFreedom 2025-08-20 18:44:57 +02:00
parent 81840e4fab
commit 841733f6fe
No known key found for this signature in database
GPG Key ID: B4ED0DB6B940024F

View File

@ -52,9 +52,8 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
def text: String = chars.mkString def text: String = chars.mkString
def text_=(value: String): Unit = { def text_=(value: String): Unit = {
chars = value.toCharArray chars = value.toCharArray
cursorPos = 0 adjustCursor(0)
cursorOffset = 0 // note: we are not setting `textChanged = true` here to avoid potential recursion with `onInput` callback
textChanged = true
} }
protected var placeholder: Array[Char] = "".toCharArray protected var placeholder: Array[Char] = "".toCharArray
@ -96,7 +95,7 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
private def charsWidth(chars: Array[Char], first: Int, last: Int): Int = { private def charsWidth(chars: Array[Char], first: Int, last: Int): Int = {
var width = 0 var width = 0
for (index <- first to last) { for (index <- (first max 0) to (last min (chars.length - 1))) {
width += font().charWidth(chars(index)) width += font().charWidth(chars(index))
} }
width width
@ -117,53 +116,32 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
width += charWidth(chars(pos)) width += charWidth(chars(pos))
if (width < absoluteX) pos += 1 if (width < absoluteX) pos += 1
} }
cursorPos = chars.length.min(pos).max(0) adjustCursor(chars.length.min(pos).max(0))
cursorOffset = if (cursorPos > 0) charsWidth(chars, 0, (cursorPos - 1).max(0)) else 0
blinkTimer = 0
adjustScroll()
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_LEFT, _) if isFocused => case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_LEFT, _) if isFocused =>
if (cursorPos != 0) { if (cursorPos > 0) adjustCursor(cursorPos - 1)
cursorOffset -= charWidth(chars(cursorPos - 1))
cursorPos -= 1
blinkTimer = 0
adjustScroll()
}
event.consume() event.consume()
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_RIGHT, _) if isFocused => case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_RIGHT, _) if isFocused =>
if (cursorPos < chars.length) { if (cursorPos < chars.length) adjustCursor(cursorPos + 1)
cursorOffset += charWidth(chars(cursorPos))
cursorPos += 1
blinkTimer = 0
adjustScroll()
}
event.consume() event.consume()
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_HOME, _) if isFocused => case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_HOME, _) if isFocused =>
cursorPos = 0 adjustCursor(0)
cursorOffset = 0
blinkTimer = 0
scroll = 0
event.consume() event.consume()
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_END, _) if isFocused => case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_END, _) if isFocused =>
val textWidth = charsWidth(chars) adjustCursor(chars.length)
cursorPos = chars.length
cursorOffset = textWidth
blinkTimer = 0
scroll = (textWidth - size.width + 16).max(0)
event.consume() event.consume()
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_BACK, _) if isFocused => case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_BACK, _) if isFocused =>
val (lhs, rhs) = chars.splitAt(cursorPos) val (lhs, rhs) = chars.splitAt(cursorPos)
if (!lhs.isEmpty) { if (!lhs.isEmpty) {
val cw = charWidth(lhs.last)
chars = lhs.take(lhs.length - 1) ++ rhs chars = lhs.take(lhs.length - 1) ++ rhs
textChanged = true textChanged = true
cursorOffset -= cw adjustCursor(cursorPos - 1)
cursorPos -= 1 // if the text overflows - scroll it into visibility, it will feel nicer
blinkTimer = 0 val cw = charWidth(lhs.last)
scroll = (scroll - cw).max(0) scroll = (scroll - cw).max(0)
} }
event.consume() event.consume()
@ -182,18 +160,12 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
event.consume() event.consume()
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_INSERT, _) if isFocused => case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_INSERT, _) if isFocused =>
// TODO: insert the whole clipboard string at once instead of going char-by-char. writeString(UiHandler.clipboard)
for (char <- UiHandler.clipboard.toCharArray) {
writeChar(char)
}
event.consume() event.consume()
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_V, _) case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_V, _)
if isFocused && KeyEvents.isControlDown => if isFocused && KeyEvents.isControlDown =>
// TODO: insert the whole clipboard string at once instead of going char-by-char. writeString(UiHandler.clipboard)
for (char <- UiHandler.clipboard.toCharArray) {
writeChar(char)
}
event.consume() event.consume()
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_RETURN, _) if isFocused => case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_RETURN, _) if isFocused =>
@ -206,13 +178,26 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
} }
private def writeString(string: String): Unit = {
val (lhs, rhs) = chars.splitAt(cursorPos)
val array = string.toCharArray
chars = lhs ++ array ++ rhs
textChanged = true
adjustCursor(cursorPos + string.length)
}
private def writeChar(char: Char): Unit = { private def writeChar(char: Char): Unit = {
val (lhs, rhs) = chars.splitAt(cursorPos) val (lhs, rhs) = chars.splitAt(cursorPos)
chars = lhs ++ Array(char) ++ rhs chars = lhs ++ Array(char) ++ rhs
textChanged = true textChanged = true
cursorOffset += charWidth(chars(cursorPos)) adjustCursor(cursorPos + 1)
cursorPos += 1 }
private def adjustCursor(position: Int): Unit = {
cursorPos = position
cursorOffset = charsWidth(chars, 0, cursorPos - 1)
blinkTimer = 0 blinkTimer = 0
adjustScroll()
} }
private def adjustScroll(): Unit = { private def adjustScroll(): Unit = {