Add button 'Remove | add grid' on lastGrid TUO & reposition triolet,hairpin arc
This commit is contained in:
parent
36a002256d
commit
792efc5aeb
3 changed files with 309 additions and 29 deletions
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue