Skip to content

[render] Make visibleSize and the render size exact on macOS, instead of rounded to pixels #102

Description

@honghaoz

Context: found while reviewing the ScrollView adapter change (#105). Decided since: legacy scroll bars keep their current behavior here, and laying the content out beside them is #111. Whether macOS snaps the offset to pixels is #110.

Goal

The viewport is logical geometry, and pixel rounding is a rendering detail that shouldn't change it. On macOS, visibleSize and the size the content lays out for should be exact, not rounded to whole pixels: a 99.2 pt wide view should report and lay out for 99.2, as on iOS.

Observation

NSClipView rounds its own size to whole backing pixels inside setFrameSize(_:). In a probe on a 2x display, AppKit's tiling passed 99.2 and the clip view stored 99, and 99.8 became 100. It rounds even outside a window, and overriding tile() to set the exact size doesn't stick, so the clip view can't be made exact.

Direction

  1. visibleSize: report the size AppKit asks the clip view for, before the clip view rounds it, for example by recording it in a clip view subclass's setFrameSize(_:). That's the view's size minus the space legacy scroll bars take.
  2. renderBounds(): use the view's exact bounds size, which also drops the pixel conversion each render pass does now.
  3. The half-pixel difference remains where AppKit's real scroll range matters:
    • Content that fits must not scroll by the leftover fraction: a 99.2 pt document in a 99 pt clip view would scroll by 0.2 pt. Along an axis the content fits, round the document view's size the way NSClipView rounds its own.
    • updateScrollElasticity() and the gesture routing in ScrollSession, including the out-of-range check from [scroll] keep a set contentOffset as set on macOS, instead of clamping it into the scrollable range #106, compare against AppKit's range, so they need a half-pixel tolerance or AppKit's own clip view geometry. Without one, a nested view resting at AppKit's real end, a fraction past the exact maxOffsetY, would count as out of range and stop passing gestures to its parent.
    • When the clip view rounds down, up to half a pixel of content at the edge is clipped. That's an accepted rendering detail.

To verify (probe first)

  • The size a clip view subclass sees in setFrameSize(_:) at fractional view sizes, with legacy scroll bars, content insets, scaled bounds and a rotated view.
  • Rounding the document view's size keeps fitting content from scrolling at fractional sizes and positions, including the rotated cases in ComposeView+RenderBoundsTests.

To do

  1. Record the exact clip view size for visibleSize, make renderBounds() exact, round the document view's size along fitting axes, and add the tolerance to the elasticity check and the gesture routing.
  2. Tests: visibleSize and the render bounds are exact at fractional sizes (99.2 stays 99.2), fitting content at a fractional size doesn't scroll, a nested view resting at its end still passes gestures on, and the elasticity follows AppKit's range. Update the tests that expect rounded sizes.

Related: #38, #91, #92, #106, #110, #111.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions