From b91a3cd63815fd34af517bcf879965ee9726cee4 Mon Sep 17 00:00:00 2001 From: Jimmy Chang Date: Mon, 10 Aug 2026 23:51:33 +0800 Subject: [PATCH] Show input method composition in text fields Text being composed by an input method was invisible until it was committed, so typing anything through an IME looked like nothing was happening. Take the composition state the engine now reports and draw it at the caret, underlined to mark it as uncommitted, and tell the engine where that caret is so the candidate window can follow it. A single-line field selects all of its text when focused; collapse that to the end of the selection once composing starts, since that is where the text will be inserted and where the candidate window belongs. --- src/Classes/EditControl.lua | 35 +++++++++++++++++++++++++++++++++++ src/Launch.lua | 9 +++++++++ src/Modules/Main.lua | 8 ++++++++ 3 files changed, 52 insertions(+) diff --git a/src/Classes/EditControl.lua b/src/Classes/EditControl.lua index 97ce2c92fe1..4f3817e2f26 100644 --- a/src/Classes/EditControl.lua +++ b/src/Classes/EditControl.lua @@ -289,6 +289,25 @@ function EditClass:Draw(viewPort, noTooltip) local marginL = textX - x - 2 local marginR = self.controls.scrollBarV:IsShown() and 14 or 0 local marginB = self.controls.scrollBarH:IsShown() and 14 or 0 + -- Focusing a single-line field selects all of it. Typing would replace + -- that selection, so drop it as soon as an input method starts composing, + -- which also puts the caret where the composition belongs. + if self.hasFocus and main.imePreedit and main.imePreedit ~= "" and self.sel and self.sel ~= self.caret then + -- Collapse to the end of the selection: composed text is appended + -- there, so the caret and the candidate window belong there too. + self.caret = m_max(self.caret, self.sel) + self.sel = nil + end + local viewOriginX, viewOriginY = textX, textY + if self.hasFocus and SetIMECaretRect and not self.lineHeight then + -- Keep the input method informed even before composing starts, + -- otherwise the candidate window opens at a stale position. A + -- selection collapses to its end, which is where typing continues. + local caretPos = self.sel and m_max(self.caret, self.sel) or self.caret + local head = self.buf:sub(1, caretPos - 1) + SetIMECaretRect(viewOriginX + DrawStringWidth(textHeight, self.font, head) - self.controls.scrollBarH.offset, + viewOriginY, 1, textHeight) + end SetViewport(textX, textY, width - 4 - marginL - marginR, height - 4 - marginB) if not self.hasFocus then if self.buf == '' and self.placeholder then @@ -413,6 +432,22 @@ function EditClass:Draw(viewPort, noTooltip) DrawString(textX, textY, "LEFT", textHeight, self.font, pre) end textX = textX + DrawStringWidth(textHeight, self.font, pre) + if self.hasFocus then + -- Text the input method is still composing sits at the caret, + -- underlined to show it isn't committed yet. + local preedit = main.imePreedit + if preedit and preedit ~= "" then + local preeditWidth = DrawStringWidth(textHeight, self.font, preedit) + DrawString(textX, textY, "LEFT", textHeight, self.font, self.textCol .. preedit) + SetDrawColor(self.textCol) + DrawImage(nil, textX, textY + textHeight - 1, preeditWidth, 1) + textX = textX + preeditWidth + end + if SetIMECaretRect then + -- Refine the position now that the composition width is known. + SetIMECaretRect(viewOriginX + textX, viewOriginY + textY, 1, textHeight) + end + end if self.protected and #post > 0 then DrawString(textX, textY, "LEFT", textHeight, self.font, string.rep(protected_replace, #post)) else diff --git a/src/Launch.lua b/src/Launch.lua index 2453884dafe..0c7aae90554 100644 --- a/src/Launch.lua +++ b/src/Launch.lua @@ -197,6 +197,15 @@ function launch:OnChar(key) end end +function launch:OnPreedit(text, caret) + if self.main and self.main.OnPreedit then + local errMsg = PCall(self.main.OnPreedit, self.main, text, caret) + if errMsg then + self:ShowErrMsg("In 'OnPreedit': %s", errMsg) + end + end +end + function launch:OnSubCall(func, ...) if func == "UpdateProgress" then self.updateProgress = string.format(...) diff --git a/src/Modules/Main.lua b/src/Modules/Main.lua index 0e7b0d48c1b..818a732fb0a 100644 --- a/src/Modules/Main.lua +++ b/src/Modules/Main.lua @@ -467,6 +467,14 @@ function main:OnChar(key) t_insert(self.inputEvents, { type = "Char", key = key }) end +-- Text an input method is still composing. It isn't committed yet, so it is +-- held as state and drawn by whichever control has focus rather than being +-- queued as an input event. +function main:OnPreedit(text, caret) + self.imePreedit = text + self.imePreeditCaret = caret +end + function main:SetMode(newMode, ...) self.newMode = newMode self.newModeArgs = {...}