diff options
| author | 2024-10-07 15:19:09 +0000 | |
|---|---|---|
| committer | 2024-10-15 13:04:00 +0000 | |
| commit | 6e547dac0fd1db3ea43ed6fb0be8105801899c72 (patch) | |
| tree | ba617828285c36fe736557dee1fe2b515b48a2a8 | |
| parent | 636e07d9ed2338c6c759c2441eb159743a5245f1 (diff) | |
STL should ignore mouse wheel interactions
Mouse wheel interactions are not supported properly at the moment, we
always assume that onPreFling is called at the end of the gesture.
To avoid unexpected behaviors this gesture will now be ignored.
Test: atest MultiPointerDraggableTest
Test: atest SwipeToSceneTest
Bug: 371984715
Flag: com.android.systemui.scene_container
Change-Id: I07dde1328144792d381813a76ac5ae15cb72b3d9
4 files changed, 155 insertions, 9 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 085157ac72b9..e5ef79b37b88 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -630,7 +630,13 @@ internal class NestedScrollHandlerImpl( return@PriorityNestedScrollConnection false } - _lastPointersInfo = pointersInfoOwner.pointersInfo() + val pointersInfo = pointersInfoOwner.pointersInfo() + + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + _lastPointersInfo = pointersInfo // If the current swipe transition is *not* closed to 0f or 1f, then we want the // scroll events to intercept the current transition to continue the scene @@ -649,7 +655,12 @@ internal class NestedScrollHandlerImpl( val isZeroOffset = if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f - _lastPointersInfo = pointersInfoOwner.pointersInfo() + val pointersInfo = pointersInfoOwner.pointersInfo() + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + _lastPointersInfo = pointersInfo val canStart = when (behavior) { @@ -684,7 +695,12 @@ internal class NestedScrollHandlerImpl( // We could start an overscroll animation canChangeScene = false - _lastPointersInfo = pointersInfoOwner.pointersInfo() + val pointersInfo = pointersInfoOwner.pointersInfo() + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + _lastPointersInfo = pointersInfo val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable) if (canStart) { @@ -707,6 +723,12 @@ internal class NestedScrollHandlerImpl( onScroll = { offsetAvailable -> val controller = dragController ?: error("Should be called after onStart") + val pointersInfo = pointersInfoOwner.pointersInfo() + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection 0f + } + // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is // initiated in a nested child. controller.onDrag(delta = offsetAvailable) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index aa70a0ce156b..8613f6da0f62 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope @@ -184,6 +185,7 @@ internal class MultiPointerDraggableNode( private var startedPosition: Offset? = null private var pointersDown: Int = 0 + private var isMouseWheel: Boolean = false internal fun pointersInfo(): PointersInfo { return PointersInfo( @@ -191,6 +193,7 @@ internal class MultiPointerDraggableNode( startedPosition = startedPosition, // We could have 0 pointers during fling or for other reasons. pointersDown = pointersDown.coerceAtLeast(1), + isMouseWheel = isMouseWheel, ) } @@ -202,8 +205,15 @@ internal class MultiPointerDraggableNode( // [requireAncestorPointersInfoOwner], to our descendants. while (currentContext.isActive) { // During the Initial pass, we receive the event after our ancestors. - val changes = awaitPointerEvent(PointerEventPass.Initial).changes + val pointerEvent = awaitPointerEvent(PointerEventPass.Initial) + + // Ignore cursor has entered the input region. + // This will only be sent after the cursor is hovering when in the input region. + if (pointerEvent.type == PointerEventType.Enter) continue + + val changes = pointerEvent.changes pointersDown = changes.countDown() + isMouseWheel = pointerEvent.type == PointerEventType.Scroll when { // There are no more pointers down. @@ -223,7 +233,8 @@ internal class MultiPointerDraggableNode( // The first pointer down, startedPosition was not set. startedPosition == null -> { - val firstPointerDown = changes.single() + // Mouse wheel could start with multiple pointer down + val firstPointerDown = changes.first() velocityPointerId = firstPointerDown.id velocityTracker.resetTracking() velocityTracker.addPointerInputChange(firstPointerDown) @@ -647,4 +658,8 @@ internal fun interface PointersInfoOwner { fun pointersInfo(): PointersInfo } -internal data class PointersInfo(val startedPosition: Offset?, val pointersDown: Int) +internal data class PointersInfo( + val startedPosition: Offset?, + val pointersDown: Int, + val isMouseWheel: Boolean, +) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index ecef6be49df8..202105bd6c3c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -128,7 +128,7 @@ class DraggableHandlerTest { val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal) var pointerInfoOwner: () -> PointersInfo = { - PointersInfo(startedPosition = Offset.Zero, pointersDown = 1) + PointersInfo(startedPosition = Offset.Zero, pointersDown = 1, isMouseWheel = false) } fun nestedScrollConnection( @@ -1188,7 +1188,9 @@ class DraggableHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Drag from the **top** of the screen - pointerInfoOwner = { PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1) } + pointerInfoOwner = { + PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = false) + } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1206,7 +1208,11 @@ class DraggableHandlerTest { // Drag from the **bottom** of the screen pointerInfoOwner = { - PointersInfo(startedPosition = Offset(0f, SCREEN_SIZE), pointersDown = 1) + PointersInfo( + startedPosition = Offset(0f, SCREEN_SIZE), + pointersDown = 1, + isMouseWheel = false, + ) } assertIdle(currentScene = SceneC) @@ -1221,6 +1227,22 @@ class DraggableHandlerTest { } @Test + fun ignoreMouseWheel() = runGestureTest { + // Start at scene C. + navigateToSceneC() + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) + + // Use mouse wheel + pointerInfoOwner = { + PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = true) + } + assertIdle(currentScene = SceneC) + + nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) + assertIdle(currentScene = SceneC) + } + + @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 1711f3145cae..3001505d0e02 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -17,6 +17,8 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -39,12 +41,14 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ScrollWheel import androidx.compose.ui.test.assertPositionInRootIsEqualTo import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performMouseInput import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeRight import androidx.compose.ui.test.swipeUp @@ -498,6 +502,89 @@ class SwipeToSceneTest { } @Test + fun mouseWheel_pointerInputApi_ignoredByStl() { + val layoutState = layoutState() + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + TestContent(layoutState) + } + + rule.onRoot().performMouseInput { + enter(middle) + scroll(touchSlop, ScrollWheel.Vertical) + } + + // Mouse wheel scroll are ignored + assertThat(layoutState.currentTransition).isNull() + } + + @Test + fun mouseWheel_scrollableCannotScroll_ignoredByStl() { + val layoutState = layoutState() + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Box( + Modifier.fillMaxSize() + // A scrollable that does not consume the scroll gesture + .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) + ) + } + scene(SceneB) { Box(Modifier.fillMaxSize()) } + } + } + + rule.onRoot().performMouseInput { + enter(middle) + scroll(touchSlop, ScrollWheel.Vertical) + } + + // Mouse wheel scroll are ignored + assertThat(layoutState.currentTransition).isNull() + } + + @Test + fun mouseWheel_scrollableConsume_ignoredByStl() { + val layoutState = layoutState() + var touchSlop = 0f + var lastScroll = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Box( + Modifier.fillMaxSize() + // A scrollable that consumes the scroll gesture + .scrollable( + rememberScrollableState { + lastScroll = it + it + }, + Orientation.Vertical, + ) + ) + } + scene(SceneB) { Box(Modifier.fillMaxSize()) } + } + } + + rule.onRoot().performMouseInput { + enter(middle) + scroll(touchSlop * 10, ScrollWheel.Vertical) + } + + // Mouse wheel scroll are ignored + assertThat(layoutState.currentTransition).isNull() + + // Mouse wheel scroll can still be consumed by the scrollable + assertThat(lastScroll).isNotEqualTo(0f) + assertThat(touchSlop).isNotEqualTo(0f) + } + + @Test fun transitionKey() { val transitionkey = TransitionKey(debugName = "foo") val state = |