Auto commit Fri Mar 20 08:01:02 PM CDT 2026

This commit is contained in:
Tretzi
2026-03-20 20:01:02 -05:00
parent a359a5e191
commit e2cd1ff4df

View File

@@ -7,7 +7,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.calculateCentroid import androidx.compose.foundation.gestures.calculateCentroid
import androidx.compose.foundation.gestures.calculatePan import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.gestures.calculateZoom import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -258,60 +258,36 @@ internal fun TimelineGraph(
.fillMaxWidth() .fillMaxWidth()
.height(190.dp) .height(190.dp)
.background(Color(0xFF0A0E18), RoundedCornerShape(10.dp)) .background(Color(0xFF0A0E18), RoundedCornerShape(10.dp))
.pointerInput(activeCurve, viewport, curveLeft, curveRight) { .pointerInput(viewport, selection, curveLeft, curveRight, activeCurve) {
awaitPointerEventScope { awaitPointerEventScope {
while (true) { while (true) {
val event = awaitPointerEvent(pass = PointerEventPass.Initial) val down = awaitFirstDown(requireUnconsumed = false)
val pressedCount = event.changes.count { it.pressed }
if (pressedCount < 2) continue
val zoom = event.calculateZoom()
val pan = event.calculatePan()
val centroid = event.calculateCentroid(useCurrent = true)
if (zoom != 1f || pan.x != 0f || pan.y != 0f) {
val newViewport = viewport.applyZoomPan(
zoomChange = zoom,
panPx = pan.x,
widthPx = size.width.toFloat(),
centroidPx = centroid.x,
)
onViewportChanged(newViewport)
}
// Reserve all 2+ finger gestures for transform handling so they don't
// compete with one-finger point/handle drags.
event.changes.forEach { it.consume() }
draggingPointIndex = null
draggingCurveId = null
dragMode = HandleDrag.NONE
}
}
}
.pointerInput(viewport, selection, curveLeft, curveRight, activeCurve) {
detectDragGestures(
onDragStart = { start ->
val w = size.width.toFloat() val w = size.width.toFloat()
val h = size.height.toFloat() val h = size.height.toFloat()
val startX = viewport.timeToX(selection.startSec, w) val startX = viewport.timeToX(selection.startSec, w)
val endX = viewport.timeToX(selection.endSec, w) val endX = viewport.timeToX(selection.endSec, w)
val hitPx = 32f val hitPx = 32f
var activePointerId = down.id
var lastPos = down.position
var isTransform = false
var moved = false
val hits = buildList { val hits = buildList {
curveLeft.hitTestPoint(start, viewport, w, h)?.let { idx -> curveLeft.hitTestPoint(down.position, viewport, w, h)?.let { idx ->
val point = curveLeft.points[idx] val point = curveLeft.points[idx]
val x = viewport.timeToX(point.tSec, w) val x = viewport.timeToX(point.tSec, w)
val y = valueToY(point.value01, h) val y = valueToY(point.value01, h)
val dx = start.x - x val dx = down.position.x - x
val dy = start.y - y val dy = down.position.y - y
add(Triple(leftId, idx, dx * dx + dy * dy)) add(Triple(leftId, idx, dx * dx + dy * dy))
} }
curveRight.hitTestPoint(start, viewport, w, h)?.let { idx -> curveRight.hitTestPoint(down.position, viewport, w, h)?.let { idx ->
val point = curveRight.points[idx] val point = curveRight.points[idx]
val x = viewport.timeToX(point.tSec, w) val x = viewport.timeToX(point.tSec, w)
val y = valueToY(point.value01, h) val y = valueToY(point.value01, h)
val dx = start.x - x val dx = down.position.x - x
val dy = start.y - y val dy = down.position.y - y
add(Triple(rightId, idx, dx * dx + dy * dy)) add(Triple(rightId, idx, dx * dx + dy * dy))
} }
} }
@@ -323,63 +299,97 @@ internal fun TimelineGraph(
dragMode = HandleDrag.NONE dragMode = HandleDrag.NONE
onTapSideSelect(if (nearestHit.first == leftId) Side.LEFT else Side.RIGHT) onTapSideSelect(if (nearestHit.first == leftId) Side.LEFT else Side.RIGHT)
} else { } else {
draggingPointIndex = null
draggingCurveId = null
dragMode = when { dragMode = when {
abs(start.x - startX) <= hitPx -> HandleDrag.START abs(down.position.x - startX) <= hitPx -> HandleDrag.START
abs(start.x - endX) <= hitPx -> HandleDrag.END abs(down.position.x - endX) <= hitPx -> HandleDrag.END
else -> HandleDrag.NONE else -> HandleDrag.NONE
} }
draggingPointIndex = null
draggingCurveId = null
} }
},
onDragEnd = { while (true) {
val event = awaitPointerEvent(pass = PointerEventPass.Initial)
val pressed = event.changes.filter { it.pressed }
if (pressed.isEmpty()) break
if (pressed.size >= 2) {
isTransform = true
draggingPointIndex = null draggingPointIndex = null
draggingCurveId = null draggingCurveId = null
dragMode = HandleDrag.NONE dragMode = HandleDrag.NONE
}, }
onDragCancel = {
draggingPointIndex = null if (isTransform) {
draggingCurveId = null val zoom = event.calculateZoom()
dragMode = HandleDrag.NONE val pan = event.calculatePan()
}, val centroid = event.calculateCentroid(useCurrent = true)
onDrag = { change, dragAmount -> if (zoom != 1f || pan.x != 0f || pan.y != 0f) {
change.consume() moved = true
val w = size.width.toFloat() val newViewport = viewport.applyZoomPan(
val h = size.height.toFloat() zoomChange = zoom,
panPx = pan.x,
widthPx = size.width.toFloat(),
centroidPx = centroid.x,
)
onViewportChanged(newViewport)
}
event.changes.forEach { it.consume() }
continue
}
val primary = pressed.firstOrNull { it.id == activePointerId } ?: pressed.first()
activePointerId = primary.id
val delta = primary.position - lastPos
lastPos = primary.position
if (delta.getDistanceSquared() > 0.5f) moved = true
val pointIndex = draggingPointIndex val pointIndex = draggingPointIndex
val curveId = draggingCurveId val curveId = draggingCurveId
if (pointIndex != null && curveId != null) { if (pointIndex != null && curveId != null) {
val targetCurve = if (curveId == leftId) curveLeft else curveRight val targetCurve = if (curveId == leftId) curveLeft else curveRight
val updated = targetCurve.movePointVertical(pointIndex, change.position.y, h) val updated = targetCurve.movePointVertical(pointIndex, primary.position.y, h)
onUpdateCurve(curveId, updated) onUpdateCurve(curveId, updated)
onCurveEditedHaptic() onCurveEditedHaptic()
return@detectDragGestures primary.consume()
continue
} }
val t = viewport.xToTimeSec(change.position.x, w).toInt().coerceAtLeast(0) val t = viewport.xToTimeSec(primary.position.x, w).toInt().coerceAtLeast(0)
when (dragMode) { when (dragMode) {
HandleDrag.START -> onSelectionChanged(TimelineSelection(startSec = min(t, selection.endSec - 60), endSec = selection.endSec)) HandleDrag.START -> {
HandleDrag.END -> onSelectionChanged(TimelineSelection(startSec = selection.startSec, endSec = max(t, selection.startSec + 60))) onSelectionChanged(TimelineSelection(startSec = min(t, selection.endSec - 60), endSec = selection.endSec))
primary.consume()
}
HandleDrag.END -> {
onSelectionChanged(TimelineSelection(startSec = selection.startSec, endSec = max(t, selection.startSec + 60)))
primary.consume()
}
HandleDrag.NONE -> { HandleDrag.NONE -> {
if (delta.x != 0f) {
val newViewport = viewport.applyZoomPan( val newViewport = viewport.applyZoomPan(
zoomChange = 1f, zoomChange = 1f,
panPx = dragAmount.x, panPx = delta.x,
widthPx = w, widthPx = w,
centroidPx = w / 2f, centroidPx = w / 2f,
) )
onViewportChanged(newViewport) onViewportChanged(newViewport)
primary.consume()
} }
} }
} }
)
} }
.pointerInput(activeCurve) {
detectTapGestures( if (!moved && !isTransform) {
onTap = { offset -> val side = if (down.position.x < size.width / 2f) Side.LEFT else Side.RIGHT
val side = if (offset.x < size.width / 2f) Side.LEFT else Side.RIGHT
onTapSideSelect(side) onTapSideSelect(side)
} }
) draggingPointIndex = null
draggingCurveId = null
dragMode = HandleDrag.NONE
}
}
} }
) { ) {
Canvas(modifier = Modifier.fillMaxSize()) { Canvas(modifier = Modifier.fillMaxSize()) {