Skip to content

Fix: ListView scroll when using SwipeControl#23249

Open
ajpinedam wants to merge 6 commits into
unoplatform:masterfrom
ajpinedam:fix/listview.skia.mobile.scroll
Open

Fix: ListView scroll when using SwipeControl#23249
ajpinedam wants to merge 6 commits into
unoplatform:masterfrom
ajpinedam:fix/listview.skia.mobile.scroll

Conversation

@ajpinedam
Copy link
Copy Markdown
Contributor

@ajpinedam ajpinedam commented May 11, 2026

GitHub Issue: closes https://github.com/unoplatform/kahua-private/issues/477

PR Type:

🐞 Bugfix

What changed? 🚀

CancelDirectManipulations cancelled every ancestor direct manipulation regardless of axis, so a SwipeControl (TranslateX) inside a ListView killed the parent ScrollContentPresenter's (TranslateY) DM and vertical scrolling died

Fix: Made ConflictsWithRequester axis-aware via a new IDirectManipulationHandler.GetCurrentlyAcceptedModes() peek — a pure-translation requester now only cancels ancestors that share an axis, while multi-pointer (Scale/Rotate) requesters and System/None-mode requesters keep the legacy "cancel everything" behavior so pinch-in-ScrollViewer fix is preserved

related to this change: #23135

PR Checklist ✅

@ajpinedam ajpinedam requested a review from ramezgerges May 11, 2026 21:59
@ajpinedam ajpinedam self-assigned this May 11, 2026
Copilot AI review requested due to automatic review settings May 11, 2026 21:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a managed-pointer direct manipulation cancellation behavior where a descendant’s CancelDirectManipulations request could incorrectly cancel orthogonal-axis ancestor manipulations (e.g., SwipeControl horizontal manipulation inside a vertically scrolling ListView), breaking vertical scrolling. It introduces an axis-aware conflict check by peeking the ancestor handlers’ currently accepted manipulation modes and adds a regression runtime test.

Changes:

  • Make ancestor direct-manipulation cancellation axis-aware via a new handler “accepted modes” preview.
  • Extend IDirectManipulationHandler with GetCurrentlyAcceptedModes() (defaulting to All) and implement it for ScrollContentPresenter.
  • Add a runtime regression test for vertical scrolling with SwipeControl items inside a ListView.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/Uno.UI/UI/Xaml/Internal/InputManager.Pointers.ManagedDirectManip.cs Adds axis-aware ancestor DM cancellation via ConflictsWithRequester and handler mode peeking.
src/Uno.UI/UI/Xaml/Internal/IDirectManipulationHandler.cs Adds GetCurrentlyAcceptedModes() with a default implementation to preserve legacy behavior.
src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.Managed.cs Implements GetCurrentlyAcceptedModes() by reusing the accepted-mode computation logic.
src/Uno.UI.RuntimeTests/Tests/Uno_UI_Xaml_Core/Given_InputManager.cs Adds regression test ensuring vertical scroll still works with SwipeControl items.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Uno.UI/UI/Xaml/Internal/InputManager.Pointers.ManagedDirectManip.cs Outdated
Comment thread src/Uno.UI/UI/Xaml/Internal/InputManager.Pointers.ManagedDirectManip.cs Outdated
@unodevops
Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23249/wasm-skia-net9/index.html

@unodevops
Copy link
Copy Markdown
Contributor

⚠️⚠️ The build 212018 has failed on Uno.UI - CI.

@ajpinedam
Copy link
Copy Markdown
Contributor Author

I see test When_DirectManipulationDisabled(TranslateX) is failing, when investigating I got this:

The test is encoding a contract that directly contradicts the premise of the axis‑aware fix on this branch. Here's the mechanics step by step:

What the test asserts (Given_InputManager.cs:1402-1445, introduced Aug 2025 by commit d72aee1 "feat: Add support for ManipulationMode=None"):

  • Outer ScrollViewer (200×200, vertical scroll enabled, content 800×800).
  • Inner Border with ManipulationMode = TranslateX and a -8000px vertical drag.
  • Expectation: sv.VerticalOffset.Should().Be(0) — i.e. setting any non-System ManipulationMode on the descendant must disable every ancestor direct manipulation, regardless of axis. The test name itself — When_DirectManipulationDisabled — codifies that "non-System mode on descendant ⇒ ancestor DMs are off" is a global invariant.

Why the new axis logic produces 620px instead of 0:

  1. During press bubble, Border.OnPointerDown (UIElement.Pointers.cs:1199) calls CancelDirectManipulations() because ManipulationMode != System.
  2. Walks _directManipulations, finds the SV's SCP DM (ancestor; predicate matches).
  3. requesterModes = TranslateX, so requesterAxes = TranslateX, requesterIsMultiPointer = false. The "cancel everything" early-returns don't fire.
  4. SCP's ComputeAcceptedManipulationModes() (just confirmed at ScrollContentPresenter.Managed.cs:374-387) returns TranslateY only (200‑wide viewport, no horizontal scroll).
  5. (TranslateX) & (TranslateY) == 0 → ConflictsWithRequester returns false → SCP DM survives.
  6. Vertical drag flows to SCP, SV scrolls 620px → Be(0) fails with found 620.0.

cc: @dr1rrb @agneszitte

Copy link
Copy Markdown
Member

@dr1rrb dr1rrb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the exact interaction result we want (i.e. we need to validate on WinUI), but looks good to me

@dr1rrb
Copy link
Copy Markdown
Member

dr1rrb commented May 12, 2026

I see test When_DirectManipulationDisabled(TranslateX) is failing, when investigating I got this:

The test is encoding a contract that directly contradicts the premise of the axis‑aware fix on this branch. Here's the mechanics step by step:

What the test asserts (Given_InputManager.cs:1402-1445, introduced Aug 2025 by commit d72aee1 "feat: Add support for ManipulationMode=None"):

* Outer ScrollViewer (200×200, vertical scroll enabled, content 800×800).

* Inner Border with ManipulationMode = TranslateX and a -8000px vertical drag.

* Expectation: sv.VerticalOffset.Should().Be(0) — i.e. setting any non-System ManipulationMode on the descendant must disable every ancestor direct manipulation, regardless of axis. The test name itself — When_DirectManipulationDisabled — codifies that "non-System mode on descendant ⇒ ancestor DMs are off" is a global invariant.

Why the new axis logic produces 620px instead of 0:

1. During press bubble, Border.OnPointerDown (UIElement.Pointers.cs:1199) calls CancelDirectManipulations() because ManipulationMode != System.

2. Walks _directManipulations, finds the SV's SCP DM (ancestor; predicate matches).

3. requesterModes = TranslateX, so requesterAxes = TranslateX, requesterIsMultiPointer = false. The "cancel everything" early-returns don't fire.

4. SCP's ComputeAcceptedManipulationModes() (just confirmed at ScrollContentPresenter.Managed.cs:374-387) returns TranslateY only (200‑wide viewport, no horizontal scroll).

5. (TranslateX) & (TranslateY) == 0 → ConflictsWithRequester returns false → SCP DM survives.

6. Vertical drag flows to SCP, SV scrolls 620px → Be(0) fails with found 620.0.

cc: @dr1rrb @agneszitte

A None should prevent all gestures kinds from any parent, but indeed the tests might be off, a TranslateX shoudl not prevent a parent to handle TranslateY

Copilot AI review requested due to automatic review settings May 13, 2026 04:30
@ajpinedam
Copy link
Copy Markdown
Contributor Author

I see test When_DirectManipulationDisabled(TranslateX) is failing, when investigating I got this:
The test is encoding a contract that directly contradicts the premise of the axis‑aware fix on this branch. Here's the mechanics step by step:
What the test asserts (Given_InputManager.cs:1402-1445, introduced Aug 2025 by commit d72aee1 "feat: Add support for ManipulationMode=None"):

* Outer ScrollViewer (200×200, vertical scroll enabled, content 800×800).

* Inner Border with ManipulationMode = TranslateX and a -8000px vertical drag.

* Expectation: sv.VerticalOffset.Should().Be(0) — i.e. setting any non-System ManipulationMode on the descendant must disable every ancestor direct manipulation, regardless of axis. The test name itself — When_DirectManipulationDisabled — codifies that "non-System mode on descendant ⇒ ancestor DMs are off" is a global invariant.

Why the new axis logic produces 620px instead of 0:

1. During press bubble, Border.OnPointerDown (UIElement.Pointers.cs:1199) calls CancelDirectManipulations() because ManipulationMode != System.

2. Walks _directManipulations, finds the SV's SCP DM (ancestor; predicate matches).

3. requesterModes = TranslateX, so requesterAxes = TranslateX, requesterIsMultiPointer = false. The "cancel everything" early-returns don't fire.

4. SCP's ComputeAcceptedManipulationModes() (just confirmed at ScrollContentPresenter.Managed.cs:374-387) returns TranslateY only (200‑wide viewport, no horizontal scroll).

5. (TranslateX) & (TranslateY) == 0 → ConflictsWithRequester returns false → SCP DM survives.

6. Vertical drag flows to SCP, SV scrolls 620px → Be(0) fails with found 620.0.

cc: @dr1rrb @agneszitte

A None should prevent all gestures kinds from any parent, but indeed the tests might be off, a TranslateX shoudl not prevent a parent to handle TranslateY

Update the TranslateX test so that it allows TranslateY .

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/Uno.UI.RuntimeTests/Tests/Uno_UI_Xaml_Core/Given_InputManager.cs:1407

  • This test currently expects ManipulationModes.TranslateRailsX to cancel the ancestor ScrollViewer’s vertical scroll (expects VerticalOffset == 0). With the new axis-aware cancellation intent, TranslateRailsX is still an X-only translation request, so a vertical drag should likely continue to scroll the ancestor (similar to the TranslateX row). If ConflictsWithRequester is updated to treat rails as axis-specific, update this expected outcome accordingly.
	[DataRow(ManipulationModes.TranslateX, true)] // Descendant claims X only -> parent's Y-axis scroll must still work.
	[DataRow(ManipulationModes.TranslateY, false)]
	[DataRow(ManipulationModes.TranslateRailsX, false)]
	[DataRow(ManipulationModes.TranslateRailsY, false)]
	[DataRow(ManipulationModes.TranslateInertia, false)]

Comment thread src/Uno.UI/UI/Xaml/Internal/InputManager.Pointers.ManagedDirectManip.cs Outdated
Comment thread src/Uno.UI/UI/Xaml/Internal/InputManager.Pointers.ManagedDirectManip.cs Outdated
@unodevops
Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23249/wasm-skia-net9/index.html

@unodevops
Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23249/wasm-skia-net9/index.html

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.

4 participants