Skip to content

Commit a81869b

Browse files
authored
Migrate scrollOnPressTrack from composed to Modifier.Node (JetBrains#2854)
Migrate `Modifier.scrollOnPressTrack` from `Modifier.composed` to `Modifier.Node` Fixes [CMP-9910](https://youtrack.jetbrains.com/issue/CMP-9910) ## Release Notes N/A
1 parent 9a34b00 commit a81869b

1 file changed

Lines changed: 118 additions & 32 deletions

File tree

  • compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation

compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/Scrollbar.skiko.kt

Lines changed: 118 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ package androidx.compose.foundation
2020

2121
import androidx.compose.animation.animateColorAsState
2222
import androidx.compose.animation.core.TweenSpec
23-
import androidx.compose.foundation.gestures.*
23+
import androidx.compose.foundation.gestures.awaitEachGesture
24+
import androidx.compose.foundation.gestures.awaitFirstDown
25+
import androidx.compose.foundation.gestures.awaitHorizontalDragOrCancellation
26+
import androidx.compose.foundation.gestures.awaitVerticalDragOrCancellation
27+
import androidx.compose.foundation.gestures.drag
2428
import androidx.compose.foundation.interaction.DragInteraction
2529
import androidx.compose.foundation.interaction.MutableInteractionSource
2630
import androidx.compose.foundation.interaction.collectIsHoveredAsState
@@ -48,13 +52,11 @@ import androidx.compose.runtime.rememberCoroutineScope
4852
import androidx.compose.runtime.setValue
4953
import androidx.compose.runtime.staticCompositionLocalOf
5054
import androidx.compose.ui.Modifier
51-
import androidx.compose.ui.composed
5255
import androidx.compose.ui.geometry.Offset
5356
import androidx.compose.ui.graphics.Color
5457
import androidx.compose.ui.graphics.Shape
5558
import androidx.compose.ui.input.pointer.PointerInputScope
5659
import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
57-
import androidx.compose.ui.input.pointer.pointerInput
5860
import androidx.compose.ui.input.pointer.positionChange
5961
import androidx.compose.ui.layout.Layout
6062
import androidx.compose.ui.layout.MeasurePolicy
@@ -892,30 +894,32 @@ private class ScrollbarDragModifierNode(
892894
private var sliderAdapter: SliderAdapter,
893895
) : DelegatingNode() {
894896

895-
private val pointerInputNode = delegate(
896-
SuspendingPointerInputModifierNode(
897-
pointerInputEventHandler = {
898-
awaitEachGesture {
899-
val down = awaitFirstDown(requireUnconsumed = false)
900-
val interaction = DragInteraction.Start()
901-
interactionSource.tryEmit(interaction)
902-
draggedInteraction.value = interaction
903-
sliderAdapter.onDragStarted()
904-
val isSuccess = drag(down.id) { change ->
905-
sliderAdapter.onDragDelta(change.positionChange())
906-
change.consume()
897+
init {
898+
delegate(
899+
SuspendingPointerInputModifierNode(
900+
pointerInputEventHandler = {
901+
awaitEachGesture {
902+
val down = awaitFirstDown(requireUnconsumed = false)
903+
val interaction = DragInteraction.Start()
904+
interactionSource.tryEmit(interaction)
905+
draggedInteraction.value = interaction
906+
sliderAdapter.onDragStarted()
907+
val isSuccess = drag(down.id) { change ->
908+
sliderAdapter.onDragDelta(change.positionChange())
909+
change.consume()
910+
}
911+
val finishInteraction = if (isSuccess) {
912+
DragInteraction.Stop(interaction)
913+
} else {
914+
DragInteraction.Cancel(interaction)
915+
}
916+
interactionSource.tryEmit(finishInteraction)
917+
draggedInteraction.value = null
907918
}
908-
val finishInteraction = if (isSuccess) {
909-
DragInteraction.Stop(interaction)
910-
} else {
911-
DragInteraction.Cancel(interaction)
912-
}
913-
interactionSource.tryEmit(finishInteraction)
914-
draggedInteraction.value = null
915919
}
916-
}
920+
)
917921
)
918-
)
922+
}
919923

920924
fun update(
921925
interactionSource: MutableInteractionSource,
@@ -932,17 +936,99 @@ private fun Modifier.scrollOnPressTrack(
932936
isVertical: Boolean,
933937
reverseLayout: Boolean,
934938
sliderAdapter: SliderAdapter,
935-
) = composed {
936-
val coroutineScope = rememberCoroutineScope()
937-
val scroller = remember(sliderAdapter, coroutineScope, reverseLayout) {
938-
TrackPressScroller(coroutineScope, sliderAdapter, reverseLayout)
939+
): Modifier = this.then(
940+
ScrollOnPressTrackElement(
941+
isVertical = isVertical,
942+
reverseLayout = reverseLayout,
943+
sliderAdapter = sliderAdapter,
944+
)
945+
)
946+
947+
private class ScrollOnPressTrackElement(
948+
private val isVertical: Boolean,
949+
private val reverseLayout: Boolean,
950+
private val sliderAdapter: SliderAdapter,
951+
): ModifierNodeElement<ScrollOnPressTrackNode>() {
952+
override fun create(): ScrollOnPressTrackNode = ScrollOnPressTrackNode(
953+
isVertical = isVertical,
954+
reverseLayout = reverseLayout,
955+
sliderAdapter = sliderAdapter,
956+
)
957+
958+
override fun update(node: ScrollOnPressTrackNode) = node.update(
959+
isVertical = isVertical,
960+
reverseLayout = reverseLayout,
961+
sliderAdapter = sliderAdapter,
962+
)
963+
964+
override fun InspectorInfo.inspectableProperties() {
965+
name = "scrollOnPressTrack"
966+
properties["isVertical"] = isVertical
967+
properties["reverseLayout"] = reverseLayout
968+
properties["sliderAdapter"] = sliderAdapter
969+
}
970+
971+
override fun hashCode(): Int {
972+
var result = isVertical.hashCode()
973+
result = 31 * result + reverseLayout.hashCode()
974+
result = 31 * result + sliderAdapter.hashCode()
975+
return result
939976
}
940-
Modifier.pointerInput(scroller) {
941-
detectScrollViaTrackGestures(
942-
isVertical = isVertical,
943-
scroller = scroller
977+
978+
override fun equals(other: Any?): Boolean {
979+
if (this === other) return true
980+
if (other !is ScrollOnPressTrackElement) return false
981+
return isVertical == other.isVertical &&
982+
reverseLayout == other.reverseLayout &&
983+
sliderAdapter == other.sliderAdapter
984+
}
985+
}
986+
987+
private class ScrollOnPressTrackNode(
988+
private var isVertical: Boolean,
989+
private var reverseLayout: Boolean,
990+
private var sliderAdapter: SliderAdapter,
991+
): DelegatingNode() {
992+
private lateinit var scroller: TrackPressScroller
993+
private lateinit var pointerInputNode: SuspendingPointerInputModifierNode
994+
995+
override fun onAttach() {
996+
super.onAttach()
997+
998+
recreateScroller()
999+
1000+
pointerInputNode = delegate(
1001+
SuspendingPointerInputModifierNode(
1002+
pointerInputEventHandler = {
1003+
detectScrollViaTrackGestures(
1004+
isVertical = isVertical,
1005+
scroller = scroller,
1006+
)
1007+
}
1008+
)
9441009
)
9451010
}
1011+
1012+
fun update(
1013+
isVertical: Boolean,
1014+
reverseLayout: Boolean,
1015+
sliderAdapter: SliderAdapter,
1016+
) {
1017+
val needsReset = isVertical != this.isVertical || reverseLayout != this.reverseLayout || sliderAdapter != this.sliderAdapter
1018+
1019+
this.isVertical = isVertical
1020+
this.reverseLayout = reverseLayout
1021+
this.sliderAdapter = sliderAdapter
1022+
1023+
if (needsReset) {
1024+
recreateScroller()
1025+
pointerInputNode.resetPointerInputHandler()
1026+
}
1027+
}
1028+
1029+
private fun recreateScroller() {
1030+
scroller = TrackPressScroller(coroutineScope, sliderAdapter, reverseLayout)
1031+
}
9461032
}
9471033

9481034
/**

0 commit comments

Comments
 (0)