From 472cb057a5f6d8d764acac5a7106063c3ec2d7e1 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 25 Jan 2023 14:21:44 +0700 Subject: [PATCH] Text input panel test --- .../entity/decorative/HoloSignBlockEntity.kt | 2 +- .../ru/dbotthepony/mc/otm/client/Ext.kt | 8 + .../render/blockentity/HoloSignRenderer.kt | 14 +- .../mc/otm/client/screen/HoloSignScreen.kt | 21 + .../otm/client/screen/panels/EditablePanel.kt | 2 +- .../mc/otm/client/screen/panels/GridPanel.kt | 5 +- .../client/screen/panels/TextInputPanel.kt | 787 ++++++++++++++++++ .../mc/otm/core/math/EuclidMath.kt | 2 + .../dbotthepony/mc/otm/menu/HoloSignMenu.kt | 9 + .../ru/dbotthepony/mc/otm/registry/MMenus.kt | 3 +- 10 files changed, 844 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/HoloSignScreen.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/TextInputPanel.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt index ba6eec801..984f2bfbd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt index 8c0eb85e6..819893727 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt @@ -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)) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/HoloSignRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/HoloSignRenderer.kt index 8af4ac339..59ede0d1a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/HoloSignRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/HoloSignRenderer.kt @@ -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() } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/HoloSignScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/HoloSignScreen.kt new file mode 100644 index 000000000..2329df6cd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/HoloSignScreen.kt @@ -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(menu, title) { + override fun makeMainFrame(): FramePanel> { + 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 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt index 9b5f3fac1..f039f7a4d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt @@ -1251,7 +1251,7 @@ open class EditablePanel @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] } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/GridPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/GridPanel.kt index 301fc1189..62a4865c4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/GridPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/GridPanel.kt @@ -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 @JvmOverloads constructor( screen: S, @@ -23,7 +22,7 @@ open class GridPanel @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 @JvmOverloads constructor( currentX = 0f lineY = 0f - get(index) ?: break + getChildren(index) ?: break } super.performLayout() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/TextInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/TextInputPanel.kt new file mode 100644 index 000000000..86f3ce71c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/TextInputPanel.kt @@ -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( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 10f, + height: Float = 10f, +) : EditablePanel(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() + private val selections = Int2ObjectOpenHashMap() + private val undo = ArrayDeque() + private val redo = ArrayDeque() + + 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 + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt index 2d9c2d4e2..5dd4654a1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt @@ -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) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/HoloSignMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/HoloSignMenu.kt index b031f4b1d..8ad731f18 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/HoloSignMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/HoloSignMenu.kt @@ -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 get() = listOf() + + override fun broadcastChanges() { + super.broadcastChanges() + + if (tile is HoloSignBlockEntity) + text = tile.text + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt index 6df9e3c7c..ae03a7a64 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt @@ -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 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, ::StorageImporterScreen) MenuScreens.register(STORAGE_POWER_SUPPLIER as MenuType, ::StoragePowerSupplierScreen) MenuScreens.register(ENERGY_SERVO as MenuType, ::EnergyServoScreen) + MenuScreens.register(HOLO_SIGN, ::HoloSignScreen) } } }