Skip to content

Commit 323e4d9

Browse files
committed
Bump core: Windows text fields report their events; add the Windows view GUI test
tools/gui/core_view_test.ps1 is the Windows counterpart of core_view_test.py: layout numbers, clicks on the real buttons, the focus event, and the re-flow after a resize. It derives the pixel scale from the client area, since view_example has no DPI manifest and Windows stretches it, and activates the window through its content padding for the same reason. 25 checks pass on the Windows host.
1 parent 3f2664b commit 323e4d9

3 files changed

Lines changed: 153 additions & 3 deletions

File tree

‎specs/view.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
> 状态:**已实施**(core 2026-09-25:`View` / `Label` / `Button` / `TextField` / `ImageView`,
44
> Absolute + Row / Column 布局,`Window::GetContentView()`;macOS 在真机上跑过
5-
> `tools/gui/core_view_test.py`,Windows 编译并启动过,Linux 只做了语法检查,见 §9)
5+
> `tools/gui/core_view_test.py`,Windows 跑过 `core_view_test.ps1`,Linux 只做了语法检查,见 §9)
66
> 适用范围:`core/src/view.h`、`core/src/window.h` 的 `GetContentView`、
77
> `foundation/geometry.h` 的 `EdgeInsets`、`foundation/id_allocator.h` 的 tag 20–24、
88
> `foundation/handle_table.h` 的基类链、六个平台目录的 `view_*` 文件、codegen 的类继承支持
@@ -594,7 +594,7 @@ Windows 后续可像 `Menu` 一样加 `ViewBackend::WinUI3`,本文不设计它
594594
| 平台 | 状态 |
595595
|---|---|
596596
| macOS | 编译、单测、`view_example` 真机运行;GUI 测试的布局断言 12 项全过。点击和缩放两段需要辅助功能权限,在有权限的终端里跑 |
597-
| Windows | 在 Windows 主机上用 MSVC 编译并启动了 `view_example`,布局输出与 macOS 一致;未做点击测试 |
597+
| Windows | MSVC 编译;`tools/gui/core_view_test.ps1` 在真机桌面上 25 项全过(布局、点击、焦点事件、缩放后重排) |
598598
| Linux | 已写(GTK 3),`clang++ -fsyntax-only` 通过,未链接未运行 |
599599
| Android / iOS / OHOS | 桩:`IsSupported()` 为 false |
600600

‎tools/gui/core_view_test.ps1‎

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# GUI test (Windows) of core's view_example (C++): a sign-in form of native controls
2+
# (Label, TextField, Button) laid out by a Column and a Row. Checks the layout the example
3+
# prints, clicks the real buttons and asserts on the status lines, then resizes the window
4+
# and checks the re-flow. The Windows counterpart of core_view_test.py.
5+
# Build first (desktop session):
6+
# cmake -S $RemoteWorkspace\core -B $RemoteScratch\core-build -G "Visual Studio 17 2022" -A x64
7+
# cmake --build $RemoteScratch\core-build --config Debug --target view_example
8+
# Run (remote-hosts skill): remote.sh <host> setup; remote.sh <host> desktop tools/gui/core_view_test.ps1 120
9+
# $env:VIEW_EXAMPLE overrides the executable. No keyboard input. ASCII only.
10+
$ErrorActionPreference = "Stop"
11+
. "$PSScriptRoot\env.ps1"; . "$PSScriptRoot\winput.ps1"; . "$PSScriptRoot\guiapp.ps1"
12+
Start-Result "$RemoteScratch\core_view_test.result.txt"
13+
14+
# The harness only hands back a console app's output when it exits; this test reads the
15+
# layout lines while the app runs, so it collects them itself.
16+
Add-Type -TypeDefinition @"
17+
using System.Collections.Concurrent;
18+
using System.Diagnostics;
19+
public static class ViewTestSink {
20+
public static ConcurrentQueue<string> Lines = new ConcurrentQueue<string>();
21+
public static void Attach(Process p) {
22+
p.OutputDataReceived += (s, e) => { if (e.Data != null) Lines.Enqueue(e.Data); };
23+
p.BeginOutputReadLine();
24+
}
25+
}
26+
"@
27+
28+
$Content = @(360, 220); $Padding = 16; $Spacing = 8
29+
30+
function Get-Lines { @([ViewTestSink]::Lines.ToArray()) }
31+
32+
# {label -> @(x, y, w, h)} from the LAST "[view] layout" block (logical points, root-relative).
33+
function Get-Layout {
34+
$out = @{}
35+
foreach ($l in Get-Lines) {
36+
if ($l -match '^\[view\] layout (\w+) (\S+) (\S+) (\S+) (\S+)') {
37+
if ($Matches[1] -eq "root") { $out = @{} }
38+
$out[$Matches[1]] = @([double]$Matches[2], [double]$Matches[3], [double]$Matches[4], [double]$Matches[5])
39+
}
40+
}
41+
$out
42+
}
43+
44+
# Callers wrap it in @(): PowerShell unrolls a one-element array into a plain string.
45+
function Get-Statuses { @(Get-Lines | ? { $_ -like "``[view``] status: *" } | % { $_.Substring(15) }) }
46+
47+
function Near([string]$What, [double]$Actual, [double]$Expected, [double]$Tolerance = 2) {
48+
Check $What ([math]::Abs($Actual - $Expected) -le $Tolerance) "$Actual vs $Expected"
49+
}
50+
51+
# Screen centre (physical) of a control, from its root-relative logical frame. The scale
52+
# comes from the client area against the root's logical width, not from the window's DPI:
53+
# the example has no DPI manifest, so Windows reports 96 dpi and stretches the window.
54+
function Get-ControlCenter($Win, $Layout, [string]$Name) {
55+
$f = $Layout[$Name]
56+
$scale = $Win.ClientW / $Layout["root"][2]
57+
@([int]($Win.ClientX + ($f[0] + $f[2] / 2) * $scale), [int]($Win.ClientY + ($f[1] + $f[3] / 2) * $scale))
58+
}
59+
60+
# Clicks a control after lifting the window over whatever else is on the desktop.
61+
function Invoke-ControlClick($App, [string]$Name, $Layout) {
62+
$w = Get-Win $App "Sign in"
63+
if (-not $w) { throw "the 'Sign in' window is gone" }
64+
if (-not $Layout.ContainsKey($Name) -or -not $Layout.ContainsKey("root")) { throw "no layout for $Name" }
65+
[WInput]::Raise($w.Hwnd)
66+
Pause 0.3
67+
$point = Get-ControlCenter $w $Layout $Name
68+
Say "click $Name at $($point -join ',') (client $($w.ClientX),$($w.ClientY) $($w.ClientW)x$($w.ClientH))"
69+
Invoke-Click $App $point
70+
}
71+
72+
Assert-Idle
73+
$exe = if ($env:VIEW_EXAMPLE) { $env:VIEW_EXAMPLE } else { "$RemoteScratch\core-build\examples\view_example\Debug\view_example.exe" }
74+
if (-not (Test-Path $exe)) { throw "$exe is missing; build it first" }
75+
$psi = New-Object System.Diagnostics.ProcessStartInfo $exe
76+
$psi.UseShellExecute = $false; $psi.CreateNoWindow = $true; $psi.RedirectStandardOutput = $true
77+
$proc = [System.Diagnostics.Process]::Start($psi)
78+
[ViewTestSink]::Attach($proc)
79+
$app = @{ Proc = $proc; Log = $null; Stdout = $null }
80+
Pause 3
81+
try {
82+
$win = Get-Win $app "Sign in"
83+
if (-not $win) { throw "no 'Sign in' window: $((Get-Wins $app | % { $_.Title }) -join ', ')" }
84+
[WInput]::Move($win.Hwnd, 120, 120)
85+
Pause 0.5
86+
$win = Get-Win $app "Sign in"
87+
# Activate by clicking the root's top padding: the default spot on the caption assumes
88+
# the reported 96 dpi, but the stretched window's caption buttons are 1.5x wider there
89+
# and that click would land on Minimize.
90+
Invoke-Activate $app $win 4
91+
92+
$layout = Get-Layout
93+
$names = @("root", "heading", "name", "password", "status", "actions", "clear", "sign_in")
94+
Check "layout printed for every control" (@($names | ? { -not $layout.ContainsKey($_) }).Count -eq 0) (($layout.Keys | Sort-Object) -join ", ")
95+
Near "root is the content area (w)" $layout["root"][2] $Content[0]
96+
Near "root is the content area (h)" $layout["root"][3] $Content[1]
97+
Near "client area has the root's aspect" ($win.ClientH / $win.ClientW) ($Content[1] / $Content[0]) 0.01
98+
Near "heading starts at the padding" $layout["heading"][1] $Padding
99+
Near "heading spans the padded width" $layout["heading"][2] ($Content[0] - 2 * $Padding)
100+
$h = $layout["heading"]; $n = $layout["name"]
101+
Near "name follows the heading" $n[1] ($h[1] + $h[3] + $Spacing)
102+
Near "password follows name" $layout["password"][1] ($n[1] + $n[3] + $Spacing)
103+
Check "a text field has a height" ($n[3] -gt 10) "$($n[3])"
104+
Near "actions row is 32 high (preferred size)" $layout["actions"][3] 32
105+
$s = $layout["sign_in"]; $c = $layout["clear"]
106+
Near "sign in hugs the trailing edge" ($s[0] + $s[2]) ($Content[0] - $Padding)
107+
Near "clear sits before sign in, spaced" ($c[0] + $c[2] + $Spacing) $s[0]
108+
Check "buttons have an intrinsic width" (($s[2] -gt 40) -and ($s[2] -lt 140)) "$($s[2])"
109+
110+
# Act: "Sign in" with an empty username is refused and focuses the username field.
111+
Invoke-ControlClick $app "sign_in" $layout
112+
Pause 1.0
113+
$st = @(Get-Statuses)
114+
Check "empty sign-in is refused" ($st.Count -gt 0 -and $st[-1] -eq "A username is required.") ($st -join " | ")
115+
Check "the refused sign-in focused the username field" (@(Get-Lines | ? { $_ -eq "[view] focused: name" }).Count -gt 0)
116+
117+
# "Clear" resets the status.
118+
Invoke-ControlClick $app "clear" (Get-Layout)
119+
Pause 1.0
120+
$st = @(Get-Statuses)
121+
Check "clear resets the status" ($st.Count -gt 0 -and $st[-1] -eq "Enter your credentials.") ($st -join " | ")
122+
123+
# Resize: the Column and the Row re-flow to the new width.
124+
$win = Get-Win $app "Sign in"
125+
$px = $win.ClientW / $Content[0]
126+
$w = $win.Right - $win.Left; $hh = $win.Bottom - $win.Top
127+
[WInput]::SetBounds($win.Hwnd, $win.Left, $win.Top, $w + [int](120 * $px), $hh + [int](60 * $px))
128+
Pause 1.0
129+
$after = Get-Layout
130+
Near "root grew with the window (w)" $after["root"][2] ($Content[0] + 120)
131+
Near "root grew with the window (h)" $after["root"][3] ($Content[1] + 60)
132+
Near "heading re-stretched" $after["heading"][2] ($Content[0] + 120 - 2 * $Padding)
133+
$s2 = $after["sign_in"]
134+
Near "sign in followed the trailing edge" ($s2[0] + $s2[2]) ($Content[0] + 120 - $Padding)
135+
Near "button width unchanged by the resize" $s2[2] $s[2]
136+
137+
# Look again: the button is still where the layout says after the re-flow.
138+
Invoke-ControlClick $app "sign_in" $after
139+
Pause 1.0
140+
$st = @(Get-Statuses)
141+
Check "sign in still answers after the re-flow" ($st.Count -ge 3 -and $st[-1] -eq "A username is required.") ($st -join " | ")
142+
} catch {
143+
Check "ran to the end" $false "$_"
144+
} finally {
145+
Stop-Process -Id $proc.Id -ErrorAction SilentlyContinue
146+
Pause 1
147+
}
148+
Get-Lines | ? { $_ -notlike "``[view``] layout *" } | % { Say "app: $_" }
149+
Say "$script:Failures failure(s)"
150+
exit $script:Failures

0 commit comments

Comments
 (0)