diff --git a/assets/keylayout/ko_kr_3set_390.ime b/assets/keylayout/ko_kr_3set_390.ime new file mode 100644 index 000000000..78e7f5123 --- /dev/null +++ b/assets/keylayout/ko_kr_3set_390.ime @@ -0,0 +1,377 @@ +let states = {"keylayouts":[[""],[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +["\u110F",")"], +["\u11C2","\u11BD"], +["\u11BB","@"], +["\u11B8","#"], +["\u116D","$"], +["\u1172","%"], +["\u1163","^"], +["\u1168","&"], +["\u1174","*"], +["\u116E","("], +["*"], +["#"], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +["\u11BC","\u11AE"], +["\u116E","!"], +["\u1166","\u11B1"], +["\u1175","\u11B0"], +["\u1167","\u11BF"], +["\u1161","\u11A9"], +["\u1173","/"], +["\u1102","'"], +["\u1106","8"], +["\u110B","4"], +["\u1100","5"], +["\u110C","6"], +["\u1112","1"], +["\u1109","0"], +["\u110E","9"], +["\u1111",">"], +["\u11BA","\u11C1"], +["\u1162","\u1164"], +["\u11AB","\u11AD"], +["\u1165",";"], +["\u1103","7"], +["\u1169","\u11B6"], +["\u11AF","\u11C0"], +["\u11A8","\u11B9"], +["\u1105","<"], +["\u11B7","\u11BE"], +[",","2"], +[".","3"], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[" "], +[undefined], +[undefined], +[undefined], +["\n"], +["\x08"], +["`","~"], +["-","_"], +["=","+"], +["[","{"], +["]","}"], +["\\","|"], +["\u1107",":"], +["\u1110",'"'], +["\u1169","?"], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined], +[undefined] +], +"code":0, +"buf":[]} +let reset = () => { + states.code = 0 + states.buf = [] +} +let inRange = (s,a,b) => (a <= s && s <= b) +let isHangul = (s) => (s === undefined) ? false : inRange(s.charCodeAt(0), 0x1100, 0x11C2) +let isChoseong = (s) => (s === undefined) ? false : inRange(s.charCodeAt(0), 0x1100, 0x1112) +let isJungseong = (s) => (s === undefined) ? false : inRange(s.charCodeAt(0), 0x1161, 0x1175) +let isJongseong = (s) => (s === undefined) ? false : inRange(s.charCodeAt(0), 0x11A8, 0x11C2) +let isChoseongDigraph = (s) => (s === undefined) ? false : ([0x1100, 0x1103, 0x1107, 0x1109, 0x110C].includes(s.charCodeAt(0))) +let isJungseongDigraph1 = (s) => (s === undefined) ? false : ([0x1169, 0x116E].includes(s.charCodeAt(0))) +let isJungseongDigraphO = (s) => (s === undefined) ? false : ([0x1161, 0x1162, 0x1175].includes(s.charCodeAt(0))) +let isJungseongDigraphU = (s) => (s === undefined) ? false : ([0x1165, 0x1166, 0x1175].includes(s.charCodeAt(0))) +let isJungseongDigraphEU = (s) => (s === undefined) ? false : ([0x1175].includes(s.charCodeAt(0))) +let isJongseongDigraphG = (s) => (s === undefined) ? false : ([0x11A8, 0x11BA].includes(s.charCodeAt(0))) +let isJongseongDigraphN = (s) => (s === undefined) ? false : ([0x11BD, 0x11C2].includes(s.charCodeAt(0))) +let isJongseongDigraphR = (s) => (s === undefined) ? false : ([0x11A8, 0x11B7, 0x11B8, 0x11BA, 0x11C0, 0x11C1, 0x11C2].includes(s.charCodeAt(0))) +let isJongseongDigraphB = (s) => (s === undefined) ? false : ([0x11BA].includes(s.charCodeAt(0))) +let choseongDigraphs = {"\u1100":"\u1101", "\u1103":"\u1104", "\u1107":"\u1108", "\u1109":"\u110A", "\u110C":"\u110D"} +let jungseongDigraphsO = {"\u1161":"\u116A", "\u1162":"\u116B", "\u1175":"\u116C"} +let jungseongDigraphsU = {"\u1165":"\u116F", "\u1166":"\u1170", "\u1175":"\u1171"} +let jungseongDigraphsEU = {"\u1175":"\u1174"} +let jongseongDigraphsG = {"\u11A8":"\u11A9", "\u11BA":"\u11AA"} +let jongseongDigraphsN = {"\u11BD":"\u11AC", "\u11C2":"\u11AD"} +let jongseongDigraphsR = {"\u11A8":"\u11B0", "\u11B7":"\u11B1", "\u11B8":"\u11B2", "\u11BA":"\u11B3", "\u11C0":"\u11B4", "\u11C1":"\u11B5", "\u11C2":"\u11B6"} +let jongseongDigraphsB = {"\u11BA":"\u11B9"} +let bufAssemble = () => { + if (states.buf[0] === undefined && states.buf[1] === undefined && states.buf[2] === undefined) + return '' + else if (states.buf[1] === undefined && isHangul(states.buf[0])) + return [states.buf[0], "\u1160", states.buf[2]].join('') + else if (states.buf[0] === undefined && isHangul(states.buf[1])) + return ["\u115F", states.buf[1], states.buf[2]].join('') + else if (isHangul(states.buf[2]) && states.buf[0] === undefined && states.buf[1] === undefined ) + return ["\u115F", "\u1160", states.buf[2]].join('') + else + return states.buf.join('') +} +Object.freeze({"n":"세벌식 3-90","states":states, +// return: [displayed output, composed output] +"accept":(keycodes,shiftin,altgrin)=>{ + let layer = 1*shiftin// + 2*altgrin + states.code = 1 + + let purekeys = keycodes.filter(it => ( + inRange(it,7,18) || // numeric + inRange(it,29,56) || // alph + it == 62 || // space + inRange(it,66,76) || // symbols + inRange(it,144,163) // numpad + )) + let headkey = purekeys[0] + + let s = states.keylayouts[headkey][layer] + let bufIndex = isJungseong(s) ? 1 : isJongseong(s) ? 2 : 0 + + console.log(`IME accepting keycodes ${keycodes}`) + + if (isHangul(s)) { + // ㄲ ㄸ ㅃ ㅆ ㅉ + if (0 == bufIndex && isChoseongDigraph(states.buf[0]) && isChoseongDigraph(s)) { + states.buf[0] = choseongDigraphs[s] + } + // ㅘ ㅙ ㅚ + else if (1 == bufIndex && "\u1169" == states.buf[1] && isJungseongDigraphO(s)) { + states.buf[1] = jungseongDigraphsO[s] + } + // ㅝ ㅞ ㅟ + else if (1 == bufIndex && "\u116E" == states.buf[1] && isJungseongDigraphU(s)) { + states.buf[1] = jungseongDigraphsU[s] + } + // ㅢ + else if (1 == bufIndex && "\u1173" == states.buf[1] && isJungseongDigraphEU(s)) { + states.buf[1] = jungseongDigraphsEU[s] + } + // ㄲ ㄳ + else if (2 == bufIndex && "\u11A8" == states.buf[2] && isJongseongDigraphG(s)) { + states.buf[2] = jongseongDigraphsG[s] + } + // ㄵ ㄶ + else if (2 == bufIndex && "\u11AB" == states.buf[2] && isJongseongDigraphN(s)) { + states.buf[2] = jongseongDigraphsN[s] + } + // ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ + else if (2 == bufIndex && "\u11AF" == states.buf[2] && isJongseongDigraphR(s)) { + states.buf[2] = jongseongDigraphsR[s] + } + // ㅄ + else if (2 == bufIndex && "\u11B8" == states.buf[2] && isJongseongDigraphB(s)) { + states.buf[2] = jongseongDigraphsB[s] + } + // key inputs that bufIndex collides (end compose and accept incoming char as a new char state) + else if (states.buf[bufIndex] !== undefined) { + let sendout = bufAssemble(); reset() + states.buf[bufIndex] = s + console.log(`sending out: ${sendout}`) + return [bufAssemble(), sendout] + } + else { + states.buf[bufIndex] = s + console.log(`assembling: ${bufAssemble()}`) + } + + return [bufAssemble(), ""] + } + else { + let sendout = bufAssemble() + s; reset() + console.log(`sending out: ${sendout}`) + return [bufAssemble(), sendout] + } +}, +"end":()=>{ + console.log(`end composing`) + reset() + return bufAssemble() +}, +"reset":()=>{ reset() }, +"composing":()=>(states.code!=0) +}) \ No newline at end of file diff --git a/src/net/torvald/terrarum/DefaultConfig.kt b/src/net/torvald/terrarum/DefaultConfig.kt index 93764ef55..15d6a9a73 100644 --- a/src/net/torvald/terrarum/DefaultConfig.kt +++ b/src/net/torvald/terrarum/DefaultConfig.kt @@ -100,7 +100,8 @@ object DefaultConfig { "fx_differential" to false, //"fx_3dlut" to false, - "basekeyboardlayout" to "us_qwerty" + "basekeyboardlayout" to "us_qwerty", + "inputmethod" to "none" // settings regarding debugger diff --git a/src/net/torvald/terrarum/gamecontroller/IME.kt b/src/net/torvald/terrarum/gamecontroller/IME.kt index 4896d212b..0f0dbe205 100644 --- a/src/net/torvald/terrarum/gamecontroller/IME.kt +++ b/src/net/torvald/terrarum/gamecontroller/IME.kt @@ -5,8 +5,16 @@ import java.io.File data class TerrarumKeyLayout( val name: String, - val symbols: Array>?, - val acceptChar: ((Int) -> String?)? = null + val symbols: Array>? +) + +data class TerrarumInputMethod( + val name: String, + // (keycodes, shiftin, altgrin) + val acceptChar: (IntArray, Boolean, Boolean) -> Pair, // Pair + val endCompose: () -> String, + val reset: () -> Unit, + val composing: () -> Boolean ) /** @@ -26,8 +34,10 @@ object IME { const val KEYLAYOUT_DIR = "assets/keylayout/" const val KEYLAYOUT_EXTENSION = "key" + const val IME_EXTENSION = "ime" private val lowLayers = HashMap() + private val highLayers = HashMap() private val context = org.graalvm.polyglot.Context.newBuilder("js") .allowHostAccess(org.graalvm.polyglot.HostAccess.NONE) @@ -40,6 +50,11 @@ object IME { printdbg(this, "Registering Low layer ${it.nameWithoutExtension.lowercase()}") lowLayers[it.nameWithoutExtension.lowercase()] = parseKeylayoutFile(it) } + + File(KEYLAYOUT_DIR).listFiles { file, s -> s.endsWith(".$IME_EXTENSION") }.forEach { + printdbg(this, "Registering High layer ${it.nameWithoutExtension.lowercase()}") + highLayers[it.nameWithoutExtension.lowercase()] = parseImeFile(it) + } } fun invoke() {} @@ -48,15 +63,22 @@ object IME { return lowLayers[name.lowercase()]!! } + fun getHighLayerByName(name: String): TerrarumInputMethod { + return highLayers[name.lowercase()]!! + } + fun getAllLowLayers(): List { return lowLayers.keys.toList() } + fun getAllHighLayers(): List { + return highLayers.keys.toList() + } private fun parseKeylayoutFile(file: File): TerrarumKeyLayout { val src = file.readText(Charsets.UTF_8) - val jsval = context.eval("js", "Object.freeze($src)") + val jsval = context.eval("js", "'use strict';Object.freeze($src)") val name = jsval.getMember("n").asString() val out = Array(256) { Array(4) { null } } for (keycode in 0L until 256L) { @@ -78,4 +100,23 @@ object IME { return TerrarumKeyLayout(name, out) } + private fun parseImeFile(file: File): TerrarumInputMethod { + val src = file.readText(Charsets.UTF_8) + val jsval = context.eval("js", "'use strict';$src") + val name = jsval.getMember("n").asString() + + + return TerrarumInputMethod(name, { it, shifted, alted -> + val a = jsval.invokeMember("accept", context.eval("js", "'${it.joinToString(",")}'.split(',')"), shifted, alted) + a.getArrayElement(0).asString() to a.getArrayElement(1).asString() + }, { + jsval.invokeMember("end").asString() + }, { + jsval.invokeMember("reset") + }, { + jsval.invokeMember("composing").asBoolean() + } + ) + } + } \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIKeyboardControlPanel.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIKeyboardControlPanel.kt index 6e9bcdfbb..e6402c0b5 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIKeyboardControlPanel.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIKeyboardControlPanel.kt @@ -138,19 +138,29 @@ class UIKeyboardControlPanel(remoCon: UIRemoCon?) : UICanvas() { private val lowLayerNames = lowLayerCodes.map { { IME.getLowLayerByName(it).name } } private val keyboardLayoutSelection = UIItemTextSelector(this, drawX + width - textSelWidth - 3, 400, lowLayerNames, lowLayerCodes.linearSearch { it == App.getConfigString("basekeyboardlayout") }!!, textSelWidth) - private val imeCodes = listOf("null", "ko_kr_2set_standard", "ko_kr_3set_390") - private val imeNames = listOf({ "$EMDASH" },{ "표준 두벌식" },{ "세벌식 3-90" }) - private val imeSelection = UIItemTextSelector(this, drawX + width - textSelWidth - 3, 440, imeNames, 0, textSelWidth) + private val imeCodes0 = IME.getAllHighLayers() + private val imeCodes = listOf("none") + IME.getAllHighLayers() + private val imeNames = listOf({"$EMDASH"}) + imeCodes0.map { { IME.getHighLayerByName(it).name } } + private val imeSelection = UIItemTextSelector(this, drawX + width - textSelWidth - 3, 440, imeNames, imeCodes.linearSearch { it == App.getConfigString("inputmethod") }!!, textSelWidth) private val keyboardTestPanel = UIItemTextLineInput(this, drawX + (width - 480) / 2 + 3, 480, 474, enableIMEButton = true, enablePasteButton = true) + private val keyboardConfigItems = listOf( + keyboardLayoutSelection, + imeSelection, + keyboardTestPanel + ) + init { keyboardLayoutSelection.selectionChangeListener = { App.setConfig("basekeyboardlayout", lowLayerCodes[it]) } + imeSelection.selectionChangeListener = { + App.setConfig("inputmethod", imeCodes[it]) + } keycaps.values.forEach { addUIitem(it) } updateKeycaps() @@ -161,9 +171,9 @@ class UIKeyboardControlPanel(remoCon: UIRemoCon?) : UICanvas() { updateKeycaps() } - addUIitem(keyboardLayoutSelection) - addUIitem(imeSelection) - addUIitem(keyboardTestPanel) +// addUIitem(keyboardLayoutSelection) +// addUIitem(imeSelection) +// addUIitem(keyboardTestPanel) } private fun resetKeyConfig() { @@ -219,6 +229,9 @@ class UIKeyboardControlPanel(remoCon: UIRemoCon?) : UICanvas() { if (keycapClicked >= 0 && controlSelected < 0) { controlPalette.update(delta) } + else { + keyboardConfigItems.forEach { it.update(delta) } + } } override fun renderUI(batch: SpriteBatch, camera: Camera) { @@ -231,14 +244,19 @@ class UIKeyboardControlPanel(remoCon: UIRemoCon?) : UICanvas() { batch.color = Color.WHITE - if (keycapClicked >= 0 && controlSelected < 0) { - controlPalette.render(batch, camera) - } App.fontGame.draw(batch, Lang["MENU_LABEL_KEYBOARD_LAYOUT"], kbx + 1, keyboardLayoutSelection.initialY) App.fontGame.draw(batch, Lang["MENU_LABEL_IME"], kbx + 1, imeSelection.initialY) + if (keycapClicked >= 0 && controlSelected < 0) { + controlPalette.render(batch, camera) + } + else { + keyboardConfigItems.forEach { it.render(batch, camera) } + } + val title = Lang["MENU_CONTROLS_KEYBOARD"] + batch.color = Color.WHITE App.fontGame.draw(batch, title, drawX.toFloat() + (width - App.fontGame.getWidth(title)) / 2, drawY.toFloat()) } @@ -252,11 +270,19 @@ class UIKeyboardControlPanel(remoCon: UIRemoCon?) : UICanvas() { override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { buttonReset.touchDown(screenX, screenY, pointer, button) + keyboardConfigItems.forEach { it.touchDown(screenX, screenY, pointer, button) } return true } override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { buttonReset.touchUp(screenX, screenY, pointer, button) + keyboardConfigItems.forEach { it.touchUp(screenX, screenY, pointer, button) } + return true + } + + override fun scrolled(amountX: Float, amountY: Float): Boolean { + super.scrolled(amountX, amountY) + keyboardConfigItems.forEach { it.scrolled(amountX, amountY) } return true } diff --git a/src/net/torvald/terrarum/ui/UIItemTextLineInput.kt b/src/net/torvald/terrarum/ui/UIItemTextLineInput.kt index cca6fc765..25b331afc 100644 --- a/src/net/torvald/terrarum/ui/UIItemTextLineInput.kt +++ b/src/net/torvald/terrarum/ui/UIItemTextLineInput.kt @@ -8,10 +8,13 @@ import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.glutils.FrameBuffer import net.torvald.terrarum.* +import net.torvald.terrarum.gamecontroller.IME import net.torvald.terrarum.gamecontroller.IngameController +import net.torvald.terrarum.gamecontroller.TerrarumInputMethod import net.torvald.terrarum.utils.Clipboard import net.torvald.terrarumsansbitmap.gdx.CodepointSequence import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack +import java.util.ArrayList import kotlin.streams.toList data class InputLenCap(val count: Int, val unit: CharLenUnit) { @@ -87,7 +90,11 @@ class UIItemTextLineInput( true ) - var isActive = true + var isActive: Boolean = true + set(value) { + resetIME() + field = value + } var cursorX = 0 var cursorDrawScroll = 0 @@ -113,6 +120,21 @@ class UIItemTextLineInput( get() = buttonsShown > 0 && relativeMouseX in btn2PosX - posX until btn2PosX - posX + WIDTH_ONEBUTTON && relativeMouseY in 0 until height private var imeOn = false + private var composingView = CodepointSequence() + + private fun getIME(): TerrarumInputMethod? { + if (!imeOn) return null + + val selectedIME = App.getConfigString("inputmethod") + + if (selectedIME == "none") return null + try { + return IME.getHighLayerByName(selectedIME) + } + catch (e: NullPointerException) { + return null + } + } private fun forceLitCursor() { cursorBlinkCounter = 0f @@ -139,13 +161,14 @@ class UIItemTextLineInput( isActive = mouseUp } - // TODO cursorDrawX kerning-aware + if (App.getConfigString("inputmethod") == "none") imeOn = false // process keypresses if (isActive) { IngameController.withKeyboardEvent { (_, char, _, keycodes) -> fboUpdateLatch = true forceLitCursor() + val ime = getIME() if (keycodes.contains(Input.Keys.V) && (keycodes.contains(Input.Keys.CONTROL_LEFT) || keycodes.contains(Input.Keys.CONTROL_RIGHT))) { paste() @@ -154,18 +177,37 @@ class UIItemTextLineInput( else if (keycodes.contains(Input.Keys.C) && (keycodes.contains(Input.Keys.CONTROL_LEFT) || keycodes.contains(Input.Keys.CONTROL_RIGHT))) { copyToClipboard() } - else if (cursorX > 0 && keycodes.contains(Input.Keys.BACKSPACE)) { - cursorX -= 1 - textbuf.removeAt(cursorX) - cursorDrawX = App.fontGame.getWidth(textbuf.subList(0, cursorX)) - tryCursorForward() + else if (keycodes.contains(Input.Keys.BACKSPACE)) { + if (ime != null && composingView.size > 0) { + resetIME() + } + else if (cursorX <= 0) { + cursorX = 0 + cursorDrawX = 0 + cursorDrawScroll = 0 + } + else { + if (cursorX > 0) { + while (true) { + cursorX -= 1 + val oldCode = textbuf.removeAt(cursorX) + // continue deleting hangul pieces because of the font... + if (cursorX == 0 || (oldCode !in 0x115F..0x11FF && oldCode !in 0xD7B0..0xD7FF)) break + } + + cursorDrawX = App.fontGame.getWidth(textbuf.subList(0, cursorX)) + tryCursorForward() + } + } } else if (cursorX > 0 && keycodes.contains(Input.Keys.LEFT)) { + // TODO IME endComposing() cursorX -= 1 cursorDrawX = App.fontGame.getWidth(textbuf.subList(0, cursorX)) tryCursorForward() } else if (cursorX < textbuf.size && keycodes.contains(Input.Keys.RIGHT)) { + // TODO IME endComposing() cursorX += 1 cursorDrawX = App.fontGame.getWidth(textbuf.subList(0, cursorX)) tryCursorBack() @@ -174,7 +216,18 @@ class UIItemTextLineInput( // - literal "<" // - keysymbol that does not start with "<" (not always has length of 1 because UTF-16) else if (char != null && char[0].code >= 32 && (char == "<" || !char.startsWith("<"))) { - val codepoints = char.toCodePoints() + val shiftin = keycodes.contains(Input.Keys.SHIFT_LEFT) || keycodes.contains(Input.Keys.SHIFT_RIGHT) + val altgrin = keycodes.contains(Input.Keys.ALT_RIGHT) + + val codepoints = if (ime != null) { + val newStatus = ime.acceptChar(keycodes, shiftin, altgrin) + composingView = CodepointSequence(newStatus.first.toCodePoints()) + + newStatus.second.toCodePoints() + } + else char.toCodePoints() + + println("textinput codepoints: ${codepoints.map { it.toString(16) }.joinToString()}") if (!maxLen.exceeds(textbuf, codepoints)) { textbuf.addAll(cursorX, codepoints) @@ -191,7 +244,7 @@ class UIItemTextLineInput( } if (textbuf.size == 0) { - currentPlaceholderText = ArrayList(placeholder().toCodePoints()) + currentPlaceholderText = CodepointSequence(placeholder().toCodePoints()) } cursorBlinkCounter += delta @@ -214,13 +267,27 @@ class UIItemTextLineInput( if (!mouseDown) mouseLatched = false } - private fun String.toCodePoints() = this.codePoints().toList() + private fun String.toCodePoints() = this.codePoints().toList().filter { it > 0 } private fun toggleIME() { + if (App.getConfigString("inputmethod") == "none") { + imeOn = false + return + } + imeOn = !imeOn + + resetIME() + } + + private fun resetIME() { + getIME()?.reset?.invoke() + composingView = CodepointSequence() } private fun paste() { + resetIME() + val codepoints = Clipboard.fetch().substringBefore('\n').substringBefore('\t').toCodePoints() val actuallyInserted = arrayListOf(0) @@ -306,20 +373,20 @@ class UIItemTextLineInput( } - // draw text batch.color = if (textbuf.isEmpty()) TEXTINPUT_COL_TEXT_DISABLED else TEXTINPUT_COL_TEXT batch.draw(fbo.colorBufferTexture, posX + 2f, posY + 2f, fbo.width.toFloat(), fbo.height.toFloat()) // draw text cursor + val cursorXOnScreen = posX - cursorDrawScroll + cursorDrawX + 3 if (isActive && cursorOn) { val baseCol = if (maxLen.exceeds(textbuf, listOf(32))) TEXTINPUT_COL_TEXT_NOMORE else TEXTINPUT_COL_TEXT batch.color = baseCol.cpy().mul(0.5f,0.5f,0.5f,1f) - Toolkit.fillArea(batch, posX - cursorDrawScroll + cursorDrawX + 3, posY, 2, 24) + Toolkit.fillArea(batch, cursorXOnScreen, posY, 2, 24) batch.color = baseCol - Toolkit.fillArea(batch, posX - cursorDrawScroll + cursorDrawX + 3, posY, 1, 23) + Toolkit.fillArea(batch, cursorXOnScreen, posY, 1, 23) } // draw icon @@ -343,6 +410,19 @@ class UIItemTextLineInput( } + // compose view background + if (composingView.size > 0) { + val previewTextWidth = App.fontGame.getWidth(composingView) + val previewWindowWidth = previewTextWidth.coerceAtLeast(20) + batch.color = TEXTINPUT_COL_BACKGROUND + Toolkit.fillArea(batch, cursorXOnScreen + 2, posY + 27, previewWindowWidth, 20) + // compose view border + batch.color = Toolkit.Theme.COL_ACTIVE + Toolkit.drawBoxBorder(batch, cursorXOnScreen + 1, posY + 26, previewWindowWidth + 2, 22) + // compose view text + App.fontGame.draw(batch, composingView, cursorXOnScreen + 2 + (previewWindowWidth - previewTextWidth) / 2, posY + 27) + } + super.render(batch, camera) } @@ -353,5 +433,13 @@ class UIItemTextLineInput( fbo.dispose() } + /*private fun CodepointSequence.toJavaString(): String { + val sb = StringBuilder() + this.forEach { + sb.append(Character.toChars(it)) + } + return sb.toString() + }*/ + +} -} \ No newline at end of file