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

Commit 1c2a75af authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge changes from topic...

Merge changes from topic "revert-25235382-revert-25221853-stl-edge-swipe-AOBBWQJDVK-QLGYWKSYMO" into main

* changes:
  Revert^2 "Add support for swipes started from an edge"
  Revert^2 "Add support for swipe with multiple fingers"
parents 7a0ccd8b 9835d090
Loading
Loading
Loading
Loading
+75 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.compose.animation.scene

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp

interface EdgeDetector {
    /**
     * Return the [Edge] associated to [position] inside a layout of size [layoutSize], given
     * [density] and [orientation].
     */
    fun edge(
        layoutSize: IntSize,
        position: IntOffset,
        density: Density,
        orientation: Orientation,
    ): Edge?
}

val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)

/** An [EdgeDetector] that detects edges assuming a fixed edge size of [size]. */
class FixedSizeEdgeDetector(val size: Dp) : EdgeDetector {
    override fun edge(
        layoutSize: IntSize,
        position: IntOffset,
        density: Density,
        orientation: Orientation,
    ): Edge? {
        val axisSize: Int
        val axisPosition: Int
        val topOrLeft: Edge
        val bottomOrRight: Edge
        when (orientation) {
            Orientation.Horizontal -> {
                axisSize = layoutSize.width
                axisPosition = position.x
                topOrLeft = Edge.Left
                bottomOrRight = Edge.Right
            }
            Orientation.Vertical -> {
                axisSize = layoutSize.height
                axisPosition = position.y
                topOrLeft = Edge.Top
                bottomOrRight = Edge.Bottom
            }
        }

        val sizePx = with(density) { size.toPx() }
        return when {
            axisPosition <= sizePx -> topOrLeft
            axisPosition >= axisSize - sizePx -> bottomOrRight
            else -> null
        }
    }
}
+2 −3
Original line number Original line Diff line number Diff line
@@ -2,7 +2,6 @@ package com.android.compose.animation.scene


import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import kotlinx.coroutines.CoroutineScope


interface GestureHandler {
interface GestureHandler {
    val draggable: DraggableHandler
    val draggable: DraggableHandler
@@ -10,9 +9,9 @@ interface GestureHandler {
}
}


interface DraggableHandler {
interface DraggableHandler {
    suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset)
    fun onDragStarted(startedPosition: Offset, pointersDown: Int = 1)
    fun onDelta(pixels: Float)
    fun onDelta(pixels: Float)
    suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float)
    fun onDragStopped(velocity: Float)
}
}


interface NestedScrollHandler {
interface NestedScrollHandler {
+191 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.compose.animation.scene

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
import androidx.compose.foundation.gestures.horizontalDrag
import androidx.compose.foundation.gestures.verticalDrag
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.input.pointer.util.addPointerInputChange
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.util.fastForEach

/**
 * Make an element draggable in the given [orientation].
 *
 * The main difference with [multiPointerDraggable] and
 * [androidx.compose.foundation.gestures.draggable] is that [onDragStarted] also receives the number
 * of pointers that are down when the drag is started. If you don't need this information, you
 * should use `draggable` instead.
 *
 * Note that the current implementation is trivial: we wait for the touch slope on the *first* down
 * pointer, then we count the number of distinct pointers that are down right before calling
 * [onDragStarted]. This means that the drag won't start when a first pointer is down (but not
 * dragged) and a second pointer is down and dragged. This is an implementation detail that might
 * change in the future.
 */
// TODO(b/291055080): Migrate to the Modifier.Node API.
@Composable
internal fun Modifier.multiPointerDraggable(
    orientation: Orientation,
    enabled: Boolean,
    startDragImmediately: Boolean,
    onDragStarted: (startedPosition: Offset, pointersDown: Int) -> Unit,
    onDragDelta: (Float) -> Unit,
    onDragStopped: (velocity: Float) -> Unit,
): Modifier {
    val onDragStarted by rememberUpdatedState(onDragStarted)
    val onDragStopped by rememberUpdatedState(onDragStopped)
    val onDragDelta by rememberUpdatedState(onDragDelta)
    val startDragImmediately by rememberUpdatedState(startDragImmediately)

    val velocityTracker = remember { VelocityTracker() }
    val maxFlingVelocity =
        LocalViewConfiguration.current.maximumFlingVelocity.let { max ->
            val maxF = max.toFloat()
            Velocity(maxF, maxF)
        }

    return this.pointerInput(enabled, orientation, maxFlingVelocity) {
        if (!enabled) {
            return@pointerInput
        }

        val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown ->
            velocityTracker.resetTracking()
            onDragStarted(startedPosition, pointersDown)
        }

        val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) }

        val onDragEnd: () -> Unit = {
            val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
            onDragStopped(
                when (orientation) {
                    Orientation.Horizontal -> velocity.x
                    Orientation.Vertical -> velocity.y
                }
            )
        }

        val onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit = { change, amount ->
            velocityTracker.addPointerInputChange(change)
            onDragDelta(amount)
        }

        detectDragGestures(
            orientation = orientation,
            startDragImmediately = { startDragImmediately },
            onDragStart = onDragStart,
            onDragEnd = onDragEnd,
            onDragCancel = onDragCancel,
            onDrag = onDrag,
        )
    }
}

/**
 * Detect drag gestures in the given [orientation].
 *
 * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
 * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for:
 * 1) starting the gesture immediately without requiring a drag >= touch slope;
 * 2) passing the number of pointers down to [onDragStart].
 */
private suspend fun PointerInputScope.detectDragGestures(
    orientation: Orientation,
    startDragImmediately: () -> Boolean,
    onDragStart: (startedPosition: Offset, pointersDown: Int) -> Unit,
    onDragEnd: () -> Unit,
    onDragCancel: () -> Unit,
    onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
) {
    awaitEachGesture {
        val initialDown = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
        var overSlop = 0f
        val drag =
            if (startDragImmediately()) {
                initialDown.consume()
                initialDown
            } else {
                val down = awaitFirstDown(requireUnconsumed = false)
                val onSlopReached = { change: PointerInputChange, over: Float ->
                    change.consume()
                    overSlop = over
                }

                // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once
                // it is public.
                when (orientation) {
                    Orientation.Horizontal ->
                        awaitHorizontalTouchSlopOrCancellation(down.id, onSlopReached)
                    Orientation.Vertical ->
                        awaitVerticalTouchSlopOrCancellation(down.id, onSlopReached)
                }
            }

        if (drag != null) {
            // Count the number of pressed pointers.
            val pressed = mutableSetOf<PointerId>()
            currentEvent.changes.fastForEach { change ->
                if (change.pressed) {
                    pressed.add(change.id)
                }
            }

            onDragStart(drag.position, pressed.size)
            onDrag(drag, overSlop)

            val successful =
                when (orientation) {
                    Orientation.Horizontal ->
                        horizontalDrag(drag.id) {
                            onDrag(it, it.positionChange().x)
                            it.consume()
                        }
                    Orientation.Vertical ->
                        verticalDrag(drag.id) {
                            onDrag(it, it.positionChange().y)
                            it.consume()
                        }
                }

            if (successful) {
                onDragEnd()
            } else {
                onDragCancel()
            }
        }
    }
}
+0 −17
Original line number Original line Diff line number Diff line
@@ -16,7 +16,6 @@


package com.android.compose.animation.scene
package com.android.compose.animation.scene


import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.State
@@ -101,19 +100,3 @@ private class SceneScopeImpl(
        MovableElement(layoutImpl, scene, key, modifier, content)
        MovableElement(layoutImpl, scene, key, modifier, content)
    }
    }
}
}

/** The destination scene when swiping up or left from [upOrLeft]. */
internal fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
    return when (orientation) {
        Orientation.Vertical -> userActions[Swipe.Up]
        Orientation.Horizontal -> userActions[Swipe.Left]
    }
}

/** The destination scene when swiping down or right from [downOrRight]. */
internal fun Scene.downOrRight(orientation: Orientation): SceneKey? {
    return when (orientation) {
        Orientation.Vertical -> userActions[Swipe.Down]
        Orientation.Horizontal -> userActions[Swipe.Right]
    }
}
+11 −6
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.compose.animation.scene
package com.android.compose.animation.scene


import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.remember
@@ -37,6 +38,7 @@ import androidx.compose.ui.platform.LocalDensity
 *   instance by triggering back navigation or by swiping to a new scene.
 *   instance by triggering back navigation or by swiping to a new scene.
 * @param transitions the definition of the transitions used to animate a change of scene.
 * @param transitions the definition of the transitions used to animate a change of scene.
 * @param state the observable state of this layout.
 * @param state the observable state of this layout.
 * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
 * @param scenes the configuration of the different scenes of this layout.
 * @param scenes the configuration of the different scenes of this layout.
 */
 */
@Composable
@Composable
@@ -46,6 +48,7 @@ fun SceneTransitionLayout(
    transitions: SceneTransitions,
    transitions: SceneTransitions,
    modifier: Modifier = Modifier,
    modifier: Modifier = Modifier,
    state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
    state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
    edgeDetector: EdgeDetector = DefaultEdgeDetector,
    scenes: SceneTransitionLayoutScope.() -> Unit,
    scenes: SceneTransitionLayoutScope.() -> Unit,
) {
) {
    val density = LocalDensity.current
    val density = LocalDensity.current
@@ -56,15 +59,17 @@ fun SceneTransitionLayout(
            transitions,
            transitions,
            state,
            state,
            density,
            density,
            edgeDetector,
        )
        )
    }
    }


    layoutImpl.onChangeScene = onChangeScene
    layoutImpl.onChangeScene = onChangeScene
    layoutImpl.transitions = transitions
    layoutImpl.transitions = transitions
    layoutImpl.density = density
    layoutImpl.density = density
    layoutImpl.edgeDetector = edgeDetector

    layoutImpl.setScenes(scenes)
    layoutImpl.setScenes(scenes)
    layoutImpl.setCurrentScene(currentScene)
    layoutImpl.setCurrentScene(currentScene)

    layoutImpl.Content(modifier)
    layoutImpl.Content(modifier)
}
}


@@ -191,9 +196,9 @@ data class Swipe(
    }
    }
}
}


enum class SwipeDirection {
enum class SwipeDirection(val orientation: Orientation) {
    Up,
    Up(Orientation.Vertical),
    Down,
    Down(Orientation.Vertical),
    Left,
    Left(Orientation.Horizontal),
    Right,
    Right(Orientation.Horizontal),
}
}
Loading