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
|
import ru.dbotthepony.mc.otm.registry.MBlocks
|
||||||
|
|
||||||
class HoloSignBlockEntity(blockPos: BlockPos, blockState: BlockState) : SynchronizedBlockEntity(MBlockEntities.HOLO_SIGN, blockPos, blockState), MenuProvider {
|
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 {
|
override fun createMenu(p_39954_: Int, p_39955_: Inventory, p_39956_: Player): AbstractContainerMenu {
|
||||||
return HoloSignMenu(p_39954_, p_39955_, this)
|
return HoloSignMenu(p_39954_, p_39955_, this)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ru.dbotthepony.mc.otm.client
|
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.Minecraft
|
||||||
import net.minecraft.client.gui.Font
|
import net.minecraft.client.gui.Font
|
||||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||||
@ -13,6 +15,12 @@ import java.nio.DoubleBuffer
|
|||||||
inline val minecraft: Minecraft get() = Minecraft.getInstance()
|
inline val minecraft: Minecraft get() = Minecraft.getInstance()
|
||||||
inline val font: Font get() = minecraft.font
|
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() {
|
fun playGuiClickSound() {
|
||||||
minecraft.soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0f))
|
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
|
p_112312_: Int
|
||||||
) {
|
) {
|
||||||
poseStack.pushPose()
|
poseStack.pushPose()
|
||||||
poseStack.rotateWithBlockFacing(tile.blockState[RotatableMatteryBlock.FACING_FULL], clarifyingAxis = Direction.SOUTH)
|
poseStack.rotateWithBlockFacing(tile.blockState[RotatableMatteryBlock.FACING_FULL])
|
||||||
poseStack.translate(0.0f, 0.0f, -0.1f)
|
poseStack.translate(0.5f, 0.5f, -0.05f)
|
||||||
poseStack.scale(0.01f, 0.01f, 0.01f)
|
poseStack.scale(0.01f, 0.01f, 0.01f)
|
||||||
|
|
||||||
val sorse = DynamicBufferSource.WORLD
|
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()
|
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
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(index: Int): EditablePanel<*>? {
|
fun getChildren(index: Int): EditablePanel<*>? {
|
||||||
if (index < 0 || index >= childrenInternal.size) return null
|
if (index < 0 || index >= childrenInternal.size) return null
|
||||||
return childrenInternal[index]
|
return childrenInternal[index]
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package ru.dbotthepony.mc.otm.client.screen.panels
|
package ru.dbotthepony.mc.otm.client.screen.panels
|
||||||
|
|
||||||
import net.minecraft.client.gui.screens.Screen
|
import net.minecraft.client.gui.screens.Screen
|
||||||
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
|
|
||||||
|
|
||||||
open class GridPanel<out S : Screen> @JvmOverloads constructor(
|
open class GridPanel<out S : Screen> @JvmOverloads constructor(
|
||||||
screen: S,
|
screen: S,
|
||||||
@ -23,7 +22,7 @@ open class GridPanel<out S : Screen> @JvmOverloads constructor(
|
|||||||
var column = 0
|
var column = 0
|
||||||
|
|
||||||
while (column < columns) {
|
while (column < columns) {
|
||||||
val child = get(index) ?: break
|
val child = getChildren(index) ?: break
|
||||||
|
|
||||||
if (child.visible && child.dock === Dock.NONE) {
|
if (child.visible && child.dock === Dock.NONE) {
|
||||||
lineY = lineY.coerceAtLeast(child.height + child.dockMargin.top + child.dockMargin.bottom)
|
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
|
currentX = 0f
|
||||||
lineY = 0f
|
lineY = 0f
|
||||||
|
|
||||||
get(index) ?: break
|
getChildren(index) ?: break
|
||||||
}
|
}
|
||||||
|
|
||||||
super.performLayout()
|
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,
|
* 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
|
* 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 {
|
fun PoseStack.rotateWithBlockFacing(rotation: Direction, clarifyingAxis: Direction? = null): PoseStack {
|
||||||
when (rotation) {
|
when (rotation) {
|
||||||
|
@ -10,6 +10,15 @@ class HoloSignMenu @JvmOverloads constructor(
|
|||||||
inventory: Inventory,
|
inventory: Inventory,
|
||||||
tile: HoloSignBlockEntity? = null
|
tile: HoloSignBlockEntity? = null
|
||||||
) : MatteryMenu(MMenus.HOLO_SIGN, containerId, inventory, tile) {
|
) : MatteryMenu(MMenus.HOLO_SIGN, containerId, inventory, tile) {
|
||||||
|
var text by mSynchronizer.string(name = "text")
|
||||||
|
|
||||||
override val storageSlots: Collection<Slot>
|
override val storageSlots: Collection<Slot>
|
||||||
get() = listOf()
|
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 PLATE_PRESS: MenuType<*> by registry.register(MNames.PLATE_PRESS) { MenuType(::PlatePressMenu) }
|
||||||
val MATTER_RECYCLER: MenuType<*> by registry.register(MNames.MATTER_RECYCLER) { MenuType(::MatterRecyclerMenu) }
|
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 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_BUS: MenuType<*> by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu) }
|
||||||
val STORAGE_EXPORTER: MenuType<*> by registry.register(MNames.STORAGE_EXPORTER) { MenuType(::StorageExporterMenu) }
|
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_IMPORTER as MenuType<StorageImporterMenu>, ::StorageImporterScreen)
|
||||||
MenuScreens.register(STORAGE_POWER_SUPPLIER as MenuType<StoragePowerSupplierMenu>, ::StoragePowerSupplierScreen)
|
MenuScreens.register(STORAGE_POWER_SUPPLIER as MenuType<StoragePowerSupplierMenu>, ::StoragePowerSupplierScreen)
|
||||||
MenuScreens.register(ENERGY_SERVO as MenuType<EnergyServoMenu>, ::EnergyServoScreen)
|
MenuScreens.register(ENERGY_SERVO as MenuType<EnergyServoMenu>, ::EnergyServoScreen)
|
||||||
|
MenuScreens.register(HOLO_SIGN, ::HoloSignScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user