Loading mechanics/src/com/android/mechanics/behavior/EdgeContainerExpansionBackground.kt 0 → 100644 +141 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.mechanics.behavior import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.node.requireGraphicsContext import androidx.compose.ui.util.fastCoerceAtLeast import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.lerp import kotlin.math.min /** * Draws the background of an edge container, and applies clipping to it. * * Intended to be used with a [EdgeContainerExpansionSpec] motion. */ fun Modifier.edgeContainerExpansionBackground( backgroundColor: Color, spec: EdgeContainerExpansionSpec, ): Modifier = this.then(EdgeContainerExpansionBackgroundElement(backgroundColor, spec)) internal class EdgeContainerExpansionBackgroundNode( var backgroundColor: Color, var spec: EdgeContainerExpansionSpec, ) : Modifier.Node(), DrawModifierNode { private var graphicsLayer: GraphicsLayer? = null private var lastOutlineSize = Size.Zero fun invalidateOutline() { lastOutlineSize = Size.Zero } override fun onAttach() { graphicsLayer = requireGraphicsContext().createGraphicsLayer().apply { clip = true } } override fun onDetach() { requireGraphicsContext().releaseGraphicsLayer(checkNotNull(graphicsLayer)) } override fun ContentDrawScope.draw() { val height = size.height // The width is growing between visibleHeight and detachHeight val visibleHeight = spec.visibleHeight.toPx() val widthFraction = ((height - visibleHeight) / (spec.detachHeight.toPx() - visibleHeight)).fastCoerceIn( 0f, 1f, ) val width = size.width - lerp(spec.widthOffset.toPx(), 0f, widthFraction) val horizontalInset = (size.width - width) / 2f // The radius is growing at the beginning of the transition val radius = height.fastCoerceIn(spec.minRadius.toPx(), spec.radius.toPx()) // Draw (at most) the bottom half of the rounded corner rectangle, aligned to the bottom. val upperHeight = height - radius // The rounded rect is drawn at 2x the radius height, to avoid smaller corner radii. // The clipRect limits this to the relevant part (-1 to avoid a hairline gap being visible // between this and the fill below. clipRect(top = (upperHeight - 1).fastCoerceAtLeast(0f)) { drawRoundRect( color = backgroundColor, cornerRadius = CornerRadius(radius), size = Size(width, radius * 2f), topLeft = Offset(horizontalInset, size.height - radius * 2f), ) } if (upperHeight > 0) { // Fill the space above the bottom shape. drawRect( color = backgroundColor, topLeft = Offset(horizontalInset, 0f), size = Size(width, upperHeight), ) } // Draw the node's content in a separate layer. val graphicsLayer = checkNotNull(graphicsLayer) graphicsLayer.record { this@draw.drawContent() } if (size != lastOutlineSize) { // The clip outline is a rounded corner shape matching the bottom of the shape. // At the top, the rounded corner shape extends by radiusPx above top. // This clipping thus would not prevent the containers content to overdraw at the top, // however this is off-screen anyways. val top = min(-radius, height - radius * 2f) val rect = Rect(left = horizontalInset, top = top, right = width, bottom = height) graphicsLayer.setRoundRectOutline(rect.topLeft, rect.size, radius) lastOutlineSize = size } this.drawLayer(graphicsLayer) } } private data class EdgeContainerExpansionBackgroundElement( val backgroundColor: Color, val spec: EdgeContainerExpansionSpec, ) : ModifierNodeElement<EdgeContainerExpansionBackgroundNode>() { override fun create(): EdgeContainerExpansionBackgroundNode = EdgeContainerExpansionBackgroundNode(backgroundColor, spec) override fun update(node: EdgeContainerExpansionBackgroundNode) { node.backgroundColor = backgroundColor if (node.spec != spec) { node.spec = spec node.invalidateOutline() } } } mechanics/src/com/android/mechanics/behavior/EdgeContainerExpansionSpec.kt 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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. */ @file:OptIn(ExperimentalMaterial3ExpressiveApi::class) package com.android.mechanics.behavior import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MotionScheme import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.lerp import com.android.mechanics.spec.Breakpoint import com.android.mechanics.spec.BreakpointKey import com.android.mechanics.spec.DirectionalMotionSpec import com.android.mechanics.spec.InputDirection import com.android.mechanics.spec.Mapping import com.android.mechanics.spec.MotionSpec import com.android.mechanics.spec.OnChangeSegmentHandler import com.android.mechanics.spec.SegmentData import com.android.mechanics.spec.SegmentKey import com.android.mechanics.spec.builder import com.android.mechanics.spec.reverseBuilder import com.android.mechanics.spring.SpringParameters /** Motion spec for a vertically expandable container. */ class EdgeContainerExpansionSpec( val visibleHeight: Dp = Defaults.VisibleHeight, val preDetachRatio: Float = Defaults.PreDetachRatio, val detachHeight: Dp = Defaults.DetachHeight, val attachHeight: Dp = Defaults.AttachHeight, val widthOffset: Dp = Defaults.WidthOffset, val minRadius: Dp = Defaults.MinRadius, val radius: Dp = Defaults.Radius, val attachSpring: SpringParameters = Defaults.AttachSpring, val detachSpring: SpringParameters = Defaults.DetachSpring, val opacitySpring: SpringParameters = Defaults.OpacitySpring, ) { fun createHeightSpec(motionScheme: MotionScheme, density: Density): MotionSpec { return with(density) { val spatialSpring = SpringParameters(motionScheme.defaultSpatialSpec()) val detachSpec = DirectionalMotionSpec.builder( initialMapping = Mapping.Zero, defaultSpring = spatialSpring, ) .toBreakpoint(0f, key = Breakpoints.Attach) .continueWith(Mapping.Linear(preDetachRatio)) .toBreakpoint(detachHeight.toPx(), key = Breakpoints.Detach) .completeWith(Mapping.Identity, detachSpring) val attachSpec = DirectionalMotionSpec.reverseBuilder(defaultSpring = spatialSpring) .toBreakpoint(attachHeight.toPx(), key = Breakpoints.Detach) .completeWith(mapping = Mapping.Zero, attachSpring) val segmentHandlers = mapOf<SegmentKey, OnChangeSegmentHandler>( SegmentKey(Breakpoints.Detach, Breakpoint.maxLimit.key, InputDirection.Min) to { currentSegment, _, newDirection -> if (newDirection != currentSegment.direction) currentSegment else null }, SegmentKey(Breakpoints.Attach, Breakpoints.Detach, InputDirection.Max) to { currentSegment: SegmentData, newInput: Float, newDirection: InputDirection -> if (newDirection != currentSegment.direction && newInput >= 0) currentSegment else null }, ) MotionSpec( maxDirection = detachSpec, minDirection = attachSpec, segmentHandlers = segmentHandlers, ) } } fun createWidthSpec( intrinsicWidth: Float, motionScheme: MotionScheme, density: Density, ): MotionSpec { return with(density) { MotionSpec.builder( SpringParameters(motionScheme.defaultSpatialSpec()), initialMapping = { input -> val fraction = (input / detachHeight.toPx()).fastCoerceIn(0f, 1f) intrinsicWidth - lerp(widthOffset.toPx(), 0f, fraction) }, ) .complete() } } fun createAlphaSpec(motionScheme: MotionScheme, density: Density): MotionSpec { return with(density) { val detachSpec = DirectionalMotionSpec.builder( SpringParameters(motionScheme.defaultEffectsSpec()), initialMapping = Mapping.Zero, ) .toBreakpoint(visibleHeight.toPx()) .completeWith(Mapping.One, opacitySpring) val attachSpec = DirectionalMotionSpec.builder( SpringParameters(motionScheme.defaultEffectsSpec()), initialMapping = Mapping.Zero, ) .toBreakpoint(visibleHeight.toPx()) .completeWith(Mapping.One, opacitySpring) MotionSpec(maxDirection = detachSpec, minDirection = attachSpec) } } companion object { object Breakpoints { val Attach = BreakpointKey("EdgeContainerExpansion::Attach") val Detach = BreakpointKey("EdgeContainerExpansion::Detach") } object Defaults { val VisibleHeight = 24.dp val PreDetachRatio = .25f val DetachHeight = 80.dp val AttachHeight = 40.dp val WidthOffset = 28.dp val MinRadius = 28.dp val Radius = 46.dp val AttachSpring = SpringParameters(stiffness = 380f, dampingRatio = 0.9f) val DetachSpring = SpringParameters(stiffness = 380f, dampingRatio = 0.9f) val OpacitySpring = SpringParameters(stiffness = 1200f, dampingRatio = 0.99f) } } } mechanics/src/com/android/mechanics/spring/MaterialSpringParameters.kt 0 → 100644 +49 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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. */ @file:OptIn(ExperimentalMaterial3ExpressiveApi::class) package com.android.mechanics.spring import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.SpringSpec import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable /** Converts a [SpringSpec] into its [SpringParameters] equivalent. */ fun SpringParameters(springSpec: SpringSpec<out Any>) = with(springSpec) { SpringParameters(stiffness, dampingRatio) } /** * Converts a [FiniteAnimationSpec] from the [MotionScheme] into its [SpringParameters] equivalent. */ @ExperimentalMaterial3ExpressiveApi fun SpringParameters(animationSpec: FiniteAnimationSpec<out Any>): SpringParameters { check(animationSpec is SpringSpec) { "animationSpec is expected to be a SpringSpec, but is $animationSpec" } return SpringParameters(animationSpec) } @Composable fun defaultSpatialSpring(): SpringParameters { return SpringParameters(MaterialTheme.motionScheme.defaultSpatialSpec()) } @Composable fun defaultEffectSpring(): SpringParameters { return SpringParameters(MaterialTheme.motionScheme.defaultEffectsSpec()) } Loading
mechanics/src/com/android/mechanics/behavior/EdgeContainerExpansionBackground.kt 0 → 100644 +141 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.mechanics.behavior import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.node.requireGraphicsContext import androidx.compose.ui.util.fastCoerceAtLeast import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.lerp import kotlin.math.min /** * Draws the background of an edge container, and applies clipping to it. * * Intended to be used with a [EdgeContainerExpansionSpec] motion. */ fun Modifier.edgeContainerExpansionBackground( backgroundColor: Color, spec: EdgeContainerExpansionSpec, ): Modifier = this.then(EdgeContainerExpansionBackgroundElement(backgroundColor, spec)) internal class EdgeContainerExpansionBackgroundNode( var backgroundColor: Color, var spec: EdgeContainerExpansionSpec, ) : Modifier.Node(), DrawModifierNode { private var graphicsLayer: GraphicsLayer? = null private var lastOutlineSize = Size.Zero fun invalidateOutline() { lastOutlineSize = Size.Zero } override fun onAttach() { graphicsLayer = requireGraphicsContext().createGraphicsLayer().apply { clip = true } } override fun onDetach() { requireGraphicsContext().releaseGraphicsLayer(checkNotNull(graphicsLayer)) } override fun ContentDrawScope.draw() { val height = size.height // The width is growing between visibleHeight and detachHeight val visibleHeight = spec.visibleHeight.toPx() val widthFraction = ((height - visibleHeight) / (spec.detachHeight.toPx() - visibleHeight)).fastCoerceIn( 0f, 1f, ) val width = size.width - lerp(spec.widthOffset.toPx(), 0f, widthFraction) val horizontalInset = (size.width - width) / 2f // The radius is growing at the beginning of the transition val radius = height.fastCoerceIn(spec.minRadius.toPx(), spec.radius.toPx()) // Draw (at most) the bottom half of the rounded corner rectangle, aligned to the bottom. val upperHeight = height - radius // The rounded rect is drawn at 2x the radius height, to avoid smaller corner radii. // The clipRect limits this to the relevant part (-1 to avoid a hairline gap being visible // between this and the fill below. clipRect(top = (upperHeight - 1).fastCoerceAtLeast(0f)) { drawRoundRect( color = backgroundColor, cornerRadius = CornerRadius(radius), size = Size(width, radius * 2f), topLeft = Offset(horizontalInset, size.height - radius * 2f), ) } if (upperHeight > 0) { // Fill the space above the bottom shape. drawRect( color = backgroundColor, topLeft = Offset(horizontalInset, 0f), size = Size(width, upperHeight), ) } // Draw the node's content in a separate layer. val graphicsLayer = checkNotNull(graphicsLayer) graphicsLayer.record { this@draw.drawContent() } if (size != lastOutlineSize) { // The clip outline is a rounded corner shape matching the bottom of the shape. // At the top, the rounded corner shape extends by radiusPx above top. // This clipping thus would not prevent the containers content to overdraw at the top, // however this is off-screen anyways. val top = min(-radius, height - radius * 2f) val rect = Rect(left = horizontalInset, top = top, right = width, bottom = height) graphicsLayer.setRoundRectOutline(rect.topLeft, rect.size, radius) lastOutlineSize = size } this.drawLayer(graphicsLayer) } } private data class EdgeContainerExpansionBackgroundElement( val backgroundColor: Color, val spec: EdgeContainerExpansionSpec, ) : ModifierNodeElement<EdgeContainerExpansionBackgroundNode>() { override fun create(): EdgeContainerExpansionBackgroundNode = EdgeContainerExpansionBackgroundNode(backgroundColor, spec) override fun update(node: EdgeContainerExpansionBackgroundNode) { node.backgroundColor = backgroundColor if (node.spec != spec) { node.spec = spec node.invalidateOutline() } } }
mechanics/src/com/android/mechanics/behavior/EdgeContainerExpansionSpec.kt 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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. */ @file:OptIn(ExperimentalMaterial3ExpressiveApi::class) package com.android.mechanics.behavior import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MotionScheme import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.lerp import com.android.mechanics.spec.Breakpoint import com.android.mechanics.spec.BreakpointKey import com.android.mechanics.spec.DirectionalMotionSpec import com.android.mechanics.spec.InputDirection import com.android.mechanics.spec.Mapping import com.android.mechanics.spec.MotionSpec import com.android.mechanics.spec.OnChangeSegmentHandler import com.android.mechanics.spec.SegmentData import com.android.mechanics.spec.SegmentKey import com.android.mechanics.spec.builder import com.android.mechanics.spec.reverseBuilder import com.android.mechanics.spring.SpringParameters /** Motion spec for a vertically expandable container. */ class EdgeContainerExpansionSpec( val visibleHeight: Dp = Defaults.VisibleHeight, val preDetachRatio: Float = Defaults.PreDetachRatio, val detachHeight: Dp = Defaults.DetachHeight, val attachHeight: Dp = Defaults.AttachHeight, val widthOffset: Dp = Defaults.WidthOffset, val minRadius: Dp = Defaults.MinRadius, val radius: Dp = Defaults.Radius, val attachSpring: SpringParameters = Defaults.AttachSpring, val detachSpring: SpringParameters = Defaults.DetachSpring, val opacitySpring: SpringParameters = Defaults.OpacitySpring, ) { fun createHeightSpec(motionScheme: MotionScheme, density: Density): MotionSpec { return with(density) { val spatialSpring = SpringParameters(motionScheme.defaultSpatialSpec()) val detachSpec = DirectionalMotionSpec.builder( initialMapping = Mapping.Zero, defaultSpring = spatialSpring, ) .toBreakpoint(0f, key = Breakpoints.Attach) .continueWith(Mapping.Linear(preDetachRatio)) .toBreakpoint(detachHeight.toPx(), key = Breakpoints.Detach) .completeWith(Mapping.Identity, detachSpring) val attachSpec = DirectionalMotionSpec.reverseBuilder(defaultSpring = spatialSpring) .toBreakpoint(attachHeight.toPx(), key = Breakpoints.Detach) .completeWith(mapping = Mapping.Zero, attachSpring) val segmentHandlers = mapOf<SegmentKey, OnChangeSegmentHandler>( SegmentKey(Breakpoints.Detach, Breakpoint.maxLimit.key, InputDirection.Min) to { currentSegment, _, newDirection -> if (newDirection != currentSegment.direction) currentSegment else null }, SegmentKey(Breakpoints.Attach, Breakpoints.Detach, InputDirection.Max) to { currentSegment: SegmentData, newInput: Float, newDirection: InputDirection -> if (newDirection != currentSegment.direction && newInput >= 0) currentSegment else null }, ) MotionSpec( maxDirection = detachSpec, minDirection = attachSpec, segmentHandlers = segmentHandlers, ) } } fun createWidthSpec( intrinsicWidth: Float, motionScheme: MotionScheme, density: Density, ): MotionSpec { return with(density) { MotionSpec.builder( SpringParameters(motionScheme.defaultSpatialSpec()), initialMapping = { input -> val fraction = (input / detachHeight.toPx()).fastCoerceIn(0f, 1f) intrinsicWidth - lerp(widthOffset.toPx(), 0f, fraction) }, ) .complete() } } fun createAlphaSpec(motionScheme: MotionScheme, density: Density): MotionSpec { return with(density) { val detachSpec = DirectionalMotionSpec.builder( SpringParameters(motionScheme.defaultEffectsSpec()), initialMapping = Mapping.Zero, ) .toBreakpoint(visibleHeight.toPx()) .completeWith(Mapping.One, opacitySpring) val attachSpec = DirectionalMotionSpec.builder( SpringParameters(motionScheme.defaultEffectsSpec()), initialMapping = Mapping.Zero, ) .toBreakpoint(visibleHeight.toPx()) .completeWith(Mapping.One, opacitySpring) MotionSpec(maxDirection = detachSpec, minDirection = attachSpec) } } companion object { object Breakpoints { val Attach = BreakpointKey("EdgeContainerExpansion::Attach") val Detach = BreakpointKey("EdgeContainerExpansion::Detach") } object Defaults { val VisibleHeight = 24.dp val PreDetachRatio = .25f val DetachHeight = 80.dp val AttachHeight = 40.dp val WidthOffset = 28.dp val MinRadius = 28.dp val Radius = 46.dp val AttachSpring = SpringParameters(stiffness = 380f, dampingRatio = 0.9f) val DetachSpring = SpringParameters(stiffness = 380f, dampingRatio = 0.9f) val OpacitySpring = SpringParameters(stiffness = 1200f, dampingRatio = 0.99f) } } }
mechanics/src/com/android/mechanics/spring/MaterialSpringParameters.kt 0 → 100644 +49 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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. */ @file:OptIn(ExperimentalMaterial3ExpressiveApi::class) package com.android.mechanics.spring import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.SpringSpec import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable /** Converts a [SpringSpec] into its [SpringParameters] equivalent. */ fun SpringParameters(springSpec: SpringSpec<out Any>) = with(springSpec) { SpringParameters(stiffness, dampingRatio) } /** * Converts a [FiniteAnimationSpec] from the [MotionScheme] into its [SpringParameters] equivalent. */ @ExperimentalMaterial3ExpressiveApi fun SpringParameters(animationSpec: FiniteAnimationSpec<out Any>): SpringParameters { check(animationSpec is SpringSpec) { "animationSpec is expected to be a SpringSpec, but is $animationSpec" } return SpringParameters(animationSpec) } @Composable fun defaultSpatialSpring(): SpringParameters { return SpringParameters(MaterialTheme.motionScheme.defaultSpatialSpec()) } @Composable fun defaultEffectSpring(): SpringParameters { return SpringParameters(MaterialTheme.motionScheme.defaultEffectsSpec()) }