Add button 'Remove | add grid' on lastGrid TUO & reposition triolet,hairpin arc

This commit is contained in:
Hasinjato 2026-07-21 12:16:16 +03:00
parent 36a002256d
commit 792efc5aeb
3 changed files with 309 additions and 29 deletions

View file

@ -0,0 +1,53 @@
package mg.dot.feufaro.solfa
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun GridActionMenu(
onAddMeasureRight: () -> Unit,
onDeleteMeasure: () -> Unit,
) {
Column {
Row {
FilledTonalButton(
onClick = {
onAddMeasureRight()
},
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = null
)
}
}
Row {
OutlinedButton(
onClick = {
onDeleteMeasure()
},
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.secondary
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.secondary.copy(alpha = 0.5f))
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null
)
}
}
}
}

View file

@ -22,7 +22,6 @@ import kotlin.math.min
//@todo: bug ffpm-629.txt sssszd au lieu de ssss-d(-rrrrtdr)
//@todo: bug ews-103.txt Triolet adds one blank
//@todo: bug ffpm-167-1.txt shoud start with : and not |
//@todo: bug ffpm-491-2.txt ${x:} and ${DS: } not in sync
class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository: FileRepository) {
private val T: MutableList<PTemplate> = mutableListOf()
@ -624,7 +623,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
fragments: List<String>,
targetIdx: Int,
editState: TUOEditState
) {
) {
// Ajout/Enlèvement grille de fin
if (editState.tuoIndex == 9999) {
AddOrRemoveGridActionOnNotes(lines, editState)
return
}
val notesByVoice = editState.notesByVoice
val originalNotes = editState.originalNotes
val templateFragment = editState.templateFragment
@ -821,12 +825,76 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
}
}
private fun AddOrRemoveGridActionOnNotes(
lines: MutableList<String>,
editState: TUOEditState
) {
for (voiceNum in 0..3) {
val linePrefix = "N${voiceNum + 1}:"
val lineIdx = lines.indexOfFirst { it.startsWith(linePrefix) }
if (lineIdx != -1) {
val currentLine = lines[lineIdx]
val prefix = currentLine.substringBefore(":") + ":"
val noteBody = currentLine.substringAfter(":")
val regex = Regex("#\\S[',]*|\\s#\\S[',]*|/|\\(|\\)|[drmfsltDRFSTzw][0-9'¹²³⁴₁₂₃₄,]*|[-.―•]")
val allTokens = regex.findAll(expandNotes(noteBody.replace(Regex("\\s+"), "")))
.map { it.value }
.filter { it != "." }
.toMutableList()
/*if (editState.templateFragment == "-") {
var lastNoteIdx = -1
for (idx in allTokens.indices.reversed()) {
val token = allTokens[idx]
val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
if (!isStructural) {
lastNoteIdx = idx
break
}
}
if (lastNoteIdx != -1) {
allTokens.removeAt(lastNoteIdx)
}
} else {*/
// +
if (editState.templateFragment == "+") {
var lastNoteIdx = -1
for (idx in allTokens.indices.reversed()) {
val token = allTokens[idx]
val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
if (!isStructural) {
lastNoteIdx = idx
break
}
}
if (lastNoteIdx != -1) {
allTokens.add(lastNoteIdx + 1, "d")
} else {
allTokens.add("d")
}
}
lines[lineIdx] = prefix + allTokens.joinToString("")
}
}
return
}
private fun updateMarkerSource(
templateLine: String,
targetPointer: Int,
initMarkerValue: String,
editState: TUOEditState
): String {
/* Add or Remove lastGrid */
if (editState.tuoIndex == 9999) {
val finalResult = AddOrRemoveGridActionOnTemplate(templateLine, editState)
return finalResult
}
/* Set Pending Hairpin */
when (initMarkerValue) {
@ -1060,6 +1128,126 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
return finalResult
}
private fun AddOrRemoveGridActionOnTemplate(templateLine: String, editState: TUOEditState): String {
val prefix: String
val body: String
val suffix: String
val isT0 = templateLine.startsWith("T0")
if (isT0) {
prefix = "T0:{"
body = templateLine.substringAfter("{").substringBeforeLast("}")
suffix = "}"
} else {
val match = Regex("^(.*?z\\d|.*?z.):").find(templateLine)
if (match != null) {
prefix = match.value
body = templateLine.substring(match.value.length)
suffix = ""
} else {
prefix = templateLine.substringBefore(":") + ":"
body = templateLine.substringAfter(":")
suffix = ""
}
}
val separators = setOf(':', '|', '!', '/', ' ')
val tokens = mutableListOf<String>()
val isSeparator = mutableListOf<Boolean>()
var temp = StringBuilder()
for (char in body) {
if (char in separators) {
if (temp.isNotEmpty()) {
tokens.add(temp.toString())
isSeparator.add(false)
temp = StringBuilder()
}
tokens.add(char.toString())
isSeparator.add(true)
} else {
temp.append(char)
}
}
if (temp.isNotEmpty()) {
tokens.add(temp.toString())
isSeparator.add(false)
}
val updatedBody = if (editState.templateFragment == "-") {
//-
var lastNoteBlockIdx = -1
for (idx in tokens.indices.reversed()) {
if (!isSeparator[idx]) {
lastNoteBlockIdx = idx
break
}
}
if (lastNoteBlockIdx != -1) {
tokens.removeAt(lastNoteBlockIdx)
isSeparator.removeAt(lastNoteBlockIdx)
}
val cleanTokens = mutableListOf<String>()
var lastWasSeparator = false
for (i in tokens.indices) {
val token = tokens[i]
val isSep = isSeparator[i]
if (isSep) {
if (!lastWasSeparator) {
cleanTokens.add(token)
lastWasSeparator = true
} else {
val prevSep = cleanTokens.last()
if (prevSep == ":" && token != ":") {
cleanTokens[cleanTokens.lastIndex] = token
}
}
} else {
cleanTokens.add(token)
lastWasSeparator = false
}
}
cleanTokens.joinToString("")
} else {
// +
var lastNoteBlockIdx = -1
for (idx in tokens.indices.reversed()) {
if (!isSeparator[idx]) {
lastNoteBlockIdx = idx
break
}
}
if (lastNoteBlockIdx != -1) {
if (lastNoteBlockIdx < tokens.lastIndex && isSeparator[lastNoteBlockIdx + 1]) {
val nextSepIdx = lastNoteBlockIdx + 1
val sep = tokens[nextSepIdx]
tokens.add(nextSepIdx + 1, "D")
isSeparator.add(nextSepIdx + 1, false)
} else {
tokens.add("!")
isSeparator.add(true)
tokens.add("D")
isSeparator.add(false)
}
} else {
tokens.add("D")
isSeparator.add(false)
}
tokens.joinToString("")
}
val finalResult = "$prefix$updatedBody$suffix"
// println("=".repeat(100))
// println("EDITION FIN GRILLE (9999) : Action = ${editState.templateFragment}")
// println("RESULTAT COMPACTÉ (9999) : $finalResult")
// println("=".repeat(100))
return finalResult
}
private fun parseNoteAndOctave(s: String): Pair<String, Int> {
val base = s.replace(Regex("[^drmfsltDRFSTzw]"), "")
var level = 0
@ -1211,6 +1399,27 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
return templateArray
}
private fun encodeDuration(duration: Int): String {
return when (duration) {
1 -> "1";
2 -> "2";
3 -> "3";
4 -> "4"
6 -> "6";
8 -> "8";
10 -> "A";
11 -> "B";
12 -> "C";
14 -> "E";
16 -> "G";
20 -> "K";
24 -> "O";
28 -> "S";
32 -> "W"
else -> duration.toString()
}
}
private fun reconstructIt(finalResult: String, measure: Int): String {
if(finalResult.startsWith("T0:")) {
/* T0 */
@ -1312,27 +1521,6 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
}
}
fun encodeDuration(duration: Int): String {
return when (duration) {
1 -> "1";
2 -> "2";
3 -> "3";
4 -> "4"
6 -> "6";
8 -> "8";
10 -> "A";
11 -> "B";
12 -> "C";
14 -> "E";
16 -> "G";
20 -> "K";
24 -> "O";
28 -> "S";
32 -> "W"
else -> duration.toString()
}
}
var k = 0
while (k < buff.size) {
if (consumedBlocks.contains(k)) {

View file

@ -13,8 +13,10 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Description
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.*
@ -780,13 +782,14 @@ fun LazyVerticalGridTUO(
if (tuo.isTriolet()) {
Canvas(modifier = Modifier.fillMaxSize()) {
val arcWidth = with(density) { size.width * 0.75f }
val arcWidth = with(density) { size.width * 0.65f }
drawArc(
color = FEUFAROO_TRIOLET_COLOR,
startAngle = 200f,
sweepAngle = 140f,
useCenter = false,
topLeft = Offset(0f, yHeight / 2),
//topLeft = Offset(0f, yHeight/* / 2*/),
topLeft = Offset(size.width *0.05f, -yHeight * 0.1f),
size = Size(arcWidth, yHeight),
style = Stroke(width = 2f)
@ -805,15 +808,24 @@ fun LazyVerticalGridTUO(
) {
val xStart = if (lastHairPinSymbol == '>') -size.width * (tuo.numBlock - hairPinStart) else size.width / 2
val xEnd = if (lastHairPinSymbol == '>') size.width / 2 else -size.width * (tuo.numBlock - hairPinStart)
val offsetUp = -yHeight * 0.30f
val hairpinHeight = yHeight * 0.70f
val yTop = 0f + offsetUp
val yCenter = (hairpinHeight / 2) + offsetUp
val yBottom = hairpinHeight + offsetUp
drawLine(
Color.DarkGray,
start = Offset(x = xStart, y = 0f),
end = Offset(xEnd, yHeight / 2)
start = Offset(x = xStart, y = yTop),
end = Offset(xEnd, yCenter),
strokeWidth = 0.5f
)
drawLine(
Color.DarkGray,
start = Offset(xStart, yHeight),
end = Offset(xEnd, yHeight / 2)
start = Offset(xStart, yBottom),
end = Offset(xEnd, yCenter),
strokeWidth = 0.5f
)
}
TimeUnitObject.endHairPin()
@ -942,6 +954,32 @@ fun LazyVerticalGridTUO(
val isActive = (globalIndex == activeRowIndex)
val isSelectedByKeyboard = (globalIndex == selectedGridIndex)
val isAbsoluteLastGrid = (globalIndex == tuoList.size - 2)
if (editMode && isAbsoluteLastGrid) {
Box(
modifier = Modifier
.width(gridWidthDp / gridColumnCount)
.fillMaxHeight(),
contentAlignment = Alignment.Center
) {
GridActionMenu(
onAddMeasureRight = {
val state = TUOEditState(
tuoIndex = 9999,
templateFragment = "+"
)
sharedScreenModel.openTUOEditor(state)
},
onDeleteMeasure = {
val state = TUOEditState(
tuoIndex = 9999,
templateFragment = "-"
)
sharedScreenModel.openTUOEditor(state)
}
)
}
} else {
val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }
var menuOffset by remember { mutableStateOf(Offset.Zero) }
val interactionSource = remember { MutableInteractionSource() }
@ -1133,6 +1171,7 @@ fun LazyVerticalGridTUO(
}
}
}
}
}
}