Skip to content

fix(tao): follow the caret with the Windows IME candidate window - #573

Draft
kdroidFilter wants to merge 1 commit into
nucleus-2.5from
fix/558-windows-ime-caret-rect
Draft

fix(tao): follow the caret with the Windows IME candidate window#573
kdroidFilter wants to merge 1 commit into
nucleus-2.5from
fix/558-windows-ime-caret-rect

Conversation

@kdroidFilter

Copy link
Copy Markdown
Collaborator

Windows half of #558. Linux is untouched — the issue stays open for the GTK / Wayland text-input side.

Summary

  • WindowsTaoPlatformContext now overrides startInputMethod the way the macOS host does: it keeps an input session for the focused BasicTextField and pushes focusedRectInRoot() to IMM32. Compose's default is awaitCancellation() with no IME spot, which is why the candidate list — and the OS-drawn composition string — sat at the client area's top-left instead of on the caret.
  • New platform/windows/ime.rs exposes nativeSetImeRect, setting both IMM32 forms. Tao's own set_ime_position only sets a COMPOSITIONFORM, which positions the inline composition window; the candidate list is placed from the CANDIDATEFORM. The caret box is passed as a CFS_EXCLUDE area so the candidate list never covers the text being edited, and both forms anchor at the caret's top-left (same as winit and as Tao's set_ime_position_physical) so the composition string stays on the caret's line.
  • nativeCancelImeComposition drops any in-flight composition when the input session ends, so a pending candidate window does not linger over unfocused content nor commit into whatever gains focus next.
  • The caret rect is clamped to textClippingRectInRoot(). It is root-relative and goes negative once the field scrolls out of the viewport, which would fling the candidate window off the window. A clamp rather than Rect.intersect + overlaps, because a caret rect can legitimately be zero-width and no overlap test accepts a degenerate rect.
  • Adds the Win32_UI_Input_Ime feature to the windows crate.

No coordinate conversion is needed: the scene covers the whole client area — the custom title bar is drawn inside it and reports no platform inset — so scene-root physical pixels are already client pixels. Both new externs are Java→native only, so no GraalVM reachability metadata is required.

Also refreshes two example API baselines that had drifted (Compose lambda name hashes in rect-stress-demo, plus a first dump for widget-demo). Unrelated to the fix, carried along from an apiDump.

Test plan

  • cargo build --release --target x86_64-pc-windows-msvc clean, no new warnings
  • build.bat produces win32-x64 + win32-aarch64 DLLs; both JNI symbols exported
  • compileKotlin, ktlintCheck, detekt, apiCheck pass
  • tao-demo launches without an UnsatisfiedLinkError
  • Manual, with a CJK IME installed (Microsoft Pinyin or Japanese IME): focus a BasicTextField in tao-demo, type nihao / konnichiha — the candidate window sits under the caret and follows it when the window moves or resizes
  • Manual: the underlined composition string renders on the caret's line, not one line below it
  • Manual: start a composition, click into another field — composition and candidate window disappear, and the pending text is not inserted into the new field
  • Manual: scroll the focused field out of the viewport mid-composition — the candidate window stays clamped to the visible text region

Marked draft: the IMM32 behaviour above still needs a run on a machine with a CJK IME installed.

`PlatformContext.startInputMethod` was implemented only on the macOS Tao
host. Compose's default is `awaitCancellation()` with no IME spot, so on
Windows the candidate list, and the OS-drawn composition string, stayed
wherever the native window default put them — typically the client area's
top-left corner — instead of following the focused field.

`WindowsTaoPlatformContext` now keeps an input session for the focused
`BasicTextField` and pushes `focusedRectInRoot()` to IMM32 through the new
`nativeSetImeRect`. Tao's own `set_ime_position` only sets a
`COMPOSITIONFORM`, which positions the inline composition window; the
candidate list is placed from the `CANDIDATEFORM`, so we set both — the
caret box becomes a `CFS_EXCLUDE` area so the candidate list never covers
the text being edited.

The caret rect is clamped to `textClippingRectInRoot()`: it is root-relative
and goes negative once the field scrolls out of the viewport, which would
fling the candidate window off the window. A clamp rather than
`Rect.intersect`, because a caret rect can legitimately be zero-width and no
overlap test accepts a degenerate rect.

`nativeCancelImeComposition` drops any in-flight composition when the session
ends, so a pending candidate window does not linger over unfocused content
nor commit into whatever gains focus next.

No coordinate conversion is needed: the scene covers the whole client area —
the custom title bar is drawn inside it and reports no platform inset — so
scene-root physical pixels are already client pixels.

Linux is unchanged; #558 stays open for the GTK / Wayland text-input side.

Also refreshes two example API baselines that had drifted (Compose lambda
name hashes in rect-stress-demo, plus a first dump for widget-demo).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant