Skip to content

ZoomContentControl: add touch gestures — pinch-to-zoom and drag-to-pan #1629

Description

@kazo0

Current behavior

ZoomContentControl only supports mouse/keyboard interactions: Ctrl + wheel zoom, wheel scrolling, and middle-click-drag panning. On touch, nothing happens — no pinch-to-zoom, no single-finger panning. Consumers targeting touch have to drive ZoomLevel from buttons/sliders (which is what the docs currently recommend).

Interestingly, the implementation already anticipates pinch: SetScrollValue's doc comment mentions "we allow unconstrained panning for MouseWheelZoom(desktop) and PinchToZoom(mobile)", and CalculateNewOffset's vpAnchor is described as "typically the cursor position or the pinch center point" — the gesture just was never wired up.

Expected behavior

Standard touch manipulation gestures work out of the box:

  • Pinch/stretch zooms around the gesture's focal point, clamped to MinZoomLevel/MaxZoomLevel, gated on IsZoomAllowed.
  • Single-finger drag pans, with the content following the finger, gated on IsPanAllowed.

Proposed implementation

Wire ManipulationMode = Scale | TranslateX | TranslateY | TranslateInertia on the control and handle OnManipulationDelta, feeding the existing zoom/scroll pipeline:

  • the pinch focal point goes through the same CalculateNewOffset(...) anchor math the Ctrl + wheel path already uses (with the same "compute offset before setting ZoomLevel, apply after" ordering caveat);
  • translation deltas are converted to scroll-value space via K so the content follows the finger on both axes;
  • mouse-originated manipulations are ignored — mouse keeps its existing middle-drag/wheel semantics.

No new public API, no visual/template changes, no behavior change for mouse users.

Why not rebase onto ScrollViewer's zoom instead?

ScrollViewer optical zoom recently landed for Skia targets (unoplatform/uno#22337), which raises the question of rebasing ZoomContentControl on it to get gestures "for free". That trade doesn't hold up:

  • ZoomFactor/offsets are read-only there; everything funnels through the async, clamping ChangeView — which conflicts with ZCC's settable, two-way-bindable ZoomLevel (two sources of truth during manipulations).
  • Several ZCC features have no ScrollViewer equivalent at all: AllowFreePanning (offsets past the clamp range), the unscaled AdditionalMargin (anything wrapped inside a ScrollViewer scales with the zoom), ScaleWheelRatio/PanWheelRatio, middle-click panning.
  • ZCC's transform-based implementation behaves identically on every target by construction; per-platform native ScrollViewer behavior would not, and uno#22337 covers Skia only.

Handling manipulations directly gets gesture support on all targets (including WinAppSDK) for ~40 lines, keeping the control's single-source-of-truth model.

Anything else we need to know?

  • Builds on the anchor math fixed in fix(zoomcontentcontrol): keep content centered when zooming #1628 (pinch zooming across the "content fits viewport" boundary relies on the corrected K-sign handling), so the implementation branch is based on fix/zoomcontentcontrol-centering.
  • Docs (doc/controls/ZoomContentControl.md interactions section) should gain a Touch subsection; the warning that there is "no automatic pinch-to-zoom" becomes obsolete.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions