Skip to content

Commit 03bec1b

Browse files
Merge pull request #22783 from unoplatform/dev/mazi/scrolllock
fix: Scrolling is locking up
2 parents 57143aa + eb53b68 commit 03bec1b

File tree

5 files changed

+138
-1
lines changed

5 files changed

+138
-1
lines changed

src/SamplesApp/UITests.Shared/UITests.Shared.projitems

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2624,6 +2624,10 @@
26242624
<SubType>Designer</SubType>
26252625
<Generator>MSBuild:Compile</Generator>
26262626
</Page>
2627+
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ScrollViewerTests\ScrollViewer_ToggleSwitch_ScrollLock.xaml">
2628+
<SubType>Designer</SubType>
2629+
<Generator>MSBuild:Compile</Generator>
2630+
</Page>
26272631
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ScrollViewerTests\ScrollViewer_IsIntermediate.xaml">
26282632
<SubType>Designer</SubType>
26292633
<Generator>MSBuild:Compile</Generator>
@@ -10012,6 +10016,9 @@
1001210016
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ScrollViewerTests\ScrollViewer_Simple.xaml.cs">
1001310017
<DependentUpon>ScrollViewer_Simple.xaml</DependentUpon>
1001410018
</Compile>
10019+
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ScrollViewerTests\ScrollViewer_ToggleSwitch_ScrollLock.xaml.cs">
10020+
<DependentUpon>ScrollViewer_ToggleSwitch_ScrollLock.xaml</DependentUpon>
10021+
</Compile>
1001510022
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ScrollViewerTests\ScrollViewer_IsIntermediate.xaml.cs">
1001610023
<DependentUpon>ScrollViewer_IsIntermediate.xaml</DependentUpon>
1001710024
</Compile>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<Page
2+
x:Class="UITests.Shared.Windows_UI_Xaml_Controls.ScrollViewerTests.ScrollViewer_ToggleSwitch_ScrollLock"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
mc:Ignorable="d"
8+
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
9+
10+
<StackPanel Padding="20" Spacing="10">
11+
<TextBlock Text="Toggle a switch via touch, then try to scroll. If scrolling still works, the fix is correct." TextWrapping="Wrap" />
12+
13+
<Border BorderBrush="Gray" BorderThickness="2" Height="300" Width="300">
14+
<ScrollViewer>
15+
<StackPanel Spacing="20" Padding="10">
16+
<ToggleSwitch Header="Toggle 1" />
17+
<ToggleSwitch Header="Toggle 2" />
18+
<ToggleSwitch Header="Toggle 3" />
19+
<ToggleSwitch Header="Toggle 4" />
20+
<ToggleSwitch Header="Toggle 5" />
21+
<ToggleSwitch Header="Toggle 6" />
22+
<Border Background="{ThemeResource SystemControlBackgroundBaseLowBrush}" Height="200">
23+
<TextBlock Text="Spacer" HorizontalAlignment="Center" VerticalAlignment="Center" />
24+
</Border>
25+
<Border Background="{ThemeResource SystemControlBackgroundBaseLowBrush}" Height="200">
26+
<TextBlock Text="Spacer" HorizontalAlignment="Center" VerticalAlignment="Center" />
27+
</Border>
28+
<Border Background="{ThemeResource SystemControlBackgroundBaseLowBrush}" Height="200">
29+
<TextBlock Text="Spacer" HorizontalAlignment="Center" VerticalAlignment="Center" />
30+
</Border>
31+
</StackPanel>
32+
</ScrollViewer>
33+
</Border>
34+
</StackPanel>
35+
</Page>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Uno.UI.Samples.Controls;
2+
using Microsoft.UI.Xaml.Controls;
3+
4+
namespace UITests.Shared.Windows_UI_Xaml_Controls.ScrollViewerTests
5+
{
6+
[Sample("Scrolling", Name = nameof(ScrollViewer_ToggleSwitch_ScrollLock), IsManualTest = true, IgnoreInSnapshotTests = true)]
7+
public sealed partial class ScrollViewer_ToggleSwitch_ScrollLock : Page
8+
{
9+
public ScrollViewer_ToggleSwitch_ScrollLock()
10+
{
11+
this.InitializeComponent();
12+
}
13+
}
14+
}

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,6 +1378,72 @@ public async Task When_DirectManipulationDisabled(ManipulationModes mode)
13781378
return cursor?.Type;
13791379
}
13801380

1381+
[TestMethod]
1382+
#if __WASM__
1383+
[Ignore("Scrolling is handled by native code and InputInjector is not yet able to inject native pointers.")]
1384+
#elif !HAS_INPUT_INJECTOR
1385+
[Ignore("InputInjector is not supported on this platform.")]
1386+
#endif
1387+
public async Task When_ToggleSwitchToggledInScrollViewer_Then_ScrollStillWorks()
1388+
{
1389+
ScrollViewer sv;
1390+
ToggleSwitch toggle;
1391+
var root = new Grid
1392+
{
1393+
Width = 300,
1394+
Height = 300,
1395+
Children =
1396+
{
1397+
(sv = new ScrollViewer
1398+
{
1399+
UpdatesMode = Uno.UI.Xaml.Controls.ScrollViewerUpdatesMode.Synchronous,
1400+
IsScrollInertiaEnabled = false,
1401+
Content = new StackPanel
1402+
{
1403+
Children =
1404+
{
1405+
(toggle = new ToggleSwitch { Header = "Toggle" }),
1406+
new Border { Height = 2000, Background = new SolidColorBrush(Colors.LightGray) }
1407+
}
1408+
}
1409+
})
1410+
}
1411+
};
1412+
1413+
await UITestHelper.Load(root);
1414+
1415+
var injector = InputInjector.TryCreate() ?? throw new InvalidOperationException("Failed to init the InputInjector");
1416+
using var finger = injector.GetFinger();
1417+
1418+
// 1. Verify scrolling works initially
1419+
var svBounds = sv.GetAbsoluteBounds();
1420+
finger.Drag(from: svBounds.GetCenter(), to: svBounds.GetCenter().Offset(y: -100));
1421+
sv.VerticalOffset.Should().BeGreaterThan(0, because: "scrolling should work before toggling");
1422+
await TestServices.WindowHelper.WaitForIdle();
1423+
1424+
// 2. Scroll back to top so the toggle is visible
1425+
sv.ChangeView(null, 0, null, disableAnimation: true);
1426+
await TestServices.WindowHelper.WaitForIdle();
1427+
1428+
// 3. Toggle the ToggleSwitch by dragging horizontally across it (simulates touch toggle)
1429+
var initialIsOn = toggle.IsOn;
1430+
var toggleBounds = toggle.GetAbsoluteBounds();
1431+
finger.Drag(
1432+
from: new Point(toggleBounds.Left + 10, toggleBounds.GetCenter().Y),
1433+
to: new Point(toggleBounds.Right - 10, toggleBounds.GetCenter().Y),
1434+
steps: 5);
1435+
await TestServices.WindowHelper.WaitForIdle();
1436+
toggle.IsOn.Should().NotBe(initialIsOn, because: "the horizontal drag should toggle the ToggleSwitch");
1437+
1438+
// 4. Try to scroll again - this is the actual test: scrolling must still work after toggle
1439+
sv.ChangeView(null, 0, null, disableAnimation: true);
1440+
await TestServices.WindowHelper.WaitForIdle();
1441+
svBounds = sv.GetAbsoluteBounds();
1442+
finger.Drag(from: svBounds.GetCenter(), to: svBounds.GetCenter().Offset(y: -100));
1443+
sv.VerticalOffset.Should().BeGreaterThan(0, because: "scrolling should still work after toggling a ToggleSwitch");
1444+
await TestServices.WindowHelper.WaitForIdle();
1445+
}
1446+
13811447
private static void SetCursorShape(CoreCursor cursor)
13821448
{
13831449
if (TestServices.WindowHelper

src/Uno.UI/UI/Xaml/Internal/DirectManipulation.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ public void ProcessDown(_PointerEventArgs args)
197197

198198
using var _ = WithCurrent(args);
199199
_recognizer.ProcessDownEvent(args.CurrentPoint);
200+
201+
// If the recognizer immediately rejected the manipulation (e.g. settings = None from PreventDirectManipulation),
202+
// complete directly. ManipulationAborted is not used here as it implies ManipulationConfigured was received.
203+
if (_state is States.Preparing && _recognizer.PendingManipulation is null)
204+
{
205+
OnDirectManipulationCompleting();
206+
}
200207
}
201208
}
202209

@@ -491,13 +498,21 @@ private void OnDirectManipulationCompleted(GestureRecognizer recognizer, Manipul
491498
private void OnDirectManipulationAborted(GestureRecognizer recognizer, GestureRecognizer.Manipulation manip)
492499
{
493500
Trace?.Invoke("[DirectManipulation] Aborted");
501+
OnDirectManipulationCompleting();
502+
}
494503

504+
/// <summary>
505+
/// Completes the direct manipulation, notifying handlers and transitioning to the Completed state.
506+
/// Used both when the manipulation is aborted via event and when the recognizer immediately rejects it.
507+
/// </summary>
508+
private void OnDirectManipulationCompleting()
509+
{
495510
_state = States.Completed;
496511

497512
// Even if cancelled we still want to notify the handlers that the manipulation has completed to avoid leaking state.
498513
foreach (var handler in Handlers)
499514
{
500-
handler.OnCompleted(recognizer, null);
515+
handler.OnCompleted(_recognizer, null);
501516
}
502517
}
503518
#endregion

0 commit comments

Comments
 (0)