1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-11-08 12:02:33 +01:00

Create modifiers for dragging

This commit is contained in:
Allan Wang 2023-06-21 22:31:09 -07:00
parent acdb04f39d
commit 5567957475
No known key found for this signature in database
GPG Key ID: C93E3F9C679D7A56
3 changed files with 103 additions and 114 deletions

View File

@ -18,24 +18,16 @@ package com.pitchedapps.frost.compose.draggable
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntSize
import com.pitchedapps.frost.ext.toDpSize
import com.pitchedapps.frost.ext.toIntOffset
@ -72,80 +64,55 @@ fun DragContainer(
fun DragTarget(
key: String = "",
draggableState: DraggableState,
content: @Composable BoxScope.(isDragging: Boolean) -> Unit
content: @Composable (isDragging: Boolean) -> Unit
) {
var isDragging by remember { mutableStateOf(false) }
var positionInWindow by remember { mutableStateOf(Offset.Zero) }
var size by remember { mutableStateOf(IntSize.Zero) }
val dragTargetState =
draggableState.rememberDragTarget(
key = key,
content = content,
)
Box(
modifier =
Modifier.onGloballyPositioned {
positionInWindow = it.positionInWindow()
size = it.size
}
.pointerInput(Unit) {
detectDragGesturesAfterLongPress(
onDragStart = {
isDragging =
draggableState.onDragStart(key) {
DraggingTargetState(
composable = content,
size = size,
dragPosition = positionInWindow,
)
}
},
onDrag = { _, offset ->
if (isDragging) {
draggableState.onDrag(key, offset)
}
},
onDragEnd = {
if (isDragging) {
draggableState.onDragEnd(key)
isDragging = false
}
},
)
},
modifier = Modifier.dragTarget(dragTargetState),
) {
if (!isDragging) {
content(false)
content(false)
}
}
private fun Modifier.dragTarget(dragTargetState: DragTargetState): Modifier {
return onGloballyPositioned {
dragTargetState.windowPosition = it.positionInWindow()
dragTargetState.size = it.size
}
}
.pointerInput(dragTargetState) {
val draggableState = dragTargetState.draggableState
val key = dragTargetState.key
detectDragGesturesAfterLongPress(
onDragStart = {
dragTargetState.dragPosition = dragTargetState.windowPosition
dragTargetState.isDragging = draggableState.onDragStart(key, dragTargetState)
},
onDrag = { _, offset ->
if (dragTargetState.isDragging) {
draggableState.onDrag(key, offset)
}
},
onDragEnd = {
if (dragTargetState.isDragging) {
draggableState.onDragEnd(key)
dragTargetState.isDragging = false
}
},
)
}
// We still need to draw to track size changes
.alpha(if (dragTargetState.isDragging) 0f else 1f)
}
fun Modifier.dropTarget(key: String, draggableState: DraggableState): Modifier {
return onGloballyPositioned { draggableState.onDropUpdateBounds(key, it.boundsInWindow()) }
}
@Composable
fun <T> DropTarget(
draggableState: DraggableState,
onDrop: (T) -> Unit,
content: @Composable BoxScope.(isHovering: Boolean, data: T?) -> Unit
) {
var hoverKey: String? by remember { mutableStateOf(null) }
var hoverData: T? by remember { mutableStateOf(null) }
var bounds: Rect by remember { mutableStateOf(Rect.Zero) }
LaunchedEffect(hoverKey, bounds, draggableState) {
// hoverKey = draggableState.getHoverKey(hoverKey, bounds)
// println("asdf new hover key $hoverKey")
}
Box(
modifier = Modifier.onGloballyPositioned { bounds = it.boundsInWindow() },
) {
content(hoverKey != null, hoverData)
}
fun Modifier.dropTarget(dropTargetState: DropTargetState): Modifier {
return onGloballyPositioned { dropTargetState.bounds = it.boundsInWindow() }
}
/**
@ -165,12 +132,12 @@ private fun DraggingContents(draggableState: DraggableState) {
}
@Composable
private fun DraggingContent(target: DraggingTargetState) {
private fun DraggingContent(target: DragTargetState) {
val density = LocalDensity.current
Box(
modifier =
Modifier.size(target.size.toDpSize(density)).offset { target.dragPosition.toIntOffset() },
) {
target.composable(this, true)
target.composable(true)
}
}

View File

@ -16,8 +16,8 @@
*/
package com.pitchedapps.frost.compose.draggable
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
@ -34,7 +34,7 @@ fun rememberDraggableState(): DraggableState {
interface DraggableState {
val targets: Collection<DraggingTargetState>
val targets: Collection<DragTargetState>
/**
* Being drag for target [key].
@ -44,39 +44,43 @@ interface DraggableState {
* Returns true if the request is accepted. It is the caller's responsibility to not propagate
* drag events if the request is denied.
*/
fun onDragStart(key: String, targetState: () -> DraggingTargetState): Boolean
fun onDragStart(key: String, dragTargetState: DragTargetState): Boolean
fun onDrag(key: String, offset: Offset)
fun onDragEnd(key: String)
fun onDropUpdateBounds(key: String, bounds: Rect)
@Composable
fun rememberDragTarget(
key: String,
content: @Composable (isDragging: Boolean) -> Unit
): DragTargetState
fun dropTarget(key: String): DropTargetState?
@Composable fun rememberDropTarget(key: String): DropTargetState
}
class DraggableStateImpl : DraggableState {
private val dragTargets = mutableStateMapOf<String, DraggingTargetState>()
private val activeDragTargets = mutableStateMapOf<String, DragTargetState>()
private val dropTargets = mutableStateMapOf<String, DropTargetState>()
override val targets: Collection<DraggingTargetState>
get() = dragTargets.values
override val targets: Collection<DragTargetState>
get() = activeDragTargets.values
override fun onDragStart(key: String, targetState: () -> DraggingTargetState): Boolean {
if (key in dragTargets) return false
dragTargets[key] = targetState()
override fun onDragStart(key: String, dragTargetState: DragTargetState): Boolean {
if (key in activeDragTargets) return false
activeDragTargets[key] = dragTargetState
return true
}
override fun onDrag(key: String, offset: Offset) {
val position = dragTargets[key] ?: return
val position = activeDragTargets[key] ?: return
position.dragPosition += offset
checkForDrag(key)
}
override fun onDragEnd(key: String) {
dragTargets.remove(key)
activeDragTargets.remove(key)
for ((dropKey, dropTarget) in dropTargets) {
if (dropTarget.hoverKey == key) {
setHover(dragKey = null, dropKey)
@ -86,42 +90,56 @@ class DraggableStateImpl : DraggableState {
}
}
override fun onDropUpdateBounds(key: String, bounds: Rect) {
dropTargets.getOrPut(key) { DropTargetState() }.bounds = bounds
checkForDrop(key)
@Composable
override fun rememberDragTarget(
key: String,
content: @Composable (isDragging: Boolean) -> Unit
): DragTargetState {
val target =
remember(key, content, this) {
DragTargetState(key = key, draggableState = this, composable = content)
}
DisposableEffect(target) { onDispose { activeDragTargets.remove(key) } }
return target
}
override fun dropTarget(key: String): DropTargetState? {
return dropTargets[key]
@Composable
override fun rememberDropTarget(key: String): DropTargetState {
val target = remember(key, this) { DropTargetState(key, this) }
DisposableEffect(target) {
dropTargets[key] = target
onDispose { dropTargets.remove(key) }
}
return target
}
private fun setHover(dragKey: String?, dropKey: String) {
val dropTarget = dropTargets[dropKey] ?: return
dropTarget.hoverKey = dragKey
println("asdf update $dragKey $dropKey")
}
/** Returns true if drag target exists and is within bounds */
private fun DropTargetState.hasValidDragTarget(): Boolean {
val currentKey = hoverKey ?: return false // no target
val dragTarget = dragTargets[currentKey] ?: return false // target not valid
val dragTarget = activeDragTargets[currentKey] ?: return false // target not valid
return dragTarget.within(bounds)
}
/** Check if drag target fits in drop */
private fun checkForDrop(dropKey: String) {
internal fun checkForDrop(dropKey: String) {
val dropTarget = dropTargets[dropKey] ?: return
val bounds = dropTarget.bounds
if (dropTarget.hasValidDragTarget()) return
// Find first target that matches
val dragKey = dragTargets.entries.firstOrNull { it.value.within(bounds) }?.key
val dragKey = activeDragTargets.entries.firstOrNull { it.value.within(bounds) }?.key
setHover(dragKey = dragKey, dropKey = dropKey)
}
/** Check drops for drag target fit */
private fun checkForDrag(dragKey: String) {
val dragTarget = dragTargets[dragKey] ?: return
internal fun checkForDrag(dragKey: String) {
val dragTarget = activeDragTargets[dragKey] ?: return
for ((dropKey, dropTarget) in dropTargets) {
// Do not override targets that are valid
if (dropTarget.hasValidDragTarget()) continue
@ -134,23 +152,30 @@ class DraggableStateImpl : DraggableState {
}
}
private fun DraggingTargetState?.within(bounds: Rect): Boolean {
private fun DragTargetState?.within(bounds: Rect): Boolean {
if (this == null) return false
val center = dragPosition + Offset(size.width * 0.5f, size.height * 0.5f)
return bounds.contains(center)
}
/** State for individual dragging target. */
class DraggingTargetState(
val composable: @Composable BoxScope.(isDragging: Boolean) -> Unit,
val size: IntSize,
dragPosition: Offset
class DragTargetState(
val key: String,
val draggableState: DraggableStateImpl,
val composable: @Composable (isDragging: Boolean) -> Unit
) {
var dragPosition by mutableStateOf(dragPosition)
var isDragging by mutableStateOf(false)
var windowPosition = Offset.Zero
var dragPosition by mutableStateOf(Offset.Zero)
var size: IntSize by mutableStateOf(IntSize.Zero)
}
class DropTargetState {
class DropTargetState(private val key: String, private val draggableState: DraggableStateImpl) {
var hoverKey: String? by mutableStateOf(null)
var hoverData: String? by mutableStateOf(null)
var bounds: Rect = Rect.Zero
set(value) {
field = value
draggableState.checkForDrop(key)
}
}

View File

@ -34,7 +34,6 @@ import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -130,18 +129,16 @@ fun TabBottomBar(
) {
NavigationBar(modifier = modifier) {
items.forEach { item ->
val key = item.key
val hasHoverKey by derivedStateOf { draggableState.dropTarget(key)?.hoverKey != null }
val dropTargetState = draggableState.rememberDropTarget(item.key)
val alpha by
animateFloatAsState(
targetValue = if (!hasHoverKey) 1f else 0f,
targetValue = if (dropTargetState.hoverKey == null) 1f else 0f,
label = "Nav Item Alpha",
)
NavigationBarItem(
modifier = Modifier.dropTarget(key, draggableState),
modifier = Modifier.dropTarget(dropTargetState),
icon = {
// println(dropTargetState.hoverKey)
Icon(