Skip to content

Commit 2894880

Browse files
committed
Sync with core 9d6b50d: replay the #75 flash reports too
The window_manager reports fixture gets startup-center, startup-maximize and flashes: frames sampled from process start and through maximize, restore, show after hide and an off-screen drag. It found maximize() before the first show reverting on the Flutter runner's first frame (fixed in core). All covered reports of #75-#78 now behave.
1 parent 5577de0 commit 2894880

4 files changed

Lines changed: 159 additions & 3 deletions

File tree

‎tools/gui/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ runner) lives in [`.agents/skills/`](../../.agents/skills); read
2222
| `flutter_window_title_bar_system_menu_test.ps1` | `window_title_bar_example` (#66) | Windows | End to end in a real Flutter app: a secondary click on the `DragToMoveArea` strip opens the native system menu at the pointer; its item states match the window (restored: Restore off; maximized: Move, Size, Maximize off); choosing Maximize maximizes and, from a second menu, Restore restores to the earlier width. Items are taken by their fixed order, so any display language works. Owner-guarded clicks, no keyboard. Set `TITLE_BAR_EXAMPLE_EXE` for a build outside `$RemoteWorkspace`. |
2323
| `flutter_window_title_bar_close_test.ps1` | `window_title_bar_example` (#65) | Windows | End to end in a real Flutter app: with Closing set to Keep open, the example cancels `WindowCloseRequestedEvent`; a real click on the window's own close button (found through DWM's caption button bounds) and the system menu's Close each leave the window open and count one cancelled request; with Allow, the close button closes the window and the app exits with code 0. Owner-guarded clicks, no keyboard. Set `TITLE_BAR_EXAMPLE_EXE` for a build outside `$RemoteWorkspace`. |
2424
| `flutter_window_title_bar_close_test_linux.py` | `window_title_bar_example` (#65) | Linux (GNOME) | The Linux twin of the close test: with Keep open, two real clicks on the header bar's close button (found by its accessible name through AT-SPI) are cancelled and counted; with Allow the click closes the window and the app exits with code 0. The Flutter view is located through AT-SPI too, since the runner's header bar sits inside the X11 client area. The system menu is gnome-shell's and is not driven. Set `TITLE_BAR_EXAMPLE_EXE` for a build outside `$REMOTE_WORKSPACE`. |
25-
| `flutter_window_reports_test.ps1` | fixture `window_reports_flutter.dart` (#76, #77, #78) | Windows | The window_manager reports carried over to nativeapi-core, replayed through the Flutter Window API in a fixture app built from the workspace's Dart packages (`REPORTS_ARCHIVE`). The app paints solid colours and announces each step; the runner measures geometry, styles, DWM caption buttons, pixels and frame-by-frame samples. Scenarios (`REPORTS_SCENARIOS`): `fullscreen` (#458 start in full screen and leave it, #579 caption back, #330 no stale content at the bottom), `hidden` (#547 top border as thick as the sides, #554/#450 maximized exactly to the work area, #378 no padding in full screen, #397 no caption flash when leaving it), `transparent` (#576 caption buttons and title with a transparent background), `events` (#294 focus last after restoring, #207 double-click on the caption maximizes and restores with Resized, #511 maximize() from onDoubleTapDown, #534 setIcon() from flutter_assets). Owner-guarded clicks on the app's own window only, no keyboard. Not covered: #480 (keyboard) and #245 (two monitors). |
25+
| `flutter_window_reports_test.ps1` | fixture `window_reports_flutter.dart` (#75, #76, #77, #78) | Windows | The window_manager reports carried over to nativeapi-core, replayed through the Flutter Window API in a fixture app built from the workspace's Dart packages (`REPORTS_ARCHIVE`). The app paints solid colours and announces each step; the runner measures geometry, styles, DWM caption buttons, pixels and frame-by-frame samples. Scenarios (`REPORTS_SCENARIOS`): `startup-center` (#428 no white or black frame, #504/#223 appears where it stays, centred), `startup-maximize` (#412/#572 maximized before the first show appears maximized and stays so), `flashes` (#383/#153/#155 no white or black frame while maximizing, restoring or showing after hide, #578 a part dragged back from off screen painted within 100 ms), `fullscreen` (#458 start in full screen and leave it, #579 caption back, #330 no stale content at the bottom), `hidden` (#547 top border as thick as the sides, #554/#450 maximized exactly to the work area, #378 no padding in full screen, #397 no caption flash when leaving it), `transparent` (#576 caption buttons and title with a transparent background), `events` (#294 focus last after restoring, #207 double-click on the caption maximizes and restores with Resized, #511 maximize() from onDoubleTapDown, #534 setIcon() from flutter_assets). Owner-guarded clicks on the app's own window only, no keyboard. Not covered: #480 (keyboard) and #245 (two monitors). |
2626
| `core_window_shadow_layout_test_linux.cpp` | core shadow | Linux Wayland / X11 | No-input regression: stable shadow geometry, contour pixels inside transparent content, input pass-through, disable and decoration restoration |
2727
| `flutter_window_shape_smoke_macos.py` / `flutter_window_shape_test_windows.ps1` | `shaped_window_example` | macOS / Windows | macOS: no-input shape and pixel-alpha smoke test; Windows: native regions, real button clicks/drag, resize, clear/reapply, click-through to the underlying app window |
2828
| `flutter_detachable_window_test.py` / `.ps1` | `detachable_window_example` | macOS / Windows | tear a panel off, exact content size, header stays under the cursor, dock into the other window, `State` preserved |

‎tools/gui/flutter_window_reports_test.ps1‎

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,137 @@ function Test-FullScreen {
269269
} finally { Stop-Scenario }
270270
}
271271

272+
# -- #75 flashes and blank frames ---------------------------------------------------
273+
function Test-Flash([int]$c) {
274+
# White or black: the colours of an unpainted Win32 window or GPU surface.
275+
((($c -shr 16) -band 255) -gt 225 -and (($c -shr 8) -band 255) -gt 225 -and ($c -band 255) -gt 225) -or
276+
((($c -shr 16) -band 255) -lt 24 -and (($c -shr 8) -band 255) -lt 24 -and ($c -band 255) -lt 24)
277+
}
278+
# From process start: (ms, visible, frame, centre pixel) until the first step.
279+
function Watch-Startup([string]$Name) {
280+
$samples = New-Object System.Collections.Generic.List[object]
281+
$watch = [System.Diagnostics.Stopwatch]::StartNew()
282+
Start-Scenario $Name
283+
$h = [IntPtr]::Zero
284+
while ($watch.ElapsedMilliseconds -lt 15000) {
285+
if ($h -eq [IntPtr]::Zero) {
286+
$w = @(Get-AppWindows $proc.Id | ? { $_.ClientW -gt 100 })
287+
if ($w.Count) { $h = [IntPtr]$w[0].Hwnd }
288+
}
289+
if ($h -ne [IntPtr]::Zero) {
290+
$visible = [ReportWin]::IsWindowVisible($h)
291+
$f = [ReportWin]::Frame($h)
292+
$px = if ($visible) { [ReportWin]::Pixel([int](($f[0] + $f[2]) / 2), [int](($f[1] + $f[3]) / 2)) } else { -1 }
293+
$samples.Add(@($watch.ElapsedMilliseconds, $visible, ($f -join ','), $px))
294+
}
295+
if (Get-Content $log -ErrorAction SilentlyContinue | ? { $_ -like 'STEP look *' }) { break }
296+
}
297+
$samples
298+
}
299+
function Test-StartupCenter {
300+
Say '== startup-center (#75: window_manager#428, #504, #223)'
301+
try {
302+
$samples = @(Watch-Startup 'startup-center')
303+
$h = Get-AppWindow
304+
$shown = @($samples | ? { $_[1] })
305+
$frames = @($shown | % { $_[2] } | Select-Object -Unique)
306+
Say " $($samples.Count) samples, $($shown.Count) visible; frames while visible: $($frames -join ' | ')"
307+
Check '#504/#223 the window appears where it stays, no jump' ($frames.Count -eq 1) ($frames -join ' | ')
308+
# Centred as Windows sees the window: its rectangle includes the invisible
309+
# resize borders, which on Windows 10/11 are at the sides and bottom only.
310+
$f = [ReportWin]::Rect($h); $work = [ReportWin]::Monitor($h, $true)
311+
$dx = [Math]::Abs(($f[0] + $f[2]) - ($work[0] + $work[2])) / 2; $dy = [Math]::Abs(($f[1] + $f[3]) - ($work[1] + $work[3])) / 2
312+
Check '#504 and it is centred' ($dx -le 1 -and $dy -le 1) "off by $dx, $dy"
313+
$flashes = @($shown | ? { Test-Flash $_[3] } | % { "$($_[0])ms $(Hex $_[3])" })
314+
Check '#428 no white or black frame before the background colour' ($flashes.Count -eq 0) ($flashes -join ' ')
315+
Go-Step 'look'
316+
} catch {
317+
Check 'startup-center ran to the end' $false "$($_.Exception.Message) at line $($_.InvocationInfo.ScriptLineNumber)"
318+
} finally { Stop-Scenario }
319+
}
320+
function Test-StartupMaximize {
321+
Say '== startup-maximize (#75: window_manager#412, #572)'
322+
try {
323+
$samples = @(Watch-Startup 'startup-maximize')
324+
$h = Get-AppWindow
325+
$shown = @($samples | ? { $_[1] })
326+
$frames = @($shown | % { $_[2] } | Select-Object -Unique)
327+
Say " $($samples.Count) samples, $($shown.Count) visible; frames while visible: $($frames -join ' | ')"
328+
Check '#572 maximized before it appears, it appears maximized: no jump' ($frames.Count -eq 1) ($frames -join ' | ')
329+
Go-Step 'look'
330+
Wait-Step 'end' | Out-Null
331+
$c = [ReportWin]::Client($h); $work = [ReportWin]::Monitor($h, $true)
332+
Check '#412 two seconds later it is still maximized' ([ReportWin]::IsZoomed($h))
333+
Check '#412 filling the work area' ([Math]::Abs($c[2] - $c[0] - ($work[2] - $work[0])) -le 16 -and $c[3] -ge $work[3] - 16) "client $($c -join ',') work $($work -join ',')"
334+
$flashes = @($shown | ? { Test-Flash $_[3] } | % { "$($_[0])ms $(Hex $_[3])" })
335+
Check '#428 no white or black frame at startup' ($flashes.Count -eq 0) ($flashes -join ' ')
336+
} catch {
337+
Check 'startup-maximize ran to the end' $false "$($_.Exception.Message) at line $($_.InvocationInfo.ScriptLineNumber)"
338+
} finally { Stop-Scenario }
339+
}
340+
# Points inside the content, clear of the panel's text.
341+
function Get-ContentPoints([IntPtr]$H) {
342+
$c = [ReportWin]::Client($H); $w = $c[2] - $c[0]; $hh = $c[3] - $c[1]
343+
@(@(0.1, 0.1), @(0.25, 0.5), @(0.75, 0.25), @(0.5, 0.97)) | % { ,@([int]($c[0] + $w * $_[0]), [int]($c[1] + $hh * $_[1])) }
344+
}
345+
function Test-Transition([string]$Step, [IntPtr]$H, [string]$Label, [int]$Ms = 1200) {
346+
$points = @(Get-ContentPoints $H)
347+
$samples = Watch-Step $Step $points $Ms
348+
$bad = @()
349+
for ($i = 0; $i -lt $points.Count; $i++) { $bad += @($samples[$i] | ? { Test-Flash $_ } | % { Hex $_ }) }
350+
Check $Label ($bad.Count -eq 0) "$($samples[0].Count) frames per point, flashes: $(($bad | Select-Object -Unique) -join ' ')"
351+
}
352+
function Test-Flashes {
353+
Say '== flashes (#75: window_manager#383, #153, #155, #578)'
354+
Start-Scenario 'flashes'
355+
try {
356+
Wait-Step 'maximize' | Out-Null
357+
$h = Get-AppWindow
358+
Activate-App
359+
Test-Transition 'maximize' $h '#383 maximize() shows no white or black frame'
360+
Wait-Step 'unmaximize' | Out-Null
361+
Test-Transition 'unmaximize' $h '#153 restoring from maximized shows no black frame'
362+
Wait-Step 'hide' | Out-Null
363+
$points = @(Get-ContentPoints $h)
364+
Go-Step 'hide'
365+
Wait-Step 'show' | Out-Null
366+
# Only frames once the window is visible again count.
367+
$seen = @($points | % { ,(New-Object System.Collections.Generic.List[int]) })
368+
Go-Step 'show'
369+
$end = (Get-Date).AddMilliseconds(1500)
370+
while ((Get-Date) -lt $end) {
371+
if (-not [ReportWin]::IsWindowVisible($h)) { continue }
372+
for ($i = 0; $i -lt $points.Count; $i++) { $seen[$i].Add([ReportWin]::Pixel($points[$i][0], $points[$i][1])) }
373+
}
374+
$bad = @(); foreach ($list in $seen) { $bad += @($list | ? { Test-Flash $_ } | % { Hex $_ }) }
375+
Check '#155 show() after hide() shows no white or black frame' ($seen[0].Count -gt 0 -and $bad.Count -eq 0) "$($seen[0].Count) frames per point, flashes: $(($bad | Select-Object -Unique) -join ' ')"
376+
Wait-Step 'drag' | Out-Null
377+
# #578: drag the window by its title bar until 40% of it is off the left
378+
# edge, back again, then time how long the uncovered part stays unpainted.
379+
$f = [ReportWin]::Frame($h); $c = [ReportWin]::Client($h)
380+
$grab = @([int]($f[0] + ($f[2] - $f[0]) * 0.7), [int](($f[1] + $c[1]) / 2))
381+
$shift = $f[0] + [int](($f[2] - $f[0]) * 0.4)
382+
Invoke-Drag $app $grab @(,@(($grab[0] - $shift), $grab[1], 600))
383+
Pause 0.6
384+
$off = [ReportWin]::Frame($h)
385+
Check '#578 the window was dragged partly off screen' ($off[0] -lt 0) "frame $($off -join ',')"
386+
$back = @(($grab[0] - $shift), $grab[1])
387+
Invoke-Drag $app $back @(,@($grab[0], $grab[1], 600))
388+
$watch = [System.Diagnostics.Stopwatch]::StartNew()
389+
$c = [ReportWin]::Client($h); $w = $c[2] - $c[0]
390+
$points = @(@(0.05, 0.3), @(0.2, 0.6), @(0.35, 0.95)) | % { ,@([int]($c[0] + $w * $_[0]), [int]($c[1] + ($c[3] - $c[1]) * $_[1])) }
391+
$lastBad = -1; $frames = 0
392+
while ($watch.ElapsedMilliseconds -lt 800) {
393+
$frames++
394+
foreach ($p in $points) { if (Test-Flash ([ReportWin]::Pixel($p[0], $p[1]))) { $lastBad = $watch.ElapsedMilliseconds } }
395+
}
396+
Check '#578 the part brought back on screen is painted within 100 ms' ($lastBad -lt 100) "last unpainted sample at $lastBad ms of $frames frames"
397+
Go-Step 'drag'
398+
} catch {
399+
Check 'flashes ran to the end' $false "$($_.Exception.Message) at line $($_.InvocationInfo.ScriptLineNumber)"
400+
} finally { Stop-Scenario }
401+
}
402+
272403
# -- #76 hidden title bar ----------------------------------------------------------
273404
function Test-Hidden {
274405
Say '== hidden (#76: window_manager#554, #450, #378, #397, #547)'
@@ -398,9 +529,12 @@ function Test-Events {
398529
Start-Result "$RemoteScratch\flutter_window_reports_test.result.txt"
399530
Assert-Idle
400531
if ($env:REPORTS_REBUILD -ne '0' -or -not (Test-Path $exe)) { Build-Fixture }
401-
$wanted = if ($env:REPORTS_SCENARIOS) { $env:REPORTS_SCENARIOS.Split(',') } else { @('fullscreen', 'hidden', 'transparent', 'events') }
532+
$wanted = if ($env:REPORTS_SCENARIOS) { $env:REPORTS_SCENARIOS.Split(',') } else { @('startup-center', 'startup-maximize', 'flashes', 'fullscreen', 'hidden', 'transparent', 'events') }
402533
foreach ($s in $wanted) {
403534
switch ($s) {
535+
'startup-center' { Test-StartupCenter }
536+
'startup-maximize' { Test-StartupMaximize }
537+
'flashes' { Test-Flashes }
404538
'fullscreen' { Test-FullScreen }
405539
'hidden' { Test-Hidden }
406540
'transparent' { Test-Transparent }

‎tools/gui/window_reports_flutter.dart‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,28 @@ final scenarios = <String, Scenario>{
123123
),
124124
],
125125
),
126+
// #75: leanflutter/window_manager#428, #504, #223: sized and centred before
127+
// the window first appears.
128+
'startup-center': Scenario(
129+
before: () async {
130+
window.setSize(const Size(900, 600).toNative(), false);
131+
window.center();
132+
},
133+
[Step('look', () {})],
134+
),
135+
// #75: leanflutter/window_manager#412, #572: maximized before it appears.
136+
'startup-maximize': Scenario(before: () async => window.maximize(), [
137+
Step('look', () {}, settle: 2000),
138+
]),
139+
// #75: leanflutter/window_manager#383, #153, #155, #578. The runner samples
140+
// the screen while each step runs, and drags (#578) before `drag` goes.
141+
'flashes': Scenario([
142+
Step('maximize', () => window.maximize(), settle: 1500),
143+
Step('unmaximize', () => window.unmaximize(), settle: 1500),
144+
Step('hide', () => window.hide(), settle: 1000),
145+
Step('show', () => window.show(), settle: 1500),
146+
Step('drag', () {}),
147+
]),
126148
// #76: leanflutter/window_manager#554, #450, #378, #397, #547.
127149
'hidden': Scenario(
128150
before: () async => window.titleBarStyle = TitleBarStyle.hidden,

0 commit comments

Comments
 (0)