diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt index f80d7df4..c00bde77 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt @@ -1,10 +1,13 @@ package top.yukonga.miuix.kmp.extra import androidx.compose.foundation.Image +import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -13,10 +16,7 @@ import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.captionBar import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -49,8 +49,9 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.platform.LocalDensity @@ -80,7 +81,6 @@ import top.yukonga.miuix.kmp.utils.MiuixPopupUtil.Companion.dismissPopup import top.yukonga.miuix.kmp.utils.MiuixPopupUtil.Companion.showPopup import top.yukonga.miuix.kmp.utils.SmoothRoundedCornerShape import top.yukonga.miuix.kmp.utils.getWindowSize -import kotlin.math.max import kotlin.math.roundToInt /** @@ -121,21 +121,23 @@ fun SuperDropdown( onSelectedIndexChange: ((Int) -> Unit)?, ) { val interactionSource = remember { MutableInteractionSource() } + val isDropdownPreExpand = remember { mutableStateOf(false) } val isDropdownExpanded = remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() val held = remember { mutableStateOf(null) } val hapticFeedback = LocalHapticFeedback.current val actionColor = if (enabled) MiuixTheme.colorScheme.onSurfaceVariantActions else MiuixTheme.colorScheme.disabledOnSecondaryVariant var alignLeft by rememberSaveable { mutableStateOf(true) } - var dropdownOffsetXPx by remember { mutableIntStateOf(0) } - var dropdownOffsetYPx by remember { mutableIntStateOf(0) } - var componentHeightPx by remember { mutableIntStateOf(0) } - var componentWidthPx by remember { mutableIntStateOf(0) } + var componentInnerOffsetXPx by remember { mutableIntStateOf(0) } + var componentInnerOffsetYPx by remember { mutableIntStateOf(0) } + var componentInnerHeightPx by remember { mutableIntStateOf(0) } + var componentInnerWidthPx by remember { mutableIntStateOf(0) } + val density = LocalDensity.current val getWindowSize = rememberUpdatedState(getWindowSize()) val windowHeightPx by rememberUpdatedState(getWindowSize.value.height) val windowWidthPx by rememberUpdatedState(getWindowSize.value.width) - var transformOrigin by mutableStateOf(TransformOrigin.Center) + var transformOrigin by remember { mutableStateOf(TransformOrigin.Center) } DisposableEffect(Unit) { onDispose { @@ -154,40 +156,74 @@ fun SuperDropdown( BasicComponent( modifier = modifier + .indication( + interactionSource = interactionSource, + indication = LocalIndication.current + ) .pointerInput(Unit) { - awaitPointerEventScope { - while (enabled) { - val event = awaitPointerEvent() - if (event.type != PointerEventType.Move) { - val eventChange = event.changes.first() - if (eventChange.pressed) { - alignLeft = eventChange.position.x < (size.width / 2) + detectTapGestures( + onPress = { position -> + if (enabled) { + alignLeft = position.x < (size.width / 2) + isDropdownPreExpand.value = true + val pressInteraction = PressInteraction.Press(position) + coroutineScope.launch { + interactionSource.emit(pressInteraction) + } + val released = tryAwaitRelease() + isDropdownPreExpand.value = false + if (released) { + isDropdownExpanded.value = enabled + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + coroutineScope.launch { + interactionSource.emit(HoldDownInteraction.Hold().also { + held.value = it + }) + interactionSource.emit(PressInteraction.Release(pressInteraction)) + } + } else { + coroutineScope.launch { + interactionSource.emit(PressInteraction.Cancel(pressInteraction)) + } } } } - } - } - .onGloballyPositioned { coordinates -> - if (isDropdownExpanded.value) { - val positionInWindow = coordinates.positionInWindow() - dropdownOffsetXPx = positionInWindow.x.toInt() - dropdownOffsetYPx = positionInWindow.y.toInt() - componentHeightPx = coordinates.size.height - componentWidthPx = coordinates.size.width - val xInWindow = dropdownOffsetXPx + if (mode == DropDownMode.AlwaysOnRight || !alignLeft) componentWidthPx else 0 - val yInWindow = dropdownOffsetYPx + componentHeightPx / 2 - transformOrigin = TransformOrigin( - xInWindow / windowWidthPx.toFloat(), - yInWindow / windowHeightPx.toFloat() - ) - } + ) }, - interactionSource = interactionSource, insideMargin = insideMargin, title = title, titleColor = titleColor, summary = summary, summaryColor = summaryColor, + leftAction = { + if (isDropdownPreExpand.value) { + Layout( + content = {}, + modifier = Modifier.onGloballyPositioned { childCoordinates -> + val parentCoordinates = + childCoordinates.parentLayoutCoordinates ?: return@onGloballyPositioned + val positionInWindow = parentCoordinates.positionInWindow() + componentInnerOffsetXPx = positionInWindow.x.toInt() + componentInnerOffsetYPx = positionInWindow.y.toInt() + componentInnerHeightPx = parentCoordinates.size.height + componentInnerWidthPx = parentCoordinates.size.width + with(density) { + val xInWindow = componentInnerOffsetXPx + if (mode == DropDownMode.AlwaysOnRight || !alignLeft) + componentInnerWidthPx - 64.dp.roundToPx() + else + 64.dp.roundToPx() + val yInWindow = componentInnerOffsetYPx + componentInnerHeightPx / 2 - 56.dp.roundToPx() + transformOrigin = TransformOrigin( + xInWindow / windowWidthPx.toFloat(), + yInWindow / windowHeightPx.toFloat() + ) + } + } + ) { _, _ -> + layout(0, 0) {} + } + } + }, rightActions = { if (showValue) { Text( @@ -210,17 +246,6 @@ fun SuperDropdown( contentDescription = null ) }, - onClick = { - if (enabled) { - isDropdownExpanded.value = enabled - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - coroutineScope.launch { - interactionSource.emit(HoldDownInteraction.Hold().also { - held.value = it - }) - } - } - }, enabled = enabled ) if (isDropdownExpanded.value) { @@ -231,9 +256,6 @@ fun SuperDropdown( } } - val density = LocalDensity.current - var offsetXPx by remember { mutableStateOf(0) } - var offsetYPx by remember { mutableStateOf(0) } val textMeasurer = rememberTextMeasurer() val textStyle = remember { TextStyle(fontWeight = FontWeight.Medium, fontSize = 16.sp) } val textWidthDp = remember(items) { items.maxOfOrNull { with(density) { textMeasurer.measure(text = it, style = textStyle).size.width.toDp() } } } @@ -246,18 +268,9 @@ fun SuperDropdown( val captionBarPx by rememberUpdatedState( with(density) { WindowInsets.captionBar.asPaddingValues().calculateBottomPadding().toPx() }.roundToInt() ) - val dropdownMaxHeight by rememberUpdatedState(with(density) { - (windowHeightPx - statusBarPx - navigationBarPx - captionBarPx).toDp() - }) val dropdownElevation by rememberUpdatedState(with(density) { 11.dp.toPx() }) - val insideLeftPx by rememberUpdatedState(with(density) { - insideMargin.calculateLeftPadding(LayoutDirection.Ltr).toPx() - }.roundToInt()) - val insideRightPx by rememberUpdatedState(with(density) { - insideMargin.calculateRightPadding(LayoutDirection.Ltr).toPx() - }.roundToInt()) val insideTopPx by rememberUpdatedState(with(density) { insideMargin.calculateTopPadding().toPx() }.roundToInt()) @@ -283,7 +296,6 @@ fun SuperDropdown( } else { popupModifier } - .fillMaxSize() .pointerInput(Unit) { detectTapGestures( onTap = { @@ -291,29 +303,41 @@ fun SuperDropdown( } ) } - .offset(x = offsetXPx.dp / density.density, y = offsetYPx.dp / density.density) - ) { - LazyColumn( - modifier = Modifier - .onGloballyPositioned { coordinates -> - offsetXPx = if (mode == DropDownMode.AlwaysOnRight || !alignLeft) { - dropdownOffsetXPx + componentWidthPx - insideRightPx - coordinates.size.width - paddingPx - if (defaultWindowInsetsPadding) displayCutoutLeftSize else 0 - } else { - dropdownOffsetXPx + paddingPx + insideLeftPx - if (defaultWindowInsetsPadding) displayCutoutLeftSize else 0 - } - offsetYPx = calculateOffsetYPx( + .layout { measurable, constraints -> + val placeable = measurable.measure( + constraints.copy( + minWidth = 200.dp.roundToPx(), + minHeight = 50.dp.roundToPx(), + maxHeight = windowHeightPx - statusBarPx - navigationBarPx - captionBarPx + ) + ) + layout(constraints.maxWidth, constraints.maxHeight) { + val xCoordinate = calculateOffsetXPx( + componentInnerOffsetXPx, + componentInnerWidthPx, + placeable.width, + paddingPx, + displayCutoutLeftSize, + defaultWindowInsetsPadding, + (mode == DropDownMode.AlwaysOnRight || !alignLeft) + ) + val yCoordinate = calculateOffsetYPx( windowHeightPx, - dropdownOffsetYPx, - coordinates.size.height, - componentHeightPx, + componentInnerOffsetYPx, + placeable.height, + componentInnerHeightPx, insideTopPx, insideBottomPx, statusBarPx, navigationBarPx, captionBarPx ) + placeable.place(xCoordinate, yCoordinate) } - .heightIn(50.dp, dropdownMaxHeight) + } + ) { + LazyColumn( + modifier = Modifier .align(AbsoluteAlignment.TopLeft) .graphicsLayer( shadowElevation = dropdownElevation, @@ -409,6 +433,34 @@ fun DropdownImpl( } } +/** + * Calculate the offset of the dropdown. + * + * @param componentInnerOffsetXPx The offset of the component inside. + * @param componentInnerWidthPx The width of the component inside. + * @param dropdownWidthPx The width of the dropdown. + * @param extraPaddingPx The extra padding of the dropdown. + * @param displayCutoutLeftSizePx The size of the display cutout on the left. + * @param defaultWindowInsetsPadding Whether to apply default window insets padding to the dropdown. + * @param alignRight Whether to align the dropdown to the right. + * @return The offset of the dropdown. + */ +fun calculateOffsetXPx( + componentInnerOffsetXPx: Int, + componentInnerWidthPx: Int, + dropdownWidthPx: Int, + extraPaddingPx: Int, + displayCutoutLeftSizePx: Int, + defaultWindowInsetsPadding: Boolean, + alignRight: Boolean +): Int { + return if (alignRight) { + componentInnerOffsetXPx + componentInnerWidthPx - dropdownWidthPx - extraPaddingPx - if (defaultWindowInsetsPadding) displayCutoutLeftSizePx else 0 + } else { + componentInnerOffsetXPx + extraPaddingPx - if (defaultWindowInsetsPadding) displayCutoutLeftSizePx else 0 + } +} + /** * Calculate the offset of the dropdown. * @@ -434,24 +486,16 @@ fun calculateOffsetYPx( navigationBarPx: Int, captionBarPx: Int ): Int { - return if (windowHeightPx - captionBarPx - navigationBarPx - dropdownOffsetPx - componentHeightPx > dropdownHeightPx) { + return (if (windowHeightPx - captionBarPx - navigationBarPx - dropdownOffsetPx - componentHeightPx > dropdownHeightPx) { // Show below - dropdownOffsetPx + componentHeightPx - insideBottomHeightPx / 2 + dropdownOffsetPx + componentHeightPx + insideBottomHeightPx / 2 } else if (dropdownOffsetPx - statusBarPx > dropdownHeightPx) { // Show above - dropdownOffsetPx - dropdownHeightPx + insideTopHeightPx / 2 - } else if (windowHeightPx - statusBarPx - captionBarPx - navigationBarPx <= dropdownHeightPx) { - // Special handling when the height of the popup is maxsize (== windowHeightPx) - statusBarPx + dropdownOffsetPx - dropdownHeightPx - insideTopHeightPx / 2 } else { - val maxInsideHeight = max(insideTopHeightPx, insideBottomHeightPx) - if (windowHeightPx - dropdownOffsetPx < dropdownHeightPx / 2 + captionBarPx + navigationBarPx + maxInsideHeight + componentHeightPx / 2) { - windowHeightPx - dropdownHeightPx - maxInsideHeight - captionBarPx - navigationBarPx - } else { - val offset = dropdownOffsetPx - dropdownHeightPx / 2 + componentHeightPx / 2 - if (offset > maxInsideHeight + statusBarPx) offset else maxInsideHeight + statusBarPx - } - } + // Middle + dropdownOffsetPx + componentHeightPx / 2 - dropdownHeightPx / 2 + }).coerceIn(statusBarPx, windowHeightPx - captionBarPx - navigationBarPx) } /** diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperSpinner.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperSpinner.kt index 12e733fa..55b7d6a6 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperSpinner.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperSpinner.kt @@ -1,10 +1,13 @@ package top.yukonga.miuix.kmp.extra import androidx.compose.foundation.Image +import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -14,11 +17,9 @@ import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.captionBar import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -51,6 +52,8 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.platform.LocalDensity @@ -60,7 +63,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch @@ -83,26 +85,25 @@ import top.yukonga.miuix.kmp.utils.SmoothRoundedCornerShape import top.yukonga.miuix.kmp.utils.getWindowSize import kotlin.math.roundToInt - /** - * A [SuperSpinner] component with Miuix style. + * A spinner component with Miuix style. * - * @param title the title of the [SuperSpinner]. - * @param items the list of [SpinnerEntry] to be shown in the [SuperSpinner]. - * @param selectedIndex the index of the selected item in the [SuperSpinner]. - * @param modifier the [Modifier] to be applied to the [SuperSpinner]. - * @param popupModifier the [Modifier] to be applied to the popup of the [SuperSpinner]. - * @param titleColor the color of the title of the [SuperSpinner]. - * @param summary the summary of the [SuperSpinner]. - * @param summaryColor the color of the summary of the [SuperSpinner]. - * @param mode the mode of the [SuperSpinner]. - * @param horizontalPadding the horizontal padding of the [SuperSpinner]. - * @param leftAction the action to be shown at the left side of the [SuperSpinner]. - * @param insideMargin the [PaddingValues] to be applied inside the [SuperSpinner]. - * @param defaultWindowInsetsPadding whether to apply the default window insets padding to the [SuperSpinner]. - * @param enabled whether the [SuperSpinner] is enabled. - * @param showValue whether to show the value of the [SuperSpinner]. - * @param onSelectedIndexChange the callback to be invoked when the selected index of the [SuperSpinner] is changed. + * @param title The title of the [SuperSpinner]. + * @param items The list of [SpinnerEntry] to be shown in the [SuperSpinner]. + * @param selectedIndex The index of the selected item in the [SuperSpinner]. + * @param modifier The [Modifier] to be applied to the [SuperSpinner]. + * @param popupModifier The [Modifier] to be applied to the popup of the [SuperSpinner]. + * @param titleColor The color of the title of the [SuperSpinner]. + * @param summary The summary of the [SuperSpinner]. + * @param summaryColor The color of the summary of the [SuperSpinner]. + * @param mode The mode of the [SuperSpinner]. + * @param horizontalPadding The horizontal padding of the [SuperSpinner]. + * @param leftAction The action to be shown at the left side of the [SuperSpinner]. + * @param insideMargin The [PaddingValues] to be applied inside the [SuperSpinner]. + * @param defaultWindowInsetsPadding Whether to apply the default window insets padding to the [SuperSpinner]. + * @param enabled Whether the [SuperSpinner] is enabled. + * @param showValue Whether to show the value of the [SuperSpinner]. + * @param onSelectedIndexChange The callback to be invoked when the selected index of the [SuperSpinner] is changed. */ @Composable fun SuperSpinner( @@ -124,21 +125,23 @@ fun SuperSpinner( onSelectedIndexChange: ((Int) -> Unit)?, ) { val interactionSource = remember { MutableInteractionSource() } + val isDropdownPreExpand = remember { mutableStateOf(false) } val isDropdownExpanded = remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() val held = remember { mutableStateOf(null) } val hapticFeedback = LocalHapticFeedback.current val actionColor = if (enabled) MiuixTheme.colorScheme.onSurfaceVariantActions else MiuixTheme.colorScheme.disabledOnSecondaryVariant var alignLeft by rememberSaveable { mutableStateOf(true) } - var dropdownOffsetXPx by remember { mutableIntStateOf(0) } - var dropdownOffsetYPx by remember { mutableIntStateOf(0) } - var componentHeightPx by remember { mutableIntStateOf(0) } - var componentWidthPx by remember { mutableIntStateOf(0) } + var componentInnerOffsetXPx by remember { mutableIntStateOf(0) } + var componentInnerOffsetYPx by remember { mutableIntStateOf(0) } + var componentInnerHeightPx by remember { mutableIntStateOf(0) } + var componentInnerWidthPx by remember { mutableIntStateOf(0) } + val density = LocalDensity.current val getWindowSize = rememberUpdatedState(getWindowSize()) val windowHeightPx by rememberUpdatedState(getWindowSize.value.height) val windowWidthPx by rememberUpdatedState(getWindowSize.value.width) - var transformOrigin by mutableStateOf(TransformOrigin.Center) + var transformOrigin by remember { mutableStateOf(TransformOrigin.Center) } DisposableEffect(Unit) { onDispose { @@ -157,41 +160,75 @@ fun SuperSpinner( BasicComponent( modifier = modifier + .indication( + interactionSource = interactionSource, + indication = LocalIndication.current + ) .pointerInput(Unit) { - awaitPointerEventScope { - while (enabled) { - val event = awaitPointerEvent() - if (event.type != PointerEventType.Move) { - val eventChange = event.changes.first() - if (eventChange.pressed) { - alignLeft = eventChange.position.x < (size.width / 2) + detectTapGestures( + onPress = { position -> + if (enabled) { + alignLeft = position.x < (size.width / 2) + isDropdownPreExpand.value = true + val pressInteraction = PressInteraction.Press(position) + coroutineScope.launch { + interactionSource.emit(pressInteraction) + } + val released = tryAwaitRelease() + isDropdownPreExpand.value = false + if (released) { + isDropdownExpanded.value = enabled + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + coroutineScope.launch { + interactionSource.emit(HoldDownInteraction.Hold().also { + held.value = it + }) + interactionSource.emit(PressInteraction.Release(pressInteraction)) + } + } else { + coroutineScope.launch { + interactionSource.emit(PressInteraction.Cancel(pressInteraction)) + } } } } - } - } - .onGloballyPositioned { coordinates -> - if (isDropdownExpanded.value) { - val positionInWindow = coordinates.positionInWindow() - dropdownOffsetXPx = positionInWindow.x.toInt() - dropdownOffsetYPx = positionInWindow.y.toInt() - componentHeightPx = coordinates.size.height - componentWidthPx = coordinates.size.width - val xInWindow = dropdownOffsetXPx + if (mode == SpinnerMode.AlwaysOnRight || !alignLeft) componentWidthPx else 0 - val yInWindow = dropdownOffsetYPx + componentHeightPx / 2 - transformOrigin = TransformOrigin( - xInWindow / windowWidthPx.toFloat(), - yInWindow / windowHeightPx.toFloat() - ) - } + ) }, - interactionSource = interactionSource, insideMargin = insideMargin, title = title, titleColor = titleColor, summary = summary, summaryColor = summaryColor, - leftAction = leftAction, + leftAction = { + if (isDropdownPreExpand.value) { + Layout( + content = {}, + modifier = Modifier.onGloballyPositioned { childCoordinates -> + val parentCoordinates = + childCoordinates.parentLayoutCoordinates ?: return@onGloballyPositioned + val positionInWindow = parentCoordinates.positionInWindow() + componentInnerOffsetXPx = positionInWindow.x.toInt() + componentInnerOffsetYPx = positionInWindow.y.toInt() + componentInnerHeightPx = parentCoordinates.size.height + componentInnerWidthPx = parentCoordinates.size.width + with(density) { + val xInWindow = componentInnerOffsetXPx + if (mode == SpinnerMode.AlwaysOnRight || !alignLeft) + componentInnerWidthPx - 64.dp.roundToPx() + else + 64.dp.roundToPx() + val yInWindow = componentInnerOffsetYPx + componentInnerHeightPx / 2 - 56.dp.roundToPx() + transformOrigin = TransformOrigin( + xInWindow / windowWidthPx.toFloat(), + yInWindow / windowHeightPx.toFloat() + ) + } + } + ) { _, _ -> + layout(0, 0) {} + } + } + leftAction?.invoke() + }, rightActions = { if (showValue) { Text( @@ -214,17 +251,6 @@ fun SuperSpinner( contentDescription = null ) }, - onClick = { - if (enabled) { - isDropdownExpanded.value = true - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - coroutineScope.launch { - interactionSource.emit(HoldDownInteraction.Hold().also { - held.value = it - }) - } - } - }, enabled = enabled ) @@ -236,9 +262,6 @@ fun SuperSpinner( } } - val density = LocalDensity.current - var offsetXPx by remember { mutableIntStateOf(0) } - var offsetYPx by remember { mutableIntStateOf(0) } val statusBarPx by rememberUpdatedState( with(density) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() }.roundToInt() ) @@ -248,18 +271,9 @@ fun SuperSpinner( val captionBarPx by rememberUpdatedState( with(density) { WindowInsets.captionBar.asPaddingValues().calculateBottomPadding().toPx() }.roundToInt() ) - val dropdownMaxHeight by rememberUpdatedState(with(density) { - (windowHeightPx - statusBarPx - navigationBarPx - captionBarPx).toDp() - }) val dropdownElevation by rememberUpdatedState(with(density) { 11.dp.toPx() }) - val insideLeftPx by rememberUpdatedState(with(density) { - insideMargin.calculateLeftPadding(LayoutDirection.Ltr).toPx() - }.roundToInt()) - val insideRightPx by rememberUpdatedState(with(density) { - insideMargin.calculateRightPadding(LayoutDirection.Ltr).toPx() - }.roundToInt()) val insideTopPx by rememberUpdatedState(with(density) { insideMargin.calculateTopPadding().toPx() }.roundToInt()) @@ -291,7 +305,6 @@ fun SuperSpinner( } else { popupModifier } - .fillMaxSize() .pointerInput(Unit) { detectTapGestures( onTap = { @@ -299,31 +312,41 @@ fun SuperSpinner( } ) } - .offset { - IntOffset(offsetXPx, offsetYPx) - } - ) { - LazyColumn( - modifier = Modifier - .onGloballyPositioned { coordinates -> - offsetXPx = if (mode == SpinnerMode.AlwaysOnRight || !alignLeft) { - dropdownOffsetXPx + componentWidthPx - insideRightPx - coordinates.size.width - paddingPx - if (defaultWindowInsetsPadding) displayCutoutLeftSize else 0 - } else { - dropdownOffsetXPx + paddingPx + insideLeftPx - if (defaultWindowInsetsPadding) displayCutoutLeftSize else 0 - } - offsetYPx = calculateOffsetYPx( + .layout { measurable, constraints -> + val placeable = measurable.measure( + constraints.copy( + minWidth = 200.dp.roundToPx(), + minHeight = 50.dp.roundToPx(), + maxHeight = windowHeightPx - statusBarPx - navigationBarPx - captionBarPx + ) + ) + layout(constraints.maxWidth, constraints.maxHeight) { + val xCoordinate = calculateOffsetXPx( + componentInnerOffsetXPx, + componentInnerWidthPx, + placeable.width, + paddingPx, + displayCutoutLeftSize, + defaultWindowInsetsPadding, + (mode == SpinnerMode.AlwaysOnRight || !alignLeft) + ) + val yCoordinate = calculateOffsetYPx( windowHeightPx, - dropdownOffsetYPx, - coordinates.size.height, - componentHeightPx, + componentInnerOffsetYPx, + placeable.height, + componentInnerHeightPx, insideTopPx, insideBottomPx, statusBarPx, navigationBarPx, captionBarPx ) + placeable.place(xCoordinate, yCoordinate) } - .heightIn(50.dp, dropdownMaxHeight) + } + ) { + LazyColumn( + modifier = Modifier .align(AbsoluteAlignment.TopLeft) .graphicsLayer( shadowElevation = dropdownElevation, diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt index 7a6db317..9218a9d3 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import top.yukonga.miuix.kmp.anim.AccelerateEasing import top.yukonga.miuix.kmp.anim.DecelerateEasing import top.yukonga.miuix.kmp.basic.Box import top.yukonga.miuix.kmp.basic.Scaffold @@ -179,17 +178,17 @@ class MiuixPopupUtil { visible = isPopupShowing.value, modifier = Modifier.zIndex(2f).fillMaxSize(), enter = fadeIn( - animationSpec = tween(150, delayMillis = 50, easing = DecelerateEasing(1.5f)) + animationSpec = tween(150, easing = DecelerateEasing(1.5f)) ) + scaleIn( initialScale = 0.8f, animationSpec = tween(150, easing = DecelerateEasing(1.5f)), transformOrigin = popupTransformOrigin.value.invoke() ), exit = fadeOut( - animationSpec = tween(150, easing = AccelerateEasing(3.0f)) + animationSpec = tween(150, easing = DecelerateEasing(1.5f)) ) + scaleOut( - targetScale = 0.85f, - animationSpec = tween(150, easing = AccelerateEasing(3.0f)), + targetScale = 0.8f, + animationSpec = tween(150, easing = DecelerateEasing(1.5f)), transformOrigin = popupTransformOrigin.value.invoke() ) ) {