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

Commit f9a01533 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[flexiglass] Adds gestureFilter lambda to STL

System UI needs a way to be able to filter out drag gestures that start
(e.g. have their first down event) in specific regions on the display.
Please see the next CL on this chain to learn more about why.

This CL adds gestureFilter as a lambda that can be passed to STL and
logic where STL checks with gestureFilter to see whether it should
continue to handle a drag, given its starting position.

Bug: 367447743
Test: manually verified with the next CL
Test: compose tests added
Flag: NONE unused change, next CL is flagged
Change-Id: I081e7628f49681eb4a6d5630e91a0332944736e6
parent 7ab5adc9
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -123,6 +123,10 @@ internal class DraggableHandlerImpl(
        overSlop: Float,
        pointersDown: Int,
    ): DragController {
        if (startedPosition != null && layoutImpl.gestureFilter(startedPosition)) {
            return NoOpDragController
        }

        if (overSlop == 0f) {
            val oldDragController = dragController
            check(oldDragController != null && oldDragController.isDrivingTransition) {
+7 −0
Original line number Diff line number Diff line
@@ -47,6 +47,9 @@ import androidx.compose.ui.unit.LayoutDirection
 * @param state the state of this layout.
 * @param swipeSourceDetector the edge detector used to detect which edge a swipe is started from,
 *   if any.
 * @param gestureFilter decides whether a drag gesture that started at the given start position
 *   should be filtered. If the lambda returns `true`, the drag gesture will be ignored. If it
 *   returns `false`, the drag gesture will be handled.
 * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
 *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
 * @param builder the configuration of the different scenes and overlays of this layout.
@@ -57,6 +60,7 @@ fun SceneTransitionLayout(
    modifier: Modifier = Modifier,
    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
    gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter,
    @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f,
    builder: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -65,6 +69,7 @@ fun SceneTransitionLayout(
        modifier,
        swipeSourceDetector,
        swipeDetector,
        gestureFilter,
        transitionInterceptionThreshold,
        onLayoutImpl = null,
        builder,
@@ -616,6 +621,7 @@ internal fun SceneTransitionLayoutForTesting(
    modifier: Modifier = Modifier,
    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
    gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter,
    transitionInterceptionThreshold: Float = 0f,
    onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
    builder: SceneTransitionLayoutScope.() -> Unit,
@@ -632,6 +638,7 @@ internal fun SceneTransitionLayoutForTesting(
                transitionInterceptionThreshold = transitionInterceptionThreshold,
                builder = builder,
                animationScope = animationScope,
                gestureFilter = gestureFilter,
            )
            .also { onLayoutImpl?.invoke(it) }
    }
+2 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.ApproachLayoutModifierNode
import androidx.compose.ui.layout.ApproachMeasureScope
import androidx.compose.ui.layout.LookaheadScope
@@ -70,6 +71,7 @@ internal class SceneTransitionLayoutImpl(
     * animations.
     */
    internal val animationScope: CoroutineScope,
    internal val gestureFilter: (startedPosition: Offset) -> Boolean,
) {
    /**
     * The map of [Scene]s.
+3 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene

import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerInputChange

/** {@link SwipeDetector} helps determine whether a swipe gestured has occurred. */
@@ -31,6 +32,8 @@ interface SwipeDetector {

val DefaultSwipeDetector = PassthroughSwipeDetector()

val DefaultGestureFilter = { _: Offset -> false }

/** An {@link SwipeDetector} implementation that recognizes a swipe on any input. */
class PassthroughSwipeDetector : SwipeDetector {
    override fun detectSwipe(change: PointerInputChange): Boolean {
+10 −0
Original line number Diff line number Diff line
@@ -108,6 +108,8 @@ class DraggableHandlerTest {

        val transitionInterceptionThreshold = 0.05f

        var gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter

        private val layoutImpl =
            SceneTransitionLayoutImpl(
                    state = layoutState,
@@ -120,6 +122,7 @@ class DraggableHandlerTest {
                    // Use testScope and not backgroundScope here because backgroundScope does not
                    // work well with advanceUntilIdle(), which is used by some tests.
                    animationScope = testScope,
                    gestureFilter = { startedPosition -> gestureFilter.invoke(startedPosition) },
                )
                .apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }

@@ -316,6 +319,13 @@ class DraggableHandlerTest {
        assertTransition(currentScene = SceneA)
    }

    @Test
    fun onDragStarted_doesNotStartTransition_whenGestureFiltered() = runGestureTest {
        gestureFilter = { _ -> true }
        onDragStarted(overSlop = down(fractionOfScreen = 0.1f), expectedConsumedOverSlop = 0f)
        assertIdle(currentScene = SceneA)
    }

    @Test
    fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
        val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))