• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.mechanics.spec
18 
19 /**
20  * Identifies a segment in a [MotionSpec].
21  *
22  * A segment only exists between two adjacent [Breakpoint]s; it cannot span multiple breakpoints.
23  * The [direction] indicates to the relevant [DirectionalMotionSpec] of the [MotionSpec].
24  *
25  * The position of the [minBreakpoint] must be less or equal to the position of the [maxBreakpoint].
26  */
27 data class SegmentKey(
28     val minBreakpoint: BreakpointKey,
29     val maxBreakpoint: BreakpointKey,
30     val direction: InputDirection,
31 )
32 
33 /**
34  * Captures denormalized segment data from a [MotionSpec].
35  *
36  * Instances are created by the [MotionSpec] and used by the [MotionValue] runtime to compute the
37  * output value. By default, the [SegmentData] is cached while [isValidForInput] returns true.
38  *
39  * The [SegmentData] has an intrinsic direction, thus the segment has an entry and exit side, at the
40  * respective breakpoint.
41  */
42 data class SegmentData(
43     val spec: MotionSpec,
44     val minBreakpoint: Breakpoint,
45     val maxBreakpoint: Breakpoint,
46     val direction: InputDirection,
47     val mapping: Mapping,
48 ) {
49     val key = SegmentKey(minBreakpoint.key, maxBreakpoint.key, direction)
50 
51     /**
52      * Whether the given [inputPosition] and [inputDirection] should be handled by this segment.
53      *
54      * The input is considered invalid only if the direction changes or the input is *at or outside*
55      * the segment on the exit-side. The input remains intentionally valid outside the segment on
56      * the entry-side, to avoid flip-flopping.
57      */
isValidForInputnull58     fun isValidForInput(inputPosition: Float, inputDirection: InputDirection): Boolean {
59         if (inputDirection != direction) return false
60 
61         return when (inputDirection) {
62             InputDirection.Max -> inputPosition < maxBreakpoint.position
63             InputDirection.Min -> inputPosition > minBreakpoint.position
64         }
65     }
66 
67     /**
68      * The breakpoint at the side of the segment's start.
69      *
70      * The [entryBreakpoint]'s [Guarantee] is the relevant guarantee for this segment.
71      */
72     val entryBreakpoint: Breakpoint
73         get() =
74             when (direction) {
75                 InputDirection.Max -> minBreakpoint
76                 InputDirection.Min -> maxBreakpoint
77             }
78 }
79 
80 /**
81  * Maps the `input` of a [MotionValue] to the desired output value.
82  *
83  * The mapping implementation can be arbitrary, but must not produce discontinuities.
84  */
interfacenull85 fun interface Mapping {
86     /** Computes the [MotionValue]'s target output, given the input. */
87     fun map(input: Float): Float
88 
89     /** `f(x) = x` */
90     object Identity : Mapping {
91         override fun map(input: Float): Float {
92             return input
93         }
94     }
95 
96     /** `f(x) = value` */
97     data class Fixed(val value: Float) : Mapping {
98         init {
99             require(value.isFinite())
100         }
101 
102         override fun map(input: Float): Float {
103             return value
104         }
105     }
106 
107     /** `f(x) = factor*x + offset` */
108     data class Linear(val factor: Float, val offset: Float = 0f) : Mapping {
109         init {
110             require(factor.isFinite())
111             require(offset.isFinite())
112         }
113 
114         override fun map(input: Float): Float {
115             return input * factor + offset
116         }
117     }
118 
119     data class Tanh(val scaling: Float, val tilt: Float, val offset: Float = 0f) : Mapping {
120 
121         init {
122             require(scaling.isFinite())
123             require(tilt.isFinite())
124             require(offset.isFinite())
125         }
126 
127         override fun map(input: Float): Float {
128             return scaling * kotlin.math.tanh((input + offset) / (scaling * tilt))
129         }
130     }
131 
132     companion object {
133         val Zero = Fixed(0f)
134         val One = Fixed(1f)
135         val Two = Fixed(2f)
136     }
137 }
138