Text input panel test
This commit is contained in:
parent
7dffa61bae
commit
472cb057a5
@ -15,7 +15,7 @@ import ru.dbotthepony.mc.otm.registry.MBlockEntities
|
||||
import ru.dbotthepony.mc.otm.registry.MBlocks
|
||||
|
||||
class HoloSignBlockEntity(blockPos: BlockPos, blockState: BlockState) : SynchronizedBlockEntity(MBlockEntities.HOLO_SIGN, blockPos, blockState), MenuProvider {
|
||||
var text by synchronizer.string("АМОГУС", name = "text")
|
||||
var text by synchronizer.string("АМОГУС\nБОНУС\nИИСУС\nПЛЮС", name = "text")
|
||||
|
||||
override fun createMenu(p_39954_: Int, p_39955_: Inventory, p_39956_: Player): AbstractContainerMenu {
|
||||
return HoloSignMenu(p_39954_, p_39955_, this)
|
||||
|
@ -1,5 +1,7 @@
|
||||
package ru.dbotthepony.mc.otm.client
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import com.mojang.blaze3d.platform.Window
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.Font
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
@ -13,6 +15,12 @@ import java.nio.DoubleBuffer
|
||||
inline val minecraft: Minecraft get() = Minecraft.getInstance()
|
||||
inline val font: Font get() = minecraft.font
|
||||
|
||||
fun Window.isKeyDown(key: Int) = InputConstants.isKeyDown(window, key)
|
||||
|
||||
val Window.isShiftDown get() = isKeyDown(InputConstants.KEY_LSHIFT) || isKeyDown(InputConstants.KEY_RSHIFT)
|
||||
val Window.isCtrlDown get() = isKeyDown(InputConstants.KEY_RCONTROL) || isKeyDown(InputConstants.KEY_LCONTROL)
|
||||
|
||||
|
||||
fun playGuiClickSound() {
|
||||
minecraft.soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0f))
|
||||
}
|
||||
|
@ -25,13 +25,21 @@ class HoloSignRenderer(private val context: BlockEntityRendererProvider.Context)
|
||||
p_112312_: Int
|
||||
) {
|
||||
poseStack.pushPose()
|
||||
poseStack.rotateWithBlockFacing(tile.blockState[RotatableMatteryBlock.FACING_FULL], clarifyingAxis = Direction.SOUTH)
|
||||
poseStack.translate(0.0f, 0.0f, -0.1f)
|
||||
poseStack.rotateWithBlockFacing(tile.blockState[RotatableMatteryBlock.FACING_FULL])
|
||||
poseStack.translate(0.5f, 0.5f, -0.05f)
|
||||
poseStack.scale(0.01f, 0.01f, 0.01f)
|
||||
|
||||
val sorse = DynamicBufferSource.WORLD
|
||||
|
||||
font.drawAligned(poseStack = poseStack, buffer = sorse, text = tile.text, align = TextAlign.CENTER_CENTER, x = 0f, y = 0f, color = RGBAColor.BLUE.toInt())
|
||||
val lines = tile.text.split('\n')
|
||||
val totalHeight = lines.size * font.lineHeight + (lines.size - 1) * 2f
|
||||
var y = -totalHeight / 2f
|
||||
|
||||
for (line in lines) {
|
||||
font.drawAligned(poseStack = poseStack, buffer = sorse, text = line, align = TextAlign.TOP_CENTER, x = 0f, y = y, color = RGBAColor.BLUE.toInt())
|
||||
y += font.lineHeight + 2f
|
||||
}
|
||||
|
||||
poseStack.popPose()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package ru.dbotthepony.mc.otm.client.screen
|
||||
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.world.entity.player.Inventory
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.TextInputPanel
|
||||
import ru.dbotthepony.mc.otm.menu.HoloSignMenu
|
||||
|
||||
class HoloSignScreen(menu: HoloSignMenu, inventory: Inventory, title: Component) : MatteryScreen<HoloSignMenu>(menu, title) {
|
||||
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
|
||||
val frame = FramePanel(this, null, 0f, 0f, 200f, 200f, getTitle())
|
||||
|
||||
val textbox = TextInputPanel(this, frame)
|
||||
textbox.dock = Dock.FILL
|
||||
textbox.text = "МОГУС\nБОНУС\n\nСУС"
|
||||
textbox.multiLine = true
|
||||
|
||||
return frame
|
||||
}
|
||||
}
|
@ -1251,7 +1251,7 @@ open class EditablePanel<out S : Screen> @JvmOverloads constructor(
|
||||
return list
|
||||
}
|
||||
|
||||
operator fun get(index: Int): EditablePanel<*>? {
|
||||
fun getChildren(index: Int): EditablePanel<*>? {
|
||||
if (index < 0 || index >= childrenInternal.size) return null
|
||||
return childrenInternal[index]
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package ru.dbotthepony.mc.otm.client.screen.panels
|
||||
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
|
||||
|
||||
open class GridPanel<out S : Screen> @JvmOverloads constructor(
|
||||
screen: S,
|
||||
@ -23,7 +22,7 @@ open class GridPanel<out S : Screen> @JvmOverloads constructor(
|
||||
var column = 0
|
||||
|
||||
while (column < columns) {
|
||||
val child = get(index) ?: break
|
||||
val child = getChildren(index) ?: break
|
||||
|
||||
if (child.visible && child.dock === Dock.NONE) {
|
||||
lineY = lineY.coerceAtLeast(child.height + child.dockMargin.top + child.dockMargin.bottom)
|
||||
@ -41,7 +40,7 @@ open class GridPanel<out S : Screen> @JvmOverloads constructor(
|
||||
currentX = 0f
|
||||
lineY = 0f
|
||||
|
||||
get(index) ?: break
|
||||
getChildren(index) ?: break
|
||||
}
|
||||
|
||||
super.performLayout()
|
||||
|
@ -0,0 +1,787 @@
|
||||
package ru.dbotthepony.mc.otm.client.screen.panels
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import it.unimi.dsi.fastutil.chars.CharOpenHashSet
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import ru.dbotthepony.mc.otm.client.isCtrlDown
|
||||
import ru.dbotthepony.mc.otm.client.isKeyDown
|
||||
import ru.dbotthepony.mc.otm.client.isShiftDown
|
||||
import ru.dbotthepony.mc.otm.client.minecraft
|
||||
import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource
|
||||
import ru.dbotthepony.mc.otm.client.render.TextAlign
|
||||
import ru.dbotthepony.mc.otm.client.render.drawAligned
|
||||
import ru.dbotthepony.mc.otm.core.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.milliTime
|
||||
|
||||
open class TextInputPanel<out S : Screen>(
|
||||
screen: S,
|
||||
parent: EditablePanel<*>?,
|
||||
x: Float = 0f,
|
||||
y: Float = 0f,
|
||||
width: Float = 10f,
|
||||
height: Float = 10f,
|
||||
) : EditablePanel<S>(screen, parent, x, y, width, height) {
|
||||
data class TextSelection(val start: Int, val end: Int) {
|
||||
init {
|
||||
require(start <= end) { "$start <= $end" }
|
||||
require(start >= 0) { "$start >= 0" }
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Snapshot {
|
||||
private val lines = ArrayList(this@TextInputPanel.lines) // ultra fast copy
|
||||
private val cursorLine = this@TextInputPanel.cursorLine
|
||||
private val cursorCharacter = this@TextInputPanel.cursorCharacter
|
||||
|
||||
fun apply() {
|
||||
this@TextInputPanel.lines.clear()
|
||||
this@TextInputPanel.lines.addAll(lines)
|
||||
this@TextInputPanel.cursorCharacter = cursorCharacter
|
||||
this@TextInputPanel.cursorLine = cursorLine
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as TextInputPanel<*>.Snapshot
|
||||
|
||||
if (lines != other.lines) return false
|
||||
if (cursorLine != other.cursorLine) return false
|
||||
if (cursorCharacter != other.cursorCharacter) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = lines.hashCode()
|
||||
result = 31 * result + cursorLine
|
||||
result = 31 * result + cursorCharacter
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
protected var ignoreUpdates = false
|
||||
var debug = true
|
||||
var multiLine = false
|
||||
var cursorLine = 0
|
||||
var cursorCharacter = 0
|
||||
var textColor = RGBAColor.WHITE
|
||||
var cursorColor = RGBAColor.BLACK
|
||||
|
||||
private var textCache: String? = null
|
||||
private val lines = ArrayList<String>()
|
||||
private val selections = Int2ObjectOpenHashMap<TextSelection>()
|
||||
private val undo = ArrayDeque<Snapshot>()
|
||||
private val redo = ArrayDeque<Snapshot>()
|
||||
|
||||
private var snapshotTimer: Long? = null
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
|
||||
if (snapshotTimer != null && snapshotTimer!! <= milliTime) {
|
||||
pushbackSnapshot()
|
||||
snapshotTimer = null
|
||||
redo.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun pushbackSnapshot() {
|
||||
val snapshot = Snapshot()
|
||||
|
||||
if (undo.isEmpty() || undo.last() != snapshot)
|
||||
undo.addLast(snapshot)
|
||||
}
|
||||
|
||||
private fun recordHistory(hard: Boolean = false) {
|
||||
if (hard) {
|
||||
pushbackSnapshot()
|
||||
snapshotTimer = null
|
||||
} else if (snapshotTimer == null) {
|
||||
snapshotTimer = milliTime + 800L
|
||||
} else if (snapshotTimer!! <= milliTime) {
|
||||
pushbackSnapshot()
|
||||
snapshotTimer = null
|
||||
}
|
||||
|
||||
redo.clear()
|
||||
}
|
||||
|
||||
// it gets really dirty
|
||||
// idk man :(
|
||||
private fun undo() {
|
||||
if (undo.isEmpty())
|
||||
return
|
||||
|
||||
if (snapshotTimer != null || redo.isEmpty()) {
|
||||
pushbackSnapshot()
|
||||
snapshotTimer = null
|
||||
}
|
||||
|
||||
val current = Snapshot()
|
||||
|
||||
while (undo.isNotEmpty()) {
|
||||
val removed = undo.removeLast()
|
||||
redo.addFirst(removed)
|
||||
|
||||
if (removed != current) {
|
||||
removed.apply()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// it gets really dirty
|
||||
// idk man :(
|
||||
private fun redo() {
|
||||
if (redo.isEmpty())
|
||||
return
|
||||
|
||||
val current = Snapshot()
|
||||
|
||||
snapshotTimer = null
|
||||
|
||||
while (redo.isNotEmpty()) {
|
||||
val removed = redo.removeFirst()
|
||||
undo.addLast(removed)
|
||||
|
||||
if (current != removed) {
|
||||
removed.apply()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(index: Int): String? {
|
||||
return lines.getOrNull(index)
|
||||
}
|
||||
|
||||
operator fun set(index: Int, value: String) {
|
||||
if (index < 0) {
|
||||
throw IndexOutOfBoundsException("negative index $index")
|
||||
}
|
||||
|
||||
lines.ensureCapacity(index)
|
||||
|
||||
while (lines.size <= index) {
|
||||
lines.add("")
|
||||
}
|
||||
|
||||
lines[index] = value
|
||||
}
|
||||
|
||||
fun insertLine(index: Int, value: String = "") {
|
||||
lines.ensureCapacity(index)
|
||||
|
||||
while (lines.size < index) {
|
||||
lines.add("")
|
||||
}
|
||||
|
||||
val upperLines = selections.int2ObjectEntrySet().filter { it.intKey >= index }
|
||||
|
||||
for (entry in upperLines) {
|
||||
selections.remove(entry.intKey)
|
||||
}
|
||||
|
||||
for (entry in upperLines) {
|
||||
selections.put(entry.intKey + 1, entry.value)
|
||||
}
|
||||
|
||||
lines.add(index, value)
|
||||
}
|
||||
|
||||
fun removeLine(index: Int): Boolean {
|
||||
if (index < 0 || index >= lines.size) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (cursorLine == index) {
|
||||
if (index != 0)
|
||||
cursorLine--
|
||||
|
||||
cursorCharacter = lines.getOrNull(cursorLine)?.length ?: 0
|
||||
}
|
||||
|
||||
lines.removeAt(index)
|
||||
selections.remove(index)
|
||||
val upperLines = selections.int2ObjectEntrySet().filter { it.intKey > index }
|
||||
|
||||
for (entry in upperLines) {
|
||||
selections.remove(entry.intKey)
|
||||
}
|
||||
|
||||
for (entry in upperLines) {
|
||||
selections.put(entry.intKey - 1, entry.value)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun moveCursors(line: Int, character: Int, moveBy: Int) {
|
||||
@Suppress("name_shadowing")
|
||||
val moveBy = if (character + moveBy < 0) -character else moveBy
|
||||
|
||||
if (cursorLine == line && cursorCharacter >= character) {
|
||||
cursorCharacter = (cursorCharacter + moveBy).coerceIn(0, this[cursorLine]?.length ?: Int.MAX_VALUE)
|
||||
}
|
||||
|
||||
val selection = selections[line]
|
||||
|
||||
if (selection != null) {
|
||||
val (start, end) = selection
|
||||
|
||||
if (character < start) {
|
||||
selections[line] = TextSelection(start + moveBy, end + moveBy)
|
||||
} else if (character > end && character + moveBy < end) {
|
||||
if (character + moveBy < start) {
|
||||
// selections.remove(line)
|
||||
selections[line] = TextSelection(0, 0)
|
||||
} else {
|
||||
selections[line] = TextSelection(start, character + moveBy)
|
||||
}
|
||||
} else if (character in start .. end) {
|
||||
if (moveBy > 0 || character + moveBy <= start) {
|
||||
selections[line] = TextSelection(start, end + moveBy)
|
||||
} else {
|
||||
selections[line] = TextSelection(character + moveBy, end + moveBy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun wipeSelection() {
|
||||
val inversed = ArrayList(selections.int2ObjectEntrySet())
|
||||
inversed.sortByDescending { it.intKey }
|
||||
|
||||
for ((lineNumber, selection) in inversed) {
|
||||
if (selection.start != selection.end) {
|
||||
if (selection.start == 0 && selection.end == this[lineNumber]!!.length) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selections.clear()
|
||||
}
|
||||
|
||||
protected open fun textChanged(oldText: String, newText: String) {}
|
||||
|
||||
var text: String
|
||||
get() {
|
||||
var textCache = textCache
|
||||
|
||||
if (textCache == null) {
|
||||
textCache = lines.joinToString("\n")
|
||||
this.textCache = textCache
|
||||
}
|
||||
|
||||
return textCache
|
||||
}
|
||||
set(value) {
|
||||
if ((textCache ?: lines.joinToString("\n")) == value)
|
||||
return
|
||||
|
||||
snapshotTimer = null
|
||||
selections.clear()
|
||||
lines.clear()
|
||||
redo.clear()
|
||||
undo.clear()
|
||||
cursorLine = 0
|
||||
cursorCharacter = 0
|
||||
lines.addAll(value.split(NEWLINES))
|
||||
}
|
||||
|
||||
fun advanceCursorLeft() {
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line != null && cursorCharacter > line.length) {
|
||||
cursorCharacter = line.length - 1
|
||||
}
|
||||
|
||||
if (cursorCharacter > 0) {
|
||||
cursorCharacter--
|
||||
} else if (cursorLine > 0) {
|
||||
cursorLine--
|
||||
cursorCharacter = 0
|
||||
|
||||
@Suppress("name_shadowing")
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line != null) {
|
||||
cursorCharacter = line.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun advanceCursorRight() {
|
||||
cursorCharacter++
|
||||
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line != null && cursorCharacter > line.length) {
|
||||
if (lines.size <= cursorLine + 1) {
|
||||
cursorCharacter = line.length
|
||||
} else {
|
||||
cursorLine++
|
||||
cursorCharacter = 0
|
||||
}
|
||||
} else if (line == null) {
|
||||
cursorCharacter = 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean {
|
||||
if (key == InputConstants.KEY_ESCAPE) {
|
||||
killFocus()
|
||||
return true
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_RETURN) {
|
||||
if (multiLine) {
|
||||
if (!minecraft.window.isShiftDown && !minecraft.window.isCtrlDown)
|
||||
wipeSelection()
|
||||
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line != null && !minecraft.window.isShiftDown) {
|
||||
if (cursorCharacter <= 0) {
|
||||
recordHistory(true)
|
||||
insertLine(cursorLine, "")
|
||||
|
||||
if (!minecraft.window.isCtrlDown) {
|
||||
cursorLine++
|
||||
cursorCharacter = 0
|
||||
}
|
||||
} else if (cursorCharacter >= line.length) {
|
||||
recordHistory(true)
|
||||
insertLine(cursorLine + 1, "")
|
||||
|
||||
if (!minecraft.window.isCtrlDown) {
|
||||
cursorLine++
|
||||
cursorCharacter = 0
|
||||
}
|
||||
} else {
|
||||
val before = line.substring(0, cursorCharacter)
|
||||
val after = line.substring(cursorCharacter)
|
||||
|
||||
recordHistory(true)
|
||||
this[cursorLine] = before
|
||||
insertLine(cursorLine + 1, after)
|
||||
|
||||
if (!minecraft.window.isCtrlDown) {
|
||||
cursorLine++
|
||||
cursorCharacter = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
recordHistory(true)
|
||||
insertLine(cursorLine + 1)
|
||||
cursorLine++
|
||||
cursorCharacter = 0
|
||||
}
|
||||
|
||||
return true
|
||||
} else {
|
||||
killFocus()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_TAB) {
|
||||
if (multiLine) {
|
||||
val lines = if (selections.isNotEmpty()) selections.keys else listOf(cursorLine)
|
||||
|
||||
|
||||
if (minecraft.window.isKeyDown(InputConstants.KEY_RSHIFT) || minecraft.window.isKeyDown(InputConstants.KEY_LSHIFT)) {
|
||||
var hit = false
|
||||
|
||||
for (lineNumber in lines) {
|
||||
val line = this[lineNumber]!!
|
||||
|
||||
if (line[0] == '\t') {
|
||||
if (!hit) {
|
||||
hit = true
|
||||
recordHistory(true)
|
||||
}
|
||||
|
||||
this[lineNumber] = line.substring(1)
|
||||
moveCursors(lineNumber, 1, -1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (lines.isNotEmpty())
|
||||
recordHistory(true)
|
||||
|
||||
for (lineNumber in lines) {
|
||||
this[lineNumber] = "\t" + this[lineNumber]!!
|
||||
moveCursors(lineNumber, 0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
killFocus()
|
||||
return true
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_LEFT) {
|
||||
if (minecraft.window.isCtrlDown) {
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line == null || cursorCharacter <= 0) {
|
||||
advanceCursorLeft()
|
||||
} else {
|
||||
if (cursorCharacter >= line.length)
|
||||
cursorCharacter = line.length - 1
|
||||
|
||||
cursorCharacter = greedyAdvanceLeft(line, cursorCharacter)
|
||||
}
|
||||
} else {
|
||||
advanceCursorLeft()
|
||||
}
|
||||
|
||||
return true
|
||||
} else if (key == InputConstants.KEY_RIGHT) {
|
||||
if (minecraft.window.isCtrlDown) {
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line == null || cursorCharacter + 1 >= line.length) {
|
||||
advanceCursorRight()
|
||||
} else {
|
||||
if (cursorCharacter < 0)
|
||||
cursorCharacter = 0
|
||||
|
||||
cursorCharacter = greedyAdvanceRight(line, cursorCharacter)
|
||||
}
|
||||
} else {
|
||||
advanceCursorRight()
|
||||
}
|
||||
|
||||
return true
|
||||
} else if (key == InputConstants.KEY_UP) {
|
||||
if (cursorLine > 0) {
|
||||
cursorLine--
|
||||
}
|
||||
|
||||
return true
|
||||
} else if (key == InputConstants.KEY_DOWN) {
|
||||
if (cursorLine < lines.size - 1) {
|
||||
cursorLine++
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_BACKSPACE) {
|
||||
if (cursorLine <= 0 && cursorCharacter <= 0) {
|
||||
return true
|
||||
} else if (cursorCharacter <= 0) {
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line == null) {
|
||||
cursorCharacter = this[--cursorLine]?.length ?: 0
|
||||
} else {
|
||||
removeLine(cursorLine)
|
||||
val newLine = this[cursorLine]
|
||||
|
||||
if (newLine == null) {
|
||||
this[cursorLine] = line
|
||||
} else {
|
||||
this[cursorLine] = newLine + line
|
||||
}
|
||||
|
||||
recordHistory()
|
||||
}
|
||||
} else {
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line != null) {
|
||||
var cursorCharacter = cursorCharacter
|
||||
|
||||
if (cursorCharacter >= line.length)
|
||||
cursorCharacter = line.length - 1
|
||||
|
||||
val newLine = line.substring(0, cursorCharacter - 1) + line.substring(cursorCharacter)
|
||||
moveCursors(cursorLine, cursorCharacter, -1)
|
||||
this[cursorLine] = newLine
|
||||
recordHistory()
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_DELETE) {
|
||||
if (cursorLine !in 0 until lines.size) {
|
||||
cursorLine = lines.size - 1
|
||||
return true
|
||||
}
|
||||
|
||||
if (minecraft.window.isShiftDown) {
|
||||
pushbackSnapshot()
|
||||
removeLine(cursorLine)
|
||||
|
||||
if (cursorLine >= lines.size)
|
||||
cursorLine = lines.size
|
||||
|
||||
cursorCharacter = 0
|
||||
recordHistory(true)
|
||||
return true
|
||||
}
|
||||
|
||||
val line = this[cursorLine]!!
|
||||
|
||||
if (cursorCharacter >= line.length) {
|
||||
if (cursorLine + 1 == lines.size) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (snapshotTimer == null)
|
||||
pushbackSnapshot()
|
||||
|
||||
val bottomLine = this[cursorLine + 1]!!
|
||||
cursorCharacter = line.length
|
||||
this[cursorLine] = line + bottomLine
|
||||
removeLine(cursorLine + 1)
|
||||
|
||||
recordHistory()
|
||||
} else {
|
||||
if (snapshotTimer == null)
|
||||
pushbackSnapshot()
|
||||
|
||||
val cursorCharacter = cursorCharacter
|
||||
moveCursors(cursorLine, cursorCharacter, -1)
|
||||
this[cursorLine] = line.substring(0, cursorCharacter) + line.substring(cursorCharacter + 1)
|
||||
|
||||
if (cursorCharacter != 0)
|
||||
this.cursorCharacter++
|
||||
|
||||
recordHistory()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_Z && minecraft.window.isCtrlDown) {
|
||||
undo()
|
||||
} else if (key == InputConstants.KEY_Y && minecraft.window.isCtrlDown) {
|
||||
redo()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun charTypedInternal(codepoint: Char, mods: Int): Boolean {
|
||||
var line = this[cursorLine]
|
||||
|
||||
if (line == null) {
|
||||
set(cursorLine, "")
|
||||
line = ""
|
||||
cursorCharacter = 0
|
||||
}
|
||||
|
||||
if (cursorCharacter >= line.length)
|
||||
line += codepoint
|
||||
else
|
||||
line = line.substring(0, cursorCharacter) + codepoint + line.substring(cursorCharacter)
|
||||
|
||||
if (snapshotTimer == null)
|
||||
pushbackSnapshot()
|
||||
|
||||
set(cursorLine, line)
|
||||
moveCursors(cursorLine, cursorCharacter, 1)
|
||||
recordHistory()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
|
||||
var y = 0f
|
||||
|
||||
for (line in lines) {
|
||||
font.drawAligned(
|
||||
poseStack = stack,
|
||||
buffer = BUFFER,
|
||||
text = line,
|
||||
align = TextAlign.TOP_LEFT,
|
||||
x = 0f,
|
||||
y = y,
|
||||
color = textColor
|
||||
)
|
||||
|
||||
y += font.lineHeight + 2f
|
||||
}
|
||||
|
||||
if (isFocused && milliTime % 1000L > 200L) {
|
||||
val activeLine = this[cursorLine]
|
||||
|
||||
if (activeLine == null || cursorCharacter >= activeLine.length) {
|
||||
font.drawAligned(
|
||||
poseStack = stack,
|
||||
buffer = BUFFER,
|
||||
text = "_",
|
||||
align = TextAlign.TOP_LEFT,
|
||||
x = if (activeLine == null) 0f else font.width(activeLine).toFloat(),
|
||||
y = cursorLine * (font.lineHeight + 2f),
|
||||
color = cursorColor
|
||||
)
|
||||
} else {
|
||||
font.drawAligned(
|
||||
poseStack = stack,
|
||||
buffer = BUFFER,
|
||||
text = "|",
|
||||
align = TextAlign.TOP_LEFT,
|
||||
x = font.width(activeLine.substring(0, cursorCharacter)).toFloat() - 1f,
|
||||
y = cursorLine * (font.lineHeight + 2f),
|
||||
color = cursorColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
font.drawAligned(
|
||||
poseStack = stack,
|
||||
buffer = BUFFER,
|
||||
text = cursorLine.toString(),
|
||||
align = TextAlign.TOP_RIGHT,
|
||||
x = width,
|
||||
y = 0f,
|
||||
color = cursorColor
|
||||
)
|
||||
|
||||
font.drawAligned(
|
||||
poseStack = stack,
|
||||
buffer = BUFFER,
|
||||
text = cursorCharacter.toString(),
|
||||
align = TextAlign.TOP_RIGHT,
|
||||
x = width,
|
||||
y = font.lineHeight + 2f,
|
||||
color = cursorColor
|
||||
)
|
||||
|
||||
font.drawAligned(
|
||||
poseStack = stack,
|
||||
buffer = BUFFER,
|
||||
text = lines.size.toString(),
|
||||
align = TextAlign.TOP_RIGHT,
|
||||
x = width,
|
||||
y = font.lineHeight * 2 + 4f,
|
||||
color = cursorColor
|
||||
)
|
||||
}
|
||||
|
||||
BUFFER.endBatch()
|
||||
}
|
||||
|
||||
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
|
||||
requestFocus()
|
||||
return true
|
||||
}
|
||||
|
||||
private enum class CharType {
|
||||
SPACES {
|
||||
override fun contains(input: Char): Boolean {
|
||||
return _SPACES.contains(input)
|
||||
}
|
||||
|
||||
override fun canJumpInto(category: CharType): Boolean {
|
||||
return true
|
||||
}
|
||||
},
|
||||
CONTROL {
|
||||
override fun contains(input: Char): Boolean {
|
||||
return _CONTROL.contains(input)
|
||||
}
|
||||
|
||||
override fun canJumpInto(category: CharType): Boolean {
|
||||
return category == CONTROL
|
||||
}
|
||||
},
|
||||
OTHER {
|
||||
override fun contains(input: Char): Boolean {
|
||||
return !_SPACES.contains(input) && !_CONTROL.contains(input)
|
||||
}
|
||||
|
||||
override fun canJumpInto(category: CharType): Boolean {
|
||||
return category == OTHER
|
||||
}
|
||||
};
|
||||
|
||||
abstract fun contains(input: Char): Boolean
|
||||
abstract fun canJumpInto(category: CharType): Boolean
|
||||
|
||||
companion object {
|
||||
private val values = values()
|
||||
|
||||
operator fun get(input: Char): CharType {
|
||||
for (value in values) {
|
||||
if (value.contains(input)) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
throw NoSuchElementException("for $input")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NEWLINES = Regex("\n\r?")
|
||||
private val BUFFER = DynamicBufferSource()
|
||||
|
||||
private val _SPACES = CharOpenHashSet().also {
|
||||
for (char in " \t\n\r\u0000")
|
||||
it.add(char)
|
||||
}
|
||||
|
||||
private val _CONTROL = CharOpenHashSet().also {
|
||||
for (char in "!@\"'#$%^&?*()~`[]/\\;:|<>.,")
|
||||
it.add(char)
|
||||
}
|
||||
|
||||
private fun greedyAdvanceLeft(input: String, position: Int): Int {
|
||||
if (position <= 1)
|
||||
return 0
|
||||
|
||||
@Suppress("name_shadowing")
|
||||
var position = position
|
||||
var type = CharType[input[--position]]
|
||||
|
||||
while (position > 0) {
|
||||
val newType = CharType[input[position]]
|
||||
|
||||
if (type.canJumpInto(newType)) {
|
||||
position--
|
||||
type = newType
|
||||
} else {
|
||||
return position + 1
|
||||
}
|
||||
}
|
||||
|
||||
return position
|
||||
}
|
||||
|
||||
private fun greedyAdvanceRight(input: String, position: Int): Int {
|
||||
@Suppress("name_shadowing")
|
||||
var position = position
|
||||
var type = CharType[input[position]]
|
||||
|
||||
while (position < input.length) {
|
||||
val newType = CharType[input[position]]
|
||||
|
||||
if (type.canJumpInto(newType)) {
|
||||
position++
|
||||
type = newType
|
||||
} else {
|
||||
return position
|
||||
}
|
||||
}
|
||||
|
||||
return position
|
||||
}
|
||||
}
|
||||
}
|
@ -566,6 +566,8 @@ const val PIf = 3.1415927f
|
||||
/**
|
||||
* aligns 0,0 with top left point of block corner when looking directly at it,
|
||||
* and swaps Y to +Y when going down and -Y when going up
|
||||
*
|
||||
* allows to draw 2D interface in 3D space
|
||||
*/
|
||||
fun PoseStack.rotateWithBlockFacing(rotation: Direction, clarifyingAxis: Direction? = null): PoseStack {
|
||||
when (rotation) {
|
||||
|
@ -10,6 +10,15 @@ class HoloSignMenu @JvmOverloads constructor(
|
||||
inventory: Inventory,
|
||||
tile: HoloSignBlockEntity? = null
|
||||
) : MatteryMenu(MMenus.HOLO_SIGN, containerId, inventory, tile) {
|
||||
var text by mSynchronizer.string(name = "text")
|
||||
|
||||
override val storageSlots: Collection<Slot>
|
||||
get() = listOf()
|
||||
|
||||
override fun broadcastChanges() {
|
||||
super.broadcastChanges()
|
||||
|
||||
if (tile is HoloSignBlockEntity)
|
||||
text = tile.text
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ object MMenus {
|
||||
val PLATE_PRESS: MenuType<*> by registry.register(MNames.PLATE_PRESS) { MenuType(::PlatePressMenu) }
|
||||
val MATTER_RECYCLER: MenuType<*> by registry.register(MNames.MATTER_RECYCLER) { MenuType(::MatterRecyclerMenu) }
|
||||
val ENERGY_SERVO: MenuType<*> by registry.register(MNames.ENERGY_SERVO) { MenuType(::EnergyServoMenu) }
|
||||
val HOLO_SIGN: MenuType<*> by registry.register(MNames.HOLO_SIGN) { MenuType(::HoloSignMenu) }
|
||||
val HOLO_SIGN: MenuType<HoloSignMenu> by registry.register(MNames.HOLO_SIGN) { MenuType(::HoloSignMenu) }
|
||||
|
||||
val STORAGE_BUS: MenuType<*> by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu) }
|
||||
val STORAGE_EXPORTER: MenuType<*> by registry.register(MNames.STORAGE_EXPORTER) { MenuType(::StorageExporterMenu) }
|
||||
@ -71,6 +71,7 @@ object MMenus {
|
||||
MenuScreens.register(STORAGE_IMPORTER as MenuType<StorageImporterMenu>, ::StorageImporterScreen)
|
||||
MenuScreens.register(STORAGE_POWER_SUPPLIER as MenuType<StoragePowerSupplierMenu>, ::StoragePowerSupplierScreen)
|
||||
MenuScreens.register(ENERGY_SERVO as MenuType<EnergyServoMenu>, ::EnergyServoScreen)
|
||||
MenuScreens.register(HOLO_SIGN, ::HoloSignScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user