Add anchor indicator & pending state when hairPin added
This commit is contained in:
parent
3bbe9367b7
commit
36a002256d
4 changed files with 148 additions and 29 deletions
|
|
@ -827,6 +827,13 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
|||
initMarkerValue: String,
|
||||
editState: TUOEditState
|
||||
): String {
|
||||
|
||||
/* Set Pending Hairpin */
|
||||
when (initMarkerValue) {
|
||||
"\${>}", "\${<}" -> sharedScreenModel.setPendingHairPin(true, targetPointer)
|
||||
"\${=}" -> sharedScreenModel.setPendingHairPin(false)
|
||||
}
|
||||
|
||||
val notesByVoice = editState.notesByVoice
|
||||
val originalNotes = editState.originalNotes
|
||||
val separat = editState.sep
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import mg.dot.feufaro.solfa.MarkerPopup
|
|||
import mg.dot.feufaro.solfa.Solfa
|
||||
import mg.dot.feufaro.solfa.TUOEditState
|
||||
import mg.dot.feufaro.solfa.TimeUnitObject
|
||||
import mg.dot.feufaro.solfa.extractAnchorFromNoteLine
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -49,6 +50,7 @@ fun TUODetailDialog(
|
|||
isEditable: Boolean,
|
||||
canAdd: Boolean,
|
||||
sharedScreenModel: SharedScreenModel,
|
||||
anchorsByVoice: Map<Int, String?>,
|
||||
onIndexChanged: (Int) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
onSave: (TUOEditState) -> Unit
|
||||
|
|
@ -348,10 +350,9 @@ fun TUODetailDialog(
|
|||
|
||||
if (!templateFragment.isNullOrBlank() && templateFragment != "-") {
|
||||
val hasMarker = marker.isNotEmpty()
|
||||
val hairPinState by sharedScreenModel.isPendingHairPinOnEdit.collectAsState()
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
|
||||
// 1. Bouton "Remove" (affiché uniquement si hasMarker est true)
|
||||
if (hasMarker) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
|
|
@ -363,7 +364,7 @@ fun TUODetailDialog(
|
|||
state = rememberTooltipState()
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(25.dp),
|
||||
modifier = Modifier.size(22.dp),
|
||||
onClick = {
|
||||
val state = TUOEditState(
|
||||
tuoIndex = globalIndex,
|
||||
|
|
@ -376,6 +377,9 @@ fun TUODetailDialog(
|
|||
originalSep = initialSep,
|
||||
sep = newSep
|
||||
)
|
||||
if(hairPinState.gridIndex == globalIndex) {
|
||||
sharedScreenModel.setPendingHairPin(false)
|
||||
}
|
||||
onSave(state)
|
||||
}
|
||||
) {
|
||||
|
|
@ -388,6 +392,38 @@ fun TUODetailDialog(
|
|||
}
|
||||
}
|
||||
|
||||
if(hairPinState.isPending && hairPinState.gridIndex < globalIndex) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip(containerColor = Color.DarkGray, contentColor = Color.White) {
|
||||
Text(
|
||||
text = "Complèter le marqueur",
|
||||
fontSize = MaterialTheme.typography.titleSmall.fontSize
|
||||
)
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState()
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(22.dp),
|
||||
onClick = {
|
||||
marker = "="
|
||||
val state = TUOEditState(
|
||||
tuoIndex = globalIndex,
|
||||
marker = marker
|
||||
)
|
||||
onSave(state)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
|
|
@ -401,7 +437,7 @@ fun TUODetailDialog(
|
|||
state = rememberTooltipState()
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(25.dp)
|
||||
modifier = Modifier.size(22.dp)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
markerButtonPos = coordinates.positionInParent().let {
|
||||
IntOffset(it.x.toInt(), it.y.toInt())
|
||||
|
|
@ -418,6 +454,7 @@ fun TUODetailDialog(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -461,6 +498,9 @@ fun TUODetailDialog(
|
|||
(0..3).forEach { voice ->
|
||||
val currentNote = notes[voice] ?: ""
|
||||
val validation = validateMusicalInput(currentNote, templateFragment)
|
||||
val voiceAnchor = anchorsByVoice[voice]?.replace("#","")
|
||||
val hasAnchor = !voiceAnchor.isNullOrBlank()
|
||||
|
||||
Box(modifier = Modifier.wrapContentSize()
|
||||
) {
|
||||
MyTextEditField(
|
||||
|
|
@ -477,6 +517,32 @@ fun TUODetailDialog(
|
|||
notes[voice] = newValue
|
||||
}
|
||||
)
|
||||
if (hasAnchor) {
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip(containerColor = Color.DarkGray, contentColor = Color.White) {
|
||||
Text("Ancrée sur ${transformMusicalInput(voiceAnchor)}", fontSize = 11.sp)
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState()
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(3.dp),
|
||||
color = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.8f),
|
||||
modifier = Modifier.padding(start = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "⚓",
|
||||
fontSize = 10.sp,
|
||||
color = Color.Black,
|
||||
modifier = Modifier.padding(horizontal = 2.dp, vertical = 1.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -594,6 +594,7 @@ fun LazyVerticalGridTUO(
|
|||
showDetailDialog = false
|
||||
showAddDialog = false
|
||||
selectedIndex = -1
|
||||
selectedGridIndex = -1
|
||||
selectedTUO = null
|
||||
}
|
||||
|
||||
|
|
@ -946,9 +947,21 @@ fun LazyVerticalGridTUO(
|
|||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isHovered by interactionSource.collectIsHoveredAsState()
|
||||
|
||||
val hairPinState by sharedScreenModel.isPendingHairPinOnEdit.collectAsState()
|
||||
|
||||
key(oneTUO.numBlock) {
|
||||
Box(modifier = Modifier
|
||||
.width(gridWidthDp / gridColumnCount)
|
||||
.then(
|
||||
if (hairPinState.isPending && hairPinState.gridIndex == globalIndex) {
|
||||
Modifier.background(
|
||||
color = MaterialTheme.colorScheme.secondary.copy(alpha = 0.3f),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.border(
|
||||
width = if (isSelectedByKeyboard && editMode) 2.dp else 0.dp,
|
||||
color = if (isSelectedByKeyboard && editMode) MaterialTheme.colorScheme.error else Color.Transparent,
|
||||
|
|
@ -971,10 +984,19 @@ fun LazyVerticalGridTUO(
|
|||
menuOffset = offset
|
||||
selectedTUO = oneTUO
|
||||
selectedIndex = globalIndex
|
||||
showContextualMenu = true
|
||||
showEditDialog = true
|
||||
} else {
|
||||
sharedScreenModel.seekToGrid(globalIndex)
|
||||
}
|
||||
},
|
||||
onDoubleTap = { offset ->
|
||||
if(editMode) {
|
||||
selectedGridIndex = globalIndex
|
||||
menuOffset = offset
|
||||
selectedTUO = oneTUO
|
||||
selectedIndex = globalIndex
|
||||
showContextualMenu = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1021,7 +1043,7 @@ fun LazyVerticalGridTUO(
|
|||
}
|
||||
|
||||
val currentSelected = selectedTUO
|
||||
if ((showEditDialog || showAddDialog || showDetailDialog) && currentSelected != null && selectedIndex == globalIndex) {
|
||||
if ((showEditDialog || showDetailDialog) && currentSelected != null && selectedIndex == globalIndex) {
|
||||
val canAdd = showAddDialog
|
||||
val canEdit = showEditDialog || canAdd
|
||||
|
||||
|
|
@ -1047,6 +1069,11 @@ fun LazyVerticalGridTUO(
|
|||
val match = blankPrefixRegex.find(afterU0T0)
|
||||
val blankPrefix = match?.groups?.get(1)?.value ?: ""
|
||||
|
||||
val anchorsByVoice = (0..3).associate { voiceIdx ->
|
||||
val linePrefix = "N${voiceIdx + 1}:"
|
||||
val fullLine = lines.firstOrNull { it.startsWith(linePrefix) } ?: ""
|
||||
voiceIdx to extractAnchorFromNoteLine(fullLine)
|
||||
}
|
||||
val editState = TUOEditState(
|
||||
tuoIndex = oneTUO.firstTuoIndex,
|
||||
notesByVoice = (0..3).associate { i ->
|
||||
|
|
@ -1077,6 +1104,7 @@ fun LazyVerticalGridTUO(
|
|||
isEditable = canEdit,
|
||||
canAdd = canAdd,
|
||||
sharedScreenModel = sharedScreenModel,
|
||||
anchorsByVoice = anchorsByVoice,
|
||||
onDismiss = {
|
||||
showAddDialog = false
|
||||
showDetailDialog = false
|
||||
|
|
@ -1089,6 +1117,7 @@ fun LazyVerticalGridTUO(
|
|||
},
|
||||
onIndexChanged = { newIndex ->
|
||||
selectedIndex = newIndex
|
||||
selectedGridIndex = newIndex
|
||||
val globalList = sharedScreenModel.tuoList.value
|
||||
val nextTUO = globalList.getOrNull(newIndex)
|
||||
|
||||
|
|
@ -1628,3 +1657,8 @@ private fun transformMusicalInput(input: String): String {
|
|||
.replace("₂₁", "₃")
|
||||
.replace("'", "¹")
|
||||
}
|
||||
|
||||
fun extractAnchorFromNoteLine(noteLine: String): String? {
|
||||
val match = Regex("#[drmfsltDRFSTz][',]*").find(noteLine)
|
||||
return match?.value
|
||||
}
|
||||
|
|
@ -234,12 +234,23 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
|||
private val _editModeState = mutableStateOf(false)
|
||||
val editModeState: State<Boolean> = _editModeState
|
||||
|
||||
data class HairPinEditState(
|
||||
val isPending: Boolean = false,
|
||||
val gridIndex: Int = 0
|
||||
)
|
||||
private val _isPendingHairPinOnEdit = MutableStateFlow(HairPinEditState())
|
||||
val isPendingHairPinOnEdit: StateFlow<HairPinEditState> = _isPendingHairPinOnEdit.asStateFlow()
|
||||
fun setPendingHairPin(isPending: Boolean, gridIdx: Int = 0) {
|
||||
_isPendingHairPinOnEdit.value = HairPinEditState(isPending, gridIdx)
|
||||
}
|
||||
|
||||
private val _modeEditor = MutableStateFlow(false)
|
||||
val modeEditor: StateFlow<Boolean> = _modeEditor
|
||||
|
||||
fun toggleEditorMode(enabled: Boolean) {
|
||||
_modeEditor.value = enabled
|
||||
}
|
||||
|
||||
private val _harmonyView = MutableStateFlow(false)
|
||||
val harmonyView: StateFlow<Boolean> = _harmonyView
|
||||
fun toggleHarmonyMode(enabled: Boolean) {
|
||||
|
|
@ -499,7 +510,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
|||
var newGridIndex = marker.gridIndex ?: 0
|
||||
val newMarkerList = mutableListOf<String>()
|
||||
|
||||
originalMarker.forEach { markerText->
|
||||
originalMarker.forEach { markerText ->
|
||||
val index = marker.gridIndex ?: 0 // On utilise l'index passé par l'UI
|
||||
val isNearEnd = index >= (tuos.size - 2)
|
||||
// println("MARKERS:> $markerText\n")
|
||||
|
|
@ -701,6 +712,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
|||
_midiMarkersList.value = emptyList()
|
||||
_tuoTimestamps.value = emptyList()
|
||||
_searchTitle.value = ""
|
||||
_isPendingHairPinOnEdit.value = HairPinEditState(false, 0)
|
||||
tempTimeUnitObjectList.clear()
|
||||
resetGridCount()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue