Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0b71c2e authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "STL should ignore mouse wheel interactions" into main

parents 2a482374 6e547dac
Loading
Loading
Loading
Loading
+25 −3
Original line number Diff line number Diff line
@@ -631,7 +631,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
@@ -650,7 +656,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) {
@@ -685,7 +696,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)
+18 −3
Original line number Diff line number Diff line
@@ -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,
)
+25 −3
Original line number Diff line number Diff line
@@ -127,7 +127,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(
@@ -1187,7 +1187,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))
@@ -1205,7 +1207,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)

@@ -1219,6 +1225,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.
+87 −0
Original line number Diff line number Diff line
@@ -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
@@ -497,6 +501,89 @@ class SwipeToSceneTest {
        assertThat(layoutState.currentTransition).isNotNull()
    }

    @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")