-
-
Save equationl/adb91edf141ac63ec62925534c784428 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.compose.foundation.gestures.awaitFirstDown | |
import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation | |
import androidx.compose.foundation.gestures.drag | |
import androidx.compose.foundation.gestures.forEachGesture | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.input.pointer.AwaitPointerEventScope | |
import androidx.compose.ui.input.pointer.PointerInputChange | |
import androidx.compose.ui.input.pointer.consumePositionChange | |
import androidx.compose.ui.input.pointer.pointerInput | |
import com.equationl.blurdraw.gesture.MotionEvent | |
/** | |
* @author SmartToolFactory | |
* | |
* from: https://github.com/SmartToolFactory/Compose-Drawing-App | |
* */ | |
suspend fun AwaitPointerEventScope.awaitDragMotionEvent( | |
onTouchEvent: (MotionEvent, PointerInputChange) -> Unit | |
) { | |
// Wait for at least one pointer to press down, and set first contact position | |
val down: PointerInputChange = awaitFirstDown() | |
onTouchEvent(MotionEvent.Down, down) | |
var pointer = down | |
// 🔥 Waits for drag threshold to be passed by pointer | |
// or it returns null if up event is triggered | |
val change: PointerInputChange? = | |
awaitTouchSlopOrCancellation(down.id) { change: PointerInputChange, over: Offset -> | |
// 🔥🔥 If consumePositionChange() is not consumed drag does not | |
// function properly. | |
// Consuming position change causes change.positionChanged() to return false. | |
change.consumePositionChange() | |
} | |
if (change != null) { | |
// 🔥 Calls awaitDragOrCancellation(pointer) in a while loop | |
drag(change.id) { pointerInputChange: PointerInputChange -> | |
pointer = pointerInputChange | |
onTouchEvent(MotionEvent.Move, pointer) | |
} | |
// All of the pointers are up | |
onTouchEvent(MotionEvent.Up, pointer) | |
} else { | |
// Drag threshold is not passed and last pointer is up | |
onTouchEvent(MotionEvent.Up, pointer) | |
} | |
} | |
fun Modifier.dragMotionEvent(onTouchEvent: (MotionEvent, PointerInputChange) -> Unit) = this.then( | |
Modifier.pointerInput(Unit) { | |
forEachGesture { | |
awaitPointerEventScope { | |
awaitDragMotionEvent(onTouchEvent) | |
} | |
} | |
} | |
) | |
suspend fun AwaitPointerEventScope.awaitDragMotionEvent( | |
onDragStart: (PointerInputChange) -> Unit = {}, | |
onDrag: (PointerInputChange) -> Unit = {}, | |
onDragEnd: (PointerInputChange) -> Unit = {} | |
) { | |
// Wait for at least one pointer to press down, and set first contact position | |
val down: PointerInputChange = awaitFirstDown() | |
onDragStart(down) | |
var pointer = down | |
// 🔥 Waits for drag threshold to be passed by pointer | |
// or it returns null if up event is triggered | |
val change: PointerInputChange? = | |
awaitTouchSlopOrCancellation(down.id) { change: PointerInputChange, over: Offset -> | |
// 🔥🔥 If consumePositionChange() is not consumed drag does not | |
// function properly. | |
// Consuming position change causes change.positionChanged() to return false. | |
change.consumePositionChange() | |
} | |
if (change != null) { | |
// 🔥 Calls awaitDragOrCancellation(pointer) in a while loop | |
drag(change.id) { pointerInputChange: PointerInputChange -> | |
pointer = pointerInputChange | |
onDrag(pointer) | |
} | |
// All of the pointers are up | |
onDragEnd(pointer) | |
} else { | |
// Drag threshold is not passed and last pointer is up | |
onDragEnd(pointer) | |
} | |
} | |
fun Modifier.dragMotionEvent( | |
onDragStart: (PointerInputChange) -> Unit = {}, | |
onDrag: (PointerInputChange) -> Unit = {}, | |
onDragEnd: (PointerInputChange) -> Unit = {} | |
) = this.then( | |
Modifier.pointerInput(Unit) { | |
forEachGesture { | |
awaitPointerEventScope { | |
awaitDragMotionEvent(onDragStart, onDrag, onDragEnd) | |
} | |
} | |
} | |
) | |
enum class MotionEvent { | |
Idle, Down, Move, Up | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.runtime.Composable | |
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.draw.blur | |
import androidx.compose.ui.draw.drawWithCache | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.graphics.BlendMode | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.graphics.Path | |
import androidx.compose.ui.graphics.drawscope.Stroke | |
import androidx.compose.ui.graphics.nativeCanvas | |
import androidx.compose.ui.input.pointer.consumeDownChange | |
import androidx.compose.ui.input.pointer.consumePositionChange | |
import androidx.compose.ui.unit.Dp | |
import androidx.compose.ui.unit.dp | |
import com.equationl.blurdraw.gesture.MotionEvent | |
import com.equationl.blurdraw.gesture.dragMotionEvent | |
@Composable | |
fun BlurView( | |
paintWidth: Dp = 15.dp, blurRadius: Dp = 10.dp, content: @Composable () -> Unit, | |
) { | |
var motionEvent by remember { mutableStateOf(MotionEvent.Idle) } | |
var currentPosition by remember { mutableStateOf(Offset.Unspecified) } | |
var previousPosition by remember { mutableStateOf(Offset.Unspecified) } | |
Box( | |
modifier = Modifier.dragMotionEvent( | |
onDragStart = { pointerInputChange -> | |
motionEvent = MotionEvent.Down | |
currentPosition = pointerInputChange.position | |
pointerInputChange.consumeDownChange() | |
}, | |
onDrag = { pointerInputChange -> | |
motionEvent = MotionEvent.Move | |
currentPosition = pointerInputChange.position | |
pointerInputChange.consumePositionChange() | |
}, | |
onDragEnd = { pointerInputChange -> | |
motionEvent = MotionEvent.Up | |
pointerInputChange.consumeDownChange() | |
}, | |
), | |
) { | |
Column { | |
content() | |
} | |
Column( | |
modifier = Modifier | |
.fillMaxSize() | |
.blur(blurRadius) | |
.drawWithCache { | |
val currentPath = Path() | |
onDrawWithContent { | |
with(drawContext.canvas.nativeCanvas) { | |
val checkPoint = saveLayer(null, null) | |
when (motionEvent) { | |
MotionEvent.Down -> { | |
currentPath.moveTo(currentPosition.x, currentPosition.y) | |
previousPosition = currentPosition | |
} | |
MotionEvent.Move -> { | |
currentPath.quadraticBezierTo( | |
previousPosition.x, | |
previousPosition.y, | |
(previousPosition.x + currentPosition.x) / 2, | |
(previousPosition.y + currentPosition.y) / 2 | |
) | |
previousPosition = currentPosition | |
} | |
MotionEvent.Up -> { | |
currentPath.lineTo(currentPosition.x, currentPosition.y) | |
currentPosition = Offset.Unspecified | |
previousPosition = currentPosition | |
motionEvent = MotionEvent.Idle | |
} | |
else -> Unit | |
} | |
drawContent() | |
drawPath( | |
currentPath, color = Color.White, alpha = 1f, | |
style = Stroke( | |
width = paintWidth.toPx(), | |
), | |
blendMode = BlendMode.DstOut, | |
) | |
restoreToCount(checkPoint) | |
} | |
} | |
}, | |
) { | |
content() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment