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

Commit 70ab84d6 authored by omarmt's avatar omarmt
Browse files

STL ignore mouse wheel

MouseWheel type events now have their own designated type. This type is
emitted even when there are no pointers present on the screen.

The NestedDraggableHandler can use this event to determine whether to
ignore the event.

A new PointerInfo.PointerDown is now emitted only when pointers are
detected on the screen.

Test: atest SwipeToSceneTest
Bug: 371984715
Flag: com.android.systemui.scene_container
Change-Id: I254256dd2c5175618cad6289a0ebf5b82617e15d
parent bca1a095
Loading
Loading
Loading
Loading
+62 −42
Original line number Diff line number Diff line
@@ -36,11 +36,11 @@ internal typealias SuspendedValue<T> = suspend () -> T

internal interface DraggableHandler {
    /**
     * Start a drag with the given [pointersInfo] and [overSlop].
     * Start a drag with the given [pointersDown] and [overSlop].
     *
     * The returned [DragController] should be used to continue or stop the drag.
     */
    fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController
    fun onDragStarted(pointersDown: PointersInfo.PointersDown?, overSlop: Float): DragController
}

/**
@@ -95,7 +95,7 @@ internal class DraggableHandlerImpl(
     * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
     * indicating that the transition should be intercepted.
     */
    internal fun shouldImmediatelyIntercept(pointersInfo: PointersInfo?): Boolean {
    internal fun shouldImmediatelyIntercept(pointersDown: PointersInfo.PointersDown?): Boolean {
        // We don't intercept the touch if we are not currently driving the transition.
        val dragController = dragController
        if (dragController?.isDrivingTransition != true) {
@@ -106,7 +106,7 @@ internal class DraggableHandlerImpl(

        // Only intercept the current transition if one of the 2 swipes results is also a transition
        // between the same pair of contents.
        val swipes = computeSwipes(pointersInfo)
        val swipes = computeSwipes(pointersDown)
        val fromContent = layoutImpl.content(swipeAnimation.currentContent)
        val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
        val currentScene = layoutImpl.state.currentScene
@@ -123,7 +123,10 @@ internal class DraggableHandlerImpl(
                ))
    }

    override fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController {
    override fun onDragStarted(
        pointersDown: PointersInfo.PointersDown?,
        overSlop: Float,
    ): DragController {
        if (overSlop == 0f) {
            val oldDragController = dragController
            check(oldDragController != null && oldDragController.isDrivingTransition) {
@@ -148,7 +151,7 @@ internal class DraggableHandlerImpl(
            return updateDragController(swipes, swipeAnimation)
        }

        val swipes = computeSwipes(pointersInfo)
        val swipes = computeSwipes(pointersDown)
        val fromContent = layoutImpl.contentForUserActions()

        swipes.updateSwipesResults(fromContent)
@@ -194,11 +197,11 @@ internal class DraggableHandlerImpl(
        )
    }

    private fun computeSwipes(pointersInfo: PointersInfo?): Swipes {
        val fromSource = pointersInfo?.let { resolveSwipeSource(it.startedPosition) }
    private fun computeSwipes(pointersDown: PointersInfo.PointersDown?): Swipes {
        val fromSource = pointersDown?.let { resolveSwipeSource(it.startedPosition) }
        return Swipes(
            upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersInfo, fromSource),
            downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersInfo, fromSource),
            upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersDown, fromSource),
            downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersDown, fromSource),
        )
    }
}
@@ -206,7 +209,7 @@ internal class DraggableHandlerImpl(
private fun resolveSwipe(
    orientation: Orientation,
    isUpOrLeft: Boolean,
    pointersInfo: PointersInfo?,
    pointersDown: PointersInfo.PointersDown?,
    fromSource: SwipeSource.Resolved?,
): Swipe.Resolved {
    return Swipe.Resolved(
@@ -227,9 +230,9 @@ private fun resolveSwipe(
                    }
            },
        // If the number of pointers is not specified, 1 is assumed.
        pointerCount = pointersInfo?.pointersDown ?: 1,
        pointerCount = pointersDown?.count ?: 1,
        // Resolves the pointer type only if all pointers are of the same type.
        pointersType = pointersInfo?.pointersDownByType?.keys?.singleOrNull(),
        pointersType = pointersDown?.countByType?.keys?.singleOrNull(),
        fromSource = fromSource,
    )
}
@@ -540,13 +543,16 @@ internal class NestedScrollHandlerImpl(

    val connection: PriorityNestedScrollConnection = nestedScrollConnection()

    private fun resolveSwipe(isUpOrLeft: Boolean, pointersInfo: PointersInfo?): Swipe.Resolved {
    private fun resolveSwipe(
        isUpOrLeft: Boolean,
        pointersDown: PointersInfo.PointersDown?,
    ): Swipe.Resolved {
        return resolveSwipe(
            orientation = draggableHandler.orientation,
            isUpOrLeft = isUpOrLeft,
            pointersInfo = pointersInfo,
            pointersDown = pointersDown,
            fromSource =
                pointersInfo?.let { draggableHandler.resolveSwipeSource(it.startedPosition) },
                pointersDown?.let { draggableHandler.resolveSwipeSource(it.startedPosition) },
        )
    }

@@ -555,7 +561,7 @@ internal class NestedScrollHandlerImpl(
        // moving on to the next scene.
        var canChangeScene = false

        var lastPointersInfo: PointersInfo? = null
        var lastPointersDown: PointersInfo.PointersDown? = null

        fun hasNextScene(amount: Float): Boolean {
            val transitionState = layoutState.transitionState
@@ -563,8 +569,8 @@ internal class NestedScrollHandlerImpl(
            val fromScene = layoutImpl.scene(scene)
            val resolvedSwipe =
                when {
                    amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersInfo)
                    amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersInfo)
                    amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersDown)
                    amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersDown)
                    else -> null
                }
            val nextScene = resolvedSwipe?.let { fromScene.findActionResultBestMatch(it) }
@@ -581,14 +587,24 @@ internal class NestedScrollHandlerImpl(
        return PriorityNestedScrollConnection(
            orientation = orientation,
            canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
                val pointersInfo = pointersInfoOwner.pointersInfo()
                val pointersDown: PointersInfo.PointersDown? =
                    when (val info = pointersInfoOwner.pointersInfo()) {
                        PointersInfo.MouseWheel -> {
                            // Do not support mouse wheel interactions
                            return@PriorityNestedScrollConnection false
                        }

                        is PointersInfo.PointersDown -> info
                        null -> null
                    }

                canChangeScene =
                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f

                val canInterceptSwipeTransition =
                    canChangeScene &&
                        offsetAvailable != 0f &&
                        draggableHandler.shouldImmediatelyIntercept(pointersInfo)
                        draggableHandler.shouldImmediatelyIntercept(pointersDown)
                if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false

                val threshold = layoutImpl.transitionInterceptionThreshold
@@ -599,11 +615,7 @@ internal class NestedScrollHandlerImpl(
                    return@PriorityNestedScrollConnection false
                }

                if (pointersInfo?.isMouseWheel == true) {
                    // Do not support mouse wheel interactions
                    return@PriorityNestedScrollConnection false
                }
                lastPointersInfo = pointersInfo
                lastPointersDown = pointersDown

                // 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
@@ -622,12 +634,17 @@ internal class NestedScrollHandlerImpl(
                val isZeroOffset =
                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f

                val pointersInfo = pointersInfoOwner.pointersInfo()
                if (pointersInfo?.isMouseWheel == true) {
                val pointersDown: PointersInfo.PointersDown? =
                    when (val info = pointersInfoOwner.pointersInfo()) {
                        PointersInfo.MouseWheel -> {
                            // Do not support mouse wheel interactions
                            return@PriorityNestedScrollConnection false
                        }
                lastPointersInfo = pointersInfo

                        is PointersInfo.PointersDown -> info
                        null -> null
                    }
                lastPointersDown = pointersDown

                val canStart =
                    when (behavior) {
@@ -664,12 +681,17 @@ internal class NestedScrollHandlerImpl(
                // We could start an overscroll animation
                canChangeScene = false

                val pointersInfo = pointersInfoOwner.pointersInfo()
                if (pointersInfo?.isMouseWheel == true) {
                val pointersDown: PointersInfo.PointersDown? =
                    when (val info = pointersInfoOwner.pointersInfo()) {
                        PointersInfo.MouseWheel -> {
                            // Do not support mouse wheel interactions
                            return@PriorityNestedScrollConnection false
                        }
                lastPointersInfo = pointersInfo

                        is PointersInfo.PointersDown -> info
                        null -> null
                    }
                lastPointersDown = pointersDown

                val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
                if (canStart) {
@@ -679,11 +701,10 @@ internal class NestedScrollHandlerImpl(
                canStart
            },
            onStart = { firstScroll ->
                val pointersInfo = lastPointersInfo
                scrollController(
                    dragController =
                        draggableHandler.onDragStarted(
                            pointersInfo = pointersInfo,
                            pointersDown = lastPointersDown,
                            overSlop = if (isIntercepting) 0f else firstScroll,
                        ),
                    canChangeScene = canChangeScene,
@@ -701,8 +722,7 @@ private fun scrollController(
): ScrollController {
    return object : ScrollController {
        override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
            val pointersInfo = pointersInfoOwner.pointersInfo()
            if (pointersInfo?.isMouseWheel == true) {
            if (pointersInfoOwner.pointersInfo() == PointersInfo.MouseWheel) {
                // Do not support mouse wheel interactions
                return 0f
            }
+61 −68
Original line number Diff line number Diff line
@@ -80,8 +80,8 @@ import kotlinx.coroutines.launch
@Stable
internal fun Modifier.multiPointerDraggable(
    orientation: Orientation,
    startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
    onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
    startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
    onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
    onFirstPointerDown: () -> Unit = {},
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
    dispatcher: NestedScrollDispatcher,
@@ -99,8 +99,9 @@ internal fun Modifier.multiPointerDraggable(

private data class MultiPointerDraggableElement(
    private val orientation: Orientation,
    private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
    private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
    private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
    private val onDragStarted:
        (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
    private val onFirstPointerDown: () -> Unit,
    private val swipeDetector: SwipeDetector,
    private val dispatcher: NestedScrollDispatcher,
@@ -126,8 +127,8 @@ private data class MultiPointerDraggableElement(

internal class MultiPointerDraggableNode(
    orientation: Orientation,
    var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
    var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
    var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
    var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
    var onFirstPointerDown: () -> Unit,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
    private val dispatcher: NestedScrollDispatcher,
@@ -185,20 +186,27 @@ internal class MultiPointerDraggableNode(

    private var lastPointerEvent: PointerEvent? = null
    private var startedPosition: Offset? = null
    private var pointersDown: Int = 0
    private var countPointersDown: Int = 0

    internal fun pointersInfo(): PointersInfo? {
        val startedPosition = startedPosition
        val lastPointerEvent = lastPointerEvent
        if (startedPosition == null || lastPointerEvent == null) {
        // This may be null, i.e. when the user uses TalkBack
            return null
        }
        val lastPointerEvent = lastPointerEvent ?: return null

        if (lastPointerEvent.type == PointerEventType.Scroll) return PointersInfo.MouseWheel

        val startedPosition = startedPosition ?: return null

        return PointersInfo(
        return PointersInfo.PointersDown(
            startedPosition = startedPosition,
            pointersDown = pointersDown,
            lastPointerEvent = lastPointerEvent,
            count = countPointersDown,
            countByType =
                buildMap {
                    lastPointerEvent.changes.fastForEach { change ->
                        if (!change.pressed) return@fastForEach
                        val newValue = (get(change.type) ?: 0) + 1
                        put(change.type, newValue)
                    }
                },
        )
    }

@@ -218,11 +226,11 @@ internal class MultiPointerDraggableNode(

                val changes = pointerEvent.changes
                lastPointerEvent = pointerEvent
                pointersDown = changes.countDown()
                countPointersDown = changes.countDown()

                when {
                    // There are no more pointers down.
                    pointersDown == 0 -> {
                    countPointersDown == 0 -> {
                        startedPosition = null

                        // In case of multiple events with 0 pointers down (not pressed) we may have
@@ -290,8 +298,8 @@ internal class MultiPointerDraggableNode(
                    detectDragGestures(
                        orientation = orientation,
                        startDragImmediately = startDragImmediately,
                        onDragStart = { pointersInfo, overSlop ->
                            onDragStarted(pointersInfo, overSlop)
                        onDragStart = { pointersDown, overSlop ->
                            onDragStarted(pointersDown, overSlop)
                        },
                        onDrag = { controller, amount ->
                            dispatchScrollEvents(
@@ -440,8 +448,8 @@ internal class MultiPointerDraggableNode(
     */
    private suspend fun AwaitPointerEventScope.detectDragGestures(
        orientation: Orientation,
        startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
        onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
        startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
        onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
        onDrag: (controller: DragController, dragAmount: Float) -> Unit,
        onDragEnd: (controller: DragController) -> Unit,
        onDragCancel: (controller: DragController) -> Unit,
@@ -466,13 +474,14 @@ internal class MultiPointerDraggableNode(
                .first()

        var overSlop = 0f
        var lastPointersInfo =
        var lastPointersDown: PointersInfo.PointersDown =
            checkNotNull(pointersInfo()) {
                "We should have pointers down, last event: $currentEvent"
            }
                as PointersInfo.PointersDown

        val drag =
            if (startDragImmediately(lastPointersInfo)) {
            if (startDragImmediately(lastPointersDown)) {
                consumablePointer.consume()
                consumablePointer
            } else {
@@ -499,10 +508,11 @@ internal class MultiPointerDraggableNode(
                            )
                    } ?: return

                lastPointersInfo =
                lastPointersDown =
                    checkNotNull(pointersInfo()) {
                        "We should have pointers down, last event: $currentEvent"
                    }
                        as PointersInfo.PointersDown
                // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
                // the touch slop. However, the overSlop we pass to onDragStarted() is used to
                // compute the direction we are dragging in, so overSlop should never be 0f unless
@@ -516,7 +526,7 @@ internal class MultiPointerDraggableNode(
                drag
            }

        val controller = onDragStart(lastPointersInfo, overSlop)
        val controller = onDragStart(lastPointersDown, overSlop)

        val successful: Boolean
        try {
@@ -666,48 +676,31 @@ internal fun interface PointersInfoOwner {
    fun pointersInfo(): PointersInfo?
}

internal sealed interface PointersInfo {
    /**
     * Holds information about pointer interactions within a composable.
     *
     * This class stores details such as the starting position of a gesture, the number of pointers
     * down, and whether the last pointer event was a mouse wheel scroll.
     *
 * @param startedPosition The starting position of the gesture. This is the position where the first
 *   pointer touched the screen, not necessarily the point where dragging begins. This may be
 *   different from the initial touch position if a child composable intercepts the gesture before
 *   this one.
 * @param pointersDown The number of pointers currently down.
 * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll.
 * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type
     * @param startedPosition The starting position of the gesture. This is the position where the
     *   first pointer touched the screen, not necessarily the point where dragging begins. This may
     *   be different from the initial touch position if a child composable intercepts the gesture
     *   before this one.
     * @param count The number of pointers currently down.
     * @param countByType Provide a map of pointer types to the count of pointers of that type
     *   currently down/pressed.
     */
internal data class PointersInfo(
    data class PointersDown(
        val startedPosition: Offset,
    val pointersDown: Int,
    val isMouseWheel: Boolean,
    val pointersDownByType: Map<PointerType, Int>,
) {
        val count: Int,
        val countByType: Map<PointerType, Int>,
    ) : PointersInfo {
        init {
        check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" }
            check(count > 0) { "We should have at least 1 pointer down, $count instead" }
        }
    }

private fun PointersInfo(
    startedPosition: Offset,
    pointersDown: Int,
    lastPointerEvent: PointerEvent,
): PointersInfo {
    return PointersInfo(
        startedPosition = startedPosition,
        pointersDown = pointersDown,
        isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll,
        pointersDownByType =
            buildMap {
                lastPointerEvent.changes.fastForEach { change ->
                    if (!change.pressed) return@fastForEach
                    val newValue = (get(change.type) ?: 0) + 1
                    put(change.type, newValue)
                }
            },
    )
    /** Indicates whether the last pointer event was a mouse wheel scroll. */
    data object MouseWheel : PointersInfo
}
+2 −2
Original line number Diff line number Diff line
@@ -200,10 +200,10 @@ private class SwipeToSceneNode(

    override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()

    private fun startDragImmediately(pointersInfo: PointersInfo): Boolean {
    private fun startDragImmediately(pointersDown: PointersInfo.PointersDown): Boolean {
        // Immediately start the drag if the user can't swipe in the other direction and the gesture
        // handler can intercept it.
        return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersInfo)
        return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersDown)
    }

    private fun canOppositeSwipe(): Boolean {
+33 −33

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -574,7 +574,7 @@ class SwipeToSceneTest {
        rule.setContent {
            touchSlop = LocalViewConfiguration.current.touchSlop
            SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) {
                scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
                scene(SceneA, userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneB)) {
                    Box(
                        Modifier.fillMaxSize()
                            // A scrollable that does not consume the scroll gesture