diff --git a/odinclient/src/main/resources/mixins.odinclient.json b/odinclient/src/main/resources/mixins.odinclient.json index 36a4957c1..55552d25c 100644 --- a/odinclient/src/main/resources/mixins.odinclient.json +++ b/odinclient/src/main/resources/mixins.odinclient.json @@ -22,7 +22,6 @@ ], "client": [ "accessors.IEntityPlayerSPAccessor", - "accessors.IEntityRendererAccessor", "accessors.IMinecraftAccessor", "mixins.MixinFontRenderer", "mixins.MixinGuiContainer", diff --git a/src/main/kotlin/me/odinmain/features/impl/render/ClickGUI.kt b/src/main/kotlin/me/odinmain/features/impl/render/ClickGUI.kt index 9f0a53c77..92246f733 100644 --- a/src/main/kotlin/me/odinmain/features/impl/render/ClickGUI.kt +++ b/src/main/kotlin/me/odinmain/features/impl/render/ClickGUI.kt @@ -62,7 +62,7 @@ object ClickGUI: Module( val enableNotification by BooleanSetting("Enable chat notifications", true, description = "Sends a message when you toggle a module with a keybind") - val forceHypixel by BooleanSetting("Force Hypixel", false, description = "Forces the hypixel check to be on (Mainly used for development. Only use if you know what you're doing)") + val forceHypixel by BooleanSetting("Force Hypixel", false, description = "Forces the Hypixel check to be on (Mainly used for development. Only use if you know what you're doing)") // make useful someday val updateMessage by SelectorSetting("Update Message", arrayListOf("Full", "Beta", "None")).hide() @@ -74,13 +74,11 @@ object ClickGUI: Module( private val devSizeX by NumberSetting("Dev Size X", 1f, -1f, 3f, 0.1, description = "X scale of the dev size.").withDependency { DevPlayers.isDev && devSize } private val devSizeY by NumberSetting("Dev Size Y", 1f, -1f, 3f, 0.1, description = "Y scale of the dev size.").withDependency { DevPlayers.isDev && devSize } private val devSizeZ by NumberSetting("Dev Size Z", 1f, -1f, 3f, 0.1, description = "Z scale of the dev size.").withDependency { DevPlayers.isDev && devSize } - private var showHidden by DropdownSetting("Show Hidden", false).withDependency { DevPlayers.isDev } // todo: censored option for textinput, max length, etc idk man its so much work - private val passcode: String by StringSetting("Passcode", "odin", description = "Passcode for dev features.").withDependency { DevPlayers.isDev && showHidden } + private val passcode by StringSetting("Passcode", "odin", description = "Passcode for dev features.").withDependency { DevPlayers.isDev }.censors() val reset by ActionSetting("Send Dev Data") { - showHidden = false scope.launch { modMessage(sendDataToServer(body = "${mc.thePlayer.name}, [${devWingsColor.red},${devWingsColor.green},${devWingsColor.blue}], [$devSizeX,$devSizeY,$devSizeZ], $devWings, $passcode", "https://tj4yzotqjuanubvfcrfo7h5qlq0opcyk.lambda-url.eu-north-1.on.aws/")) DevPlayers.updateDevs() @@ -140,6 +138,7 @@ object ClickGUI: Module( } override fun onKeybind() { + open(clickGUI()) this.toggle() } diff --git a/src/main/kotlin/me/odinmain/utils/ui/elements/TextInput.kt b/src/main/kotlin/me/odinmain/utils/ui/elements/TextInput.kt index 5db0b4744..24c91edf7 100644 --- a/src/main/kotlin/me/odinmain/utils/ui/elements/TextInput.kt +++ b/src/main/kotlin/me/odinmain/utils/ui/elements/TextInput.kt @@ -2,29 +2,19 @@ package me.odinmain.utils.ui.elements import com.github.stivais.ui.UI import com.github.stivais.ui.color.Color -import com.github.stivais.ui.color.color -import com.github.stivais.ui.color.darker -import com.github.stivais.ui.constraints.Positions -import com.github.stivais.ui.constraints.Size -import com.github.stivais.ui.constraints.Type -import com.github.stivais.ui.constraints.at +import com.github.stivais.ui.constraints.* import com.github.stivais.ui.elements.impl.TextElement import com.github.stivais.ui.events.Event import com.github.stivais.ui.events.Focused import com.github.stivais.ui.events.Key import com.github.stivais.ui.events.Mouse -import me.odinmain.utils.skyblock.devMessage -import me.odinmain.utils.writeToClipboard import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiScreen import net.minecraft.util.ChatAllowedCharacters import org.lwjgl.input.Keyboard -import kotlin.math.abs -import kotlin.math.max import kotlin.math.min +import kotlin.math.max -// odin exclusive -// official element will come when keyboard input gets redone class TextInput( text: String, private val placeholder: String, @@ -36,111 +26,48 @@ class TextInput( onTextChange: (event: TextChanged) -> Unit ) : TextElement(text, UI.defaultFont, Color.WHITE, position ?: at(), size) { + private val cursorBlinkRate = 500L + private var lastBlinkTime = System.currentTimeMillis() + private var cursorVisible = true + override var text: String = text set(value) { if (field == value) return - val event = TextChanged(value) accept(event) if (!event.cancelled) { field = value redraw = true previousHeight = 0f - - // text input stuff if (history.last() != value) history.add(value) - if (censorInput) censorCache = buildString { repeat(text.length) { append('*') } } - + if (censorInput) censorCache = "*".repeat(value.length) updateCaret() + updateSelectionX() } } - private val _text: String - get() = if (censorInput) censorCache!! else text - - private val placeholderColor: Color = color { color!!.rgba.darker(0.75) } - - var censorInput = censor - set(value) { - if (value == field) return - censorCache = if (value) buildString { repeat(text.length) { append('*') } } else null - redraw = true - previousHeight = 0f - field = value - } - - private var censorCache: String? = if (censor) buildString { repeat(text.length) { append('*') } } else null - - private var caretPosition: Int = text.length + private var caretPosition = text.length set(value) { field = value.coerceIn(0, text.length) updateCaret() - // start animation + lastBlinkTime = System.currentTimeMillis() + cursorVisible = true } - private var selectionStart: Int = caretPosition + private var selectionStart = caretPosition set(value) { field = value.coerceIn(0, text.length) - selectionX = renderer.textWidth(text.substring(0, field), size = height) + updateSelectionX() } + private var caretX = 0f + private var selectionX = 0f private var dragging = false - - private var offs = 0f - - private var caretX: Float = 0f - - private var history: MutableList = mutableListOf(text) - private var selectionX: Float = 0f - private var lastClickTime: Long = 0L - private var clickCount: Int = 0 - - override fun preSize() { - super.preSize() - if (widthLimit != null) { - val maxW = widthLimit.get(this, Type.W) - if (width >= maxW) { - offs = width - maxW - width = maxW - } else { - offs = 0f - } - } - } - - override fun getTextWidth(): Float { - return when { - text.isEmpty() -> renderer.textWidth(placeholder, height) - censorInput -> renderer.textWidth(censorCache!!, height) - else -> super.getTextWidth() - } - } - - override fun draw() { - val focused = ui.isFocused(this) - // cleanup - if (selectionStart != caretPosition) { - val startX = x + min(selectionX, caretX).toInt() - val endX = x + max(selectionX, caretX).toInt() - renderer.rect(startX - offs, y, endX - startX, height - 4, Color.RGB(0, 0, 255, 0.5f).rgba) - } - when { - text.isEmpty() && !focused -> { - renderer.text(placeholder, x, y, height, placeholderColor.rgba) - } - censorInput -> { - renderer.text(censorCache!!, x, y, height, color!!.get(this)) - } - else -> { - renderer.text(text, x - offs, y, height, Color.WHITE.rgba) - } - } - - if (focused) { - renderer.rect(x + caretX - offs, y, 1f, height - 2, Color.WHITE.rgba) - } -// renderer.hollowRect(x, y, width, height, 1f, Color.WHITE.rgba) - } + var censorInput = censor + private var censorCache: String? = if (censor) "*".repeat(text.length) else null + private val history by lazy { mutableListOf(text) } + private var lastClickTime = 0L + private var clickCount = 0 init { TextChanged().register { @@ -150,271 +77,346 @@ class TextInput( Focused.Gained register { Keyboard.enableRepeatEvents(true) - setCaretPositionBasedOnMouse() + updateCaretFromMouse() selectionStart = caretPosition false } + Focused.Lost register { selectionStart = caretPosition Keyboard.enableRepeatEvents(false) false } - // todo bring function inside this + Key.CodePressed(-1, true) register { - handleKeyPress(it.code) + handleKeyInput(it.code) true } - Focused.Clicked(0) register { - if (it.button == 0) { - val current = System.currentTimeMillis() - if (current - lastClickTime < 300) clickCount++ else clickCount = 1 - lastClickTime = current - - when (clickCount) { - 1 -> { - setCaretPositionBasedOnMouse() - if (!isShiftKeyDown()) selectionStart = caretPosition - } - 2 -> { - val word = getCurrentWord(caretPosition) - selectionStart = word?.first ?: (caretPosition - 1) - caretPosition = word?.second ?: caretPosition - } - 3 -> { - selectionStart = 0 - caretPosition = text.length - } - } - dragging = true - return@register true - } + Mouse.Clicked(0) register { + dragging = true + ui.focus(this) + handleMouseClick() false } + Mouse.Moved register { if (dragging) { - setCaretPositionBasedOnMouse() + updateCaretFromMouse() return@register true } lastClickTime = 0L false } - Mouse.Clicked(0) register { - dragging = true - ui.focus(this) - false - } + Mouse.Released(0) register { dragging = false false } } - private fun handleKeyPress(code: Int) { - val eventChar = Keyboard.getEventCharacter() - when { - isKeyComboCtrlA(code) -> { - caretPosition = this.text.length - selectionStart = 0 - } + override fun draw() { + val currentTime = System.currentTimeMillis() + if (currentTime - lastBlinkTime > cursorBlinkRate) { + cursorVisible = !cursorVisible + lastBlinkTime = currentTime + redraw = true + } - isKeyComboCtrlC(code) -> writeToClipboard(getSelectedText(selectionStart, caretPosition)) + // Draw selection background + if (selectionStart != caretPosition) { + val startX = x + min(selectionX, caretX) + val endX = x + max(selectionX, caretX) + renderer.rect(startX, y + 2, endX - startX, height - 2, Color.RGB(0, 120, 215, 0.4f).rgba) + } - isKeyComboCtrlV(code) -> { - val clipboard = GuiScreen.getClipboardString() - caretPosition = (caretPosition + clipboard.length) - insert(GuiScreen.getClipboardString()) + // Draw text or placeholder + when { + text.isEmpty() && !ui.isFocused(this) -> { + renderer.text(placeholder, x, y, height, Color.RGB(169, 169, 169).rgba) } - - isKeyComboCtrlX(code) -> { - writeToClipboard(getSelectedText(selectionStart, caretPosition)) - insert("") + censorInput -> { + renderer.text(censorCache!!, x, y, height, color!!.get(this)) } - - isKeyComboCtrlZ(code) -> { - if (history.size > 1) { - history.removeAt(history.size - 1) - this.text = history.last() - } + else -> { + renderer.text(text, x, y, height, color!!.get(this)) } + } - else -> when (code) { + // Draw cursor + if (ui.isFocused(this) && cursorVisible) { + renderer.rect(x + caretX, y + 2, 1f, height - 4, Color.WHITE.rgba) + } + } - Keyboard.KEY_BACK -> { - if (isCtrlKeyDown()) deleteWords(-1) - else deleteFromCaret(-1) - } + override fun getTextWidth(): Float { + return when { + text.isEmpty() -> renderer.textWidth(placeholder, height) + censorInput -> renderer.textWidth(censorCache!!, height) + else -> super.getTextWidth() + } + } - Keyboard.KEY_HOME -> { - if (isShiftKeyDown()) moveCaretBy(-caretPosition) - else moveSelectionAndCaretBy(-caretPosition) - } +// override fun preSize() { +// super.preSize() +// if (widthLimit != null) { +// val maxW = widthLimit.get(this, Type.W) +// if (width >= maxW) { +// offs = width - maxW +// width = maxW +// } else { +// offs = 0f +// } +// } +// } - Keyboard.KEY_LEFT -> { - if (isShiftKeyDown()) { - if (isCtrlKeyDown()) moveCaretBy(getNthWordFromCaret(-1) - caretPosition) - else moveCaretBy(-1) - } - else { - if (isCtrlKeyDown()) moveSelectionAndCaretBy(getNthWordFromCaret(-1) - caretPosition) - else moveSelectionAndCaretBy(-1) - } - } + private fun updateCaret() { + val visibleText = if (censorInput) censorCache!! else text + val safeCaretPosition = caretPosition.coerceIn(0, visibleText.length) + caretX = renderer.textWidth(visibleText.substring(0, safeCaretPosition), height) + } - Keyboard.KEY_RIGHT -> { - if (isShiftKeyDown()) { - if (isCtrlKeyDown()) moveCaretBy(getNthWordFromCaret(1) - caretPosition) - else moveCaretBy(1) - } - else { - if (isCtrlKeyDown()) moveSelectionAndCaretBy(getNthWordFromCaret(1) - caretPosition) - else moveSelectionAndCaretBy(1) - } - } + private fun updateSelectionX() { + val visibleText = if (censorInput) censorCache!! else text + val safeSelectionStart = selectionStart.coerceIn(0, visibleText.length) + selectionX = renderer.textWidth(visibleText.substring(0, safeSelectionStart), height) + } - Keyboard.KEY_END -> { - if (isShiftKeyDown()) moveCaretBy(text.length - caretPosition) - else moveSelectionAndCaretBy(text.length - caretPosition) - } + private fun updateCaretFromMouse() { + val mouseX = ui.mx - x + var newPos = 0 + var currentWidth = 0f + val visibleText = if (censorInput) censorCache!! else text + + for (i in visibleText.indices) { + val charWidth = renderer.textWidth(visibleText[i].toString(), height) + if (currentWidth + (charWidth / 2) > mouseX) break + currentWidth += charWidth + newPos = i + 1 + } - Keyboard.KEY_ESCAPE, Keyboard.KEY_NUMPADENTER, Keyboard.KEY_RETURN -> { - selectionStart = 0 - caretPosition = 0 - ui.unfocus() - } + caretPosition = newPos + if (!isShiftKeyDown() && !dragging) { + selectionStart = caretPosition + } + } - Keyboard.KEY_DELETE -> { - if (isCtrlKeyDown()) deleteWords(1) - else deleteFromCaret(1) - } + private fun handleKeyInput(keyCode: Int) { + val char = Keyboard.getEventCharacter() - else -> { + when { + isKeyComboCtrlA(keyCode) -> selectAll() + isKeyComboCtrlC(keyCode) -> copySelection() + isKeyComboCtrlV(keyCode) -> paste() + isKeyComboCtrlX(keyCode) -> cut() + isKeyComboCtrlZ(keyCode) -> undo() + + keyCode == Keyboard.KEY_LEFT -> handleLeftArrow() + keyCode == Keyboard.KEY_RIGHT -> handleRightArrow() + keyCode == Keyboard.KEY_HOME -> handleHome() + keyCode == Keyboard.KEY_END -> handleEnd() + keyCode == Keyboard.KEY_BACK -> handleBackspace() + keyCode == Keyboard.KEY_DELETE -> handleDelete() + + keyCode == Keyboard.KEY_RETURN || keyCode == Keyboard.KEY_NUMPADENTER -> ui.unfocus() + keyCode == Keyboard.KEY_ESCAPE -> { + text = history.first() + ui.unfocus() + } + + else -> { + if (ChatAllowedCharacters.isAllowedCharacter(char)) { if (onlyNumbers) { - val insert = eventChar.isDigit() || (eventChar == '-' && text.isEmpty()) || (eventChar == '.' && !text.contains('.')) - if (insert) insert(eventChar.toString()) + if (char.isDigit() || (char == '-' && text.isEmpty()) || (char == '.' && !text.contains('.'))) { + insertText(char.toString()) + } } else { - if (ChatAllowedCharacters.isAllowedCharacter(eventChar)) insert(eventChar.toString()) + insertText(char.toString()) } } } } - devMessage("cursorPosition: $caretPosition, startSelect: $selectionStart string: ${this.text}") } - // cleanup everything under here - // remove unnecessary stuff and variables + private fun handleMouseClick() { + val currentTime = System.currentTimeMillis() + if (currentTime - lastClickTime < 500) { + clickCount++ + } else { + clickCount = 1 + } + lastClickTime = currentTime - private fun moveCaretBy(amount: Int) { - caretPosition = (caretPosition + amount).coerceIn(0, text.length) + when (clickCount) { + 1 -> { + updateCaretFromMouse() + if (!isShiftKeyDown()) selectionStart = caretPosition + } + 2 -> selectWord() + 3 -> selectAll() + } } - private fun moveSelectionAndCaretBy(amount: Int) { - caretPosition = (caretPosition + amount).coerceIn(0, text.length) + private fun insertText(str: String) { + val start = min(caretPosition, selectionStart) + val end = max(caretPosition, selectionStart) + + text = text.substring(0, start) + str + text.substring(end) + caretPosition = start + str.length selectionStart = caretPosition } - private fun insert(text: String) { - if (text.length >= 30) return - val min = min(caretPosition, selectionStart) - val max = max(caretPosition, selectionStart) - val maxLength = 30 - text.length + max - min - - val addedText = ChatAllowedCharacters.filterAllowedCharacters(text).take(maxLength) - - this.text = this.text.take(min) + addedText + this.text.substring(max) - - moveSelectionAndCaretBy(min - selectionStart + addedText.length) + private fun deleteSelection() { + if (selectionStart == caretPosition) return + val start = min(caretPosition, selectionStart) + val end = max(caretPosition, selectionStart) + text = text.substring(0, start) + text.substring(end) + caretPosition = start selectionStart = caretPosition } - private fun deleteWords(num: Int) = deleteFromCaret(getNthWordFromCaret(num) - caretPosition) + // Helper functions for keyboard shortcuts + private fun selectAll() { + selectionStart = 0 + caretPosition = text.length + } - private fun deleteFromCaret(num: Int) { - if (text.isEmpty()) return - if (selectionStart != caretPosition) insert("") - else { - val target = (caretPosition + num).coerceIn(0, text.length) - if (num < 0) { - text = text.removeRange(target, caretPosition) - moveSelectionAndCaretBy(num) - } else - text = text.removeRange(caretPosition, target) + private fun copySelection() { + if (selectionStart != caretPosition) { + val start = min(selectionStart, caretPosition) + val end = max(selectionStart, caretPosition) + GuiScreen.setClipboardString(text.substring(start, end)) } } - private fun getNthWordFromCaret(n: Int): Int = getNthWordFromPos(n, caretPosition) + private fun paste() { + val clipboard = GuiScreen.getClipboardString() + if (clipboard.isNotEmpty()) { + deleteSelection() + insertText(clipboard) + } + } - private fun getNthWordFromPos(n: Int, pos: Int): Int { - var i = pos - val negative = n < 0 + private fun cut() { + copySelection() + deleteSelection() + } - repeat(abs(n)) { - if (negative) { - while (i > 0 && text[i - 1].code == 32) i-- - while (i > 0 && text[i - 1].code != 32) i-- - } else { - while (i < text.length && text[i].code == 32) i++ - i = text.indexOf(32.toChar(), i) - if (i == -1) { - i = text.length - } - } + private fun undo() { + if (history.size > 1) { + history.removeAt(history.lastIndex) + text = history.last() + caretPosition = text.length + selectionStart = caretPosition } - return i } - private fun getCurrentWord(pos: Int): Pair? { - val length = text.length - var start = pos - var end = pos + private fun selectWord() { + var start = caretPosition + var end = caretPosition - // Move start left until a space or the beginning of the string - while (start > 0 && text[start - 1].code != 32) { - start-- - } + // Move start left until a space or the beginning + while (start > 0 && !text[start - 1].isWhitespace()) start-- + // Move end right until a space or the end + while (end < text.length && !text[end].isWhitespace()) end++ - // Move end right until a space or the end of the string - while (end < length && text[end].code != 32) { - end++ - } + selectionStart = start + caretPosition = end + } - // Check if the word is surrounded by text or is at the edges of the string - val isStartValid = start == 0 || text[start - 1].code == 32 - val isEndValid = end == length || text[end].code == 32 + // Standard keyboard input handlers + private fun handleLeftArrow() { + if (isCtrlKeyDown()) { + var newPos = caretPosition + // Skip spaces + while (newPos > 0 && text[newPos - 1].isWhitespace()) newPos-- + // Skip word + while (newPos > 0 && !text[newPos - 1].isWhitespace()) newPos-- + if (isShiftKeyDown()) caretPosition = newPos else { + caretPosition = newPos + selectionStart = caretPosition + } + } else { + if (isShiftKeyDown()) caretPosition-- else { + caretPosition-- + selectionStart = caretPosition + } + } + } - return if (isStartValid && isEndValid && start != end) Pair(start, end) else null + private fun handleRightArrow() { + if (isCtrlKeyDown()) { + var newPos = caretPosition + // Skip spaces + while (newPos < text.length && text[newPos].isWhitespace()) newPos++ + // Skip word + while (newPos < text.length && !text[newPos].isWhitespace()) newPos++ + if (isShiftKeyDown()) caretPosition = newPos else { + caretPosition = newPos + selectionStart = caretPosition + } + } else { + if (isShiftKeyDown()) caretPosition++ else { + caretPosition++ + selectionStart = caretPosition + } + } } - private fun getSelectedText(selectionStart: Int, caretPosition: Int): String { - return substringSafe(text, selectionStart, caretPosition) + private fun handleHome() { + if (isShiftKeyDown()) caretPosition = 0 else { + caretPosition = 0 + selectionStart = 0 + } } - private fun updateCaret() { - val str = if (caretPosition <= text.length) _text.substring(0, caretPosition) else "" - caretX = renderer.textWidth(str, size = height) + private fun handleEnd() { + if (isShiftKeyDown()) caretPosition = text.length else { + caretPosition = text.length + selectionStart = text.length + } } - private fun setCaretPositionBasedOnMouse() { - caretPosition = if (this.text.isEmpty()) 0 - else ((ui.mx - x) / ((width - offs) / this.text.length)).toInt().coerceIn(0, this.text.length) + private fun handleBackspace() { + if (selectionStart != caretPosition) { + deleteSelection() + } else if (caretPosition > 0) { + if (isCtrlKeyDown()) { + var newPos = caretPosition + while (newPos > 0 && text[newPos - 1].isWhitespace()) newPos-- + while (newPos > 0 && !text[newPos - 1].isWhitespace()) newPos-- + text = text.substring(0, newPos) + text.substring(caretPosition) + caretPosition = newPos + } else { + text = text.substring(0, caretPosition - 1) + text.substring(caretPosition) + caretPosition-- + } + selectionStart = caretPosition + } } - private fun substringSafe(string: String, start: Int, end: Int): String { - if (start == end) return "" - val s: Int - val e: Int - // check if start is bigger than end, if so, swap them - if (start > end) { s = end; e = start } else { s = start; e = end } - return string.substring(s, max(e, string.length - 1)) + private fun handleDelete() { + if (selectionStart != caretPosition) { + deleteSelection() + } else if (caretPosition < text.length) { + if (isCtrlKeyDown()) { + var newPos = caretPosition + while (newPos < text.length && text[newPos].isWhitespace()) newPos++ + while (newPos < text.length && !text[newPos].isWhitespace()) newPos++ + text = text.substring(0, caretPosition) + text.substring(newPos) + } else { + text = text.substring(0, caretPosition) + text.substring(caretPosition + 1) + } + } } - fun isCtrlKeyDown(): Boolean { + private fun isCtrlKeyDown(): Boolean { return if (Minecraft.isRunningOnMac) Keyboard.isKeyDown(219) || Keyboard.isKeyDown(220) else Keyboard.isKeyDown( 29 ) || Keyboard.isKeyDown(157) } - fun isShiftKeyDown(): Boolean { + private fun isShiftKeyDown(): Boolean { return Keyboard.isKeyDown(42) || Keyboard.isKeyDown(54) } @@ -422,23 +424,23 @@ class TextInput( return Keyboard.isKeyDown(56) || Keyboard.isKeyDown(184) } - fun isKeyComboCtrlX(keyID: Int): Boolean { + private fun isKeyComboCtrlX(keyID: Int): Boolean { return keyID == 45 && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown() } - fun isKeyComboCtrlV(keyID: Int): Boolean { + private fun isKeyComboCtrlV(keyID: Int): Boolean { return keyID == 47 && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown() } - fun isKeyComboCtrlC(keyID: Int): Boolean { + private fun isKeyComboCtrlC(keyID: Int): Boolean { return keyID == 46 && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown() } - fun isKeyComboCtrlA(keyID: Int): Boolean { + private fun isKeyComboCtrlA(keyID: Int): Boolean { return keyID == 30 && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown() } - fun isKeyComboCtrlZ(keyID: Int): Boolean { + private fun isKeyComboCtrlZ(keyID: Int): Boolean { return keyID == Keyboard.KEY_Z && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown() }