![JAR search and dependency download from the Maven repository](/logo.png)
net.peanuuutz.fork.ui.foundation.input.ContentDrag.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fork-ui Show documentation
Show all versions of fork-ui Show documentation
Comprehensive API designed for Minecraft modders
The newest version!
package net.peanuuutz.fork.ui.foundation.input
import androidx.compose.runtime.Stable
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import net.peanuuutz.fork.ui.foundation.draw.clip
import net.peanuuutz.fork.ui.ui.context.pointer.PointerEvent
import net.peanuuutz.fork.ui.ui.layout.Constraints
import net.peanuuutz.fork.ui.ui.layout.Constraints.Companion.Unlimited
import net.peanuuutz.fork.ui.ui.layout.Measurable
import net.peanuuutz.fork.ui.ui.layout.MeasureResult
import net.peanuuutz.fork.ui.ui.layout.constrainHeight
import net.peanuuutz.fork.ui.ui.layout.constrainWidth
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.ui.ui.modifier.ModifierNodeElement
import net.peanuuutz.fork.ui.ui.modifier.composed
import net.peanuuutz.fork.ui.ui.modifier.input.SuspendingPointerInputModifierNode
import net.peanuuutz.fork.ui.ui.node.BranchingModifierNode
import net.peanuuutz.fork.ui.ui.node.LayoutCallbackModifierNode
import net.peanuuutz.fork.ui.ui.node.LayoutInfo
import net.peanuuutz.fork.ui.ui.node.LayoutModifierNode
import net.peanuuutz.fork.ui.ui.node.ModifierNode
import net.peanuuutz.fork.ui.ui.node.PointerEventPass
import net.peanuuutz.fork.ui.ui.node.PointerInputModifierNode
import net.peanuuutz.fork.ui.ui.unit.FloatOffset
@Stable
fun Modifier.contentDrag(
mode: ContentDragMode = ContentDragMode.Default,
isEnabled: Boolean = true
): Modifier {
if (!isEnabled) {
return this
}
return composed {
contentDrag(
state = rememberContentDragState(),
mode = mode
)
}
}
@Stable
fun Modifier.contentDrag(
state: ContentDragState,
mode: ContentDragMode = ContentDragMode.Default,
isEnabled: Boolean = true
): Modifier {
return this
.clip()
.contentDraggable(
state = state,
mode = mode,
isEnabled = isEnabled
)
.contentDragLayout(state)
}
@Stable
fun Modifier.contentDraggable(
state: ContentDragState,
mode: ContentDragMode = ContentDragMode.Default,
isEnabled: Boolean = true
): Modifier {
if (!isEnabled) {
return this
}
val element = ContentDraggableModifier(
mode = mode,
state = state
)
return this then element
}
@Stable
fun Modifier.contentDragLayout(state: ContentDragState): Modifier {
return this then ContentDragLayoutModifier(state)
}
// ======== Internal ========
private data class ContentDragLayoutModifier(
val state: ContentDragState
) : ModifierNodeElement() {
override fun create(): ContentDragLayoutModifierNode {
return ContentDragLayoutModifierNode(state)
}
override fun update(node: ContentDragLayoutModifierNode) {
node.state = state
}
}
private class ContentDragLayoutModifierNode(
state: ContentDragState
) : ModifierNode(),
LayoutModifierNode,
LayoutCallbackModifierNode
{
var state: ContentDragState = state
set(value) {
if (field == value) {
return
}
field.detach()
field = value
}
override fun onDetach() {
state.detach()
}
override fun measure(measurable: Measurable, constraints: Constraints): MeasureResult {
val contentConstraints = constraints.copy(
maxWidth = Unlimited,
maxHeight = Unlimited
)
val placeable = measurable.measure(contentConstraints)
val displayWidth = constraints.constrainWidth(placeable.width)
val displayHeight = constraints.constrainHeight(placeable.height)
return MeasureResult(displayWidth, displayHeight) {
val state = state
state.maxOffset = FloatOffset(
x = placeable.width - displayWidth,
y = placeable.height - displayHeight
)
val (x, y) = state.roundedOffset
placeable.place(-x, -y)
}
}
override fun onPlaced(info: LayoutInfo) {
state.attach(info)
}
}
private data class ContentDraggableModifier(
val mode: ContentDragMode,
val state: ContentDragState
) : ModifierNodeElement() {
override fun create(): ContentDraggableModifierNode {
return ContentDraggableModifierNode(
mode = mode,
state = state
)
}
override fun update(node: ContentDraggableModifierNode) {
node.mode = mode
node.state = state
}
}
private class ContentDraggableModifierNode(
var mode: ContentDragMode,
var state: ContentDragState
) : BranchingModifierNode(), PointerInputModifierNode {
private val pointerInputHandler: SuspendingPointerInputModifierNode = branch {
SuspendingPointerInputModifierNode {
coroutineScope {
detectDrag(
onStop = { lastMovementEvent, _ ->
val velocityModifier = mode.velocityModifier
val velocity = lastMovementEvent.offset * velocityModifier
val testMovement = velocity * TestMovementDuration
if (state.canDrag(testMovement).not()) {
return@detectDrag
}
launch {
state.drag {
with(mode.flingBehavior) {
fling(velocity)
}
}
}
},
onDrag = { moveEvent ->
val movementModifier = mode.movementModifier
val movement = moveEvent.offset * movementModifier
state.dragBy(movement)
}
)
}
}
}
override fun onPointerEvent(pass: PointerEventPass, pointerEvent: PointerEvent) {
pointerInputHandler.onPointerEvent(pass, pointerEvent)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy