Fix state persistence, drag tracking, and shader rotation sync
State persistence: - Use rememberUpdatedState to always get latest params inside pointerInput - Capture gestureStartParams at beginning of each gesture - All adjustments now use initial values + accumulated change Drag tracking: - Track initialDragCentroid at drag start - Calculate total drag offset from initial point (not frame-by-frame) - Drag now properly moves focus center 1:1 Shader rotation sync: - Adjust angle by -90° in shader to compensate for portrait texture rotation - Preview blur effect now rotates in sync with overlay UI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2736d42e74
commit
e8a5fa4811
2 changed files with 36 additions and 35 deletions
|
|
@ -11,6 +11,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
|
@ -60,12 +61,12 @@ fun TiltShiftOverlay(
|
||||||
onZoomChange: (Float) -> Unit,
|
onZoomChange: (Float) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
// Use rememberUpdatedState to always get latest values inside pointerInput
|
||||||
|
val currentParams by rememberUpdatedState(params)
|
||||||
|
val currentOnParamsChange by rememberUpdatedState(onParamsChange)
|
||||||
|
val currentOnZoomChange by rememberUpdatedState(onZoomChange)
|
||||||
|
|
||||||
var currentGesture by remember { mutableStateOf(GestureType.NONE) }
|
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) }
|
|
||||||
|
|
||||||
Canvas(
|
Canvas(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -75,14 +76,15 @@ fun TiltShiftOverlay(
|
||||||
val firstDown = awaitFirstDown(requireUnconsumed = false)
|
val firstDown = awaitFirstDown(requireUnconsumed = false)
|
||||||
currentGesture = GestureType.NONE
|
currentGesture = GestureType.NONE
|
||||||
|
|
||||||
var previousCentroid = firstDown.position
|
// Capture initial state at gesture start
|
||||||
|
val gestureStartParams = currentParams
|
||||||
|
var initialTouchAngle = 0f
|
||||||
|
var initialDragCentroid = firstDown.position
|
||||||
var accumulatedZoom = 1f
|
var accumulatedZoom = 1f
|
||||||
var rotationInitialized = false
|
var rotationInitialized = false
|
||||||
|
var dragInitialized = false
|
||||||
|
|
||||||
initialAngle = params.angle
|
var previousCentroid = firstDown.position
|
||||||
initialSize = params.size
|
|
||||||
initialPositionX = params.positionX
|
|
||||||
initialPositionY = params.positionY
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
val event = awaitPointerEvent()
|
val event = awaitPointerEvent()
|
||||||
|
|
@ -110,35 +112,32 @@ fun TiltShiftOverlay(
|
||||||
centroid,
|
centroid,
|
||||||
size.width.toFloat(),
|
size.width.toFloat(),
|
||||||
size.height.toFloat(),
|
size.height.toFloat(),
|
||||||
params
|
currentParams
|
||||||
)
|
)
|
||||||
rotationInitialized = false
|
rotationInitialized = false
|
||||||
}
|
}
|
||||||
|
|
||||||
when (currentGesture) {
|
when (currentGesture) {
|
||||||
GestureType.ROTATE -> {
|
GestureType.ROTATE -> {
|
||||||
// Initialize rotation reference on first frame
|
|
||||||
if (!rotationInitialized) {
|
if (!rotationInitialized) {
|
||||||
initialTouchAngle = currentTouchAngle
|
initialTouchAngle = currentTouchAngle
|
||||||
initialAngle = params.angle
|
|
||||||
rotationInitialized = true
|
rotationInitialized = true
|
||||||
} else {
|
} else {
|
||||||
// Direct angle mapping: effect angle = initial + (current touch angle - initial touch angle)
|
|
||||||
val angleDelta = currentTouchAngle - initialTouchAngle
|
val angleDelta = currentTouchAngle - initialTouchAngle
|
||||||
val newAngle = initialAngle + angleDelta
|
val newAngle = gestureStartParams.angle + angleDelta
|
||||||
onParamsChange(params.copy(angle = newAngle))
|
currentOnParamsChange(currentParams.copy(angle = newAngle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GestureType.PINCH_SIZE -> {
|
GestureType.PINCH_SIZE -> {
|
||||||
val dampenedZoom = 1f + (zoom - 1f) * SIZE_SENSITIVITY
|
val dampenedZoom = 1f + (zoom - 1f) * SIZE_SENSITIVITY
|
||||||
accumulatedZoom *= dampenedZoom
|
accumulatedZoom *= dampenedZoom
|
||||||
val newSize = (initialSize * accumulatedZoom)
|
val newSize = (gestureStartParams.size * accumulatedZoom)
|
||||||
.coerceIn(BlurParameters.MIN_SIZE, BlurParameters.MAX_SIZE)
|
.coerceIn(BlurParameters.MIN_SIZE, BlurParameters.MAX_SIZE)
|
||||||
onParamsChange(params.copy(size = newSize))
|
currentOnParamsChange(currentParams.copy(size = newSize))
|
||||||
}
|
}
|
||||||
GestureType.PINCH_ZOOM -> {
|
GestureType.PINCH_ZOOM -> {
|
||||||
val dampenedZoom = 1f + (zoom - 1f) * ZOOM_SENSITIVITY
|
val dampenedZoom = 1f + (zoom - 1f) * ZOOM_SENSITIVITY
|
||||||
onZoomChange(dampenedZoom)
|
currentOnZoomChange(dampenedZoom)
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
@ -148,26 +147,26 @@ fun TiltShiftOverlay(
|
||||||
pointers.size == 1 -> {
|
pointers.size == 1 -> {
|
||||||
if (currentGesture == GestureType.NONE) {
|
if (currentGesture == GestureType.NONE) {
|
||||||
currentGesture = GestureType.DRAG_POSITION
|
currentGesture = GestureType.DRAG_POSITION
|
||||||
// Reset initial position at drag start
|
dragInitialized = false
|
||||||
initialPositionX = params.positionX
|
|
||||||
initialPositionY = params.positionY
|
|
||||||
previousCentroid = centroid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentGesture == GestureType.DRAG_POSITION) {
|
if (currentGesture == GestureType.DRAG_POSITION) {
|
||||||
// 1:1 drag - finger movement directly maps to position change
|
if (!dragInitialized) {
|
||||||
val deltaX = (centroid.x - previousCentroid.x) / size.width
|
initialDragCentroid = centroid
|
||||||
val deltaY = (centroid.y - previousCentroid.y) / size.height
|
dragInitialized = true
|
||||||
val newX = (params.positionX + deltaX).coerceIn(0f, 1f)
|
} else {
|
||||||
val newY = (params.positionY + deltaY).coerceIn(0f, 1f)
|
// Calculate total drag from initial touch point
|
||||||
onParamsChange(params.copy(positionX = newX, positionY = newY))
|
val totalDragX = (centroid.x - initialDragCentroid.x) / size.width
|
||||||
|
val totalDragY = (centroid.y - initialDragCentroid.y) / size.height
|
||||||
|
val newX = (gestureStartParams.positionX + totalDragX).coerceIn(0f, 1f)
|
||||||
|
val newY = (gestureStartParams.positionY + totalDragY).coerceIn(0f, 1f)
|
||||||
|
currentOnParamsChange(currentParams.copy(positionX = newX, positionY = newY))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previousCentroid = centroid
|
previousCentroid = centroid
|
||||||
|
|
||||||
// Consume all pointer changes
|
|
||||||
pointers.forEach { it.consume() }
|
pointers.forEach { it.consume() }
|
||||||
} while (event.type != PointerEventType.Release)
|
} while (event.type != PointerEventType.Release)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,10 @@ float focusDistance(vec2 uv) {
|
||||||
vec2 center = vec2(uPositionX, uPositionY);
|
vec2 center = vec2(uPositionX, uPositionY);
|
||||||
vec2 offset = uv - center;
|
vec2 offset = uv - center;
|
||||||
|
|
||||||
float cosA = cos(uAngle);
|
// Adjust angle by -90 degrees to compensate for portrait texture rotation
|
||||||
float sinA = sin(uAngle);
|
float adjustedAngle = uAngle - 1.5707963;
|
||||||
|
float cosA = cos(adjustedAngle);
|
||||||
|
float sinA = sin(adjustedAngle);
|
||||||
|
|
||||||
// After rotation, measure perpendicular distance from center line
|
// After rotation, measure perpendicular distance from center line
|
||||||
float rotatedY = -offset.x * sinA + offset.y * cosA;
|
float rotatedY = -offset.x * sinA + offset.y * cosA;
|
||||||
|
|
@ -70,8 +72,8 @@ vec4 sampleBlurred(vec2 uv, float blur) {
|
||||||
vec4 color = vec4(0.0);
|
vec4 color = vec4(0.0);
|
||||||
vec2 texelSize = 1.0 / uResolution;
|
vec2 texelSize = 1.0 / uResolution;
|
||||||
|
|
||||||
// Blur direction perpendicular to focus line
|
// Blur direction perpendicular to focus line (adjusted for portrait texture rotation)
|
||||||
float blurAngle = uAngle + 1.5707963; // +90 degrees
|
float blurAngle = uAngle; // Already perpendicular after -90 adjustment in focusDistance
|
||||||
vec2 blurDir = vec2(cos(blurAngle), sin(blurAngle));
|
vec2 blurDir = vec2(cos(blurAngle), sin(blurAngle));
|
||||||
|
|
||||||
// Scale blur radius by blur amount
|
// Scale blur radius by blur amount
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue