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

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

package net.peanuuutz.fork.ui.foundation.input

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.isActive
import net.peanuuutz.fork.ui.ui.context.pointer.MouseButton
import net.peanuuutz.fork.ui.ui.context.pointer.PointerEvent
import net.peanuuutz.fork.ui.ui.context.pointer.PointerMoveEvent
import net.peanuuutz.fork.ui.ui.context.pointer.PointerScrollEvent
import net.peanuuutz.fork.ui.ui.context.pointer.PointerTapEvent
import net.peanuuutz.fork.ui.ui.context.pointer.isReleased
import net.peanuuutz.fork.ui.ui.context.pointer.isUnconsumed
import net.peanuuutz.fork.ui.ui.modifier.input.PointerInputScope
import net.peanuuutz.fork.ui.ui.modifier.input.filter
import net.peanuuutz.fork.ui.ui.unit.FloatOffset
import kotlin.math.abs

suspend fun PointerInputScope.detect(onEvent: (PointerEvent) -> Unit) {
    receive {
        while (listenerContext.isActive) {
            val event = filter { it.isCaptured }
            onEvent(event) // Consumed or not is up to user
        }
    }
}

suspend fun PointerInputScope.detectEnterExit(
    onEnter: (PointerEvent) -> Unit = Noop1,
    onCancel: () -> Unit = Noop0,
    onExit: (PointerEvent) -> Unit = Noop1
) {
    receive {
        var isInside = false
        try {
            while (listenerContext.isActive) {
                val event = listen()
                val isInsideBefore = isInside
                isInside = event.isCaptured
                if (isInside && !isInsideBefore) {
                    onEnter(event)
                } else if (!isInside && isInsideBefore) {
                    onExit(event)
                }
            }
        } catch (e: CancellationException) {
            if (isInside) {
                onCancel()
            }
            throw e
        }
    }
}

suspend fun PointerInputScope.detectPressRelease(
    button: MouseButton = MouseButton.Left,
    shouldAutoConsume: Boolean = true,
    onPress: (PointerTapEvent) -> Unit = Noop1,
    onCancel: () -> Unit = Noop0,
    onRelease: (PointerTapEvent) -> Unit = Noop1
) {
    receive {
        var pressing = false
        try {
            while (listenerContext.isActive) {
                val pressEvent = filter { tapEvent ->
                    button == tapEvent.button &&
                            tapEvent.isPressed &&
                            tapEvent.isCaptured &&
                            tapEvent.isUnconsumed
                }
                onPress(pressEvent)
                if (shouldAutoConsume) {
                    pressEvent.consume()
                }
                pressing = true
                while (listenerContext.isActive) {
                    when (val event = listen()) {
                        is PointerTapEvent -> {
                            if (button != event.button || event.isPressed) {
                                continue
                            }
                            if (event.isConsumed) {
                                onCancel()
                            } else {
                                onRelease(event)
                            }
                            break
                        }
                        is PointerMoveEvent -> {
                            if (button != event.button) {
                                continue
                            }
                            onCancel()
                            break
                        }
                        is PointerScrollEvent -> {}
                    }
                }
                pressing = false
            }
        } catch (e: CancellationException) {
            if (pressing) {
                onCancel()
            }
            throw e
        }
    }
}

suspend fun PointerInputScope.detectDrag(
    button: MouseButton = MouseButton.Left,
    shouldAutoConsume: Boolean = true,
    onStart: (PointerTapEvent) -> Unit = Noop1,
    onStop: (lastMoveEvent: PointerMoveEvent, breakEvent: PointerTapEvent) -> Unit = Noop2,
    onCancel: () -> Unit = Noop0,
    onDrag: (PointerMoveEvent) -> Unit = Noop1
) {
    receive {
        var dragging = false
        try {
            main@ while (listenerContext.isActive) {
                val pressEvent = filter { event ->
                    button == event.button &&
                            event.isPressed &&
                            event.isCaptured
                }
                val pressEventCopy = PointerTapEvent(
                    position = pressEvent.position,
                    modifier = pressEvent.modifier,
                    button = button,
                    isPressed = true
                )
                var lastMoveEvent = PointerMoveEvent(
                    position = pressEvent.position,
                    modifier = pressEvent.modifier,
                    offset = FloatOffset.Zero,
                    button = button
                )
                while (listenerContext.isActive) {
                    when (val event = listen()) {
                        is PointerMoveEvent -> {
                            if (button != event.button) {
                                continue
                            }
                            if (event.isConsumed) {
                                continue@main
                            }
                            onStart(pressEventCopy)
                            if (shouldAutoConsume) {
                                pressEventCopy.consume()
                            }
                            onDrag(event)
                            if (shouldAutoConsume) {
                                event.consume()
                            }
                            lastMoveEvent = event
                            break
                        }
                        is PointerTapEvent -> {
                            if (button == event.button && event.isReleased) {
                                continue@main
                            }
                        }
                        is PointerScrollEvent -> {}
                    }
                }
                dragging = true
                while (listenerContext.isActive) {
                    when (val event = listen()) {
                        is PointerMoveEvent -> {
                            if (button != event.button) {
                                continue
                            }
                            if (event.isConsumed) {
                                onCancel()
                                break
                            }
                            lastMoveEvent = event
                            onDrag(event)
                            if (shouldAutoConsume) {
                                event.consume()
                            }
                        }
                        is PointerTapEvent -> {
                            onStop(lastMoveEvent, event)
                            break
                        }
                        is PointerScrollEvent -> {}
                    }
                }
                dragging = false
            }
        } catch (e: CancellationException) {
            if (dragging) {
                onCancel()
            }
            throw e
        }
    }
}

suspend fun PointerInputScope.detectScroll(
    shouldAutoConsume: Boolean = true,
    onScroll: (PointerScrollEvent) -> Unit
) {
    receive {
        while (listenerContext.isActive) {
            val scrollEvent = filter { scrollEvent ->
                scrollEvent.isCaptured && scrollEvent.isUnconsumed
            }
            onScroll(scrollEvent)
            if (abs(scrollEvent.amount) < ScrollEventConsumedEpsilon && shouldAutoConsume) {
                scrollEvent.consume()
            }
        }
    }
}

// ======== Internal ========

private val Noop0: () -> Unit = {}

private val Noop1: (PointerEvent) -> Unit = {}

private val Noop2: (PointerEvent, PointerEvent) -> Unit = { _, _ -> }

private const val ScrollEventConsumedEpsilon: Float = 0.1f




© 2015 - 2025 Weber Informatics LLC | Privacy Policy