feat(viewport): Translator — the half of a viewport that was missing - #9
Merged
Conversation
Clipping existed; coordinate translation did not. A viewport is both, and the gap had a real cost: with no way to say "draw my child 250 pixels higher", Clipper's only customer — the toolkit's ScrollView — moved the child's BOUNDS instead, drew, and put them back. Geometry that changes for the duration of a paint is invisible to anything reading it from outside. The accessibility bridges read bounds between frames, so a screen reader was told a control sat a quarter of a window below where it was painted, and the toolkit needed a childOffsetter interface purely to compensate. A translation shifts the PAINT, not the widget: a child lays out and reports its bounds wherever it genuinely is, and the viewport decides where those pixels land. Nothing has to be moved and restored. The offset is applied in the ONE write every primitive funnels through — PutPixel for pixels, set/setFg for cells. Shifting in each public method instead would apply it once per layer of composition, moving a rounded rectangle three times as far as a pixel (FillRoundRect -> FillRect -> PutPixel); a test pins that down. A clip pushed while translated moves with it, since a caller expresses it in the same coordinates as everything else it draws. Optional capability, like Clipper: a back-end that cannot translate simply does not implement it and the type assertion is skipped. Both PixelPainter and CellPainter do. Nothing is moved onto it here. The toolkit's ScrollView follows in its own change, so any rendering regression has one commit to point at. Package coverage stays at 100%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Clipping existed; coordinate translation did not. A viewport is both, and the
gap had a real cost.
With no way to say "draw my child 250 pixels higher",
Clipper's onlycustomer — the toolkit's
ScrollView— moved the child's bounds instead,drew, and put them back:
Geometry that changes for the duration of a paint is invisible to anything
reading it from outside. The accessibility bridges read bounds between frames, so
a screen reader was told a control sat a quarter of a window below where it was
painted — and the toolkit needed a
childOffsetterinterface purely tocompensate.
A translation shifts the paint, not the widget: a child lays out and reports
its bounds wherever it genuinely is, and the viewport decides where those pixels
land. Nothing has to be moved and restored.
The part worth reviewing
The offset is applied in the one write every primitive funnels through —
PutPixelfor pixels,set/setFgfor cells — not in each public method.Primitives compose:
FillRoundRect→FillRect→PutPixel. Shifting perpublic method would apply the offset once per layer, moving a rounded rectangle
three times as far as a pixel.
TestTranslationAppliedOncePerWritepins thatdown.
A clip pushed while translated moves with it, since a caller expresses it in the
same coordinates as everything else it draws.
Optional capability, exactly like
Clipper: a back-end that cannot translatedoes not implement it and the assertion is skipped. Both
PixelPainterandCellPainterdo.Deliberately not included
Nothing is moved onto it here. The toolkit's
ScrollViewfollows in its ownchange, so if any rendering regresses there is one commit to point at. The
existing clip, path and golden tests all pass unchanged — with no translation
pushed the offset is zero and every write is byte-identical.
Package coverage stays at 100%.
🤖 Generated with Claude Code