gui: text marking

This commit is contained in:
Bixilon 2022-02-06 18:06:50 +01:00
parent c587451f22
commit 9eadbe5da6
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
3 changed files with 200 additions and 17 deletions

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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,
}

View File

@ -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()
}