uno icon indicating copy to clipboard operation
uno copied to clipboard

Scrolling a combobox with touch swipes keeps resetting the scrolling position.

Open christianfo opened this issue 10 months ago • 5 comments

Current behavior

On an Android device or in the simulator, scrolling a combobox list down with touch swipes (or simulated touch with the mouse) frequently resets the scroll position, seemingly in an effort to bring the selected item back into view (for no reason). This makes scrolling a combobox challenging if not entirely impossible on an Android mobile device. This seems to have regressed recently as I had not noticed the issue before a recent update to latest released Uno.

Expected behavior

Scrolling to work correctly in the ComboBox list on Android.

How to reproduce it (as minimally and precisely as possible)

Run attached sample app. Main page contains a ComboBox with a few dozen items:

  • Ensure first item is selected
  • scroll down with a swipe from the bottom of the list up
  • selected first item is now out of the viewport
  • tap again toward the bottom of the list to start another swipe up
  • first item scrolls back into view, cancelling the scroll

This can be repro-ed on Android in the emulator or on a device. I have not been able to repro this on WASM on a touch laptop.

Sample app: UnoApp20.zip

Video of the repro: https://github.com/unoplatform/uno/assets/15091389/4a43016a-ce24-4869-90e0-9b85800829b9

Workaround

None found so far

Works on UWP/WinUI

Yes

Environment

No response

NuGet package version(s)

Affected platforms

Android

IDE

Visual Studio 2022

IDE version

17.9.3

Relevant plugins

No response

Anything else we need to know?

I have not tested iOS.

christianfo avatar Apr 01 '24 00:04 christianfo

Looks like if some pointer events are not flagged as handled properly and the popup is being dismissed.

dr1rrb avatar Apr 09 '24 12:04 dr1rrb

tl;dr: to repro, scroll until selection is out of view, press-and-hold on any item container


cant repro with flick scrolling use old-man scrolling to repro: (press,stop,drag,stop,release)x2 __^ actually, scroll however you want, stop, then press-and-hold on any item container and it'll BringIntoView the current selection ____^ you need to click on the item container; around the flyout or between item doesnt work; the faintly grayed parts on this picture: image if you dont hesitate/wait between press-(here)-drag, the issue never occur

Xiaoy312 avatar Apr 16 '24 16:04 Xiaoy312

Thanks @Xiaoy312 for looking into the repro. I can confirm that:

  1. Press and hold on any item with the selection out of view will repro the problem.
  2. Clicking/tapping outside of an item container does not repro the problem.
  3. Swipes without hesitation at the start (i.e., hold) do not repro the problem.

Also, I had Uno 5.1.0-dev.469 before and I was not seeing the issue then. I then upgraded to 5.1.80 and I believe that is when the problem started. I can spend some time double checking this if that is of interest.

christianfo avatar Apr 16 '24 18:04 christianfo

So, the reason why the scroll resets is because we are sending two layout calls to the ScrollViewer, with different sizes. And, this causes the SV to reset on android. While the exact mechanism/purpose of this android ScrollViewer behavior is unclear, we can prevent this from the uno side by keeping size the same. This difference in size are due to the margin of ComboBox Flyout, where one calls took it into consideration and the other didn't.

In the meantime, as a workaround, you can override the default ComboBox style, and remove this line: https://github.com/unoplatform/uno/blob/5.2.80/src/Uno.UI/UI/Xaml/Style/Generic/Generic.xaml#L785 Or, clear the margin of PopupBorder from the .Loaded event of the ComboBox:

#if __ANDROID__
var combo = ...;
combo.Loaded += (s, e) =>
{
	var border = combo.GetTemplateChild("PopupBorder");
	border.Margin = new Thickness(0);
}
#endif

Xiaoy312 avatar Apr 30 '24 20:04 Xiaoy312

debug note
> https://github.com/unoplatform/uno/issues/16074
> Scrolling a combobox with touch swipes keeps resetting the scrolling position.

can repro on emulator:
    cant repro with flick scrolling
    use _old-man_ scrolling to repro: (press,drag,stop,release)x2
        ^ scroll however you want,stop,then press-and-hold anywhere on the combo flyout
        ^ and it'll BringIntoView current selection
    if you dont hesitate/wait between press-(*here*)-drag, the issue never occur

# tree
Border#PopupBorder > SV > Border > SCP > NativeSCP > ItemsPresenter > 
    ContentControl
    CarouselPanel > N*(ComboBoxItem > Grid > CP > ImplicitTextBlock)
    ContentControl

Border#PopupBorder // LTRB=547,155,741,929, Actual=73.9047619047619x294.85714285714283, HV=Stretch/Stretch, CornerRadius=0, Margin=[0,-1], Padding=0, Opacity=1, Visibility=Visible
    ScrollViewer#ScrollViewer // LTRB=3,3,191,771, Actual=71.9047619047619x292.85714285714283, HV=Stretch/Stretch, Offset=(0,0), Viewport=(292.95238095238096,72), Extent=(1594.952380952381,72), CornerRadius=0, Margin=0, Padding=0, Opacity=1, Visibility=Visible
        Border // LTRB=0,0,189,769, Actual=71.9047619047619x292.85714285714283, HV=Stretch/Stretch, CornerRadius=0, Margin=0, Padding=0, Opacity=1, Visibility=Visible
            ScrollContentPresenter#ScrollContentPresenter // LTRB=0,0,189,769, Actual=71.9047619047619x292.85714285714283, HV=Stretch/Stretch, CornerRadius=0, Margin=0, Padding=0, Opacity=1, Visibility=Visible
                NativeScrollContentPresenter // LTRB=0,0,189,769, Margin=0, Opacity=1, Visibility=Visible
                    ItemsPresenter // LTRB=0,18,189,4168, Actual=72x1580.952380952381, HV=Stretch/Stretch, Margin=[0,7], Padding=0, Opacity=1, Visibility=Visible
                        ContentControl // LTRB=0,0,189,0, Actual=72x0, HV=Stretch/Stretch, CornerRadius=0, Margin=0, Padding=0, Opacity=1, Visibility=Visible
                        CarouselPanel // LTRB=0,0,189,4150, Actual=72x1580.9523809523819, HV=Stretch/Stretch, CornerRadius=0, Margin=0, Padding=0, Opacity=1, Visibility=Visible
                            ComboBoxItem // LTRB=0,0,189,83, Actual=72x31.61904761904762, HV=Stretch/Stretch, CornerRadius=0, Margin=0, Padding=[11,5,11,7], Opacity=1, Visibility=Visible
                                Grid#LayoutRoot // LTRB=0,0,189,83, Actual=72x31.61904761904762, HV=Stretch/Stretch, CornerRadius=0, Margin=0, Padding=0, Opacity=1, Visibility=Visible
                                    ContentPresenter#ContentPresenter // LTRB=29,13,160,64, Actual=50x19.428571428571427, HV=Stretch/Center, CornerRadius=0, Margin=[11,5,11,7], Padding=0, Opacity=1, Visibility=Visible
                                        ImplicitTextBlock // LTRB=0,0,131,51, Actual=39.61904761904762x19.428571428571427, HV=Stretch/Stretch, Margin=0, Padding=0, Opacity=1, Visibility=Visible
                            ComboBoxItem // LTRB=0,4067,189,4150, Actual=72x31.61904761904762, HV=Stretch/Stretch, CornerRadius=0, Margin=0, Padding=[11,5,11,7], Opacity=1, Visibility=Visible
                                Grid#LayoutRoot // LTRB=0,0,189,83, Actual=72x31.61904761904762, HV=Stretch/Stretch, CornerRadius=0, Margin=0, Padding=0, Opacity=1, Visibility=Visible
                                    ContentPresenter#ContentPresenter // LTRB=29,13,160,64, Actual=50x19.428571428571427, HV=Stretch/Center, CornerRadius=0, Margin=[11,5,11,7], Padding=0, Opacity=1, Visibility=Visible
                                        ImplicitTextBlock // LTRB=0,0,131,51, Actual=47.61904761904762x19.428571428571427, HV=Stretch/Stretch, Margin=0, Padding=0, Opacity=1, Visibility=Visible
                        ContentControl // LTRB=0,4150,189,4150, Actual=72x0, HV=Stretch/Stretch, CornerRadius=0, Margin=0, Padding=0, Opacity=1, Visibility=Visible

# debugging
nothing obvious from ComboBox,Selector,ScrollViewer is happening
none of Selector events trigger when it happens
SCP::OnNativeScroll fires, but we no context of the caller as it was lost crossing android-uno barrier
nooping ComboBoxItem's Pressed&PointerOver visual-states doesnt help neither

SV::BringIntoViewOnFocusChange is true // lead to nowhere?
FocusManager::GotFocus only procs once on open when focus changes between ComboBox to selected ComboBoxItem

the scrolling can be traced back from layout
    there are always two layouts calls for NativeSCP every time the reset occurs
    [Rect [email protected],0.0] [Rect [email protected],0.0]
    ^ blocking one of them prevents the issue from happening
    ^ the scrolling is due to a change in the viewport..?

logs:
    @xy ComboBox::Arange: 411.4x659.4, [Rect [email protected],0.0], 73.9x292.9
    @xy child.Arrange: "Border", [Rect [email protected],60.0]
    @xy Layouter::ArrangeChildOverride // view=NativeSCP#924873582, frame=[Rect [email protected],0.0]
    @xy Layouter::ArrangeChildOverride // view=NativeSCP#924873582, frame=[Rect [email protected],0.0]

Uno.UI.ViewHelper.Scale: 2.625

both layout calls are originated from
    DropDownLayouter::Arrange ->
    UIElement::Arrange
        if (this is not FrameworkElement fwe) return; // this=Border#PopupBorder

        var layouter = ((ILayouterElement)fwe).Layouter;
        layouter.Arrange(finalRect);                // 290
        layouter.ArrangeChild(fwe, finalRect);      // 292
        ^ the difference between 290 and 292 is from the border's margin of [0,-1]

logs:
    @ margin = 0
    @xy ComboBox::Arange: 411.4x659.4, [Rect [email protected],0.0], 73.9x294.9
    @xy child.Arrange: "Border", [Rect [email protected],60.0]
    @xy Layouter::ArrangeChildOverride // view=NativeSCP#408604404, frame=[Rect [email protected],0.0]
    ---
    @ border-thickness = 0
    @xy ComboBox::Arange: 411.4x659.4, [Rect [email protected],0.0], 73.9x292.9
    @xy child.Arrange: "Border", [Rect [email protected],60.0]
    @xy Layouter::ArrangeChildOverride // view=NativeSCP#929768762, frame=[Rect [email protected],0.0] // <-- X
    @xy Layouter::ArrangeChildOverride // view=NativeSCP#929768762, frame=[Rect [email protected],0.0] // <-- O
^ border-thickness always takes in effect, margin is only applied to one of them

# idea
void: follow selectionchanged to trace up pointer events entry points
    and from there debug pointer event(s) that deals with long-press, but not click
    and see if what they dispatch
void: diff tree-graphs of two layout calls: 290 vs 292

comparison of Arrange and ArrangeChild: 16074.callstack.diff.txt

Xiaoy312 avatar May 01 '24 23:05 Xiaoy312