Fix gesture tracking: 1:1 rotation and drag, better zone detection
Rotation: - Use direct angle calculation between touch points (atan2) - Track initial touch angle and effect angle separately - Effect rotation now matches finger rotation exactly Position drag: - Remove all sensitivity dampening - 1:1 mapping: finger moves 100px, effect center moves 100px Gesture zones rebalanced: - Rotation: only very center of focus zone (< 30% of focus height) - Size adjustment: large area around effect (30% - 200%) - Camera zoom: only far outside the effect (> 200%) This prevents rotation from dominating size adjustments. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e53155e8ee
commit
2736d42e74
1 changed files with 48 additions and 30 deletions
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.Canvas
|
|||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.calculateCentroid
|
||||
import androidx.compose.foundation.gestures.calculateRotation
|
||||
import androidx.compose.foundation.gestures.calculateZoom
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -25,6 +24,7 @@ import androidx.compose.ui.input.pointer.pointerInput
|
|||
import androidx.compose.ui.unit.dp
|
||||
import no.naiv.tiltshift.effect.BlurParameters
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
|
|
@ -39,11 +39,16 @@ private enum class GestureType {
|
|||
PINCH_ZOOM // Pinch in center to zoom camera
|
||||
}
|
||||
|
||||
// Sensitivity factors for gesture controls (lower = less sensitive)
|
||||
// Rotation uses 1:1 tracking (no dampening) for natural feel
|
||||
private const val POSITION_SENSITIVITY = 0.5f // Drag to move focus center
|
||||
private const val SIZE_SENSITIVITY = 0.3f // Pinch to resize blur zone
|
||||
private const val ZOOM_SENSITIVITY = 0.5f // Pinch to zoom camera
|
||||
// Sensitivity factor for size pinch (lower = less sensitive)
|
||||
private const val SIZE_SENSITIVITY = 0.3f
|
||||
private const val ZOOM_SENSITIVITY = 0.5f
|
||||
|
||||
/**
|
||||
* Calculates the angle between two touch points.
|
||||
*/
|
||||
private fun angleBetweenPoints(p1: Offset, p2: Offset): Float {
|
||||
return atan2(p2.y - p1.y, p2.x - p1.x)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overlay that shows tilt-shift effect controls and handles gestures.
|
||||
|
|
@ -57,6 +62,7 @@ fun TiltShiftOverlay(
|
|||
) {
|
||||
var currentGesture by remember { mutableStateOf(GestureType.NONE) }
|
||||
var initialAngle by remember { mutableFloatStateOf(0f) }
|
||||
var initialTouchAngle by remember { mutableFloatStateOf(0f) }
|
||||
var initialSize by remember { mutableFloatStateOf(0.3f) }
|
||||
var initialPositionX by remember { mutableFloatStateOf(0.5f) }
|
||||
var initialPositionY by remember { mutableFloatStateOf(0.5f) }
|
||||
|
|
@ -70,10 +76,8 @@ fun TiltShiftOverlay(
|
|||
currentGesture = GestureType.NONE
|
||||
|
||||
var previousCentroid = firstDown.position
|
||||
var accumulatedRotation = 0f
|
||||
var accumulatedZoom = 1f
|
||||
var accumulatedDragX = 0f
|
||||
var accumulatedDragY = 0f
|
||||
var rotationInitialized = false
|
||||
|
||||
initialAngle = params.angle
|
||||
initialSize = params.size
|
||||
|
|
@ -95,7 +99,9 @@ fun TiltShiftOverlay(
|
|||
when {
|
||||
// Two or more fingers
|
||||
pointers.size >= 2 -> {
|
||||
val rotation = event.calculateRotation()
|
||||
val p1 = pointers[0].position
|
||||
val p2 = pointers[1].position
|
||||
val currentTouchAngle = angleBetweenPoints(p1, p2)
|
||||
val zoom = event.calculateZoom()
|
||||
|
||||
// Determine gesture type based on touch positions
|
||||
|
|
@ -106,17 +112,24 @@ fun TiltShiftOverlay(
|
|||
size.height.toFloat(),
|
||||
params
|
||||
)
|
||||
rotationInitialized = false
|
||||
}
|
||||
|
||||
when (currentGesture) {
|
||||
GestureType.ROTATE -> {
|
||||
// 1:1 rotation tracking - no dampening
|
||||
accumulatedRotation += rotation
|
||||
val newAngle = initialAngle + accumulatedRotation
|
||||
onParamsChange(params.copy(angle = newAngle))
|
||||
// Initialize rotation reference on first frame
|
||||
if (!rotationInitialized) {
|
||||
initialTouchAngle = currentTouchAngle
|
||||
initialAngle = params.angle
|
||||
rotationInitialized = true
|
||||
} else {
|
||||
// Direct angle mapping: effect angle = initial + (current touch angle - initial touch angle)
|
||||
val angleDelta = currentTouchAngle - initialTouchAngle
|
||||
val newAngle = initialAngle + angleDelta
|
||||
onParamsChange(params.copy(angle = newAngle))
|
||||
}
|
||||
}
|
||||
GestureType.PINCH_SIZE -> {
|
||||
// Apply dampening to size change
|
||||
val dampenedZoom = 1f + (zoom - 1f) * SIZE_SENSITIVITY
|
||||
accumulatedZoom *= dampenedZoom
|
||||
val newSize = (initialSize * accumulatedZoom)
|
||||
|
|
@ -124,7 +137,6 @@ fun TiltShiftOverlay(
|
|||
onParamsChange(params.copy(size = newSize))
|
||||
}
|
||||
GestureType.PINCH_ZOOM -> {
|
||||
// Apply dampening to camera zoom
|
||||
val dampenedZoom = 1f + (zoom - 1f) * ZOOM_SENSITIVITY
|
||||
onZoomChange(dampenedZoom)
|
||||
}
|
||||
|
|
@ -132,19 +144,22 @@ fun TiltShiftOverlay(
|
|||
}
|
||||
}
|
||||
|
||||
// Single finger - drag to move focus center (2D)
|
||||
// Single finger - drag to move focus center 1:1
|
||||
pointers.size == 1 -> {
|
||||
if (currentGesture == GestureType.NONE) {
|
||||
currentGesture = GestureType.DRAG_POSITION
|
||||
// Reset initial position at drag start
|
||||
initialPositionX = params.positionX
|
||||
initialPositionY = params.positionY
|
||||
previousCentroid = centroid
|
||||
}
|
||||
|
||||
if (currentGesture == GestureType.DRAG_POSITION) {
|
||||
// 1:1 drag - finger movement directly maps to position change
|
||||
val deltaX = (centroid.x - previousCentroid.x) / size.width
|
||||
val deltaY = (centroid.y - previousCentroid.y) / size.height
|
||||
accumulatedDragX += deltaX * POSITION_SENSITIVITY
|
||||
accumulatedDragY += deltaY * POSITION_SENSITIVITY
|
||||
val newX = (initialPositionX + accumulatedDragX).coerceIn(0f, 1f)
|
||||
val newY = (initialPositionY + accumulatedDragY).coerceIn(0f, 1f)
|
||||
val newX = (params.positionX + deltaX).coerceIn(0f, 1f)
|
||||
val newY = (params.positionY + deltaY).coerceIn(0f, 1f)
|
||||
onParamsChange(params.copy(positionX = newX, positionY = newY))
|
||||
}
|
||||
}
|
||||
|
|
@ -166,6 +181,11 @@ fun TiltShiftOverlay(
|
|||
|
||||
/**
|
||||
* Determines the type of two-finger gesture based on touch position.
|
||||
*
|
||||
* Zones (from center outward):
|
||||
* - Very center (< 30% of focus height): Rotation
|
||||
* - Near focus line (30% - 200% of focus height): Size adjustment
|
||||
* - Far outside (> 200%): Camera zoom
|
||||
*/
|
||||
private fun determineGestureType(
|
||||
centroid: Offset,
|
||||
|
|
@ -186,15 +206,15 @@ private fun determineGestureType(
|
|||
val distFromCenter = kotlin.math.abs(rotatedY)
|
||||
|
||||
return when {
|
||||
// Near the edges of the blur zone -> size adjustment
|
||||
distFromCenter > focusHalfHeight * 0.7f && distFromCenter < focusHalfHeight * 1.5f -> {
|
||||
GestureType.PINCH_SIZE
|
||||
}
|
||||
// Inside the focus zone -> rotation
|
||||
distFromCenter < focusHalfHeight * 0.7f -> {
|
||||
// Very center of focus zone -> rotation (small area)
|
||||
distFromCenter < focusHalfHeight * 0.3f -> {
|
||||
GestureType.ROTATE
|
||||
}
|
||||
// Outside -> camera zoom
|
||||
// Anywhere near the blur effect -> size adjustment (large area)
|
||||
distFromCenter < focusHalfHeight * 2.0f -> {
|
||||
GestureType.PINCH_SIZE
|
||||
}
|
||||
// Far outside -> camera zoom
|
||||
else -> {
|
||||
GestureType.PINCH_ZOOM
|
||||
}
|
||||
|
|
@ -225,13 +245,11 @@ private fun DrawScope.drawTiltShiftOverlay(params: BlurParameters) {
|
|||
|
||||
rotate(angleDegrees, pivot = Offset(centerX, centerY)) {
|
||||
// Draw blur zone indicators (top and bottom) - extended horizontally
|
||||
// Top blur zone: from far above to the top edge of focus area
|
||||
drawRect(
|
||||
color = blurZoneColor,
|
||||
topLeft = Offset(centerX - extendedHalf, centerY - focusHalfHeight - extendedHalf),
|
||||
size = androidx.compose.ui.geometry.Size(extendedHalf * 2, extendedHalf)
|
||||
)
|
||||
// Bottom blur zone: from bottom edge of focus area to far below
|
||||
drawRect(
|
||||
color = blurZoneColor,
|
||||
topLeft = Offset(centerX - extendedHalf, centerY + focusHalfHeight),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue