From e0b9b851845de46053918668b606ee147ababb35 Mon Sep 17 00:00:00 2001 From: Daniel Frett Date: Thu, 5 Sep 2024 14:01:30 -0600 Subject: [PATCH 1/4] update forked LazyDropdownMenu --- .../material3/ui/menu/LazyDropdownMenu.kt | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt b/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt index 90fef473f..b76a8bc73 100644 --- a/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt +++ b/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt @@ -7,10 +7,14 @@ import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuDefaults import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable @@ -19,10 +23,13 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect @@ -42,20 +49,28 @@ fun LazyDropdownMenu( onDismissRequest: () -> Unit, modifier: Modifier = Modifier, offset: DpOffset = DpOffset(0.dp, 0.dp), + scrollState: ScrollState = rememberScrollState(), properties: PopupProperties = PopupProperties(focusable = true), + shape: Shape = MenuDefaults.shape, + containerColor: Color = MenuDefaults.containerColor, + tonalElevation: Dp = MenuDefaults.TonalElevation, + shadowElevation: Dp = MenuDefaults.ShadowElevation, + border: BorderStroke? = null, content: LazyListScope.() -> Unit ) { - val expandedStates = remember { MutableTransitionState(false) } - expandedStates.targetState = expanded + val expandedState = remember { MutableTransitionState(false) } + expandedState.targetState = expanded - if (expandedStates.currentState || expandedStates.targetState) { + if (expandedState.currentState || expandedState.targetState) { val transformOriginState = remember { mutableStateOf(TransformOrigin.Center) } val density = LocalDensity.current - val popupPositionProvider = DropdownMenuPositionProvider( - offset, - density - ) { parentBounds, menuBounds -> - transformOriginState.value = calculateTransformOrigin(parentBounds, menuBounds) + val popupPositionProvider = remember(offset, density) { + DropdownMenuPositionProvider( + offset, + density, + ) { parentBounds, menuBounds -> + transformOriginState.value = calculateTransformOrigin(parentBounds, menuBounds) + } } Popup( @@ -64,10 +79,16 @@ fun LazyDropdownMenu( properties = properties ) { LazyDropdownMenuContent( - expandedStates = expandedStates, + expandedStates = expandedState, transformOriginState = transformOriginState, +// scrollState = scrollState, +// shape = shape, +// containerColor = containerColor, +// tonalElevation = tonalElevation, +// shadowElevation = shadowElevation, +// border = border, modifier = modifier, - content = content + content = content, ) } } From 2764e18dd9f5f65372171b20c9ab9241209c8709 Mon Sep 17 00:00:00 2001 From: Daniel Frett Date: Thu, 5 Sep 2024 14:08:46 -0600 Subject: [PATCH 2/4] update forked LazyDropdownMenuContent --- .../material3/ui/menu/LazyDropdownMenu.kt | 100 ++++++++++-------- 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt b/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt index b76a8bc73..13ab24ab2 100644 --- a/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt +++ b/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt @@ -8,12 +8,9 @@ import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.rememberScrollState -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MenuDefaults import androidx.compose.material3.Surface import androidx.compose.runtime.Composable @@ -28,6 +25,7 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset @@ -49,7 +47,6 @@ fun LazyDropdownMenu( onDismissRequest: () -> Unit, modifier: Modifier = Modifier, offset: DpOffset = DpOffset(0.dp, 0.dp), - scrollState: ScrollState = rememberScrollState(), properties: PopupProperties = PopupProperties(focusable = true), shape: Shape = MenuDefaults.shape, containerColor: Color = MenuDefaults.containerColor, @@ -79,14 +76,13 @@ fun LazyDropdownMenu( properties = properties ) { LazyDropdownMenuContent( - expandedStates = expandedState, + expandedState = expandedState, transformOriginState = transformOriginState, -// scrollState = scrollState, -// shape = shape, -// containerColor = containerColor, -// tonalElevation = tonalElevation, -// shadowElevation = shadowElevation, -// border = border, + shape = shape, + containerColor = containerColor, + tonalElevation = tonalElevation, + shadowElevation = shadowElevation, + border = border, modifier = modifier, content = content, ) @@ -98,42 +94,38 @@ fun LazyDropdownMenu( @Composable @Suppress( "ktlint:compose:modifier-not-used-at-root", + "ktlint:compose:modifier-without-default-check", "ktlint:compose:mutable-state-param-check", + "ktlint:standard:multiline-if-else", "TransitionPropertiesLabel", ) private fun LazyDropdownMenuContent( - expandedStates: MutableTransitionState, + modifier: Modifier, + expandedState: MutableTransitionState, transformOriginState: MutableState, - modifier: Modifier = Modifier, + shape: Shape, + containerColor: Color, + tonalElevation: Dp, + shadowElevation: Dp, + border: BorderStroke?, content: LazyListScope.() -> Unit ) { // Menu open/close animation. - val transition = updateTransition(expandedStates, "DropDownMenu") + @Suppress("DEPRECATION") + val transition = updateTransition(expandedState, "DropDownMenu") val scale by transition.animateFloat( transitionSpec = { if (false isTransitioningTo true) { // Dismissed to expanded - tween( - durationMillis = InTransitionDuration, - easing = LinearOutSlowInEasing - ) + tween(durationMillis = InTransitionDuration, easing = LinearOutSlowInEasing) } else { // Expanded to dismissed. - tween( - durationMillis = 1, - delayMillis = OutTransitionDuration - 1 - ) + tween(durationMillis = 1, delayMillis = OutTransitionDuration - 1) } - }, - ) { - if (it) { - // Menu is expanded. - 1f - } else { - // Menu is dismissed. - 0.8f } + ) { expanded -> + if (expanded) ExpandedScaleTarget else ClosedScaleTarget } val alpha by transition.animateFloat( @@ -146,26 +138,30 @@ private fun LazyDropdownMenuContent( tween(durationMillis = OutTransitionDuration) } } - ) { - if (it) { - // Menu is expanded. - 1f - } else { - // Menu is dismissed. - 0f - } + ) { expanded -> + if (expanded) ExpandedAlphaTarget else ClosedAlphaTarget } + + val isInspecting = LocalInspectionMode.current Surface( - modifier = Modifier.graphicsLayer { - scaleX = scale - scaleY = scale - this.alpha = alpha + modifier = + Modifier.graphicsLayer { + scaleX = + if (!isInspecting) scale + else if (expandedState.targetState) ExpandedScaleTarget else ClosedScaleTarget + scaleY = + if (!isInspecting) scale + else if (expandedState.targetState) ExpandedScaleTarget else ClosedScaleTarget + this.alpha = + if (!isInspecting) alpha + else if (expandedState.targetState) ExpandedAlphaTarget else ClosedAlphaTarget transformOrigin = transformOriginState.value }, - shape = MaterialTheme.shapes.extraSmall, - color = MaterialTheme.colorScheme.surface, - tonalElevation = 3.dp, - shadowElevation = 3.dp + shape = shape, + color = containerColor, + tonalElevation = tonalElevation, + shadowElevation = shadowElevation, + border = border, ) { LazyColumn( modifier = modifier.padding(vertical = DropdownMenuVerticalPadding), @@ -187,6 +183,18 @@ private const val InTransitionDuration = 120 /** Copied from [androidx.compose.material3.OutTransitionDuration] */ private const val OutTransitionDuration = 75 +/** Copied from [androidx.compose.material3.ExpandedScaleTarget] */ +private const val ExpandedScaleTarget = 1f + +/** Copied from [androidx.compose.material3.ClosedScaleTarget] */ +private const val ClosedScaleTarget = 0.8f + +/** Copied from [androidx.compose.material3.ExpandedAlphaTarget] */ +private const val ExpandedAlphaTarget = 1f + +/** Copied from [androidx.compose.material3.ClosedAlphaTarget] */ +private const val ClosedAlphaTarget = 0f + /** * Copied from [androidx.compose.material3.DropdownMenuPositionProvider] */ From b0cd2ce57a867339bab9a3f8fd092b7e2a0f60ce Mon Sep 17 00:00:00 2001 From: Daniel Frett Date: Thu, 5 Sep 2024 14:10:37 -0600 Subject: [PATCH 3/4] update forked calculateTransformOrigin --- .../compose/material3/ui/menu/LazyDropdownMenu.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt b/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt index 13ab24ab2..95e6e7610 100644 --- a/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt +++ b/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt @@ -261,24 +261,24 @@ private data class DropdownMenuPositionProvider( /** * Copied from [androidx.compose.material3.calculateTransformOrigin] */ -private fun calculateTransformOrigin(parentBounds: IntRect, menuBounds: IntRect): TransformOrigin { +private fun calculateTransformOrigin(anchorBounds: IntRect, menuBounds: IntRect): TransformOrigin { val pivotX = when { - menuBounds.left >= parentBounds.right -> 0f - menuBounds.right <= parentBounds.left -> 1f + menuBounds.left >= anchorBounds.right -> 0f + menuBounds.right <= anchorBounds.left -> 1f menuBounds.width == 0 -> 0f else -> { val intersectionCenter = - (max(parentBounds.left, menuBounds.left) + min(parentBounds.right, menuBounds.right)) / 2 + (max(anchorBounds.left, menuBounds.left) + min(anchorBounds.right, menuBounds.right)) / 2 (intersectionCenter - menuBounds.left).toFloat() / menuBounds.width } } val pivotY = when { - menuBounds.top >= parentBounds.bottom -> 0f - menuBounds.bottom <= parentBounds.top -> 1f + menuBounds.top >= anchorBounds.bottom -> 0f + menuBounds.bottom <= anchorBounds.top -> 1f menuBounds.height == 0 -> 0f else -> { val intersectionCenter = - (max(parentBounds.top, menuBounds.top) + min(parentBounds.bottom, menuBounds.bottom)) / 2 + (max(anchorBounds.top, menuBounds.top) + min(anchorBounds.bottom, menuBounds.bottom)) / 2 (intersectionCenter - menuBounds.top).toFloat() / menuBounds.height } } From 8a7cf4618bbd9657873088d2b7c35e29c4e3b532 Mon Sep 17 00:00:00 2001 From: Daniel Frett Date: Thu, 5 Sep 2024 14:19:39 -0600 Subject: [PATCH 4/4] include a link to the open Android issue to officially support this --- .../androidx/compose/material3/ui/menu/LazyDropdownMenu.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt b/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt index 95e6e7610..d849c32a6 100644 --- a/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt +++ b/gto-support-androidx-compose-material3/src/main/kotlin/org/ccci/gto/android/common/androidx/compose/material3/ui/menu/LazyDropdownMenu.kt @@ -40,7 +40,11 @@ import androidx.compose.ui.window.PopupProperties import kotlin.math.max import kotlin.math.min -/** Forked version of [androidx.compose.material3.DropdownMenu] supporting a LazyColumn */ +/** + * Forked version of [androidx.compose.material3.DropdownMenu] supporting a LazyColumn + * + * This can go away once this issue is officially supported: https://issuetracker.google.com/issues/242398344 + */ @Composable fun LazyDropdownMenu( expanded: Boolean,