Skip to content

Add ScrollView to Pixely.Ui with wheel routing and overlay scrollbars - #513

Merged
stanoddly merged 4 commits into
mainfrom
worktree-ui-scroll-view-proposal
Sep 15, 2026
Merged

stanoddly merged 4 commits into
mainfrom
worktree-ui-scroll-view-proposal

Conversation

@botoddly

Copy link
Copy Markdown
Collaborator

Adds ScrollView, a container that shows a window onto children larger than itself, scrolled by the mouse wheel, by its bars and from code. The design was reviewed by codex before implementation.

ScrollView is a Column that clips, with the children slid by ScrollOffset: its Layout is the ordinary one and may be replaced, so a horizontal strip is a scroll view with a horizontal stack and ScrollAxes.Horizontal.

  • Axes (measure property) says which axes scroll; on those the children are measured without a bound, so Grow and Percent degrade there as documented and Grow on the cross axis fills a definite width as in a column
  • ScrollOffset is an arrange property, clamped into [0, MaxScrollOffset] at the next build, so int.MaxValue means the end once laid out; ScrollBy moves within the last build's range synchronously and reports whether it moved
  • padding scrolls with the children, so the extent is children plus padding and the clip stays the element's bounds
  • overlay bars along the trailing edges, ScrollBars Auto/Hidden/Visible, never on an axis that does not scroll; a track press pages by one viewport; thumb dragging is left for a later move notification
  • ScrollAppearance on UiStyle.Scroll: Thumb per state, optional Track, Thickness, MinimumThumbLength

Wheel input:

  • IScrollTarget.OnScroll(position, delta) returns the ScrollAxes it took; refusing means an outward delta at an end or an unsupported axis, and taking includes banking a touchpad fraction too small to move a pixel
  • UiInputSystem subscribes to the wheel and UiRoot.PointerScrolled routes it: the topmost element of either kind under the pointer, then up through its ancestors, each taking the axes it can use, so a dialog over a list does not scroll it, a wheel over a button inside a list does, and a horizontal strip inside a vertical list splits a mixed delta between the two; consumed only when something took some of it
  • the hit list carries a HitKind per entry, so a scroll-only element stays transparent to the pointer

Element changes:

  • ResolveContentConstraints virtual, called once per measure, is what ILayoutHost resolves every child's sizing against; the default commits what was offered
  • internal adornments (AttachAdornment, Adornments): owned elements outside Children, painted and hit-collected after them, never laid out, invalidated with the subtree; the bars use it, so Children.Clear() cannot reach them

Also docs/ui.md (Scrolling section, wheel and style notes), the Pixely.Tutorials.UiScrollView tutorial, and 45 tests across ScrollViewTests, ScrollRoutingTests and UiInputSystemTests. The tutorial was verified headless with screenshots: wheel over the list, paging by a track press, hover following the wheel, a click on a button scrolled into view.

@stanoddly
stanoddly force-pushed the worktree-ui-scroll-view-proposal branch from 25946c0 to 81f2896 Compare September 15, 2026 04:11
@stanoddly
stanoddly enabled auto-merge (squash) September 15, 2026 04:16
@stanoddly
stanoddly merged commit 333e9df into main Sep 15, 2026
1 check passed
@stanoddly
stanoddly deleted the worktree-ui-scroll-view-proposal branch September 15, 2026 04:19
@github-actions

Copy link
Copy Markdown

📦 Development package Pixely 0.0.60 is available.

Workflow run (attempt 1)

@github-actions

Copy link
Copy Markdown

✅ Development package validation passed on all platforms.

Workflow run (attempt 1)

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.

2 participants