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