Auto commit Sat Mar 21 09:01:02 PM CDT 2026

This commit is contained in:
Tretzi
2026-03-21 21:01:02 -05:00
parent 835a486794
commit 93607c0d6a

View File

@@ -14,10 +14,13 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.weight
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -264,9 +267,47 @@ internal fun TimelineGraph(
(context as? android.app.Activity)?.window?.decorView?.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) (context as? android.app.Activity)?.window?.decorView?.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
} }
val yLabels = if (isVisual) {
listOf(
formatMsLabel(ParameterMapping.logMap01(1f, ParameterRanges.FLASH_INTERVAL_MS_MIN, ParameterRanges.FLASH_INTERVAL_MS_MAX)),
formatMsLabel(ParameterMapping.logMap01(0.5f, ParameterRanges.FLASH_INTERVAL_MS_MIN, ParameterRanges.FLASH_INTERVAL_MS_MAX)),
formatMsLabel(ParameterMapping.logMap01(0f, ParameterRanges.FLASH_INTERVAL_MS_MIN, ParameterRanges.FLASH_INTERVAL_MS_MAX)),
)
} else {
listOf(
formatHzLabel(ParameterMapping.logMap01(1f, ParameterRanges.CARRIER_HZ_MIN, ParameterRanges.CARRIER_HZ_MAX)),
formatHzLabel(ParameterMapping.logMap01(0.5f, ParameterRanges.CARRIER_HZ_MIN, ParameterRanges.CARRIER_HZ_MAX)),
formatHzLabel(ParameterMapping.logMap01(0f, ParameterRanges.CARRIER_HZ_MIN, ParameterRanges.CARRIER_HZ_MAX)),
)
}
val xStart = formatTimeLabel(viewport.startSec)
val xMid = formatTimeLabel(viewport.startSec + viewport.secondsPerScreen / 2f)
val xEnd = formatTimeLabel(viewport.startSec + viewport.secondsPerScreen)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.height(190.dp).width(48.dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.End,
) {
yLabels.forEach { label ->
Text(
text = label,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f),
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.End,
modifier = Modifier.fillMaxWidth(),
)
}
}
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .weight(1f)
.height(190.dp) .height(190.dp)
.background(Color(0xFF0A0E18), RoundedCornerShape(10.dp)) .background(Color(0xFF0A0E18), RoundedCornerShape(10.dp))
.testTag("timeline_graph") .testTag("timeline_graph")
@@ -274,10 +315,10 @@ internal fun TimelineGraph(
awaitPointerEventScope { awaitPointerEventScope {
while (true) { while (true) {
val down = awaitFirstDown(requireUnconsumed = false) val down = awaitFirstDown(requireUnconsumed = false)
val viewport = currentViewport var viewport = currentViewport
val selection = currentSelection var selection = currentSelection
val curveLeft = currentCurveLeft var curveLeft = currentCurveLeft
val curveRight = currentCurveRight var curveRight = currentCurveRight
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)
@@ -351,6 +392,7 @@ internal fun TimelineGraph(
widthPx = size.width.toFloat(), widthPx = size.width.toFloat(),
centroidPx = centroid.x, centroidPx = centroid.x,
) )
viewport = newViewport
currentOnViewportChanged(newViewport) currentOnViewportChanged(newViewport)
} }
event.changes.forEach { it.consume() } event.changes.forEach { it.consume() }
@@ -369,6 +411,7 @@ internal fun TimelineGraph(
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.movePointVerticalWithPropagation(pointIndex, primary.position.y, h) val updated = targetCurve.movePointVerticalWithPropagation(pointIndex, primary.position.y, h)
if (curveId == leftId) curveLeft = updated else curveRight = updated
currentOnUpdateCurve(curveId, updated) currentOnUpdateCurve(curveId, updated)
onCurveEditedHaptic() onCurveEditedHaptic()
primary.consume() primary.consume()
@@ -378,11 +421,15 @@ internal fun TimelineGraph(
val t = viewport.xToTimeSec(primary.position.x, w).toInt().coerceAtLeast(0) val t = viewport.xToTimeSec(primary.position.x, w).toInt().coerceAtLeast(0)
when (dragMode) { when (dragMode) {
HandleDrag.START -> { HandleDrag.START -> {
currentOnSelectionChanged(TimelineSelection(startSec = min(t, selection.endSec - 60), endSec = selection.endSec)) val newSelection = TimelineSelection(startSec = min(t, selection.endSec - 60), endSec = selection.endSec)
selection = newSelection
currentOnSelectionChanged(newSelection)
primary.consume() primary.consume()
} }
HandleDrag.END -> { HandleDrag.END -> {
currentOnSelectionChanged(TimelineSelection(startSec = selection.startSec, endSec = max(t, selection.startSec + 60))) val newSelection = TimelineSelection(startSec = selection.startSec, endSec = max(t, selection.startSec + 60))
selection = newSelection
currentOnSelectionChanged(newSelection)
primary.consume() primary.consume()
} }
HandleDrag.NONE -> { HandleDrag.NONE -> {
@@ -393,6 +440,7 @@ internal fun TimelineGraph(
widthPx = w, widthPx = w,
centroidPx = w / 2f, centroidPx = w / 2f,
) )
viewport = newViewport
currentOnViewportChanged(newViewport) currentOnViewportChanged(newViewport)
primary.consume() primary.consume()
} }
@@ -436,6 +484,25 @@ internal fun TimelineGraph(
} }
} }
}
}
Row(
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Spacer(modifier = Modifier.width(48.dp))
Row(
modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(xStart, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f), style = MaterialTheme.typography.labelSmall)
Text(xMid, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f), style = MaterialTheme.typography.labelSmall)
Text(xEnd, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f), style = MaterialTheme.typography.labelSmall)
}
}
Text( Text(
"One dot per minute • Drag selected curve dots up/down • Pinch zoom • Drag background pan • Drag handles to set range", "One dot per minute • Drag selected curve dots up/down • Pinch zoom • Drag background pan • Drag handles to set range",
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f), color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f),
@@ -448,6 +515,25 @@ internal fun TimelineGraph(
} }
} }
private fun formatTimeLabel(totalSec: Float): String {
val totalMinutes = (totalSec / 60f).toInt().coerceAtLeast(0)
val hours = totalMinutes / 60
val minutes = totalMinutes % 60
return if (hours > 0) {
if (minutes == 0) "${hours}h" else "${hours}h ${minutes}m"
} else {
"${minutes}m"
}
}
private fun formatMsLabel(value: Float): String = "${value.toInt()} ms"
private fun formatHzLabel(value: Float): String = if (value >= 10f) {
"${value.toInt()} Hz"
} else {
String.format("%.1f Hz", value)
}
private fun androidx.compose.ui.graphics.drawscope.DrawScope.drawGrid() { private fun androidx.compose.ui.graphics.drawscope.DrawScope.drawGrid() {
val stepX = size.width / 8f val stepX = size.width / 8f
val stepY = size.height / 4f val stepY = size.height / 4f