All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.peanuuutz.fork.ui.foundation.input.ContentDrag.kt Maven / Gradle / Ivy

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