From eb0592c68f57137a2c99efa06088900920391676 Mon Sep 17 00:00:00 2001 From: howie Date: Wed, 4 Dec 2024 02:45:44 +0800 Subject: [PATCH 1/4] fix: fix width anomaly when Dropdown item is too wide; fix: fix crash when Dropdown list is too long; --- composeApp/src/commonMain/kotlin/UITest.kt | 23 ++++----- .../kotlin/component/TextComponent.kt | 15 +++++- .../top/yukonga/miuix/kmp/basic/ListPopup.kt | 48 ++++++++++++++++++- .../yukonga/miuix/kmp/extra/SuperDropdown.kt | 18 +++---- .../yukonga/miuix/kmp/extra/SuperSpinner.kt | 10 ++-- 5 files changed, 81 insertions(+), 33 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/UITest.kt b/composeApp/src/commonMain/kotlin/UITest.kt index adbf52ee..399fe468 100644 --- a/composeApp/src/commonMain/kotlin/UITest.kt +++ b/composeApp/src/commonMain/kotlin/UITest.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable @@ -38,6 +37,7 @@ import top.yukonga.miuix.kmp.basic.HorizontalPager import top.yukonga.miuix.kmp.basic.Icon import top.yukonga.miuix.kmp.basic.IconButton import top.yukonga.miuix.kmp.basic.ListPopup +import top.yukonga.miuix.kmp.basic.ListPopupColumn import top.yukonga.miuix.kmp.basic.ListPopupDefaults import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior import top.yukonga.miuix.kmp.basic.NavigationBar @@ -131,10 +131,10 @@ fun UITest( isTopPopupExpanded.value = false } ) { - LazyColumn { - items(items.take(3).size) { index -> + ListPopupColumn { + items.take(3).forEachIndexed { index, navigationItem -> DropdownImpl( - text = items[index].label, + text = navigationItem.label, optionSize = items.take(3).size, isSelected = items[index] == items[targetPage], onSelectedIndexChange = { @@ -145,7 +145,6 @@ fun UITest( dismissPopup(showTopPopup) isTopPopupExpanded.value = false }, - textWidthDp = 100.dp, index = index ) } @@ -180,10 +179,10 @@ fun UITest( isTopPopupExpanded.value = false } ) { - LazyColumn { - items(items.take(3).size) { index -> + ListPopupColumn { + items.take(3).forEachIndexed { index, navigationItem -> DropdownImpl( - text = items[index].label, + text = navigationItem.label, optionSize = items.take(3).size, isSelected = items[index] == items[targetPage], onSelectedIndexChange = { @@ -194,7 +193,6 @@ fun UITest( dismissPopup(showTopPopup) isTopPopupExpanded.value = false }, - textWidthDp = 100.dp, index = index ) } @@ -234,10 +232,10 @@ fun UITest( isBottomPopupExpanded.value = false } ) { - LazyColumn { - items(items.take(3).size) { index -> + ListPopupColumn { + items.take(3).forEachIndexed { index, navigationItem -> DropdownImpl( - text = items[index].label, + text = navigationItem.label, optionSize = items.take(3).size, isSelected = items[index] == items[targetPage], onSelectedIndexChange = { @@ -248,7 +246,6 @@ fun UITest( dismissPopup(showBottomPopup) isBottomPopupExpanded.value = false }, - textWidthDp = 100.dp, index = index ) } diff --git a/composeApp/src/commonMain/kotlin/component/TextComponent.kt b/composeApp/src/commonMain/kotlin/component/TextComponent.kt index 4ea3affc..ca0cbbde 100644 --- a/composeApp/src/commonMain/kotlin/component/TextComponent.kt +++ b/composeApp/src/commonMain/kotlin/component/TextComponent.kt @@ -74,12 +74,23 @@ fun TextComponent( miuixSuperSwitchAnimState: MutableState, ) { val dropdownOptions = listOf("Option 1", "Option 2", "Option 3", "Option 4") + val dropdownOptions2 = listOf( + "Option 1", "Option 2 (this is a long long long option)", "Option 3", "Option 4", + "Option 5", "Option 6", "Option 7", "Option 8", + "Option 9", "Option 10", "Option 11", "Option 12" + ) val spinnerOptions = listOf( SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFFFF5B29)) }, "Option 1", "Red"), SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFF36D167)) }, "Option 2", "Green"), SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFF3482FF)) }, "Option 3", "Blue"), SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFFFFB21D)) }, "Option 4", "Yellow"), ) + val spinnerOptions2 = listOf( + SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFFFF5B29)) }, "Option 1", "Red"), + SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFF36D167)) }, "Option 2 (this is a long option)", "Green"), + SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFF3482FF)) }, "Option 3", "Blue"), + SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFFFFB21D)) }, "Option 4", "Yellow"), + ) SmallTitle(text = "Basic") Card( modifier = Modifier @@ -329,7 +340,7 @@ fun TextComponent( SuperDropdown( title = "Dropdown", summary = "Popup always on right", - items = dropdownOptions, + items = dropdownOptions2, selectedIndex = dropdownOptionSelectedRight.value, onSelectedIndexChange = { newOption -> dropdownOptionSelectedRight.value = newOption }, mode = DropDownMode.AlwaysOnRight @@ -361,7 +372,7 @@ fun TextComponent( SuperSpinner( title = "Spinner", summary = "Spinner always on right", - items = spinnerOptions, + items = spinnerOptions2, selectedIndex = spinnerOptionSelectedRight.value, onSelectedIndexChange = { newOption -> spinnerOptionSelectedRight.value = newOption }, mode = SpinnerMode.AlwaysOnRight, diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt index f5bca37d..3f1b1ec6 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt @@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.captionBar import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -27,6 +29,7 @@ import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInWindow @@ -43,6 +46,7 @@ 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.min /** * A popup with a list of items. @@ -52,7 +56,7 @@ import top.yukonga.miuix.kmp.utils.getWindowSize * @param popupPositionProvider The [PopupPositionProvider] of the [ListPopup]. * @param alignment The alignment of the [ListPopup]. * @param onDismissRequest The callback when the [ListPopup] is dismissed. - * @param content The [Composable] content of the [ListPopup]. + * @param content The [Composable] content of the [ListPopup]. You should use the [ListPopupColumn] in general. */ @Composable fun ListPopup( @@ -151,7 +155,7 @@ fun ListPopup( constraints.copy( minWidth = 200.dp.roundToPx(), minHeight = 50.dp.roundToPx(), - maxHeight = windowBounds.height + maxHeight = windowBounds.height - popupMargin.top - popupMargin.bottom ) ) popupContentSize = IntSize(placeable.width, placeable.height) @@ -222,6 +226,46 @@ fun ListPopup( } } +/** + * A column that automatically aligns the width to the widest item + * @param content The items + */ +@Composable +fun ListPopupColumn( + content: @Composable () -> Unit +) { + SubcomposeLayout( + modifier = Modifier.verticalScroll(rememberScrollState()) + ) { constraints -> + var itemCount = 0 + var listHeight = 0 + var visibleHeight = 0 + val listWidth = subcompose("miuixPopupListFake", content).map { + it.measure(constraints.copy( + minWidth = 200.dp.roundToPx(), maxWidth = 288.dp.roundToPx() + )) + }.maxOf { it.width }.coerceIn(200.dp.roundToPx(), 288.dp.roundToPx()) + val placeables = subcompose("miuixPopupListReal", content).map { + val placeable = it.measure(constraints.copy( + minWidth = listWidth, maxWidth = listWidth + )) + if (itemCount < 8) { + visibleHeight += placeable.height + } + listHeight += placeable.height + itemCount++ + placeable + } + layout(listWidth, min(constraints.maxHeight, listHeight)) { + var height = 0 + placeables.forEach { + it.place(0, height) + height += it.height + } + } + } +} + interface PopupPositionProvider { /** * Calculate the position (offset) of Popup 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 27aea370..f1ec843c 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 @@ -9,9 +9,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -33,13 +31,13 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.text.font.FontWeight 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.dp import kotlinx.coroutines.launch import top.yukonga.miuix.kmp.basic.BasicComponent import top.yukonga.miuix.kmp.basic.BasicComponentColors import top.yukonga.miuix.kmp.basic.BasicComponentDefaults import top.yukonga.miuix.kmp.basic.ListPopup +import top.yukonga.miuix.kmp.basic.ListPopupColumn import top.yukonga.miuix.kmp.basic.PopupPositionProvider import top.yukonga.miuix.kmp.basic.Text import top.yukonga.miuix.kmp.icon.MiuixIcons @@ -139,10 +137,10 @@ fun SuperDropdown( isDropdownExpanded.value = false } ) { - LazyColumn { - items(items.size) { index -> + ListPopupColumn { + items.forEachIndexed { index, string -> DropdownImpl( - text = items[index], + text = string, optionSize = items.size, isSelected = selectedIndex == index, onSelectedIndexChange = { @@ -151,7 +149,6 @@ fun SuperDropdown( dismissPopup(showPopup) isDropdownExpanded.value = false }, - textWidthDp = 128.dp, index = index ) } @@ -205,7 +202,6 @@ fun SuperDropdown( * @param isSelected Whether the option is selected. * @param index The index of the current option in the options. * @param onSelectedIndexChange The callback when the index is selected. - * @param textWidthDp The maximum width of text in options. */ @Composable fun DropdownImpl( @@ -213,8 +209,7 @@ fun DropdownImpl( optionSize: Int, isSelected: Boolean, index: Int, - onSelectedIndexChange: (Int) -> Unit, - textWidthDp: Dp? + onSelectedIndexChange: (Int) -> Unit ) { val additionalTopPadding = if (index == 0) 20f.dp else 12f.dp val additionalBottomPadding = if (index == optionSize - 1) 20f.dp else 12f.dp @@ -241,12 +236,11 @@ fun DropdownImpl( onSelectedIndexChange(index) } .background(backgroundColor) - .widthIn(200.dp, 288.dp) .padding(horizontal = 20.dp) .padding(top = additionalTopPadding, bottom = additionalBottomPadding) ) { Text( - modifier = Modifier.width(textWidthDp ?: 128.dp), + modifier = Modifier.widthIn(max = 216.dp), text = text, fontSize = MiuixTheme.textStyles.body1.fontSize, fontWeight = FontWeight.Medium, 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 39a1cdeb..a277cbf3 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 @@ -47,6 +47,7 @@ import top.yukonga.miuix.kmp.basic.BasicComponent import top.yukonga.miuix.kmp.basic.BasicComponentColors import top.yukonga.miuix.kmp.basic.BasicComponentDefaults import top.yukonga.miuix.kmp.basic.ListPopup +import top.yukonga.miuix.kmp.basic.ListPopupColumn import top.yukonga.miuix.kmp.basic.PopupPositionProvider import top.yukonga.miuix.kmp.basic.Text import top.yukonga.miuix.kmp.basic.TextButton @@ -150,10 +151,10 @@ fun SuperSpinner( isDropdownExpanded.value = false } ) { - LazyColumn { - items(items.size) { index -> + ListPopupColumn { + items.forEachIndexed { index, spinnerEntry -> SpinnerItemImpl( - entry = items[index], + entry = spinnerEntry, entryCount = items.size, isSelected = selectedIndex == index, index = index, @@ -434,11 +435,12 @@ fun SpinnerItemImpl( .background(backgroundColor) .then( if (dialogMode) Modifier.heightIn(min = 56.dp).widthIn(min = 200.dp).fillMaxWidth().padding(horizontal = 28.dp) - else Modifier.widthIn(200.dp, 288.dp).padding(horizontal = 20.dp) + else Modifier.padding(horizontal = 20.dp) ) .padding(top = additionalTopPadding, bottom = additionalBottomPadding) ) { Row( + modifier = if (dialogMode) Modifier else Modifier.widthIn(max = 216.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start ) { From 8907ccd7d4e6e7bf4c7747caaae0df470d9c03c8 Mon Sep 17 00:00:00 2001 From: howie Date: Wed, 4 Dec 2024 02:55:53 +0800 Subject: [PATCH 2/4] misc: remove redundant variable; --- .../kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt index 3f1b1ec6..dc82e563 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt @@ -237,9 +237,7 @@ fun ListPopupColumn( SubcomposeLayout( modifier = Modifier.verticalScroll(rememberScrollState()) ) { constraints -> - var itemCount = 0 var listHeight = 0 - var visibleHeight = 0 val listWidth = subcompose("miuixPopupListFake", content).map { it.measure(constraints.copy( minWidth = 200.dp.roundToPx(), maxWidth = 288.dp.roundToPx() @@ -249,11 +247,7 @@ fun ListPopupColumn( val placeable = it.measure(constraints.copy( minWidth = listWidth, maxWidth = listWidth )) - if (itemCount < 8) { - visibleHeight += placeable.height - } listHeight += placeable.height - itemCount++ placeable } layout(listWidth, min(constraints.maxHeight, listHeight)) { From c6522ff98259dbac4a1710c41ca2dce572f366d8 Mon Sep 17 00:00:00 2001 From: howie Date: Wed, 4 Dec 2024 02:58:27 +0800 Subject: [PATCH 3/4] misc: clean up code; --- .../top/yukonga/miuix/kmp/basic/ListPopup.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt index dc82e563..98070919 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt @@ -238,15 +238,17 @@ fun ListPopupColumn( modifier = Modifier.verticalScroll(rememberScrollState()) ) { constraints -> var listHeight = 0 + val tempConstraints = constraints.copy( + minWidth = 200.dp.roundToPx(), maxWidth = 288.dp.roundToPx() + ) val listWidth = subcompose("miuixPopupListFake", content).map { - it.measure(constraints.copy( - minWidth = 200.dp.roundToPx(), maxWidth = 288.dp.roundToPx() - )) + it.measure(tempConstraints) }.maxOf { it.width }.coerceIn(200.dp.roundToPx(), 288.dp.roundToPx()) + val childConstraints = constraints.copy( + minWidth = listWidth, maxWidth = listWidth + ) val placeables = subcompose("miuixPopupListReal", content).map { - val placeable = it.measure(constraints.copy( - minWidth = listWidth, maxWidth = listWidth - )) + val placeable = it.measure(childConstraints) listHeight += placeable.height placeable } From 397376876ec9da76c0e536937a608df26348660a Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:10:54 +0800 Subject: [PATCH 4/4] 1 --- .../src/commonMain/kotlin/component/TextComponent.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/component/TextComponent.kt b/composeApp/src/commonMain/kotlin/component/TextComponent.kt index ca0cbbde..37edb3ce 100644 --- a/composeApp/src/commonMain/kotlin/component/TextComponent.kt +++ b/composeApp/src/commonMain/kotlin/component/TextComponent.kt @@ -75,9 +75,9 @@ fun TextComponent( ) { val dropdownOptions = listOf("Option 1", "Option 2", "Option 3", "Option 4") val dropdownOptions2 = listOf( - "Option 1", "Option 2 (this is a long long long option)", "Option 3", "Option 4", + "Option 1", "Option 2", "Option 3", "Option 4", "Option 5", "Option 6", "Option 7", "Option 8", - "Option 9", "Option 10", "Option 11", "Option 12" + "Option 9", "Option 10", "Option 11", "Option 12 (this is a long long long option)" ) val spinnerOptions = listOf( SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFFFF5B29)) }, "Option 1", "Red"), @@ -87,9 +87,9 @@ fun TextComponent( ) val spinnerOptions2 = listOf( SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFFFF5B29)) }, "Option 1", "Red"), - SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFF36D167)) }, "Option 2 (this is a long option)", "Green"), + SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFF36D167)) }, "Option 2", "Green"), SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFF3482FF)) }, "Option 3", "Blue"), - SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFFFFB21D)) }, "Option 4", "Yellow"), + SpinnerEntry(icon = { Icon(RoundedRectanglePainter(), "Icon", Modifier.padding(end = 12.dp), Color(0xFFFFB21D)) }, "Option 4 (this is a long option)", "Yellow"), ) SmallTitle(text = "Basic") Card(