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:
parent
acdb04f39d
commit
5567957475
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user