
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