• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 @file:OptIn(ExperimentalAnimatableApi::class, ExperimentalMaterial3ExpressiveApi::class)
18 
19 package com.android.mechanics.demo.presentation
20 
21 import androidx.compose.animation.core.ExperimentalAnimatableApi
22 import androidx.compose.foundation.background
23 import androidx.compose.foundation.layout.Arrangement
24 import androidx.compose.foundation.layout.Box
25 import androidx.compose.foundation.layout.Column
26 import androidx.compose.foundation.layout.ColumnScope
27 import androidx.compose.foundation.layout.Row
28 import androidx.compose.foundation.layout.fillMaxWidth
29 import androidx.compose.foundation.layout.height
30 import androidx.compose.foundation.layout.padding
31 import androidx.compose.foundation.layout.size
32 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
33 import androidx.compose.material3.MaterialTheme
34 import androidx.compose.material3.Slider
35 import androidx.compose.material3.Text
36 import androidx.compose.runtime.Composable
37 import androidx.compose.runtime.getValue
38 import androidx.compose.runtime.mutableFloatStateOf
39 import androidx.compose.runtime.mutableStateOf
40 import androidx.compose.runtime.remember
41 import androidx.compose.runtime.setValue
42 import androidx.compose.ui.Modifier
43 import androidx.compose.ui.graphics.graphicsLayer
44 import androidx.compose.ui.platform.LocalDensity
45 import androidx.compose.ui.unit.dp
46 import com.android.mechanics.debug.DebugMotionValueVisualization
47 import com.android.mechanics.debug.debugMotionValueGraph
48 import com.android.mechanics.demo.staging.defaultSpatialSpring
49 import com.android.mechanics.demo.staging.rememberDistanceGestureContext
50 import com.android.mechanics.demo.staging.rememberMotionValue
51 import com.android.mechanics.demo.tuneable.Demo
52 import com.android.mechanics.spec.Breakpoint
53 import com.android.mechanics.spec.BreakpointKey
54 import com.android.mechanics.spec.Guarantee
55 import com.android.mechanics.spec.Mapping
56 import com.android.mechanics.spec.MotionSpec
57 import com.android.mechanics.spec.builder
58 import com.android.mechanics.spring.SpringParameters
59 
60 object GuaranteeFadeDemo : Demo<GuaranteeFadeDemo.Config> {
61     object Keys {
62         val Start = BreakpointKey("Start")
63         val Detach = BreakpointKey("Detach")
64         val End = Breakpoint.maxLimit.key
65     }
66 
67     data class Config(val defaultSpring: SpringParameters)
68 
69     var inputRange by mutableStateOf(0f..200f)
70 
71     @Composable
DemoUinull72     override fun DemoUi(config: Config, modifier: Modifier) {
73         val colors = MaterialTheme.colorScheme
74 
75         val density = LocalDensity.current
76 
77         val guaranteeDistance = remember { mutableFloatStateOf(80f) }
78 
79         // Also using GestureContext.dragOffset as input.
80         val gestureContext = rememberDistanceGestureContext()
81         val spec = rememberSpec(inputOutputRange = inputRange, config, { 0f })
82         val guaranteeSpec =
83             rememberSpec(inputOutputRange = inputRange, config, guaranteeDistance::floatValue)
84 
85         val withoutGuarantee =
86             rememberMotionValue(gestureContext::dragOffset, { spec }, gestureContext)
87 
88         val withGuarantee =
89             rememberMotionValue(gestureContext::dragOffset, { guaranteeSpec }, gestureContext)
90 
91         val defaultValueColor = MaterialTheme.colorScheme.primary
92         val guaranteeValueColor = MaterialTheme.colorScheme.secondary
93 
94         Column(
95             verticalArrangement = Arrangement.spacedBy(24.dp),
96             modifier = modifier.fillMaxWidth().padding(vertical = 24.dp, horizontal = 48.dp),
97         ) {
98             Text("Guarantee Distance")
99 
100             Slider(
101                 value = guaranteeDistance.value,
102                 valueRange = inputRange,
103                 onValueChange = { guaranteeDistance.value = it },
104                 modifier = Modifier.fillMaxWidth(),
105             )
106 
107             Row(
108                 horizontalArrangement = Arrangement.Center,
109                 modifier = Modifier.fillMaxWidth().padding(16.dp),
110             ) {
111                 Box(
112                     modifier =
113                         Modifier.size(48.dp)
114                             .graphicsLayer { this.alpha = withoutGuarantee.floatValue }
115                             .background(defaultValueColor)
116                 )
117                 Box(
118                     modifier =
119                         Modifier.size(48.dp)
120                             .graphicsLayer { this.alpha = withGuarantee.floatValue }
121                             .background(guaranteeValueColor)
122                 )
123             }
124 
125             // MotionValue visualization
126             DebugMotionValueVisualization(
127                 withGuarantee,
128                 inputRange,
129                 modifier =
130                     Modifier.fillMaxWidth()
131                         .height(64.dp)
132                         .debugMotionValueGraph(
133                             withoutGuarantee,
134                             defaultValueColor,
135                             inputRange,
136                             0f..1f,
137                         ),
138             )
139 
140             // Input visualization
141             Slider(
142                 value = gestureContext.dragOffset,
143                 valueRange = inputRange,
144                 onValueChange = { gestureContext.dragOffset = it },
145                 modifier = Modifier.fillMaxWidth(),
146             )
147         }
148     }
149 
150     @Composable
rememberSpecnull151     fun rememberSpec(
152         inputOutputRange: ClosedFloatingPointRange<Float>,
153         config: Config,
154         guaranteeDistance: () -> Float,
155     ): MotionSpec {
156         val distance = guaranteeDistance()
157         val guarantee = if (distance > 0) Guarantee.InputDelta(distance) else Guarantee.None
158 
159         return remember(guarantee, config, inputOutputRange) {
160             MotionSpec.builder(config.defaultSpring, initialMapping = Mapping.Zero)
161                 .toBreakpoint((inputOutputRange.start + inputOutputRange.endInclusive) / 2f)
162                 .completeWith(Mapping.One, guarantee = guarantee)
163         }
164     }
165 
166     @Composable
rememberDefaultConfignull167     override fun rememberDefaultConfig(): Config {
168         val defaultSpring = defaultSpatialSpring()
169         return remember(defaultSpring) { Config(defaultSpring) }
170     }
171 
172     override val visualizationInputRange: ClosedFloatingPointRange<Float>
173         get() = inputRange
174 
175     @Composable
ConfigUinull176     override fun ColumnScope.ConfigUi(config: Config, onConfigChanged: (Config) -> Unit) {}
177 
178     override val identifier: String = "GuaranteeFadeDemo"
179 }
180