Auto commit Fri Mar 20 08:01:02 PM CDT 2026
This commit is contained in:
@@ -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,65 +299,99 @@ 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 {
|
||||||
dragMode = when {
|
|
||||||
abs(start.x - startX) <= hitPx -> HandleDrag.START
|
|
||||||
abs(start.x - endX) <= hitPx -> HandleDrag.END
|
|
||||||
else -> HandleDrag.NONE
|
|
||||||
}
|
|
||||||
draggingPointIndex = null
|
draggingPointIndex = null
|
||||||
draggingCurveId = null
|
draggingCurveId = null
|
||||||
}
|
dragMode = when {
|
||||||
},
|
abs(down.position.x - startX) <= hitPx -> HandleDrag.START
|
||||||
onDragEnd = {
|
abs(down.position.x - endX) <= hitPx -> HandleDrag.END
|
||||||
draggingPointIndex = null
|
else -> HandleDrag.NONE
|
||||||
draggingCurveId = null
|
|
||||||
dragMode = HandleDrag.NONE
|
|
||||||
},
|
|
||||||
onDragCancel = {
|
|
||||||
draggingPointIndex = null
|
|
||||||
draggingCurveId = null
|
|
||||||
dragMode = HandleDrag.NONE
|
|
||||||
},
|
|
||||||
onDrag = { change, dragAmount ->
|
|
||||||
change.consume()
|
|
||||||
val w = size.width.toFloat()
|
|
||||||
val h = size.height.toFloat()
|
|
||||||
val pointIndex = draggingPointIndex
|
|
||||||
val curveId = draggingCurveId
|
|
||||||
if (pointIndex != null && curveId != null) {
|
|
||||||
val targetCurve = if (curveId == leftId) curveLeft else curveRight
|
|
||||||
val updated = targetCurve.movePointVertical(pointIndex, change.position.y, h)
|
|
||||||
onUpdateCurve(curveId, updated)
|
|
||||||
onCurveEditedHaptic()
|
|
||||||
return@detectDragGestures
|
|
||||||
}
|
|
||||||
|
|
||||||
val t = viewport.xToTimeSec(change.position.x, w).toInt().coerceAtLeast(0)
|
|
||||||
when (dragMode) {
|
|
||||||
HandleDrag.START -> onSelectionChanged(TimelineSelection(startSec = min(t, selection.endSec - 60), endSec = selection.endSec))
|
|
||||||
HandleDrag.END -> onSelectionChanged(TimelineSelection(startSec = selection.startSec, endSec = max(t, selection.startSec + 60)))
|
|
||||||
HandleDrag.NONE -> {
|
|
||||||
val newViewport = viewport.applyZoomPan(
|
|
||||||
zoomChange = 1f,
|
|
||||||
panPx = dragAmount.x,
|
|
||||||
widthPx = w,
|
|
||||||
centroidPx = w / 2f,
|
|
||||||
)
|
|
||||||
onViewportChanged(newViewport)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
draggingCurveId = null
|
||||||
|
dragMode = HandleDrag.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTransform) {
|
||||||
|
val zoom = event.calculateZoom()
|
||||||
|
val pan = event.calculatePan()
|
||||||
|
val centroid = event.calculateCentroid(useCurrent = true)
|
||||||
|
if (zoom != 1f || pan.x != 0f || pan.y != 0f) {
|
||||||
|
moved = true
|
||||||
|
val newViewport = viewport.applyZoomPan(
|
||||||
|
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 curveId = draggingCurveId
|
||||||
|
if (pointIndex != null && curveId != null) {
|
||||||
|
val targetCurve = if (curveId == leftId) curveLeft else curveRight
|
||||||
|
val updated = targetCurve.movePointVertical(pointIndex, primary.position.y, h)
|
||||||
|
onUpdateCurve(curveId, updated)
|
||||||
|
onCurveEditedHaptic()
|
||||||
|
primary.consume()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val t = viewport.xToTimeSec(primary.position.x, w).toInt().coerceAtLeast(0)
|
||||||
|
when (dragMode) {
|
||||||
|
HandleDrag.START -> {
|
||||||
|
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 -> {
|
||||||
|
if (delta.x != 0f) {
|
||||||
|
val newViewport = viewport.applyZoomPan(
|
||||||
|
zoomChange = 1f,
|
||||||
|
panPx = delta.x,
|
||||||
|
widthPx = w,
|
||||||
|
centroidPx = w / 2f,
|
||||||
|
)
|
||||||
|
onViewportChanged(newViewport)
|
||||||
|
primary.consume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!moved && !isTransform) {
|
||||||
|
val side = if (down.position.x < size.width / 2f) Side.LEFT else Side.RIGHT
|
||||||
|
onTapSideSelect(side)
|
||||||
|
}
|
||||||
|
draggingPointIndex = null
|
||||||
|
draggingCurveId = null
|
||||||
|
dragMode = HandleDrag.NONE
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
.pointerInput(activeCurve) {
|
) {
|
||||||
detectTapGestures(
|
|
||||||
onTap = { offset ->
|
|
||||||
val side = if (offset.x < size.width / 2f) Side.LEFT else Side.RIGHT
|
|
||||||
onTapSideSelect(side)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
drawGrid()
|
drawGrid()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user