Skip to content

Fix the Windows head items from the review - #890

Merged
SimonCropp merged 1 commit into
mainfrom
fix-windows-head
Sep 23, 2026
Merged

SimonCropp merged 1 commit into
mainfrom
fix-windows-head

Conversation

@SimonCropp

Copy link
Copy Markdown
Member

The "Windows head" group from todo.md. The repros from the review are now FormsHeadTests, which drive a real form and canvas with real window messages. The image cache test moved to ImageCacheTests.

Item Fix What the test shows
Selection highlight and hit-test drift from the glyphs MonoFont.Advance, the unrounded advance, places the highlight, ColumnAt and the subtitle width. The integer cell stays for the grid Drift at column 99 is 0.2 px at 96 DPI and 0.4 px at 192. The bar at column 66 sits inside its highlight and hit-tests as 66
ImageCache never evicts Keep drops every picture that isn't on the current screen Nothing held after ten accepted pairs
The first window is 1100×700 device pixels InitialClientSize scales it to the display's DPI once, when the handle is created, and keeps it inside the working area 34 characters a pane at 100% and at 200%, where it was 4
Discrete input overwrites itself within a pump Keys, clicks and menu events are queued and drained one per frame, in order. The frame skips its wait while input is queued Two Downs scroll 2. d then a click discards the entry that was on screen
Right-clicking the row whose menu is open Fixed by the queue: the close and the right-click are now two frames, so the popup reopens Popup and model agree
The thumb drag, or a move or resize, freezes the panes New optional ILoopHooks in core. The loop hands the WinForms head a frame to run on a timer during user32's modal loops: thumb tracking, and WM_ENTERSIZEMOVE to WM_EXITSIZEMOVE 22 frames during a 566 ms drag, with the panes following the thumb before release
Lost capture mid drag OnMouseCaptureChanged ends the drag, and so does a move with the left button up The selection stops following, and the splitter stays put
Logoff can end before PersistOwned ILoopHooks.SessionEnding, called from OnFormClosed for a session end, sets Closing and stages the queue inside WM_ENDSESSION Closing is set when WM_ENDSESSION returns
ImageCache ignores the hash and holds files FileShare.Read The hash is part of the key. FileSide reads pending files through a ReadWrite | Delete shared stream, and the cache reads through it too A same-stamp rewrite shows the new picture

Not covered by a test: the move and resize half of the modal-loop fix, which needs a real title-bar drag. It uses the same timer as the thumb.

Ten WindowsPixelTests baselines changed. The subtitle moved a few pixels, because its width is now the true advance, and in Selection the highlight now lines up exactly with its glyphs.

The full solution passes locally in Release: 2,078 passed, 24 skipped.

- Glyph positions (the selection highlight, the column a click lands in, the
  subtitle's width) use the unrounded advance GDI+ draws at, not the whole-pixel
  cell the grid is laid out in: at 96 DPI 8.8 against 9, a character off by
  column 22 and more than three at 175%.
- The first window is sized for the display it opens on, and kept inside the
  working area. Unscaled, a pane held four characters at 200%.
- Keys, clicks and menu events are queued and handed over one per frame, in the
  order they happened. A slot per kind lost the second of two keys, and applied
  a click before the key pressed ahead of it - d then a click discarded an entry
  the reader never looked at. The frame does not wait while input is queued.
  That also reopens the menu on a right click of the row it was open on.
- user32's modal loops - the scroll bar's while the thumb is dragged, the
  frame's while the window is moved or sized - run frames on a timer, through
  ILoopHooks.Frame, so the panes follow the thumb instead of freezing until the
  release.
- Losing mouse capture, or a move with the button up, ends a selection or
  splitter drag.
- At logoff the form stops arrivals and stages the queue itself, inside
  WM_ENDSESSION, through ILoopHooks.SessionEnding: Windows may end the process
  once that returns, before the loop reaches its own shutdown.
- ImageCache keeps only what is on screen, and keys a picture by the content
  hash as well as its stamp. Pending files are read shared with writers and
  deleters, so a read overlapping an accept or a re-run cannot fail it.
@SimonCropp
SimonCropp merged commit e100abf into main Sep 23, 2026
8 of 9 checks passed
@SimonCropp
SimonCropp deleted the fix-windows-head branch September 23, 2026 05:13
SimonCropp added a commit that referenced this pull request Sep 23, 2026
A top level window is held to the screen's size. The windows build agent's
screen is 1024 by 768, so every canvas CanvasHost docked came out 1028 by 749
whatever was asked for: HighlightAtColumn66CoversItsGlyph drew no column 66,
and WhatTheFirstWindowHoldsAtEachScale measured a 200% window at 1028 wide.
Both have failed the windows job on main since #890. A child control has no
such limit.
SimonCropp added a commit that referenced this pull request Sep 23, 2026
* Fix the native items from the review

- Both native renderers drop decoded pictures the frame just drawn did not
  use. An entry used to go only when its own path was asked for again and had
  changed, so every image reviewed in a session stayed decoded until it ended.
- A macOS drag clamps to the rows the pane drew, as Linux's RowAt does, rather
  than to the body's capacity. A drag past the end reached rows the managed
  side holds back, so the status line counted, and a copy took, rows nobody
  saw highlighted.
- The Linux queue header says "Pending (N)", as every other renderer does.
- RenderDrawData flips scissor rectangles with the draw data's height rather
  than the window's, which a capture's render texture need not share.
- The Linux gutter takes one width for every row of a frame, wide enough for
  the longest line number drawn. Formatted per row, a five digit number moved
  its own row's text a cell right of the rows above, where the hit test did not
  look.
- Every macOS export runs in an autorelease pool. With no NSApplication.run to
  drain one per event, everything a frame autoreleased stayed until the
  process exited.
- An app delegate turns a Dock Quit or logout into a close, which the managed
  loop answers by persisting an owned queue and exiting. terminate: exited from
  inside the pump, so that cleanup never ran.
- NativeResolver probes a musl RID and nothing else, as BundledViewerDirectory
  does. The linux-{arch} candidate after it names the glibc build.

* Rebuild native renderer binaries

* Size the test canvas itself rather than docking it to a form

A top level window is held to the screen's size. The windows build agent's
screen is 1024 by 768, so every canvas CanvasHost docked came out 1028 by 749
whatever was asked for: HighlightAtColumn66CoversItsGlyph drew no column 66,
and WhatTheFirstWindowHoldsAtEachScale measured a 200% window at 1028 wide.
Both have failed the windows job on main since #890. A child control has no
such limit.

* Temporarily drop the Linux pixel baselines to recapture them

* Re-approve the Linux queue baselines for the pending count

---------

Co-authored-by: SimonCropp <122666+SimonCropp@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant