Auto commit Sat Mar 21 09:01:02 PM CDT 2026
This commit is contained in:
@@ -14,10 +14,13 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
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.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -264,9 +267,47 @@ internal fun TimelineGraph(
|
||||
(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(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.height(190.dp)
|
||||
.background(Color(0xFF0A0E18), RoundedCornerShape(10.dp))
|
||||
.testTag("timeline_graph")
|
||||
@@ -274,10 +315,10 @@ internal fun TimelineGraph(
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val down = awaitFirstDown(requireUnconsumed = false)
|
||||
val viewport = currentViewport
|
||||
val selection = currentSelection
|
||||
val curveLeft = currentCurveLeft
|
||||
val curveRight = currentCurveRight
|
||||
var viewport = currentViewport
|
||||
var selection = currentSelection
|
||||
var curveLeft = currentCurveLeft
|
||||
var curveRight = currentCurveRight
|
||||
val w = size.width.toFloat()
|
||||
val h = size.height.toFloat()
|
||||
val startX = viewport.timeToX(selection.startSec, w)
|
||||
@@ -351,6 +392,7 @@ internal fun TimelineGraph(
|
||||
widthPx = size.width.toFloat(),
|
||||
centroidPx = centroid.x,
|
||||
)
|
||||
viewport = newViewport
|
||||
currentOnViewportChanged(newViewport)
|
||||
}
|
||||
event.changes.forEach { it.consume() }
|
||||
@@ -369,6 +411,7 @@ internal fun TimelineGraph(
|
||||
if (pointIndex != null && curveId != null) {
|
||||
val targetCurve = if (curveId == leftId) curveLeft else curveRight
|
||||
val updated = targetCurve.movePointVerticalWithPropagation(pointIndex, primary.position.y, h)
|
||||
if (curveId == leftId) curveLeft = updated else curveRight = updated
|
||||
currentOnUpdateCurve(curveId, updated)
|
||||
onCurveEditedHaptic()
|
||||
primary.consume()
|
||||
@@ -378,11 +421,15 @@ internal fun TimelineGraph(
|
||||
val t = viewport.xToTimeSec(primary.position.x, w).toInt().coerceAtLeast(0)
|
||||
when (dragMode) {
|
||||
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()
|
||||
}
|
||||
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()
|
||||
}
|
||||
HandleDrag.NONE -> {
|
||||
@@ -393,6 +440,7 @@ internal fun TimelineGraph(
|
||||
widthPx = w,
|
||||
centroidPx = w / 2f,
|
||||
)
|
||||
viewport = newViewport
|
||||
currentOnViewportChanged(newViewport)
|
||||
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(
|
||||
"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),
|
||||
@@ -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() {
|
||||
val stepX = size.width / 8f
|
||||
val stepY = size.height / 4f
|
||||
|
||||
Reference in New Issue
Block a user