Loading mechanics/src/com/android/mechanics/GestureContext.kt +30 −29 Original line number Diff line number Diff line Loading @@ -52,16 +52,16 @@ interface GestureContext { /** * The gesture distance of the current gesture, in pixels. * * Used solely for the [GestureDistance] [Guarantee]. Can be hard-coded to a static value if * Used solely for the [GestureDragDelta] [Guarantee]. Can be hard-coded to a static value if * this type of [Guarantee] is not used. */ val distance: Float val dragOffset: Float } /** [GestureContext] implementation for manually set values. */ class ProvidedGestureContext(direction: InputDirection, distance: Float) : GestureContext { class ProvidedGestureContext(direction: InputDirection, dragOffset: Float) : GestureContext { override var direction by mutableStateOf(direction) override var distance by mutableFloatStateOf(distance) override var dragOffset by mutableFloatStateOf(dragOffset) } /** Loading @@ -70,13 +70,13 @@ class ProvidedGestureContext(direction: InputDirection, distance: Float) : Gestu * The direction is determined from the gesture input, where going further than * [directionChangeSlop] in the opposite direction toggles the direction. * * @param initialDistance The initial [distance] of the [GestureContext] * @param initialDragOffset The initial [dragOffset] of the [GestureContext] * @param initialDirection The initial [direction] of the [GestureContext] * @param directionChangeSlop the amount [distance] must be moved in the opposite direction for the * [direction] to flip. * @param directionChangeSlop the amount [dragOffset] must be moved in the opposite direction for * the [direction] to flip. */ class DistanceGestureContext( initialDistance: Float, initialDragOffset: Float, initialDirection: InputDirection, directionChangeSlop: Float, ) : GestureContext { Loading @@ -89,37 +89,38 @@ class DistanceGestureContext( override var direction by mutableStateOf(initialDirection) private set private var furthestDistance by mutableFloatStateOf(initialDistance) private var _distance by mutableFloatStateOf(initialDistance) private var furthestDragOffset by mutableFloatStateOf(initialDragOffset) override var distance: Float get() = _distance private var _dragOffset by mutableFloatStateOf(initialDragOffset) override var dragOffset: Float get() = _dragOffset /** * Updates the [distance]. * Updates the [dragOffset]. * * This flips the [direction], if the [value] is further than [directionChangeSlop] away * from the furthest recorded value regarding to the current [direction]. */ set(value) { _distance = value _dragOffset = value this.direction = when (direction) { InputDirection.Max -> { if (furthestDistance - value > directionChangeSlop) { furthestDistance = value if (furthestDragOffset - value > directionChangeSlop) { furthestDragOffset = value InputDirection.Min } else { furthestDistance = max(value, furthestDistance) furthestDragOffset = max(value, furthestDragOffset) InputDirection.Max } } InputDirection.Min -> { if (value - furthestDistance > directionChangeSlop) { furthestDistance = value if (value - furthestDragOffset > directionChangeSlop) { furthestDragOffset = value InputDirection.Max } else { furthestDistance = min(value, furthestDistance) furthestDragOffset = min(value, furthestDragOffset) InputDirection.Min } } Loading @@ -143,14 +144,14 @@ class DistanceGestureContext( when (direction) { InputDirection.Max -> { if (furthestDistance - distance > directionChangeSlop) { furthestDistance = distance if (furthestDragOffset - dragOffset > directionChangeSlop) { furthestDragOffset = dragOffset direction = InputDirection.Min } } InputDirection.Min -> { if (distance - furthestDistance > directionChangeSlop) { furthestDistance = value if (dragOffset - furthestDragOffset > directionChangeSlop) { furthestDragOffset = value direction = InputDirection.Max } } Loading @@ -158,14 +159,14 @@ class DistanceGestureContext( } /** * Sets [distance] and [direction] to the specified values. * Sets [dragOffset] and [direction] to the specified values. * * This also resets memoized [furthestDistance], which is used to determine the direction * This also resets memoized [furthestDragOffset], which is used to determine the direction * change. */ fun reset(distance: Float, direction: InputDirection) { this.distance = distance fun reset(dragOffset: Float, direction: InputDirection) { this.dragOffset = dragOffset this.direction = direction this.furthestDistance = distance this.furthestDragOffset = dragOffset } } mechanics/src/com/android/mechanics/MotionValue.kt +22 −22 Original line number Diff line number Diff line Loading @@ -175,7 +175,7 @@ class MotionValue( var result = spec.hashCode() result = result * 31 + currentInput().hashCode() result = result * 31 + currentDirection.hashCode() result = result * 31 + currentGestureDistance.hashCode() result = result * 31 + currentGestureDragOffset.hashCode() // Track whether the spring needs animation frames to finish // In fact, whether the spring is settling is the only relevant bit to Loading Loading @@ -222,7 +222,7 @@ class MotionValue( // Capture the last frames input. lastFrameTimeNanos = currentAnimationTimeNanos lastInput = currentInput() lastGestureDistance = currentGestureDistance lastGestureDragOffset = currentGestureDragOffset // Not capturing currentDirection and spec explicitly, they are included in // lastSegment Loading @@ -236,7 +236,7 @@ class MotionValue( FrameData( lastInput, currentDirection, lastGestureDistance, lastGestureDragOffset, lastFrameTimeNanos, lastSpringState, lastSegment, Loading Loading @@ -354,8 +354,8 @@ class MotionValue( /** The [currentInput] of the last frame */ private var lastInput by mutableFloatStateOf(currentInput()) /** The [currentGestureDistance] input of the last frame. */ private var lastGestureDistance by mutableFloatStateOf(currentGestureDistance) /** The [currentGestureDragOffset] input of the last frame. */ private var lastGestureDragOffset by mutableFloatStateOf(currentGestureDragOffset) // ---- Declarative Update --------------------------------------------------------------------- Loading @@ -373,9 +373,9 @@ class MotionValue( private inline val currentDirection: InputDirection get() = gestureContext.direction /** [gestureContext]'s [GestureContext.distance], exists solely for consistent naming. */ private inline val currentGestureDistance: Float get() = gestureContext.distance /** [gestureContext]'s [GestureContext.dragOffset], exists solely for consistent naming. */ private inline val currentGestureDragOffset: Float get() = gestureContext.dragOffset /** * The current segment, which defines the [Mapping] function used to transform the input to the Loading Loading @@ -536,7 +536,7 @@ class MotionValue( GuaranteeState.withStartValue( when (entryBreakpoint.guarantee) { is Guarantee.InputDelta -> currentInput() is Guarantee.GestureDistance -> gestureContext.distance is Guarantee.GestureDragDelta -> gestureContext.dragOffset is Guarantee.None -> return@derivedStateOf GuaranteeState.Inactive } ) Loading @@ -548,16 +548,16 @@ class MotionValue( GuaranteeState.withStartValue( when (entryBreakpoint.guarantee) { is Guarantee.InputDelta -> entryBreakpoint.position is Guarantee.GestureDistance -> { // Guess the [GestureDistance] origin - since the gesture distance is Guarantee.GestureDragDelta -> { // Guess the GestureDragDelta origin - since the gesture dragOffset // is sampled, interpolate it according to when the breakpoint was // crossed in the last frame. val fractionalBreakpointPos = lastFrameFractionOfPosition(entryBreakpoint.position) lerp( lastGestureDistance, gestureContext.distance, lastGestureDragOffset, gestureContext.dragOffset, fractionalBreakpointPos, ) } Loading @@ -573,7 +573,7 @@ class MotionValue( guaranteeOriginState.withCurrentValue( when (entryBreakpoint.guarantee) { is Guarantee.InputDelta -> currentInput() is Guarantee.GestureDistance -> gestureContext.distance is Guarantee.GestureDragDelta -> gestureContext.dragOffset is Guarantee.None -> return@derivedStateOf GuaranteeState.Inactive }, currentSegment.direction, Loading Loading @@ -689,10 +689,10 @@ class MotionValue( val guaranteeValueAtNextBreakpoint = when (lastBreakpoint.guarantee) { is Guarantee.InputDelta -> nextBreakpoint.position is Guarantee.GestureDistance -> is Guarantee.GestureDragDelta -> lerp( lastGestureDistance, gestureContext.distance, lastGestureDragOffset, gestureContext.dragOffset, nextBreakpointFrameFraction, ) Loading Loading @@ -734,11 +734,11 @@ class MotionValue( is Guarantee.InputDelta -> GuaranteeState.withStartValue(nextBreakpoint.position) is Guarantee.GestureDistance -> is Guarantee.GestureDragDelta -> GuaranteeState.withStartValue( lerp( lastGestureDistance, gestureContext.distance, lastGestureDragOffset, gestureContext.dragOffset, nextBreakpointFrameFraction, ) ) Loading Loading @@ -827,7 +827,7 @@ class MotionValue( FrameData( lastInput, lastSegment.direction, lastGestureDistance, lastGestureDragOffset, lastFrameTimeNanos, lastSpringState, lastSegment, Loading Loading @@ -898,7 +898,7 @@ internal value class GuaranteeState(val packedValue: Long) { when (val guarantee = breakpoint.guarantee) { is Guarantee.None -> return breakpoint.spring is Guarantee.InputDelta -> guarantee.delta is Guarantee.GestureDistance -> guarantee.distance is Guarantee.GestureDragDelta -> guarantee.delta } val springTighteningFraction = maxDelta / denominator Loading mechanics/src/com/android/mechanics/debug/DebugInspector.kt +1 −1 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ data class FrameData internal constructor( val input: Float, val gestureDirection: InputDirection, val gestureDistance: Float, val gestureDragOffset: Float, val frameTimeNanos: Long, val springState: SpringState, private val segment: SegmentData, Loading mechanics/src/com/android/mechanics/spec/Guarantee.kt +2 −2 Original line number Diff line number Diff line Loading @@ -38,8 +38,8 @@ sealed class Guarantee { data class InputDelta(val delta: Float) : Guarantee() /** * Guarantees to complete the animation before the gesture is [distance] away from the gesture * Guarantees to complete the animation before the gesture is [delta] away from the gesture * position captured when the breakpoint was crossed. */ data class GestureDistance(val distance: Float) : Guarantee() data class GestureDragDelta(val delta: Float) : Guarantee() } mechanics/src/com/android/mechanics/spec/Segment.kt +7 −0 Original line number Diff line number Diff line Loading @@ -107,6 +107,13 @@ fun interface Mapping { } } data class Tanh(val scaling: Float, val tilt: Float, val offset: Float = 0f) : Mapping { override fun map(input: Float): Float { return scaling * kotlin.math.tanh((input + offset) / (scaling * tilt)) } } companion object { val Zero = Fixed(0f) val One = Fixed(1f) Loading Loading
mechanics/src/com/android/mechanics/GestureContext.kt +30 −29 Original line number Diff line number Diff line Loading @@ -52,16 +52,16 @@ interface GestureContext { /** * The gesture distance of the current gesture, in pixels. * * Used solely for the [GestureDistance] [Guarantee]. Can be hard-coded to a static value if * Used solely for the [GestureDragDelta] [Guarantee]. Can be hard-coded to a static value if * this type of [Guarantee] is not used. */ val distance: Float val dragOffset: Float } /** [GestureContext] implementation for manually set values. */ class ProvidedGestureContext(direction: InputDirection, distance: Float) : GestureContext { class ProvidedGestureContext(direction: InputDirection, dragOffset: Float) : GestureContext { override var direction by mutableStateOf(direction) override var distance by mutableFloatStateOf(distance) override var dragOffset by mutableFloatStateOf(dragOffset) } /** Loading @@ -70,13 +70,13 @@ class ProvidedGestureContext(direction: InputDirection, distance: Float) : Gestu * The direction is determined from the gesture input, where going further than * [directionChangeSlop] in the opposite direction toggles the direction. * * @param initialDistance The initial [distance] of the [GestureContext] * @param initialDragOffset The initial [dragOffset] of the [GestureContext] * @param initialDirection The initial [direction] of the [GestureContext] * @param directionChangeSlop the amount [distance] must be moved in the opposite direction for the * [direction] to flip. * @param directionChangeSlop the amount [dragOffset] must be moved in the opposite direction for * the [direction] to flip. */ class DistanceGestureContext( initialDistance: Float, initialDragOffset: Float, initialDirection: InputDirection, directionChangeSlop: Float, ) : GestureContext { Loading @@ -89,37 +89,38 @@ class DistanceGestureContext( override var direction by mutableStateOf(initialDirection) private set private var furthestDistance by mutableFloatStateOf(initialDistance) private var _distance by mutableFloatStateOf(initialDistance) private var furthestDragOffset by mutableFloatStateOf(initialDragOffset) override var distance: Float get() = _distance private var _dragOffset by mutableFloatStateOf(initialDragOffset) override var dragOffset: Float get() = _dragOffset /** * Updates the [distance]. * Updates the [dragOffset]. * * This flips the [direction], if the [value] is further than [directionChangeSlop] away * from the furthest recorded value regarding to the current [direction]. */ set(value) { _distance = value _dragOffset = value this.direction = when (direction) { InputDirection.Max -> { if (furthestDistance - value > directionChangeSlop) { furthestDistance = value if (furthestDragOffset - value > directionChangeSlop) { furthestDragOffset = value InputDirection.Min } else { furthestDistance = max(value, furthestDistance) furthestDragOffset = max(value, furthestDragOffset) InputDirection.Max } } InputDirection.Min -> { if (value - furthestDistance > directionChangeSlop) { furthestDistance = value if (value - furthestDragOffset > directionChangeSlop) { furthestDragOffset = value InputDirection.Max } else { furthestDistance = min(value, furthestDistance) furthestDragOffset = min(value, furthestDragOffset) InputDirection.Min } } Loading @@ -143,14 +144,14 @@ class DistanceGestureContext( when (direction) { InputDirection.Max -> { if (furthestDistance - distance > directionChangeSlop) { furthestDistance = distance if (furthestDragOffset - dragOffset > directionChangeSlop) { furthestDragOffset = dragOffset direction = InputDirection.Min } } InputDirection.Min -> { if (distance - furthestDistance > directionChangeSlop) { furthestDistance = value if (dragOffset - furthestDragOffset > directionChangeSlop) { furthestDragOffset = value direction = InputDirection.Max } } Loading @@ -158,14 +159,14 @@ class DistanceGestureContext( } /** * Sets [distance] and [direction] to the specified values. * Sets [dragOffset] and [direction] to the specified values. * * This also resets memoized [furthestDistance], which is used to determine the direction * This also resets memoized [furthestDragOffset], which is used to determine the direction * change. */ fun reset(distance: Float, direction: InputDirection) { this.distance = distance fun reset(dragOffset: Float, direction: InputDirection) { this.dragOffset = dragOffset this.direction = direction this.furthestDistance = distance this.furthestDragOffset = dragOffset } }
mechanics/src/com/android/mechanics/MotionValue.kt +22 −22 Original line number Diff line number Diff line Loading @@ -175,7 +175,7 @@ class MotionValue( var result = spec.hashCode() result = result * 31 + currentInput().hashCode() result = result * 31 + currentDirection.hashCode() result = result * 31 + currentGestureDistance.hashCode() result = result * 31 + currentGestureDragOffset.hashCode() // Track whether the spring needs animation frames to finish // In fact, whether the spring is settling is the only relevant bit to Loading Loading @@ -222,7 +222,7 @@ class MotionValue( // Capture the last frames input. lastFrameTimeNanos = currentAnimationTimeNanos lastInput = currentInput() lastGestureDistance = currentGestureDistance lastGestureDragOffset = currentGestureDragOffset // Not capturing currentDirection and spec explicitly, they are included in // lastSegment Loading @@ -236,7 +236,7 @@ class MotionValue( FrameData( lastInput, currentDirection, lastGestureDistance, lastGestureDragOffset, lastFrameTimeNanos, lastSpringState, lastSegment, Loading Loading @@ -354,8 +354,8 @@ class MotionValue( /** The [currentInput] of the last frame */ private var lastInput by mutableFloatStateOf(currentInput()) /** The [currentGestureDistance] input of the last frame. */ private var lastGestureDistance by mutableFloatStateOf(currentGestureDistance) /** The [currentGestureDragOffset] input of the last frame. */ private var lastGestureDragOffset by mutableFloatStateOf(currentGestureDragOffset) // ---- Declarative Update --------------------------------------------------------------------- Loading @@ -373,9 +373,9 @@ class MotionValue( private inline val currentDirection: InputDirection get() = gestureContext.direction /** [gestureContext]'s [GestureContext.distance], exists solely for consistent naming. */ private inline val currentGestureDistance: Float get() = gestureContext.distance /** [gestureContext]'s [GestureContext.dragOffset], exists solely for consistent naming. */ private inline val currentGestureDragOffset: Float get() = gestureContext.dragOffset /** * The current segment, which defines the [Mapping] function used to transform the input to the Loading Loading @@ -536,7 +536,7 @@ class MotionValue( GuaranteeState.withStartValue( when (entryBreakpoint.guarantee) { is Guarantee.InputDelta -> currentInput() is Guarantee.GestureDistance -> gestureContext.distance is Guarantee.GestureDragDelta -> gestureContext.dragOffset is Guarantee.None -> return@derivedStateOf GuaranteeState.Inactive } ) Loading @@ -548,16 +548,16 @@ class MotionValue( GuaranteeState.withStartValue( when (entryBreakpoint.guarantee) { is Guarantee.InputDelta -> entryBreakpoint.position is Guarantee.GestureDistance -> { // Guess the [GestureDistance] origin - since the gesture distance is Guarantee.GestureDragDelta -> { // Guess the GestureDragDelta origin - since the gesture dragOffset // is sampled, interpolate it according to when the breakpoint was // crossed in the last frame. val fractionalBreakpointPos = lastFrameFractionOfPosition(entryBreakpoint.position) lerp( lastGestureDistance, gestureContext.distance, lastGestureDragOffset, gestureContext.dragOffset, fractionalBreakpointPos, ) } Loading @@ -573,7 +573,7 @@ class MotionValue( guaranteeOriginState.withCurrentValue( when (entryBreakpoint.guarantee) { is Guarantee.InputDelta -> currentInput() is Guarantee.GestureDistance -> gestureContext.distance is Guarantee.GestureDragDelta -> gestureContext.dragOffset is Guarantee.None -> return@derivedStateOf GuaranteeState.Inactive }, currentSegment.direction, Loading Loading @@ -689,10 +689,10 @@ class MotionValue( val guaranteeValueAtNextBreakpoint = when (lastBreakpoint.guarantee) { is Guarantee.InputDelta -> nextBreakpoint.position is Guarantee.GestureDistance -> is Guarantee.GestureDragDelta -> lerp( lastGestureDistance, gestureContext.distance, lastGestureDragOffset, gestureContext.dragOffset, nextBreakpointFrameFraction, ) Loading Loading @@ -734,11 +734,11 @@ class MotionValue( is Guarantee.InputDelta -> GuaranteeState.withStartValue(nextBreakpoint.position) is Guarantee.GestureDistance -> is Guarantee.GestureDragDelta -> GuaranteeState.withStartValue( lerp( lastGestureDistance, gestureContext.distance, lastGestureDragOffset, gestureContext.dragOffset, nextBreakpointFrameFraction, ) ) Loading Loading @@ -827,7 +827,7 @@ class MotionValue( FrameData( lastInput, lastSegment.direction, lastGestureDistance, lastGestureDragOffset, lastFrameTimeNanos, lastSpringState, lastSegment, Loading Loading @@ -898,7 +898,7 @@ internal value class GuaranteeState(val packedValue: Long) { when (val guarantee = breakpoint.guarantee) { is Guarantee.None -> return breakpoint.spring is Guarantee.InputDelta -> guarantee.delta is Guarantee.GestureDistance -> guarantee.distance is Guarantee.GestureDragDelta -> guarantee.delta } val springTighteningFraction = maxDelta / denominator Loading
mechanics/src/com/android/mechanics/debug/DebugInspector.kt +1 −1 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ data class FrameData internal constructor( val input: Float, val gestureDirection: InputDirection, val gestureDistance: Float, val gestureDragOffset: Float, val frameTimeNanos: Long, val springState: SpringState, private val segment: SegmentData, Loading
mechanics/src/com/android/mechanics/spec/Guarantee.kt +2 −2 Original line number Diff line number Diff line Loading @@ -38,8 +38,8 @@ sealed class Guarantee { data class InputDelta(val delta: Float) : Guarantee() /** * Guarantees to complete the animation before the gesture is [distance] away from the gesture * Guarantees to complete the animation before the gesture is [delta] away from the gesture * position captured when the breakpoint was crossed. */ data class GestureDistance(val distance: Float) : Guarantee() data class GestureDragDelta(val delta: Float) : Guarantee() }
mechanics/src/com/android/mechanics/spec/Segment.kt +7 −0 Original line number Diff line number Diff line Loading @@ -107,6 +107,13 @@ fun interface Mapping { } } data class Tanh(val scaling: Float, val tilt: Float, val offset: Float = 0f) : Mapping { override fun map(input: Float): Float { return scaling * kotlin.math.tanh((input + offset) / (scaling * tilt)) } } companion object { val Zero = Fixed(0f) val One = Fixed(1f) Loading