Text input selection test
This commit is contained in:
parent
409c5bb443
commit
b62ac72bc5
@ -1,10 +1,16 @@
|
|||||||
package ru.dbotthepony.mc.otm.client.screen.panels
|
package ru.dbotthepony.mc.otm.client.screen.panels
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.GlStateManager
|
||||||
import com.mojang.blaze3d.platform.InputConstants
|
import com.mojang.blaze3d.platform.InputConstants
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem
|
||||||
|
import com.mojang.blaze3d.vertex.DefaultVertexFormat
|
||||||
import com.mojang.blaze3d.vertex.PoseStack
|
import com.mojang.blaze3d.vertex.PoseStack
|
||||||
|
import com.mojang.blaze3d.vertex.VertexFormat
|
||||||
import it.unimi.dsi.fastutil.chars.CharOpenHashSet
|
import it.unimi.dsi.fastutil.chars.CharOpenHashSet
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||||
import net.minecraft.client.gui.screens.Screen
|
import net.minecraft.client.gui.screens.Screen
|
||||||
|
import net.minecraft.client.renderer.GameRenderer
|
||||||
import ru.dbotthepony.mc.otm.client.isCtrlDown
|
import ru.dbotthepony.mc.otm.client.isCtrlDown
|
||||||
import ru.dbotthepony.mc.otm.client.isKeyDown
|
import ru.dbotthepony.mc.otm.client.isKeyDown
|
||||||
import ru.dbotthepony.mc.otm.client.isShiftDown
|
import ru.dbotthepony.mc.otm.client.isShiftDown
|
||||||
@ -12,6 +18,8 @@ import ru.dbotthepony.mc.otm.client.minecraft
|
|||||||
import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource
|
import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource
|
||||||
import ru.dbotthepony.mc.otm.client.render.TextAlign
|
import ru.dbotthepony.mc.otm.client.render.TextAlign
|
||||||
import ru.dbotthepony.mc.otm.client.render.drawAligned
|
import ru.dbotthepony.mc.otm.client.render.drawAligned
|
||||||
|
import ru.dbotthepony.mc.otm.client.render.tesselator
|
||||||
|
import ru.dbotthepony.mc.otm.core.addAll
|
||||||
import ru.dbotthepony.mc.otm.core.math.RGBAColor
|
import ru.dbotthepony.mc.otm.core.math.RGBAColor
|
||||||
import ru.dbotthepony.mc.otm.milliTime
|
import ru.dbotthepony.mc.otm.milliTime
|
||||||
|
|
||||||
@ -23,10 +31,39 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
width: Float = 10f,
|
width: Float = 10f,
|
||||||
height: Float = 10f,
|
height: Float = 10f,
|
||||||
) : EditablePanel<S>(screen, parent, x, y, width, height) {
|
) : EditablePanel<S>(screen, parent, x, y, width, height) {
|
||||||
data class TextSelection(val start: Int, val end: Int) {
|
private data class TextSelection(val start: Int, val end: Int) {
|
||||||
init {
|
val isInversed get() = start > end
|
||||||
require(start <= end) { "$start <= $end" }
|
val isNotEmpty get() = start != end
|
||||||
require(start >= 0) { "$start >= 0" }
|
|
||||||
|
val actualStart get() = if (isInversed) this.end else this.start
|
||||||
|
val actualEnd get() = if (isInversed) this.start else this.end
|
||||||
|
|
||||||
|
fun sub(line: String): Pair<String, String> {
|
||||||
|
val before = line.substring(0, actualStart.coerceIn(0, line.length))
|
||||||
|
val selected = line.substring(actualStart.coerceIn(0, line.length), actualEnd.coerceAtMost(line.length))
|
||||||
|
|
||||||
|
return before to selected
|
||||||
|
}
|
||||||
|
|
||||||
|
fun coversEntireString(value: String?): Boolean {
|
||||||
|
if (value == null)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return value.length <= actualEnd && actualStart <= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun coversEntireLine(value: String?): Boolean {
|
||||||
|
if (value == null)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return value.length < actualEnd && actualStart <= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun coversNewline(value: String?): Boolean {
|
||||||
|
if (value == null)
|
||||||
|
return actualEnd == Int.MAX_VALUE
|
||||||
|
|
||||||
|
return value.length < actualEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,10 +71,13 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
private val lines = ArrayList(this@TextInputPanel.lines) // ultra fast copy
|
private val lines = ArrayList(this@TextInputPanel.lines) // ultra fast copy
|
||||||
private val cursorLine = this@TextInputPanel.cursorLine
|
private val cursorLine = this@TextInputPanel.cursorLine
|
||||||
private val cursorCharacter = this@TextInputPanel.cursorCharacter
|
private val cursorCharacter = this@TextInputPanel.cursorCharacter
|
||||||
|
private val selections = Int2ObjectAVLTreeMap(this@TextInputPanel.selections)
|
||||||
|
|
||||||
fun apply() {
|
fun apply() {
|
||||||
this@TextInputPanel.lines.clear()
|
this@TextInputPanel.lines.clear()
|
||||||
this@TextInputPanel.lines.addAll(lines)
|
this@TextInputPanel.lines.addAll(lines)
|
||||||
|
this@TextInputPanel.selections.clear()
|
||||||
|
this@TextInputPanel.selections.putAll(selections)
|
||||||
this@TextInputPanel.cursorCharacter = cursorCharacter
|
this@TextInputPanel.cursorCharacter = cursorCharacter
|
||||||
this@TextInputPanel.cursorLine = cursorLine
|
this@TextInputPanel.cursorLine = cursorLine
|
||||||
}
|
}
|
||||||
@ -51,6 +91,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
if (lines != other.lines) return false
|
if (lines != other.lines) return false
|
||||||
if (cursorLine != other.cursorLine) return false
|
if (cursorLine != other.cursorLine) return false
|
||||||
if (cursorCharacter != other.cursorCharacter) return false
|
if (cursorCharacter != other.cursorCharacter) return false
|
||||||
|
if (selections != other.selections) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -73,7 +114,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
|
|
||||||
private var textCache: String? = null
|
private var textCache: String? = null
|
||||||
private val lines = ArrayList<String>()
|
private val lines = ArrayList<String>()
|
||||||
private val selections = Int2ObjectOpenHashMap<TextSelection>()
|
private val selections = Int2ObjectAVLTreeMap<TextSelection>()
|
||||||
private val undo = ArrayDeque<Snapshot>()
|
private val undo = ArrayDeque<Snapshot>()
|
||||||
private val redo = ArrayDeque<Snapshot>()
|
private val redo = ArrayDeque<Snapshot>()
|
||||||
|
|
||||||
@ -96,6 +137,11 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
undo.addLast(snapshot)
|
undo.addLast(snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun pushbackSnapshotIfNoTimer() {
|
||||||
|
if (snapshotTimer == null)
|
||||||
|
pushbackSnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
private fun recordHistory(hard: Boolean = false) {
|
private fun recordHistory(hard: Boolean = false) {
|
||||||
if (hard) {
|
if (hard) {
|
||||||
pushbackSnapshot()
|
pushbackSnapshot()
|
||||||
@ -173,6 +219,19 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
lines[index] = value
|
lines[index] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun selectionIndex(index: Int) = object : Int2ObjectMap.Entry<TextSelection> {
|
||||||
|
override fun setValue(newValue: TextSelection): TextSelection? {
|
||||||
|
return selections.put(index, newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIntKey(): Int {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
override val value: TextSelection
|
||||||
|
get() = selections[index] ?: throw NoSuchElementException()
|
||||||
|
}
|
||||||
|
|
||||||
fun insertLine(index: Int, value: String = "") {
|
fun insertLine(index: Int, value: String = "") {
|
||||||
lines.ensureCapacity(index)
|
lines.ensureCapacity(index)
|
||||||
|
|
||||||
@ -180,7 +239,8 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
lines.add("")
|
lines.add("")
|
||||||
}
|
}
|
||||||
|
|
||||||
val upperLines = selections.int2ObjectEntrySet().filter { it.intKey >= index }
|
val upperLines = ArrayList<Int2ObjectMap.Entry<TextSelection>>()
|
||||||
|
upperLines.addAll(selections.int2ObjectEntrySet().iterator(selectionIndex(index)))
|
||||||
|
|
||||||
for (entry in upperLines) {
|
for (entry in upperLines) {
|
||||||
selections.remove(entry.intKey)
|
selections.remove(entry.intKey)
|
||||||
@ -207,7 +267,9 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
|
|
||||||
lines.removeAt(index)
|
lines.removeAt(index)
|
||||||
selections.remove(index)
|
selections.remove(index)
|
||||||
val upperLines = selections.int2ObjectEntrySet().filter { it.intKey > index }
|
|
||||||
|
val upperLines = ArrayList<Int2ObjectMap.Entry<TextSelection>>()
|
||||||
|
upperLines.addAll(selections.int2ObjectEntrySet().iterator(selectionIndex(index)))
|
||||||
|
|
||||||
for (entry in upperLines) {
|
for (entry in upperLines) {
|
||||||
selections.remove(entry.intKey)
|
selections.remove(entry.intKey)
|
||||||
@ -220,7 +282,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveCursors(line: Int, character: Int, moveBy: Int) {
|
protected fun moveCursors(line: Int, character: Int, moveBy: Int) {
|
||||||
@Suppress("name_shadowing")
|
@Suppress("name_shadowing")
|
||||||
val moveBy = if (character + moveBy < 0) -character else moveBy
|
val moveBy = if (character + moveBy < 0) -character else moveBy
|
||||||
|
|
||||||
@ -252,9 +314,24 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun wipeSelection() {
|
protected fun wipeSelection() {
|
||||||
|
if (selections.isEmpty())
|
||||||
|
return
|
||||||
|
|
||||||
|
pushbackSnapshotIfNoTimer()
|
||||||
|
|
||||||
val inversed = ArrayList(selections.int2ObjectEntrySet())
|
val inversed = ArrayList(selections.int2ObjectEntrySet())
|
||||||
inversed.sortByDescending { it.intKey }
|
|
||||||
|
var downTo = inversed.size.ushr(1)
|
||||||
|
if (inversed.size and 1 == 1) downTo++
|
||||||
|
|
||||||
|
for (i in inversed.size - 1 downTo downTo) {
|
||||||
|
val i2 = inversed.size - i
|
||||||
|
val a = inversed[i]
|
||||||
|
val b = inversed[i2]
|
||||||
|
inversed[i] = b
|
||||||
|
inversed[i2] = a
|
||||||
|
}
|
||||||
|
|
||||||
for ((lineNumber, selection) in inversed) {
|
for ((lineNumber, selection) in inversed) {
|
||||||
if (selection.start != selection.end) {
|
if (selection.start != selection.end) {
|
||||||
@ -264,7 +341,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selections.clear()
|
// selections.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun textChanged(oldText: String, newText: String) {}
|
protected open fun textChanged(oldText: String, newText: String) {}
|
||||||
@ -294,18 +371,38 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
lines.addAll(value.split(NEWLINES))
|
lines.addAll(value.split(NEWLINES))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun advanceCursorLeft() {
|
data class CursorAdvanceResult(
|
||||||
|
val oldLine: Int,
|
||||||
|
val newLine: Int,
|
||||||
|
val oldCharacter: Int,
|
||||||
|
val newCharacter: Int,
|
||||||
|
val couldHaveChangedLine: Boolean
|
||||||
|
) {
|
||||||
|
val linesChanged get() = oldLine != newLine
|
||||||
|
val charsChanged get() = linesChanged || oldCharacter != newCharacter
|
||||||
|
val advancedChars get() = newCharacter - oldCharacter
|
||||||
|
}
|
||||||
|
|
||||||
|
fun advanceCursorLeft(greedy: Boolean = false): CursorAdvanceResult {
|
||||||
|
val oldLine = cursorLine
|
||||||
|
val oldChar = cursorCharacter
|
||||||
val line = this[cursorLine]
|
val line = this[cursorLine]
|
||||||
|
var couldHaveChangedLine = false
|
||||||
|
|
||||||
if (line != null && cursorCharacter > line.length) {
|
if (line != null && cursorCharacter > line.length) {
|
||||||
cursorCharacter = line.length - 1
|
cursorCharacter = line.length - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursorCharacter > 0) {
|
if (cursorCharacter > 0) {
|
||||||
|
if (greedy && line != null) {
|
||||||
|
cursorCharacter = greedyAdvanceLeft(line, cursorCharacter)
|
||||||
|
} else {
|
||||||
cursorCharacter--
|
cursorCharacter--
|
||||||
|
}
|
||||||
} else if (cursorLine > 0) {
|
} else if (cursorLine > 0) {
|
||||||
cursorLine--
|
cursorLine--
|
||||||
cursorCharacter = 0
|
cursorCharacter = 0
|
||||||
|
couldHaveChangedLine = true
|
||||||
|
|
||||||
@Suppress("name_shadowing")
|
@Suppress("name_shadowing")
|
||||||
val line = this[cursorLine]
|
val line = this[cursorLine]
|
||||||
@ -313,15 +410,29 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
if (line != null) {
|
if (line != null) {
|
||||||
cursorCharacter = line.length
|
cursorCharacter = line.length
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
couldHaveChangedLine = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun advanceCursorRight() {
|
return CursorAdvanceResult(oldLine, cursorLine, oldChar, cursorCharacter, couldHaveChangedLine = couldHaveChangedLine)
|
||||||
cursorCharacter++
|
}
|
||||||
|
|
||||||
|
fun advanceCursorRight(greedy: Boolean = false): CursorAdvanceResult {
|
||||||
|
val oldLine = cursorLine
|
||||||
|
val oldChar = cursorCharacter
|
||||||
|
var couldHaveChangedLine = false
|
||||||
|
|
||||||
val line = this[cursorLine]
|
val line = this[cursorLine]
|
||||||
|
|
||||||
|
if (greedy && line != null && cursorCharacter + 1 < line.length) {
|
||||||
|
cursorCharacter = greedyAdvanceRight(line, cursorCharacter)
|
||||||
|
} else {
|
||||||
|
cursorCharacter++
|
||||||
|
}
|
||||||
|
|
||||||
if (line != null && cursorCharacter > line.length) {
|
if (line != null && cursorCharacter > line.length) {
|
||||||
|
couldHaveChangedLine = true
|
||||||
|
|
||||||
if (lines.size <= cursorLine + 1) {
|
if (lines.size <= cursorLine + 1) {
|
||||||
cursorCharacter = line.length
|
cursorCharacter = line.length
|
||||||
} else {
|
} else {
|
||||||
@ -331,6 +442,8 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
} else if (line == null) {
|
} else if (line == null) {
|
||||||
cursorCharacter = 0
|
cursorCharacter = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return CursorAdvanceResult(oldLine, cursorLine, oldChar, cursorCharacter, couldHaveChangedLine = couldHaveChangedLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean {
|
override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean {
|
||||||
@ -394,7 +507,6 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
if (multiLine) {
|
if (multiLine) {
|
||||||
val lines = if (selections.isNotEmpty()) selections.keys else listOf(cursorLine)
|
val lines = if (selections.isNotEmpty()) selections.keys else listOf(cursorLine)
|
||||||
|
|
||||||
|
|
||||||
if (minecraft.window.isKeyDown(InputConstants.KEY_RSHIFT) || minecraft.window.isKeyDown(InputConstants.KEY_LSHIFT)) {
|
if (minecraft.window.isKeyDown(InputConstants.KEY_RSHIFT) || minecraft.window.isKeyDown(InputConstants.KEY_LSHIFT)) {
|
||||||
var hit = false
|
var hit = false
|
||||||
|
|
||||||
@ -429,36 +541,54 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key == InputConstants.KEY_LEFT) {
|
if (key == InputConstants.KEY_LEFT) {
|
||||||
if (minecraft.window.isCtrlDown) {
|
if (!minecraft.window.isShiftDown) {
|
||||||
val line = this[cursorLine]
|
advanceCursorLeft(minecraft.window.isCtrlDown)
|
||||||
|
selections.clear()
|
||||||
if (line == null || cursorCharacter <= 0) {
|
|
||||||
advanceCursorLeft()
|
|
||||||
} else {
|
} else {
|
||||||
if (cursorCharacter >= line.length)
|
if (this[cursorLine] == null)
|
||||||
cursorCharacter = line.length - 1
|
return true
|
||||||
|
|
||||||
cursorCharacter = greedyAdvanceLeft(line, cursorCharacter)
|
val existing = selections[cursorLine]
|
||||||
|
val result = advanceCursorLeft(minecraft.window.isCtrlDown)
|
||||||
|
|
||||||
|
if (!result.linesChanged)
|
||||||
|
selections[result.oldLine] = TextSelection(
|
||||||
|
existing?.start ?: result.oldCharacter,
|
||||||
|
result.newCharacter)
|
||||||
|
else {
|
||||||
|
this[result.newLine]?.let {
|
||||||
|
val existingNewline = selections[result.newLine]
|
||||||
|
|
||||||
|
if (existingNewline == null) {
|
||||||
|
selections[result.newLine] = TextSelection(
|
||||||
|
Int.MAX_VALUE,
|
||||||
|
it.length)
|
||||||
|
} else {
|
||||||
|
if (!existingNewline.isInversed) {
|
||||||
|
selections[result.newLine] = TextSelection(
|
||||||
|
existingNewline.start,
|
||||||
|
it.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
advanceCursorLeft()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} else if (key == InputConstants.KEY_RIGHT) {
|
} else if (key == InputConstants.KEY_RIGHT) {
|
||||||
if (minecraft.window.isCtrlDown) {
|
if (!minecraft.window.isShiftDown) {
|
||||||
val line = this[cursorLine]
|
advanceCursorRight(minecraft.window.isCtrlDown)
|
||||||
|
selections.clear()
|
||||||
if (line == null || cursorCharacter + 1 >= line.length) {
|
|
||||||
advanceCursorRight()
|
|
||||||
} else {
|
} else {
|
||||||
if (cursorCharacter < 0)
|
if (this[cursorLine] == null)
|
||||||
cursorCharacter = 0
|
return true
|
||||||
|
|
||||||
cursorCharacter = greedyAdvanceRight(line, cursorCharacter)
|
val existing = selections[cursorLine]
|
||||||
}
|
val result = advanceCursorRight(minecraft.window.isCtrlDown)
|
||||||
} else {
|
|
||||||
advanceCursorRight()
|
selections[result.oldLine] = TextSelection(
|
||||||
|
existing?.start ?: result.oldCharacter,
|
||||||
|
if (result.couldHaveChangedLine) Int.MAX_VALUE else result.newCharacter)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -467,23 +597,30 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
cursorLine--
|
cursorLine--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selections.clear()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} else if (key == InputConstants.KEY_DOWN) {
|
} else if (key == InputConstants.KEY_DOWN) {
|
||||||
if (cursorLine < lines.size - 1) {
|
if (cursorLine < lines.size - 1) {
|
||||||
cursorLine++
|
cursorLine++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selections.clear()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == InputConstants.KEY_BACKSPACE) {
|
if (key == InputConstants.KEY_BACKSPACE) {
|
||||||
if (cursorLine <= 0 && cursorCharacter <= 0) {
|
wipeSelection()
|
||||||
return true
|
|
||||||
} else if (cursorCharacter <= 0) {
|
|
||||||
val line = this[cursorLine]
|
val line = this[cursorLine]
|
||||||
|
|
||||||
|
if (cursorLine <= 0 && cursorCharacter <= 0) {
|
||||||
|
return true
|
||||||
|
} else if (cursorCharacter <= 0 || line?.length == 0) {
|
||||||
if (line == null) {
|
if (line == null) {
|
||||||
cursorCharacter = this[--cursorLine]?.length ?: 0
|
cursorCharacter = this[--cursorLine]?.length ?: 0
|
||||||
|
recordHistory()
|
||||||
} else {
|
} else {
|
||||||
removeLine(cursorLine)
|
removeLine(cursorLine)
|
||||||
val newLine = this[cursorLine]
|
val newLine = this[cursorLine]
|
||||||
@ -497,25 +634,33 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
recordHistory()
|
recordHistory()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val line = this[cursorLine]
|
|
||||||
|
|
||||||
if (line != null) {
|
if (line != null) {
|
||||||
var cursorCharacter = cursorCharacter
|
pushbackSnapshotIfNoTimer()
|
||||||
|
|
||||||
if (cursorCharacter >= line.length)
|
|
||||||
cursorCharacter = line.length - 1
|
|
||||||
|
|
||||||
|
// remove from very end
|
||||||
|
if (cursorCharacter >= line.length) {
|
||||||
|
val newLine = line.substring(0, line.length - 1)
|
||||||
|
moveCursors(cursorLine, cursorCharacter, -1)
|
||||||
|
this[cursorLine] = newLine
|
||||||
|
recordHistory()
|
||||||
|
} else {
|
||||||
val newLine = line.substring(0, cursorCharacter - 1) + line.substring(cursorCharacter)
|
val newLine = line.substring(0, cursorCharacter - 1) + line.substring(cursorCharacter)
|
||||||
moveCursors(cursorLine, cursorCharacter, -1)
|
moveCursors(cursorLine, cursorCharacter, -1)
|
||||||
this[cursorLine] = newLine
|
this[cursorLine] = newLine
|
||||||
recordHistory()
|
recordHistory()
|
||||||
}
|
}
|
||||||
|
} else if (cursorLine > 0) {
|
||||||
|
cursorLine--
|
||||||
|
recordHistory()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == InputConstants.KEY_DELETE) {
|
if (key == InputConstants.KEY_DELETE) {
|
||||||
|
wipeSelection()
|
||||||
|
|
||||||
if (cursorLine !in 0 until lines.size) {
|
if (cursorLine !in 0 until lines.size) {
|
||||||
cursorLine = lines.size - 1
|
cursorLine = lines.size - 1
|
||||||
return true
|
return true
|
||||||
@ -540,8 +685,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshotTimer == null)
|
pushbackSnapshotIfNoTimer()
|
||||||
pushbackSnapshot()
|
|
||||||
|
|
||||||
val bottomLine = this[cursorLine + 1]!!
|
val bottomLine = this[cursorLine + 1]!!
|
||||||
cursorCharacter = line.length
|
cursorCharacter = line.length
|
||||||
@ -550,8 +694,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
|
|
||||||
recordHistory()
|
recordHistory()
|
||||||
} else {
|
} else {
|
||||||
if (snapshotTimer == null)
|
pushbackSnapshotIfNoTimer()
|
||||||
pushbackSnapshot()
|
|
||||||
|
|
||||||
val cursorCharacter = cursorCharacter
|
val cursorCharacter = cursorCharacter
|
||||||
moveCursors(cursorLine, cursorCharacter, -1)
|
moveCursors(cursorLine, cursorCharacter, -1)
|
||||||
@ -572,10 +715,19 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
redo()
|
redo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key == InputConstants.KEY_A && minecraft.window.isCtrlDown) {
|
||||||
|
selections.clear()
|
||||||
|
|
||||||
|
for ((i, line) in lines.withIndex()) {
|
||||||
|
selections[i] = TextSelection(0, Int.MAX_VALUE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun charTypedInternal(codepoint: Char, mods: Int): Boolean {
|
override fun charTypedInternal(codepoint: Char, mods: Int): Boolean {
|
||||||
|
wipeSelection()
|
||||||
var line = this[cursorLine]
|
var line = this[cursorLine]
|
||||||
|
|
||||||
if (line == null) {
|
if (line == null) {
|
||||||
@ -589,8 +741,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
else
|
else
|
||||||
line = line.substring(0, cursorCharacter) + codepoint + line.substring(cursorCharacter)
|
line = line.substring(0, cursorCharacter) + codepoint + line.substring(cursorCharacter)
|
||||||
|
|
||||||
if (snapshotTimer == null)
|
pushbackSnapshotIfNoTimer()
|
||||||
pushbackSnapshot()
|
|
||||||
|
|
||||||
set(cursorLine, line)
|
set(cursorLine, line)
|
||||||
moveCursors(cursorLine, cursorCharacter, 1)
|
moveCursors(cursorLine, cursorCharacter, 1)
|
||||||
@ -602,7 +753,9 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
|
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
|
||||||
var y = 0f
|
var y = 0f
|
||||||
|
|
||||||
for (line in lines) {
|
for ((i, line) in lines.withIndex()) {
|
||||||
|
val selection = selections[i]
|
||||||
|
|
||||||
font.drawAligned(
|
font.drawAligned(
|
||||||
poseStack = stack,
|
poseStack = stack,
|
||||||
buffer = BUFFER,
|
buffer = BUFFER,
|
||||||
@ -613,6 +766,42 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
color = textColor
|
color = textColor
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (selection != null && (selection.isNotEmpty || selection.coversEntireLine(line))) {
|
||||||
|
val (before, selected) = selection.sub(line)
|
||||||
|
|
||||||
|
var x = 0f
|
||||||
|
|
||||||
|
if (before.isNotEmpty()) {
|
||||||
|
x = font.width(before).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
val width = if (selection.coversNewline(line)) this.width - x else font.width(selected).toFloat()
|
||||||
|
|
||||||
|
RenderSystem.setShader(GameRenderer::getPositionShader)
|
||||||
|
RenderSystem.setShaderColor(0.0f, 0.0f, 1.0f, 1.0f)
|
||||||
|
RenderSystem.disableTexture()
|
||||||
|
RenderSystem.enableColorLogicOp()
|
||||||
|
RenderSystem.logicOp(GlStateManager.LogicOp.OR_REVERSE)
|
||||||
|
RenderSystem.disableDepthTest()
|
||||||
|
RenderSystem.defaultBlendFunc()
|
||||||
|
|
||||||
|
val builder = tesselator.builder
|
||||||
|
|
||||||
|
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
|
||||||
|
|
||||||
|
builder.vertex(stack.last().pose(), x, y + font.lineHeight + 2f, 0f).endVertex()
|
||||||
|
builder.vertex(stack.last().pose(), x + width, y + font.lineHeight + 2f, 0f).endVertex()
|
||||||
|
builder.vertex(stack.last().pose(), x + width, y, 0f).endVertex()
|
||||||
|
builder.vertex(stack.last().pose(), x, y, 0f).endVertex()
|
||||||
|
|
||||||
|
tesselator.end()
|
||||||
|
|
||||||
|
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
|
||||||
|
RenderSystem.disableColorLogicOp()
|
||||||
|
RenderSystem.enableTexture()
|
||||||
|
RenderSystem.enableDepthTest()
|
||||||
|
}
|
||||||
|
|
||||||
y += font.lineHeight + 2f
|
y += font.lineHeight + 2f
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -678,6 +867,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
|
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
|
||||||
|
selections.clear()
|
||||||
requestFocus()
|
requestFocus()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -745,7 +935,7 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
|
|
||||||
private fun greedyAdvanceLeft(input: String, position: Int): Int {
|
private fun greedyAdvanceLeft(input: String, position: Int): Int {
|
||||||
if (position <= 1)
|
if (position <= 1)
|
||||||
return 0
|
return -1
|
||||||
|
|
||||||
@Suppress("name_shadowing")
|
@Suppress("name_shadowing")
|
||||||
var position = position
|
var position = position
|
||||||
@ -768,6 +958,12 @@ open class TextInputPanel<out S : Screen>(
|
|||||||
private fun greedyAdvanceRight(input: String, position: Int): Int {
|
private fun greedyAdvanceRight(input: String, position: Int): Int {
|
||||||
@Suppress("name_shadowing")
|
@Suppress("name_shadowing")
|
||||||
var position = position
|
var position = position
|
||||||
|
|
||||||
|
if (position < 0)
|
||||||
|
position = 0
|
||||||
|
else if (position >= input.length)
|
||||||
|
return position + 1
|
||||||
|
|
||||||
var type = CharType[input[position]]
|
var type = CharType[input[position]]
|
||||||
|
|
||||||
while (position < input.length) {
|
while (position < input.length) {
|
||||||
|
Loading…
Reference in New Issue
Block a user