feat(focus): 完成阶段 2 通用焦点系统

This commit is contained in:
Codex
2026-10-08 18:37:34 +08:00
parent 81341fab2c
commit 255aba688f
25 changed files with 1533 additions and 5 deletions
+45 -2
View File
@@ -1,7 +1,7 @@
# 外部 UI 驱动:启动确定性 fixture,注入真实鼠标输入,并保存像素与状态诊断。
param(
[int]$Repeat = 1,
[ValidateSet('ownership', 'geometry', 'tooltip', 'tab', 'table', 'dialog', 'input-shadow')]
[ValidateSet('ownership', 'geometry', 'tooltip', 'tab', 'table', 'dialog', 'focus', 'input-shadow')]
[string[]]$OnlyScenario = @()
)
@@ -243,6 +243,27 @@ function Assert-NotRgb($pixel, [int]$r, [int]$g, [int]$b, [string]$message) {
}
}
function Assert-RegionContainsRgb([Drawing.Bitmap]$bitmap, [int]$left, [int]$top,
[int]$right, [int]$bottom, [int]$r, [int]$g, [int]$b, [string]$message) {
for ($y = $top; $y -lt $bottom; $y++) {
for ($x = $left; $x -lt $right; $x++) {
$pixel = $bitmap.GetPixel($x, $y)
if ($pixel.R -eq $r -and $pixel.G -eq $g -and $pixel.B -eq $b) { return }
}
}
throw $message
}
function Assert-RegionsDiffer([Drawing.Bitmap]$before, [Drawing.Bitmap]$after,
[int]$left, [int]$top, [int]$right, [int]$bottom, [string]$message) {
for ($y = $top; $y -lt $bottom; $y++) {
for ($x = $left; $x -lt $right; $x++) {
if ($before.GetPixel($x, $y).ToArgb() -ne $after.GetPixel($x, $y).ToArgb()) { return }
}
}
throw $message
}
function Wait-ClientRgb([IntPtr]$h, [int]$x, [int]$y, [int]$r, [int]$g, [int]$b,
[string]$capturePath, [int]$timeoutMs = 15000) {
# 某些绘制在消息批次尾才完成,因此对目标像素进行有界轮询而不是固定猜测延时。
@@ -262,7 +283,7 @@ function Wait-ClientRgb([IntPtr]$h, [int]$x, [int]$y, [int]$r, [int]$g, [int]$b,
$savedCursor = New-Object StellarXUiNative+POINT
# UI 测试会抢占前台并移动鼠标,外层 finally 必须恢复用户原来的光标位置。
[void][StellarXUiNative]::GetCursorPos([ref]$savedCursor)
$allScenarios = @('ownership', 'geometry', 'tooltip', 'tab', 'table', 'dialog', 'input-shadow')
$allScenarios = @('ownership', 'geometry', 'tooltip', 'tab', 'table', 'dialog', 'focus', 'input-shadow')
$scenarios = if ($OnlyScenario.Count -gt 0) { @($OnlyScenario) } else { $allScenarios }
$sessionRoot = Join-Path $resultRoot ((Get-Date -Format 'yyyyMMdd-HHmmss-fff') + "-$PID")
@@ -288,6 +309,28 @@ try {
'tooltip' { Move-Mouse $h 80 40; Wait-ClientRgb $h 200 61 255 255 255 (Join-Path $runDir 'tooltip-wait.png') }
'tab' { Click-Mouse $h 230 35; Click-Mouse $h 395 238; $result = Wait-Result $resultFile; if ($result.active -ne 1 -or $result.firstSelected -or -not $result.secondSelected -or $result.firstPageVisible -or -not $result.secondPageVisible) { throw 'tab uniqueness result failed' } }
'table' { Click-Mouse $h 395 268; $result = Wait-Result $resultFile; if ($result.width -le 40 -or $result.coverageRight -le $result.coverageLeft -or $result.coverageBottom -le $result.coverageTop) { throw 'table minimum width or coverage failed' } }
'focus' {
$initialFocus = Capture-Client $h (Join-Path $runDir 'focus-textbox.png')
try {
Assert-RegionContainsRgb $initialFocus 20 78 182 118 32 96 192 'TextBox focus frame was not rendered'
Click-Mouse $h 80 40
Move-Mouse $h 470 310
$buttonFocus = Capture-Client $h (Join-Path $runDir 'focus-button.png')
try {
Assert-RegionContainsRgb $buttonFocus 20 20 140 60 32 32 32 'Button focus frame was not rendered'
Assert-RegionsDiffer $initialFocus $buttonFocus 20 20 140 60 'Button focus visual did not change after pointer focus'
}
finally { $buttonFocus.Dispose() }
}
finally { $initialFocus.Dispose() }
Click-Mouse $h 405 278
$result = Wait-Result $resultFile
if (-not $result.buttonFocusObserved -or $result.tableTabStops -ne 1 -or
-not $result.activePageChildPresent -or $result.inactivePageChildPresent -or
-not $result.allSubNodesZero) {
throw 'focus tree integration result failed'
}
}
'dialog' {
Click-Mouse $h 90 40
Start-Sleep -Milliseconds 350