net.peanuuutz.fork.ui.foundation.input.AxisDragState.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.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import net.peanuuutz.fork.ui.animation.animate
import net.peanuuutz.fork.ui.animation.spec.target.DefaultFloatAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.composite.FiniteAnimationSpec
import net.peanuuutz.fork.ui.ui.unit.FloatOffset
import net.peanuuutz.fork.ui.ui.unit.dot
import net.peanuuutz.fork.ui.ui.unit.isNonZero
import net.peanuuutz.fork.ui.ui.unit.isSpecified
import net.peanuuutz.fork.ui.ui.unit.isZero
import net.peanuuutz.fork.ui.ui.unit.normalize
import net.peanuuutz.fork.ui.util.MutationPriority
import kotlin.math.roundToInt
@Composable
fun rememberAxisDragState(
initialOffset: Float = 0.0f
): AxisDragState {
return rememberSaveable(
saver = AxisDragState.Saver
) {
AxisDragState(initialOffset)
}
}
@Stable
class AxisDragState(
initialOffset: Float = 0.0f
) : DragState {
// -------- States --------
var offset: Float
get() = _offset
set(value) {
if (_offset == value) {
return
}
_offset = value.coerceAtMost(maxOffset)
}
val roundedOffset: Int by derivedStateOf { offset.roundToInt() }
var maxOffset: Float
get() = _maxOffset
set(value) {
if (_maxOffset == value) {
return
}
_maxOffset = value
_offset = offset.coerceAtMost(value)
}
// -------- Parameters --------
fun updateAxisDirection(axisDirection: FloatOffset) {
require(axisDirection.isSpecified() && axisDirection.isNonZero())
this.axisDirection = axisDirection.normalize()
}
// ======== Internal ========
// -------- Delegate --------
private val delegate: DragState = DragState { movement ->
if (movement.isZero()) {
return@DragState FloatOffset.Zero
}
val currentOffset = offset
val deltaOffset = axisDirection dot movement
val newOffset = currentOffset + deltaOffset
val coercedNewOffset = newOffset.coerceIn(0.0f, maxOffset)
val consumedOffset = coercedNewOffset - currentOffset
val consumedMovement = movement * (consumedOffset / deltaOffset)
_offset = coercedNewOffset
consumedMovement
}
override val isDragging: Boolean
get() = delegate.isDragging
override suspend fun drag(priority: MutationPriority, scope: suspend DragScope.() -> Unit) {
delegate.drag(priority, scope)
}
override fun dragBy(movement: FloatOffset): FloatOffset {
return delegate.dragBy(movement)
}
// -------- States --------
private var _offset: Float by mutableStateOf(initialOffset)
private var _maxOffset: Float by mutableStateOf(0.0f)
// -------- Parameters --------
// Users shouldn't get but only update this
internal var axisDirection: FloatOffset = FloatOffset.Right
// ======== Public ========
companion object {
val Saver: Saver = Saver(
save = { it.offset },
restore = { AxisDragState(it) }
)
}
}
// -------- States --------
fun AxisDragState.canDrag(deltaOffset: Float): Boolean {
return offset + deltaOffset in 0.0f..maxOffset
}
fun AxisDragState.canDrag(movement: FloatOffset): Boolean {
return canDrag(deltaOffset = axisDirection dot movement)
}
// -------- State Operations --------
suspend fun AxisDragState.animateTo(
targetOffset: Float,
animationSpec: FiniteAnimationSpec = DefaultFloatAnimationSpec,
priority: MutationPriority = MutationPriority.Default
): Float {
var previousAxisMovement = 0.0f
drag(priority) {
animate(
initialValue = 0.0f,
targetValue = convertTargetOffsetToAxisMovement(targetOffset),
animationSpec = animationSpec
) { offset, _ ->
val axisDirection = axisDirection
val movement = axisDirection * (offset - previousAxisMovement)
previousAxisMovement += axisDirection dot dragBy(movement)
}
}
return previousAxisMovement
}
suspend fun AxisDragState.snapTo(
targetOffset: Float,
priority: MutationPriority = MutationPriority.Default
): Float {
var axisMovement = 0.0f
drag(priority) {
val axisDirection = axisDirection
val providedAxisMovement = convertTargetOffsetToAxisMovement(targetOffset)
val providedMovement = axisDirection * providedAxisMovement
axisMovement = axisDirection dot dragBy(providedMovement)
}
return axisMovement
}
// ======== Internal ========
// -------- State Operations --------
private fun AxisDragState.convertTargetOffsetToAxisMovement(targetOffset: Float): Float {
return targetOffset.coerceIn(0.0f, maxOffset) - offset
}