net.peanuuutz.fork.ui.foundation.input.Pressable.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!
/*
* Copyright 2020 The Android Open Source Project
* Modifications Copyright 2022 Peanuuutz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.peanuuutz.fork.ui.foundation.input
import androidx.compose.runtime.Stable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import net.peanuuutz.fork.ui.foundation.input.interaction.MutableInteractionSource
import net.peanuuutz.fork.ui.foundation.input.interaction.PressInteraction
import net.peanuuutz.fork.ui.foundation.input.interaction.detectAndEmitPressInteractions
import net.peanuuutz.fork.ui.foundation.input.interaction.emitCancelOnPress
import net.peanuuutz.fork.ui.foundation.input.interaction.emitPress
import net.peanuuutz.fork.ui.foundation.input.interaction.emitRelease
import net.peanuuutz.fork.ui.foundation.input.interaction.tryEmitCancelOnPress
import net.peanuuutz.fork.ui.inspection.InspectInfo
import net.peanuuutz.fork.ui.ui.context.key.Key
import net.peanuuutz.fork.ui.ui.context.key.KeyEvent
import net.peanuuutz.fork.ui.ui.context.pointer.PointerEvent
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.ui.ui.modifier.ModifierNodeElement
import net.peanuuutz.fork.ui.ui.modifier.input.SuspendingPointerInputModifierNode
import net.peanuuutz.fork.ui.ui.node.BranchingModifierNode
import net.peanuuutz.fork.ui.ui.node.KeyEventPass
import net.peanuuutz.fork.ui.ui.node.KeyEventPass.Out
import net.peanuuutz.fork.ui.ui.node.KeyInputModifierNode
import net.peanuuutz.fork.ui.ui.node.PointerEventPass
import net.peanuuutz.fork.ui.ui.node.PointerInputModifierNode
@Stable
fun Modifier.pressable(
interactionSource: MutableInteractionSource? = null,
label: Any? = null,
isEnabled: Boolean = true,
onPress: (CoroutineScope.() -> Unit)? = null,
onRelease: (CoroutineScope.() -> Unit)? = null
): Modifier {
if (!isEnabled) {
return this
}
val element = PressableModifier(
interactionSource = interactionSource,
label = label,
onPress = onPress,
onRelease = onRelease
)
return this then element
}
// ======== Internal ========
private data class PressableModifier(
val interactionSource: MutableInteractionSource?,
val label: Any?,
val onPress: (CoroutineScope.() -> Unit)?,
val onRelease: (CoroutineScope.() -> Unit)?
) : ModifierNodeElement() {
override fun create(): PressableModifierNode {
return PressableModifierNode(
interactionSource = interactionSource,
label = label,
onPress = onPress,
onRelease = onRelease
)
}
override fun update(node: PressableModifierNode) {
node.interactionSource = interactionSource
node.label = label
node.onPress = onPress
node.onRelease = onRelease
}
override fun InspectInfo.inspect() {
set("label", label)
}
}
private class PressableModifierNode(
interactionSource: MutableInteractionSource?,
label: Any?,
var onPress: (CoroutineScope.() -> Unit)?,
var onRelease: (CoroutineScope.() -> Unit)?
) : BranchingModifierNode(),
PointerInputModifierNode,
KeyInputModifierNode
{
var interactionSource: MutableInteractionSource? = interactionSource
set(value) {
if (field == value) {
return
}
field?.tryEmitCancelOnPress(
stateProvider = this::pressState,
stateUpdater = this::pressState::set
)
field = value
}
var label: Any? = label
set(value) {
if (field == value) {
return
}
interactionSource?.tryEmitCancelOnPress(
stateProvider = this::pressState,
stateUpdater = this::pressState::set
)
field = value
}
private val pointerInputHandler: SuspendingPointerInputModifierNode = branch {
SuspendingPointerInputModifierNode {
coroutineScope {
launch {
detectAndEmitPressInteractions(
interactionSourceProvider = this@PressableModifierNode::interactionSource,
stateProvider = this@PressableModifierNode::pressState,
stateUpdater = this@PressableModifierNode::pressState::set,
labelProvider = this@PressableModifierNode::label
)
}
launch {
detectPressRelease(
onPress = {
val onPress = onPress
if (onPress != null) {
onPress()
}
},
onRelease = {
val onRelease = onRelease
if (onRelease != null) {
onRelease()
}
}
)
}
}
}
}
private var pressState: PressInteraction.Press? = null
override fun onDetach() {
interactionSource?.tryEmitCancelOnPress(
stateProvider = this::pressState,
stateUpdater = this::pressState::set
)
}
override fun onPointerEvent(pass: PointerEventPass, pointerEvent: PointerEvent) {
pointerInputHandler.onPointerEvent(pass, pointerEvent)
}
override fun onKeyEvent(pass: KeyEventPass, keyEvent: KeyEvent): Boolean {
if (pass != Out || keyEvent.key != Key.Enter) {
return false
}
if (keyEvent.isPressed) {
nodeScope.launch {
val effectInteractionSource = interactionSource ?: return@launch
effectInteractionSource.emitCancelOnPress(
stateProvider = this@PressableModifierNode::pressState,
stateUpdater = this@PressableModifierNode::pressState::set
)
effectInteractionSource.emitPress(
stateProvider = this@PressableModifierNode::pressState,
stateUpdater = this@PressableModifierNode::pressState::set,
labelProvider = this@PressableModifierNode::label
)
}
val onPress = onPress
if (onPress != null) {
onPress(nodeScope)
}
} else {
nodeScope.launch {
interactionSource?.emitRelease(
stateProvider = this@PressableModifierNode::pressState,
stateUpdater = this@PressableModifierNode::pressState::set
)
}
val onRelease = onRelease
if (onRelease != null) {
onRelease(nodeScope)
}
}
return true
}
}