diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/MarkTextElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/MarkTextElement.kt new file mode 100644 index 000000000..dd511e63e --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/MarkTextElement.kt @@ -0,0 +1,103 @@ +/* + * Minosoft + * Copyright (C) 2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.gui.elements.text.mark + +import de.bixilon.minosoft.config.key.KeyCodes +import de.bixilon.minosoft.data.text.ChatComponent +import de.bixilon.minosoft.data.text.RGBColor +import de.bixilon.minosoft.gui.rendering.RenderConstants +import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer +import de.bixilon.minosoft.gui.rendering.gui.elements.Element +import de.bixilon.minosoft.gui.rendering.gui.elements.HorizontalAlignments +import de.bixilon.minosoft.gui.rendering.gui.elements.text.TextElement +import de.bixilon.minosoft.gui.rendering.gui.input.ModifierKeys +import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer +import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions +import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes +import glm_.vec2.Vec2i + +class MarkTextElement( + guiRenderer: GUIRenderer, + text: Any, + fontAlignment: HorizontalAlignments = HorizontalAlignments.LEFT, + background: Boolean = true, + backgroundColor: RGBColor = RenderConstants.TEXT_BACKGROUND_COLOR, + noBorder: Boolean = false, + parent: Element? = null, + scale: Float = 1.0f, +) : TextElement(guiRenderer, text, fontAlignment, background, backgroundColor, noBorder, parent, scale) { + var markStartPosition = 0 + var markEndPosition = 0 + + val marked: Boolean + get() = markStartPosition >= 0 + + val markedText: String + get() { + if (!marked) { + return "" + } + return chatComponent.message.substring(markStartPosition, markEndPosition) + } + + override var chatComponent: ChatComponent + get() = super.chatComponent + set(value) { + super.chatComponent = value + unmark() + } + + fun mark(start: Int, end: Int) { + markStartPosition = start + markEndPosition = end + forceSilentApply() + } + + fun unmark() { + if (!marked) { + return + } + markStartPosition = -1 + markEndPosition = -1 + forceSilentApply() + } + + override fun forceRender(offset: Vec2i, z: Int, consumer: GUIVertexConsumer, options: GUIVertexOptions?): Int { + + if (markStartPosition >= 0) { + for (line in renderInfo.lines) { + // ToDo + } + } + + return super.forceRender(offset, z, consumer, options) + 1 + } + + override fun onKey(key: KeyCodes, type: KeyChangeTypes) { + super.onKey(key, type) + + val controlDown = guiRenderer.isKeyDown(ModifierKeys.CONTROL) + + when (key) { + KeyCodes.KEY_A -> { + if (!controlDown) { + return + } + mark(0, chatComponent.message.length) + } + KeyCodes.KEY_ESCAPE -> unmark() + else -> return + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/TextCursorStyles.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/TextCursorStyles.kt new file mode 100644 index 000000000..2818e676c --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/TextCursorStyles.kt @@ -0,0 +1,36 @@ +/* + * Minosoft + * Copyright (C) 2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.gui.elements.text.mark + +enum class TextCursorStyles { + /** + * The cursor is always hidden + */ + DISABLED, + + /** + * Cursor is only visible when text is marked + */ + MARKED, + + /** + * Cursor is visible when something is marked, or you clicked in + */ + CLICKED, + + /** + * Cursor is always visible + */ + ALWAYS, +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt index f648f4c9d..1de523ec6 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt @@ -20,6 +20,8 @@ import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer import de.bixilon.minosoft.gui.rendering.gui.elements.Element import de.bixilon.minosoft.gui.rendering.gui.elements.primitive.ColorElement import de.bixilon.minosoft.gui.rendering.gui.elements.text.TextElement +import de.bixilon.minosoft.gui.rendering.gui.elements.text.mark.MarkTextElement +import de.bixilon.minosoft.gui.rendering.gui.elements.text.mark.TextCursorStyles import de.bixilon.minosoft.gui.rendering.gui.input.ModifierKeys import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions @@ -30,33 +32,36 @@ import glm_.vec2.Vec2i class TextInputElement( guiRenderer: GUIRenderer, val maxLength: Int = Int.MAX_VALUE, + val cursorStyles: TextCursorStyles = TextCursorStyles.CLICKED, ) : Element(guiRenderer) { private val cursor = ColorElement(guiRenderer, size = Vec2i(1, Font.TOTAL_CHAR_HEIGHT)) - private val textElement = TextElement(guiRenderer, "", background = false, parent = this) + private val textElement = MarkTextElement(guiRenderer, "", background = false, parent = this) private val background = ColorElement(guiRenderer, Vec2i.EMPTY, RenderConstants.TEXT_BACKGROUND_COLOR) private var cursorOffset: Vec2i = Vec2i.EMPTY - private var _value: String = "" + private val _value = StringBuffer(256) var value: String - get() = _value + get() = _value.toString() set(value) { pointer = 0 - if (_value == value) { + if (_value.equals(value)) { return } - _value = value + _value.replace(0, _value.length, value) forceApply() } private var pointer = 0 private var cursorTick = 0 + override fun forceRender(offset: Vec2i, z: Int, consumer: GUIVertexConsumer, options: GUIVertexOptions?): Int { - background.render(offset, z, consumer, options) - textElement.render(offset, z + 1, consumer, options) + var zOffset = background.render(offset, z, consumer, options) + + zOffset += textElement.render(offset, z + zOffset, consumer, options) if (cursorTick < 20) { - cursor.render(offset + cursorOffset, z + 1 + TextElement.LAYERS, consumer, options) + cursor.render(offset + cursorOffset, z + zOffset, consumer, options) } - return TextElement.LAYERS + 2 + return zOffset + 1 } override fun forceSilentApply() { @@ -84,9 +89,13 @@ class TextInputElement( cacheUpToDate = false } - private fun silentAppend(string: String) { - val appendLength = minOf(string.length, maxLength - _value.length) - _value = _value.substring(0, pointer) + string.substring(0, appendLength) + _value.substring(pointer, _value.length) + private fun insert(string: String) { + val insert = string.replace("\n", "").replace("\r", "").replace('ยง', '&') + if (textElement.markStartPosition > 0) { + _value.delete(textElement.markStartPosition, textElement.markEndPosition) + } + val appendLength = minOf(insert.length, maxLength - _value.length) + _value.insert(pointer, insert.substring(0, appendLength)) pointer += appendLength } @@ -95,48 +104,83 @@ class TextInputElement( return } cursorTick = CURSOR_TICK_ON_ACTION - silentAppend(char.toChar().toString()) + insert(char.toChar().toString()) forceApply() } + private fun mark(mark: Boolean, right: Boolean) { + if (mark) { + var start: Int = textElement.markStartPosition + var end: Int = textElement.markEndPosition + if (right) { + if (start < 0) { + start = pointer + end = start + } + end++ + } else { + if (start < 0) { + end = pointer + start = end + } + start-- + } + textElement.mark(start, end) + } else { + textElement.unmark() + } + } + override fun onKey(key: KeyCodes, type: KeyChangeTypes) { if (type == KeyChangeTypes.RELEASE) { return } val controlDown = guiRenderer.isKeyDown(ModifierKeys.CONTROL) + val shiftDown = guiRenderer.isKeyDown(ModifierKeys.SHIFT) cursorTick = CURSOR_TICK_ON_ACTION when (key) { KeyCodes.KEY_V -> { if (controlDown) { - silentAppend(guiRenderer.renderWindow.window.clipboardText) + insert(guiRenderer.renderWindow.window.clipboardText) + } + } + KeyCodes.KEY_C -> { + if (controlDown) { + val markedText = textElement.markedText + if (markedText.isEmpty()) { + return + } + renderWindow.window.clipboardText = markedText } } KeyCodes.KEY_BACKSPACE -> { if (pointer == 0 || _value.isEmpty()) { return } - _value = _value.removeRange(pointer - 1, pointer) + _value.deleteCharAt(pointer - 1) pointer-- } KeyCodes.KEY_DELETE -> { if (pointer == _value.length || _value.isEmpty()) { return } - _value = _value.removeRange(pointer, pointer + 1) + _value.deleteCharAt(pointer) } KeyCodes.KEY_LEFT -> { if (pointer == 0) { return } + mark(shiftDown, false) pointer-- } KeyCodes.KEY_RIGHT -> { if (pointer == _value.length) { return } + mark(shiftDown, true) pointer++ } - else -> return + else -> return textElement.onKey(key, type) } forceApply() }