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,
|
initMarkerValue: String,
|
||||||
editState: TUOEditState
|
editState: TUOEditState
|
||||||
): String {
|
): String {
|
||||||
|
|
||||||
|
/* Set Pending Hairpin */
|
||||||
|
when (initMarkerValue) {
|
||||||
|
"\${>}", "\${<}" -> sharedScreenModel.setPendingHairPin(true, targetPointer)
|
||||||
|
"\${=}" -> sharedScreenModel.setPendingHairPin(false)
|
||||||
|
}
|
||||||
|
|
||||||
val notesByVoice = editState.notesByVoice
|
val notesByVoice = editState.notesByVoice
|
||||||
val originalNotes = editState.originalNotes
|
val originalNotes = editState.originalNotes
|
||||||
val separat = editState.sep
|
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.Solfa
|
||||||
import mg.dot.feufaro.solfa.TUOEditState
|
import mg.dot.feufaro.solfa.TUOEditState
|
||||||
import mg.dot.feufaro.solfa.TimeUnitObject
|
import mg.dot.feufaro.solfa.TimeUnitObject
|
||||||
|
import mg.dot.feufaro.solfa.extractAnchorFromNoteLine
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -49,6 +50,7 @@ fun TUODetailDialog(
|
||||||
isEditable: Boolean,
|
isEditable: Boolean,
|
||||||
canAdd: Boolean,
|
canAdd: Boolean,
|
||||||
sharedScreenModel: SharedScreenModel,
|
sharedScreenModel: SharedScreenModel,
|
||||||
|
anchorsByVoice: Map<Int, String?>,
|
||||||
onIndexChanged: (Int) -> Unit,
|
onIndexChanged: (Int) -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onSave: (TUOEditState) -> Unit
|
onSave: (TUOEditState) -> Unit
|
||||||
|
|
@ -348,10 +350,9 @@ fun TUODetailDialog(
|
||||||
|
|
||||||
if (!templateFragment.isNullOrBlank() && templateFragment != "-") {
|
if (!templateFragment.isNullOrBlank() && templateFragment != "-") {
|
||||||
val hasMarker = marker.isNotEmpty()
|
val hasMarker = marker.isNotEmpty()
|
||||||
|
val hairPinState by sharedScreenModel.isPendingHairPinOnEdit.collectAsState()
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|
||||||
// 1. Bouton "Remove" (affiché uniquement si hasMarker est true)
|
|
||||||
if (hasMarker) {
|
if (hasMarker) {
|
||||||
TooltipBox(
|
TooltipBox(
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
|
@ -363,7 +364,7 @@ fun TUODetailDialog(
|
||||||
state = rememberTooltipState()
|
state = rememberTooltipState()
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(25.dp),
|
modifier = Modifier.size(22.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
val state = TUOEditState(
|
val state = TUOEditState(
|
||||||
tuoIndex = globalIndex,
|
tuoIndex = globalIndex,
|
||||||
|
|
@ -376,6 +377,9 @@ fun TUODetailDialog(
|
||||||
originalSep = initialSep,
|
originalSep = initialSep,
|
||||||
sep = newSep
|
sep = newSep
|
||||||
)
|
)
|
||||||
|
if(hairPinState.gridIndex == globalIndex) {
|
||||||
|
sharedScreenModel.setPendingHairPin(false)
|
||||||
|
}
|
||||||
onSave(state)
|
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(
|
TooltipBox(
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
tooltip = {
|
tooltip = {
|
||||||
|
|
@ -401,7 +437,7 @@ fun TUODetailDialog(
|
||||||
state = rememberTooltipState()
|
state = rememberTooltipState()
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(25.dp)
|
modifier = Modifier.size(22.dp)
|
||||||
.onGloballyPositioned { coordinates ->
|
.onGloballyPositioned { coordinates ->
|
||||||
markerButtonPos = coordinates.positionInParent().let {
|
markerButtonPos = coordinates.positionInParent().let {
|
||||||
IntOffset(it.x.toInt(), it.y.toInt())
|
IntOffset(it.x.toInt(), it.y.toInt())
|
||||||
|
|
@ -418,6 +454,7 @@ fun TUODetailDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -461,6 +498,9 @@ fun TUODetailDialog(
|
||||||
(0..3).forEach { voice ->
|
(0..3).forEach { voice ->
|
||||||
val currentNote = notes[voice] ?: ""
|
val currentNote = notes[voice] ?: ""
|
||||||
val validation = validateMusicalInput(currentNote, templateFragment)
|
val validation = validateMusicalInput(currentNote, templateFragment)
|
||||||
|
val voiceAnchor = anchorsByVoice[voice]?.replace("#","")
|
||||||
|
val hasAnchor = !voiceAnchor.isNullOrBlank()
|
||||||
|
|
||||||
Box(modifier = Modifier.wrapContentSize()
|
Box(modifier = Modifier.wrapContentSize()
|
||||||
) {
|
) {
|
||||||
MyTextEditField(
|
MyTextEditField(
|
||||||
|
|
@ -477,6 +517,32 @@ fun TUODetailDialog(
|
||||||
notes[voice] = newValue
|
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
|
showDetailDialog = false
|
||||||
showAddDialog = false
|
showAddDialog = false
|
||||||
selectedIndex = -1
|
selectedIndex = -1
|
||||||
|
selectedGridIndex = -1
|
||||||
selectedTUO = null
|
selectedTUO = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -946,9 +947,21 @@ fun LazyVerticalGridTUO(
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val isHovered by interactionSource.collectIsHoveredAsState()
|
val isHovered by interactionSource.collectIsHoveredAsState()
|
||||||
|
|
||||||
|
val hairPinState by sharedScreenModel.isPendingHairPinOnEdit.collectAsState()
|
||||||
|
|
||||||
key(oneTUO.numBlock) {
|
key(oneTUO.numBlock) {
|
||||||
Box(modifier = Modifier
|
Box(modifier = Modifier
|
||||||
.width(gridWidthDp / gridColumnCount)
|
.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(
|
.border(
|
||||||
width = if (isSelectedByKeyboard && editMode) 2.dp else 0.dp,
|
width = if (isSelectedByKeyboard && editMode) 2.dp else 0.dp,
|
||||||
color = if (isSelectedByKeyboard && editMode) MaterialTheme.colorScheme.error else Color.Transparent,
|
color = if (isSelectedByKeyboard && editMode) MaterialTheme.colorScheme.error else Color.Transparent,
|
||||||
|
|
@ -971,10 +984,19 @@ fun LazyVerticalGridTUO(
|
||||||
menuOffset = offset
|
menuOffset = offset
|
||||||
selectedTUO = oneTUO
|
selectedTUO = oneTUO
|
||||||
selectedIndex = globalIndex
|
selectedIndex = globalIndex
|
||||||
showContextualMenu = true
|
showEditDialog = true
|
||||||
} else {
|
} else {
|
||||||
sharedScreenModel.seekToGrid(globalIndex)
|
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
|
val currentSelected = selectedTUO
|
||||||
if ((showEditDialog || showAddDialog || showDetailDialog) && currentSelected != null && selectedIndex == globalIndex) {
|
if ((showEditDialog || showDetailDialog) && currentSelected != null && selectedIndex == globalIndex) {
|
||||||
val canAdd = showAddDialog
|
val canAdd = showAddDialog
|
||||||
val canEdit = showEditDialog || canAdd
|
val canEdit = showEditDialog || canAdd
|
||||||
|
|
||||||
|
|
@ -1047,6 +1069,11 @@ fun LazyVerticalGridTUO(
|
||||||
val match = blankPrefixRegex.find(afterU0T0)
|
val match = blankPrefixRegex.find(afterU0T0)
|
||||||
val blankPrefix = match?.groups?.get(1)?.value ?: ""
|
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(
|
val editState = TUOEditState(
|
||||||
tuoIndex = oneTUO.firstTuoIndex,
|
tuoIndex = oneTUO.firstTuoIndex,
|
||||||
notesByVoice = (0..3).associate { i ->
|
notesByVoice = (0..3).associate { i ->
|
||||||
|
|
@ -1077,6 +1104,7 @@ fun LazyVerticalGridTUO(
|
||||||
isEditable = canEdit,
|
isEditable = canEdit,
|
||||||
canAdd = canAdd,
|
canAdd = canAdd,
|
||||||
sharedScreenModel = sharedScreenModel,
|
sharedScreenModel = sharedScreenModel,
|
||||||
|
anchorsByVoice = anchorsByVoice,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
showAddDialog = false
|
showAddDialog = false
|
||||||
showDetailDialog = false
|
showDetailDialog = false
|
||||||
|
|
@ -1089,6 +1117,7 @@ fun LazyVerticalGridTUO(
|
||||||
},
|
},
|
||||||
onIndexChanged = { newIndex ->
|
onIndexChanged = { newIndex ->
|
||||||
selectedIndex = newIndex
|
selectedIndex = newIndex
|
||||||
|
selectedGridIndex = newIndex
|
||||||
val globalList = sharedScreenModel.tuoList.value
|
val globalList = sharedScreenModel.tuoList.value
|
||||||
val nextTUO = globalList.getOrNull(newIndex)
|
val nextTUO = globalList.getOrNull(newIndex)
|
||||||
|
|
||||||
|
|
@ -1628,3 +1657,8 @@ private fun transformMusicalInput(input: String): String {
|
||||||
.replace("₂₁", "₃")
|
.replace("₂₁", "₃")
|
||||||
.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)
|
private val _editModeState = mutableStateOf(false)
|
||||||
val editModeState: State<Boolean> = _editModeState
|
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)
|
private val _modeEditor = MutableStateFlow(false)
|
||||||
val modeEditor: StateFlow<Boolean> = _modeEditor
|
val modeEditor: StateFlow<Boolean> = _modeEditor
|
||||||
|
|
||||||
fun toggleEditorMode(enabled: Boolean) {
|
fun toggleEditorMode(enabled: Boolean) {
|
||||||
_modeEditor.value = enabled
|
_modeEditor.value = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _harmonyView = MutableStateFlow(false)
|
private val _harmonyView = MutableStateFlow(false)
|
||||||
val harmonyView: StateFlow<Boolean> = _harmonyView
|
val harmonyView: StateFlow<Boolean> = _harmonyView
|
||||||
fun toggleHarmonyMode(enabled: Boolean) {
|
fun toggleHarmonyMode(enabled: Boolean) {
|
||||||
|
|
@ -499,7 +510,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
var newGridIndex = marker.gridIndex ?: 0
|
var newGridIndex = marker.gridIndex ?: 0
|
||||||
val newMarkerList = mutableListOf<String>()
|
val newMarkerList = mutableListOf<String>()
|
||||||
|
|
||||||
originalMarker.forEach { markerText->
|
originalMarker.forEach { markerText ->
|
||||||
val index = marker.gridIndex ?: 0 // On utilise l'index passé par l'UI
|
val index = marker.gridIndex ?: 0 // On utilise l'index passé par l'UI
|
||||||
val isNearEnd = index >= (tuos.size - 2)
|
val isNearEnd = index >= (tuos.size - 2)
|
||||||
// println("MARKERS:> $markerText\n")
|
// println("MARKERS:> $markerText\n")
|
||||||
|
|
@ -701,6 +712,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
_midiMarkersList.value = emptyList()
|
_midiMarkersList.value = emptyList()
|
||||||
_tuoTimestamps.value = emptyList()
|
_tuoTimestamps.value = emptyList()
|
||||||
_searchTitle.value = ""
|
_searchTitle.value = ""
|
||||||
|
_isPendingHairPinOnEdit.value = HairPinEditState(false, 0)
|
||||||
tempTimeUnitObjectList.clear()
|
tempTimeUnitObjectList.clear()
|
||||||
resetGridCount()
|
resetGridCount()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue