NarratorOverlayWindow

@Composable
fun ApplicationScope.NarratorOverlayWindow(initialBounds: NarratorOverlayWindowBounds, onBoundsChange: (x: Int, y: Int, width: Int, height: Int) -> Unit, modifier: Modifier = Modifier)

ゲーム画面に重ねて表示するオーバーレイ専用ウィンドウ(デスクトップアプリのみ、常時表示)。

  • WindowDecoration.Undecorated() かつ resizable = true の組み合わせで、Compose Multiplatform 独自の リサイズハンドル(androidx.compose.ui.window.UndecoratedWindowResizer)が有効になる。

  • パネル全体のドラッグで移動できるよう、detectDragGestures でウィンドウ位置を直接更新する。 androidx.compose.foundation.gestures.PointerInputChange のdragAmountはウィンドウ内のローカル座標 系での差分であり、これを毎フレーム加算してウィンドウを動かすと、ウィンドウの移動そのものが次フレームの ローカル座標の基準をずらしてしまい、位置計算がフィードバックループを起こしてガタつく(実機で確認済み)。 そのため、ドラッグ開始時のウィンドウ位置とポインタのスクリーン座標(MouseInfo.getPointerInfo)を基準に 保持し、以降は「現在のポインタのスクリーン座標との差分」から絶対位置としてウィンドウ位置を計算する。

  • タスクバーに表示させないため Window.Type.UTILITY を設定する。java.awt.Window#setType は ウィンドウが displayable になった後に呼ぶと IllegalComponentStateException を送出するため、 通常の Windowcomposable の content 内 LaunchedEffect から設定すると、環境によっては 既に displayable になっていて例外が発生しうる(実機で確認済み)。SwingWindow の init は ComposeWindow 生成直後・displayable になる前に一度だけ呼ばれることが保証されているため、 最小/最大サイズの設定とあわせてここで行う。

  • 表示ON/OFFはその他タブの「オーバーレイ設定」で切り替える(NarratorOverlayWindowHost を参照)。

  • 位置・サイズは initialBounds で復元し、変更を onBoundsChange で通知する(永続化は NarratorOverlayWindowHost が行う)。window.location を直接書き換えるドラッグ移動も、 SwingWindow が AWT の componentMoved / componentResized を WindowState へ反映するため、 WindowState を監視するだけで移動・リサイズの両方を拾える。

  • 常時最前面の詳細な制御(フォーカス連動等)は別PRで対応する。

  • alwaysOnTop = true により通常のウィンドウより手前には表示されるが、排他的フルスクリーンで 起動したゲームには他の常駐オーバーレイツールと同様に表示されないため、LMU 側をボーダーレス ウィンドウモードで起動する前提となる。

  • ゲーム画面をなるべく隠さないよう transparent = true でウィンドウ背景を透過させ、コンテンツ側 (NarratorOverlayScreen)の半透明な背景色と組み合わせている。isTransparent(transparent パラメータの実体)はウィンドウが表示済みの状態で変更すると例外を送出するが、SwingWindow の update ブロックは値に変化がない限り再設定を行わないため、固定値 true を渡す限りは ウィンドウがまだ displayable になる前の初回 update 呼び出し時にのみ設定される。