Merge pull request #162 from springmin/ohos-ime-window-offset

ohos: refresh the IME cursor offset with the window
This commit is contained in:
Tomás Romero authored and GitHub committed 2026-11-04 22:37:24 +01:00
commit d6f079cbf2
1 file changed
+48 -20
@@ -8,6 +8,8 @@ import { window } from '@kit.ArkUI';
import { util } from '@kit.ArkTS';
const LOG_TAG = 'FlectarOhos';
// The XComponent, as both its native id and its component id.
const SURFACE_ID = 'flectar_mail_surface';
// One document is copied for the Files pane, whose transfers stop here.
// Several are bounded by the free space alone, which a copy never takes
// below the reserve. The same limits apply on Android and iOS.
@@ -63,8 +65,11 @@ struct Index {
private imeAttachPending: boolean = false;
private imePassword: boolean | undefined = undefined;
private pendingImePassword: boolean | undefined = undefined;
private windowOffsetX: number = 0;
private windowOffsetY: number = 0;
private observedWindow: window.Window | undefined = undefined;
// Moving the window moves the surface on the screen without a layout pass.
private windowRectHandler = (_options: window.RectChangeOptions): void => {
this.applyImeCursor();
};
private appBackgroundHandler: () => void = (): void => {
this.nativeModule?.suspendPreview?.();
};
@@ -95,7 +100,7 @@ struct Index {
onPageShow(): void {
this.applySystemColorScheme(this.colorMode);
this.updateWindowOffset();
this.observeWindow();
this.refreshClipboard();
}
@@ -130,6 +135,7 @@ struct Index {
(error as BusinessError).message);
}
this.systemPasteboard = undefined;
this.releaseWindowObserver();
this.imeAttached = false;
this.imeAttachPending = false;
this.imePassword = undefined;
@@ -140,12 +146,17 @@ struct Index {
build(): void {
Column() {
XComponent({
id: 'flectar_mail_surface',
id: SURFACE_ID,
type: XComponentType.SURFACE,
libraryname: 'flectar_mail_ohos',
})
.id(SURFACE_ID)
.width('100%')
.height('100%')
// Maximizing, restoring and split screen lay the surface out again.
.onAreaChange((): void => {
this.applyImeCursor();
})
.focusable(true)
.defaultFocus(true)
.onLoad((context?: object): void => {
@@ -218,7 +229,7 @@ struct Index {
},
});
this.applySystemColorScheme(this.colorMode);
this.updateWindowOffset();
this.observeWindow();
this.installClipboardListener();
}
@@ -529,15 +540,20 @@ struct Index {
return;
}
const cursor = this.pendingImeCursor;
const key = this.imeCursorKey(cursor);
if (key === this.lastImeCursor) {
return;
}
const rect = this.imeCursorKey(cursor);
try {
// CursorInfo is in px on the screen, while Slint reports the caret in vp
// relative to the surface. Offset it by where the surface is on the
// screen now, which follows window moves, maximizing and split screen.
const ui = this.getUIContext();
const origin = ui.getComponentUtils().getRectangleById(SURFACE_ID).screenOffset;
const key = `${origin.x},${origin.y},${rect}`;
if (key === this.lastImeCursor) {
return;
}
const px: inputMethod.CursorInfo = {
left: this.windowOffsetX + ui.vp2px(cursor.left),
top: this.windowOffsetY + ui.vp2px(cursor.top),
left: origin.x + ui.vp2px(cursor.left),
top: origin.y + ui.vp2px(cursor.top),
width: ui.vp2px(cursor.width),
height: ui.vp2px(cursor.height),
};
@@ -545,7 +561,7 @@ struct Index {
// Only remember a rectangle that is still the latest one. An older
// request completing late must not overwrite a newer caret.
const current = this.pendingImeCursor;
if (current !== undefined && this.imeCursorKey(current) === key) {
if (current !== undefined && this.imeCursorKey(current) === rect) {
this.lastImeCursor = key;
this.imeCursorRetries = 0;
} else {
@@ -558,7 +574,7 @@ struct Index {
// as applied; retry the current one shortly, otherwise identical
// updates would be deduplicated forever.
const current = this.pendingImeCursor;
if (current === undefined || this.imeCursorKey(current) !== key) {
if (current === undefined || this.imeCursorKey(current) !== rect) {
this.applyImeCursor();
return;
}
@@ -594,21 +610,33 @@ struct Index {
}
}
// CursorInfo is in absolute physical screen coordinates. The Slint caret
// rectangle is relative to the XComponent, so add the window origin.
private async updateWindowOffset(): Promise<void> {
// Follow the app window once, so moving it re-places the IME candidate.
private async observeWindow(): Promise<void> {
if (this.observedWindow !== undefined) {
return;
}
try {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
const appWindow = await window.getLastWindow(context);
const rect = appWindow.getWindowProperties().windowRect;
this.windowOffsetX = rect.left;
this.windowOffsetY = rect.top;
if (this.observedWindow !== undefined) {
return;
}
this.observedWindow = appWindow;
appWindow.on('windowRectChange', this.windowRectHandler);
} catch (error) {
hilog.error(0x0000, LOG_TAG, 'could not read the window origin: %{public}s',
hilog.error(0x0000, LOG_TAG, 'could not observe the window: %{public}s',
(error as BusinessError).message);
}
}
private releaseWindowObserver(): void {
if (this.observedWindow === undefined) {
return;
}
this.observedWindow.off('windowRectChange', this.windowRectHandler);
this.observedWindow = undefined;
}
private applySystemColorScheme(mode: number): void {
try {
let colorMode = mode;