Remember the desired cursor position when changing the text

This commit is contained in:
Fingercomp 2025-09-03 19:26:44 +03:00
parent d676a4a5bf
commit 052f61ad43
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
2 changed files with 36 additions and 19 deletions

View File

@ -11,7 +11,7 @@ import ocelot.desktop.ui.event.{DoubleClickEvent, DragEvent, KeyEvent, MouseEven
import ocelot.desktop.ui.widget.TextInput.{Cursor, Selection, Text} import ocelot.desktop.ui.widget.TextInput.{Cursor, Selection, Text}
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.ui.widget.traits.HoverAnimation import ocelot.desktop.ui.widget.traits.HoverAnimation
import ocelot.desktop.util.{DrawUtils, Register, Watcher} import ocelot.desktop.util.{BaseWatcher, DrawUtils, Register, Watcher}
import ocelot.desktop.util.animation.ColorAnimation import ocelot.desktop.util.animation.ColorAnimation
import org.lwjgl.input.Keyboard import org.lwjgl.input.Keyboard
@ -28,7 +28,7 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
// model // model
private val _text: Text = new Text(initialText.codePoints().toArray) private val _text: Text = new Text(initialText.codePoints().toArray)
private val cursor: Cursor = new Cursor() private val cursor: Cursor = new Cursor()
private val selectionWatcher = new Watcher[Option[Selection]](None) private val selectionWatcher = Watcher[Option[Selection]](None)
// updated after all events are processed so that event handlers can refer to the previous position. // updated after all events are processed so that event handlers can refer to the previous position.
private val prevCursorPosition = Register.sampling(cursor.position) private val prevCursorPosition = Register.sampling(cursor.position)
@ -47,13 +47,13 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
selectionWatcher.value = newValue selectionWatcher.value = newValue
} }
cursor.onChange(position => { cursor.onChange = { position =>
cursorOffset = charsWidth(_text.chars, 0, position) cursorOffset = charsWidth(_text.chars, 0, position)
blinkTimer = 0 blinkTimer = 0
adjustScroll() adjustScroll()
}) }
selectionWatcher.onChange(newValue => { selectionWatcher.onChange = { newValue =>
selectionOffsets = newValue.map { selectionOffsets = newValue.map {
case Selection.Ordered(start, end) => case Selection.Ordered(start, end) =>
( (
@ -61,7 +61,7 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
charsWidth(_text.chars, 0, end), charsWidth(_text.chars, 0, end),
) )
} }
}) }
private val foregroundAnimation = new ColorAnimation(targetForegroundColor, 7f) private val foregroundAnimation = new ColorAnimation(targetForegroundColor, 7f)
private val borderAnimation = new ColorAnimation(targetBorderColor, 7f) private val borderAnimation = new ColorAnimation(targetBorderColor, 7f)
@ -81,7 +81,10 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
def text_=(value: String): Unit = { def text_=(value: String): Unit = {
_text.chars = value.codePoints().toArray _text.chars = value.codePoints().toArray
selection = None selection = None
cursor.position = cursor.position max 0 min _text.chars.length
val desiredPosition = cursor.desiredPosition
cursor.position = desiredPosition max 0 min _text.chars.length
cursor.desiredPosition = desiredPosition
} }
private def selectedText: String = selection match { private def selectedText: String = selection match {
@ -448,14 +451,20 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
} }
object TextInput { object TextInput {
class Text(initialValue: Array[Int]) extends Watcher(initialValue) { class Text(initialValue: Array[Int]) extends BaseWatcher(initialValue) {
def chars: Array[Int] = value def chars: Array[Int] = value
def chars_=(newValue: Array[Int]): Unit = value = newValue def chars_=(newValue: Array[Int]): Unit = value = newValue
} }
class Cursor(initialValue: Int = 0) extends Watcher(initialValue) { class Cursor(initialValue: Int = 0) extends BaseWatcher(initialValue) {
var desiredPosition: Int = initialValue
def position: Int = value def position: Int = value
def position_=(newValue: Int): Unit = value = newValue
def position_=(newValue: Int): Unit = {
value = newValue
desiredPosition = newValue
}
} }
case class Selection(start: Int, end: Int) { case class Selection(start: Int, end: Int) {

View File

@ -1,24 +1,27 @@
package ocelot.desktop.util package ocelot.desktop.util
/** /**
* Keeps a reference to an object * Keeps a value and tracks its changes.
* and tells whether there were any changes to the value since the last check.
*/ */
class Watcher[T](initialValue: T) { abstract class BaseWatcher[T](initialValue: T) {
private var dirty = false private var dirty = false
private var _callback: Option[T => Unit] = None private var _callback: Option[T => Unit] = None
private var _value: T = initialValue private var _value: T = initialValue
def value: T = _value protected def value: T = _value
def value_=(newValue: T): Unit = { protected def value_=(newValue: T): Unit = {
dirty = _value != newValue dirty = _value != newValue
if (dirty) { if (dirty) {
_callback.foreach(_(newValue)) _callback.foreach(_(newValue))
} }
_value = newValue _value = newValue
} }
def onChange: Option[T => Unit] = _callback
def onChange(callback: T => Unit): Unit = _callback = Some(callback) def onChange_=(callback: T => Unit): Unit = {
_callback = Some(callback)
}
def changed(): Boolean = { def changed(): Boolean = {
if (dirty) { if (dirty) {
@ -29,5 +32,10 @@ class Watcher[T](initialValue: T) {
} }
object Watcher { object Watcher {
def apply[T](value: T) = new Watcher(value) def apply[T](initialValue: T) = new Watcher(initialValue)
class Watcher[T](initialValue: T) extends BaseWatcher(initialValue) {
override def value: T = super.value
override def value_=(newValue: T): Unit = super.value_=(newValue)
}
} }