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

Add data to draggable

This commit is contained in:
Allan Wang 2023-06-21 22:52:20 -07:00
parent 5567957475
commit a1bf575da1
No known key found for this signature in database
GPG Key ID: C93E3F9C679D7A56
3 changed files with 58 additions and 43 deletions

View File

@ -38,9 +38,9 @@ import com.pitchedapps.frost.ext.toIntOffset
*/ */
@Composable @Composable
fun DragContainer( fun <T> DragContainer(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
draggableState: DraggableState, draggableState: DraggableState<T>,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
Box(modifier = modifier) { Box(modifier = modifier) {
@ -61,26 +61,28 @@ fun DragContainer(
* dragged. * dragged.
*/ */
@Composable @Composable
fun DragTarget( fun <T> DragTarget(
key: String = "", key: String = "",
draggableState: DraggableState, data: T,
content: @Composable (isDragging: Boolean) -> Unit draggableState: DraggableState<T>,
content: DraggableComposeContent,
) { ) {
val dragTargetState = val dragTargetState =
draggableState.rememberDragTarget( draggableState.rememberDragTarget(
key = key, key = key,
data = data,
content = content, content = content,
) )
Box( Box(
modifier = Modifier.dragTarget(dragTargetState), modifier = Modifier.dragTarget(dragTargetState),
) { ) {
content(false) content(isDragging = false)
} }
} }
private fun Modifier.dragTarget(dragTargetState: DragTargetState): Modifier { private fun <T> Modifier.dragTarget(dragTargetState: DragTargetState<T>): Modifier {
return onGloballyPositioned { return onGloballyPositioned {
dragTargetState.windowPosition = it.positionInWindow() dragTargetState.windowPosition = it.positionInWindow()
dragTargetState.size = it.size dragTargetState.size = it.size
@ -111,7 +113,7 @@ private fun Modifier.dragTarget(dragTargetState: DragTargetState): Modifier {
.alpha(if (dragTargetState.isDragging) 0f else 1f) .alpha(if (dragTargetState.isDragging) 0f else 1f)
} }
fun Modifier.dropTarget(dropTargetState: DropTargetState): Modifier { fun <T> Modifier.dropTarget(dropTargetState: DropTargetState<T>): Modifier {
return onGloballyPositioned { dropTargetState.bounds = it.boundsInWindow() } return onGloballyPositioned { dropTargetState.bounds = it.boundsInWindow() }
} }
@ -125,14 +127,14 @@ fun Modifier.dropTarget(dropTargetState: DropTargetState): Modifier {
* fillMaxWidth in a grid, but would have a full parent width here without the sizing constraints. * fillMaxWidth in a grid, but would have a full parent width here without the sizing constraints.
*/ */
@Composable @Composable
private fun DraggingContents(draggableState: DraggableState) { private fun <T> DraggingContents(draggableState: DraggableState<T>) {
for (target in draggableState.targets) { for (target in draggableState.targets) {
DraggingContent(target = target) DraggingContent(target = target)
} }
} }
@Composable @Composable
private fun DraggingContent(target: DragTargetState) { private fun <T> DraggingContent(target: DragTargetState<T>) {
val density = LocalDensity.current val density = LocalDensity.current
Box( Box(
modifier = modifier =

View File

@ -28,13 +28,15 @@ import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
@Composable @Composable
fun rememberDraggableState(): DraggableState { fun <T> rememberDraggableState(): DraggableState<T> {
return remember { DraggableStateImpl() } return remember { DraggableStateImpl() }
} }
interface DraggableState { typealias DraggableComposeContent = @Composable (isDragging: Boolean) -> Unit
val targets: Collection<DragTargetState> interface DraggableState<T> {
val targets: Collection<DragTargetState<T>>
/** /**
* Being drag for target [key]. * Being drag for target [key].
@ -44,30 +46,27 @@ interface DraggableState {
* Returns true if the request is accepted. It is the caller's responsibility to not propagate * Returns true if the request is accepted. It is the caller's responsibility to not propagate
* drag events if the request is denied. * drag events if the request is denied.
*/ */
fun onDragStart(key: String, dragTargetState: DragTargetState): Boolean fun onDragStart(key: String, dragTargetState: DragTargetState<T>): Boolean
fun onDrag(key: String, offset: Offset) fun onDrag(key: String, offset: Offset)
fun onDragEnd(key: String) fun onDragEnd(key: String)
@Composable @Composable
fun rememberDragTarget( fun rememberDragTarget(key: String, data: T, content: DraggableComposeContent): DragTargetState<T>
key: String,
content: @Composable (isDragging: Boolean) -> Unit
): DragTargetState
@Composable fun rememberDropTarget(key: String): DropTargetState @Composable fun rememberDropTarget(key: String): DropTargetState<T>
} }
class DraggableStateImpl : DraggableState { class DraggableStateImpl<T> : DraggableState<T> {
private val activeDragTargets = mutableStateMapOf<String, DragTargetState>() private val activeDragTargets = mutableStateMapOf<String, DragTargetState<T>>()
private val dropTargets = mutableStateMapOf<String, DropTargetState>() private val dropTargets = mutableStateMapOf<String, DropTargetState<T>>()
override val targets: Collection<DragTargetState> override val targets: Collection<DragTargetState<T>>
get() = activeDragTargets.values get() = activeDragTargets.values
override fun onDragStart(key: String, dragTargetState: DragTargetState): Boolean { override fun onDragStart(key: String, dragTargetState: DragTargetState<T>): Boolean {
if (key in activeDragTargets) return false if (key in activeDragTargets) return false
activeDragTargets[key] = dragTargetState activeDragTargets[key] = dragTargetState
return true return true
@ -93,18 +92,19 @@ class DraggableStateImpl : DraggableState {
@Composable @Composable
override fun rememberDragTarget( override fun rememberDragTarget(
key: String, key: String,
content: @Composable (isDragging: Boolean) -> Unit data: T,
): DragTargetState { content: DraggableComposeContent,
): DragTargetState<T> {
val target = val target =
remember(key, content, this) { remember(key, data, content, this) {
DragTargetState(key = key, draggableState = this, composable = content) DragTargetState(key = key, data = data, draggableState = this, composable = content)
} }
DisposableEffect(target) { onDispose { activeDragTargets.remove(key) } } DisposableEffect(target) { onDispose { activeDragTargets.remove(key) } }
return target return target
} }
@Composable @Composable
override fun rememberDropTarget(key: String): DropTargetState { override fun rememberDropTarget(key: String): DropTargetState<T> {
val target = remember(key, this) { DropTargetState(key, this) } val target = remember(key, this) { DropTargetState(key, this) }
DisposableEffect(target) { DisposableEffect(target) {
dropTargets[key] = target dropTargets[key] = target
@ -116,11 +116,14 @@ class DraggableStateImpl : DraggableState {
private fun setHover(dragKey: String?, dropKey: String) { private fun setHover(dragKey: String?, dropKey: String) {
val dropTarget = dropTargets[dropKey] ?: return val dropTarget = dropTargets[dropKey] ?: return
dropTarget.hoverKey = dragKey // Safety check; we only want to register active keys
val dragTarget = if (dragKey != null) activeDragTargets[dragKey] else null
dropTarget.hoverKey = dragTarget?.key
dropTarget.hoverData = dragTarget?.data
} }
/** Returns true if drag target exists and is within bounds */ /** Returns true if drag target exists and is within bounds */
private fun DropTargetState.hasValidDragTarget(): Boolean { private fun DropTargetState<T>.hasValidDragTarget(): Boolean {
val currentKey = hoverKey ?: return false // no target val currentKey = hoverKey ?: return false // no target
val dragTarget = activeDragTargets[currentKey] ?: return false // target not valid val dragTarget = activeDragTargets[currentKey] ?: return false // target not valid
return dragTarget.within(bounds) return dragTarget.within(bounds)
@ -152,17 +155,18 @@ class DraggableStateImpl : DraggableState {
} }
} }
private fun DragTargetState?.within(bounds: Rect): Boolean { private fun DragTargetState<*>?.within(bounds: Rect): Boolean {
if (this == null) return false if (this == null) return false
val center = dragPosition + Offset(size.width * 0.5f, size.height * 0.5f) val center = dragPosition + Offset(size.width * 0.5f, size.height * 0.5f)
return bounds.contains(center) return bounds.contains(center)
} }
/** State for individual dragging target. */ /** State for individual dragging target. */
class DragTargetState( class DragTargetState<T>(
val key: String, val key: String,
val draggableState: DraggableStateImpl, val data: T,
val composable: @Composable (isDragging: Boolean) -> Unit val draggableState: DraggableStateImpl<T>,
val composable: DraggableComposeContent,
) { ) {
var isDragging by mutableStateOf(false) var isDragging by mutableStateOf(false)
var windowPosition = Offset.Zero var windowPosition = Offset.Zero
@ -170,12 +174,18 @@ class DragTargetState(
var size: IntSize by mutableStateOf(IntSize.Zero) var size: IntSize by mutableStateOf(IntSize.Zero)
} }
class DropTargetState(private val key: String, private val draggableState: DraggableStateImpl) { class DropTargetState<T>(
private val key: String,
private val draggableState: DraggableStateImpl<T>
) {
var hoverKey: String? by mutableStateOf(null) var hoverKey: String? by mutableStateOf(null)
var hoverData: String? by mutableStateOf(null) var hoverData: T? by mutableStateOf(null)
var bounds: Rect = Rect.Zero var bounds: Rect = Rect.Zero
set(value) { set(value) {
field = value field = value
draggableState.checkForDrop(key) draggableState.checkForDrop(key)
} }
val isHovered
get() = hoverKey != null
} }

View File

@ -87,7 +87,7 @@ fun TabSelector(
unselected: List<TabData>, unselected: List<TabData>,
onSelect: (List<TabData>) -> Unit onSelect: (List<TabData>) -> Unit
) { ) {
val draggableState = rememberDraggableState() val draggableState = rememberDraggableState<TabData>()
DragContainer(modifier = modifier, draggableState = draggableState) { DragContainer(modifier = modifier, draggableState = draggableState) {
Column(modifier = Modifier.statusBarsPadding()) { Column(modifier = Modifier.statusBarsPadding()) {
@ -96,7 +96,7 @@ fun TabSelector(
columns = GridCells.Fixed(4), columns = GridCells.Fixed(4),
) { ) {
items(unselected, key = { it.key }) { items(unselected, key = { it.key }) {
DragTarget(key = it.key, draggableState = draggableState) { isDragging -> DragTarget(key = it.key, data = it, draggableState = draggableState) { isDragging ->
TabItem( TabItem(
modifier = modifier =
Modifier.thenIf(!isDragging) { Modifier.thenIf(!isDragging) {
@ -124,7 +124,7 @@ fun TabSelector(
@Composable @Composable
fun TabBottomBar( fun TabBottomBar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
draggableState: DraggableState, draggableState: DraggableState<TabData>,
items: List<TabData> items: List<TabData>
) { ) {
NavigationBar(modifier = modifier) { NavigationBar(modifier = modifier) {
@ -133,7 +133,7 @@ fun TabBottomBar(
val alpha by val alpha by
animateFloatAsState( animateFloatAsState(
targetValue = if (dropTargetState.hoverKey == null) 1f else 0f, targetValue = if (!dropTargetState.isHovered) 1f else 0.3f,
label = "Nav Item Alpha", label = "Nav Item Alpha",
) )
@ -141,10 +141,13 @@ fun TabBottomBar(
modifier = Modifier.dropTarget(dropTargetState), modifier = Modifier.dropTarget(dropTargetState),
icon = { icon = {
// println(dropTargetState.hoverKey) // println(dropTargetState.hoverKey)
val iconItem = dropTargetState.hoverData ?: item
Icon( Icon(
modifier = Modifier.size(24.dp).alpha(alpha), modifier = Modifier.size(24.dp).alpha(alpha),
imageVector = item.icon, imageVector = iconItem.icon,
contentDescription = item.title, contentDescription = iconItem.title,
) )
}, },
selected = false, selected = false,