Skip to content

Add Pencuil scrolling with clipping, pointer capture and scrollbars - #464

Open
botoddly wants to merge 3 commits into
mainfrom
pencuil-scrollbar
Open

Add Pencuil scrolling with clipping, pointer capture and scrollbars#464
botoddly wants to merge 3 commits into
mainfrom
pencuil-scrollbar

Conversation

@botoddly

@botoddly botoddly commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • fix Rectangle.Intersects and ShortRectangle.Intersects treating the far edge as inclusive, so hit testing covers exactly the pixels the renderer paints
  • undo SDL's flipped wheel direction in MouseService, which would otherwise invert scrolling for anyone using natural scrolling
  • add Pencil.WithClip, clipping instructions, hit tests and hover patches as they are emitted, with texture UVs trimmed to match
  • add pointer capture to Pencil, kept separate from keyboard focus, plus wheel delta latching in PencilSystem
  • add ScrollBar and the ScrollView scope that composes clipping, offsetting and the bar
  • add the ScrollList tutorial and docs/scrolling.md

Notes

Rectangle.Intersects reported hits for one column and one row beyond the drawn area: a rect of Width covered Width + 1 pixel columns, while PencuilRenderer paints the half-open range [X, X + Width). Nothing depended on the old behavior, and the ClickThrough tutorial's per-pixel hit mask was oversized by the same pixel and is now correct.

Clipping is done on the CPU rather than with a GPU scissor. Everything Pencuil draws is an axis-aligned rectangle with a linear UV mapping, so trimming is exact, and rows scrolled out of view cost no draw calls. WithClip does not change shape if this ever moves to a scissor.

Pointer capture is a separate slot from FocusedControlId so dragging a scrollbar does not blur a text field being edited. While captured, every motion invalidates and a cursor leaving the window no longer clears the cursor position, so a drag that wanders outside keeps working.

Shift+wheel is deliberately not mapped to horizontal scrolling: macOS performs that swap in the OS while Windows and X11 do not, so doing it here would apply it twice on one platform. Horizontal scrolling uses the wheel's horizontal axis.

ScrollView takes contentExtent from the caller, since only the caller knows the content size before it is built. Auto-measurement needs either a two-pass build or previous-frame state and can be added as an overload later.

GuiStyle gained ScrollBarThickness, ScrollStep and MinimumThumbLength with defaults, so existing positional construction still compiles.

Validation

  • dotnet build Pixely.slnx (clean, no warnings)
  • dotnet test tests/Pixely.Tests/Pixely.Tests.csproj (261 passed, 56 new)
  • all 12 active test suites pass
  • ran tutorials/Pixely.Tutorials.ScrollList under Xvfb and captured a screenshot: both scrollbars render and text is cut mid-glyph at the viewport edges on both axes, confirming UV trimming

Real pointer input was not exercised: no input synthesis tooling was available in this environment, so drag, paging and wheel are covered by unit tests against Pencil rather than through SDL. The full SDL to PencilSystem path is worth a manual pass.

Related: #462 (mouse coordinates are in window points while Pencuil hit tests in render pixels, latent until high pixel density is supported)

https://claude.ai/code/session_01W3HByFSStA1EJGzaEKU15D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant