Skip to content

Commit bbd2e44

Browse files
committed
fix(datepicker): use pixel-accurate wheel center detection and settle callback
Replace imprecise firstVisibleItemIndex + halfVisible calculation with layoutInfo.visibleItemsInfo to find the item whose center is closest to the viewport midpoint. This prevents selected item highlight from jumping one position too early/late during drags. Only emit onSelectedChanged after scroll/fling settles, not on every frame during dragging. This prevents spurious intermediate selection callbacks that could corrupt caller state.
1 parent 2025cd7 commit bbd2e44

1 file changed

Lines changed: 25 additions & 16 deletions

File tree

  • macosui/src/commonMain/kotlin/io/github/kdroidfilter/nucleus/ui/apple/macos/components

macosui/src/commonMain/kotlin/io/github/kdroidfilter/nucleus/ui/apple/macos/components/DatePicker.kt

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import androidx.compose.runtime.getValue
3434
import androidx.compose.runtime.mutableStateOf
3535
import androidx.compose.runtime.remember
3636
import androidx.compose.runtime.setValue
37-
import androidx.compose.runtime.snapshotFlow
3837
import androidx.compose.ui.Alignment
3938
import androidx.compose.ui.Modifier
4039
import androidx.compose.ui.draw.alpha
@@ -53,7 +52,6 @@ import androidx.compose.ui.unit.dp
5352
import androidx.compose.ui.unit.sp
5453
import androidx.compose.ui.window.Popup
5554
import androidx.compose.ui.window.PopupProperties
56-
import kotlinx.coroutines.flow.distinctUntilChanged
5755
import io.github.kdroidfilter.nucleus.ui.apple.macos.icons.Icon
5856
import io.github.kdroidfilter.nucleus.ui.apple.macos.icons.Icons
5957
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.macosGlass
@@ -1243,8 +1241,11 @@ private fun <T> WheelColumn(
12431241

12441242
val useVirtualScroll = items.size > visibleItems
12451243
val paddedCount = if (useVirtualScroll) items.size * 1000 else items.size + halfVisible * 2
1244+
// midOffset is the largest multiple of items.size that is <= paddedCount/2,
1245+
// so that (absoluteIndex - midOffset).mod(items.size) == realIndex.
1246+
val midOffset = if (useVirtualScroll) (paddedCount / 2) - ((paddedCount / 2) % items.size) else 0
12461247
val initialScroll = if (useVirtualScroll) {
1247-
(paddedCount / 2) - ((paddedCount / 2) % items.size) + initialIndex - halfVisible
1248+
midOffset + initialIndex - halfVisible
12481249
} else {
12491250
initialIndex
12501251
}
@@ -1258,23 +1259,31 @@ private fun <T> WheelColumn(
12581259
val topFade = Brush.verticalGradient(0f to fadeBgColor, 1f to Color.Transparent)
12591260
val bottomFade = Brush.verticalGradient(0f to Color.Transparent, 1f to fadeBgColor)
12601261

1261-
val currentCenter by remember {
1262+
// Pixel-accurate: find the item whose center is closest to the viewport midpoint.
1263+
// This is more precise than firstVisibleItemIndex + halfVisible, which only updates
1264+
// when an item fully scrolls off screen rather than when it crosses the center.
1265+
val centerItemIndex by remember {
12621266
derivedStateOf {
1263-
listState.firstVisibleItemIndex + halfVisible
1267+
with(listState.layoutInfo) {
1268+
val viewportMid = viewportSize.height / 2
1269+
visibleItemsInfo
1270+
.minByOrNull { kotlin.math.abs(it.offset + it.size / 2 - viewportMid) }
1271+
?.index ?: (listState.firstVisibleItemIndex + halfVisible)
1272+
}
12641273
}
12651274
}
12661275

1267-
LaunchedEffect(Unit) {
1268-
snapshotFlow { currentCenter }
1269-
.distinctUntilChanged()
1270-
.collect { centerIndex ->
1271-
val realIndex = if (useVirtualScroll) {
1272-
centerIndex % items.size
1273-
} else {
1274-
(centerIndex - halfVisible).coerceIn(0, items.size - 1)
1275-
}
1276-
onSelectedChanged(realIndex)
1276+
// Notify the caller only once the scroll/fling has fully settled, not on every
1277+
// frame during a drag, to avoid spurious intermediate selection callbacks.
1278+
LaunchedEffect(listState.isScrollInProgress) {
1279+
if (!listState.isScrollInProgress) {
1280+
val realIndex = if (useVirtualScroll) {
1281+
(centerItemIndex - midOffset).mod(items.size)
1282+
} else {
1283+
(centerItemIndex - halfVisible).coerceIn(0, items.size - 1)
12771284
}
1285+
onSelectedChanged(realIndex)
1286+
}
12781287
}
12791288

12801289
Box(
@@ -1296,7 +1305,7 @@ private fun <T> WheelColumn(
12961305
} else {
12971306
(index - halfVisible).coerceIn(0, items.size - 1)
12981307
}
1299-
val distFromCenter = kotlin.math.abs(index - currentCenter)
1308+
val distFromCenter = kotlin.math.abs(index - centerItemIndex)
13001309
val alpha = when {
13011310
isSpacerItem -> 0f
13021311
distFromCenter == 0 -> 1f

0 commit comments

Comments
 (0)