From dd0590f9fd18a4d2132813ab51a387ed93bb23b2 Mon Sep 17 00:00:00 2001 From: "hasinarak3@gmail.com" Date: Fri, 23 Jan 2026 14:52:41 +0300 Subject: [PATCH] solve syllabe(lyrics) collision with adaptive padding --- .../mg/dot/feufaro/solfa/TimeUnitObject.kt | 232 +++++++++++++----- 1 file changed, 171 insertions(+), 61 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt index 3434c5a..7f65db8 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt @@ -1,50 +1,34 @@ package mg.dot.feufaro.solfa +import SharedScreenModel +import androidx.compose.foundation.Canvas import androidx.compose.foundation.background -import androidx.compose.runtime.Composable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyGridState -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.ui.Modifier -import androidx.compose.material3.Text import androidx.compose.material3.LocalTextStyle -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.border -import androidx.compose.foundation.combinedClickable +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.TextLayoutResult -import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.text.style.BaselineShift import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import mg.dot.feufaro.data.GridTUOData import kotlin.math.min -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.BaselineShift -import androidx.compose.ui.text.withStyle -import SharedScreenModel -import androidx.compose.runtime.collectAsState -import kotlinx.coroutines.delay val FEUFAROO_TRIOLET_COLOR = Color.DarkGray val FEUFAROO_KEY_CHANGE_COLOR = Color.Blue @@ -225,6 +209,23 @@ class TimeUnitObject (val pTemplate: PTemplate, val prevTUO: TimeUnitObject?, co } return result.joinToString(separator = "\n") { it } } + fun getSingleSyllable(stanzaNumber: Int): List { + val finalLines = mutableListOf() + + lyrics.forEach { stanzaLyrics -> + val lines = stanzaLyrics.toList(stanzaNumber) + lines.forEachIndexed { index, text -> + val trimmed = text.trim() + if (index < finalLines.size) { + finalLines[index] = "${finalLines[index]} $trimmed".trim() + } else { + finalLines.add(trimmed) + } + } + } + + return finalLines + } } @Composable @@ -423,13 +424,13 @@ fun TimeUnitComposable( ) } } - AutoResizingText( + /*AutoResizingText( text = tuo.lyricsAsMultiString(stanzaNumber), minFontSize = 16.sp, maxFontSize = 16.sp, maxLines = 2, modifier = Modifier.fillMaxWidth() - ) + )*/ } } @@ -562,41 +563,150 @@ fun LazyVerticalGridTUO( } else { -1 } - FlowRow( - modifier = Modifier.fillMaxWidth(), - - horizontalArrangement = Arrangement.spacedBy(horizontalArrangementSpacing), - verticalArrangement = Arrangement.spacedBy(8.dp), - //contentPadding = PaddingValues(8.dp), // c'est juste le padding extérieur de toute la liste - // state = lazyGridState - ) { - tuoList.drop(n=1).forEachIndexed { relativeIndex, oneTUO -> - val isActive = (relativeIndex == activeRowIndex) - Box( - modifier = Modifier - .fillMaxWidth(flowRowSize) - .background(Color.Transparent) - .combinedClickable( - onClick = { - sharedScreenModel.updatePositionFromPartition(relativeIndex, nbTotalDesRow) - println("590: relative $relativeIndex active? $isActive") - println("TimeUnitObj:566 Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex FL $flowRowSize totaRow $nbTotalDesRow") - } , - onDoubleClick = { - println("Double-Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex") + val measures = tuoList.drop(1).chunked(gridColumnCount) + Column( + modifier = Modifier.fillMaxWidth() + ){ + measures.forEachIndexed { measureIndex, measureTUOs -> + Column(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) { + Row(modifier = Modifier.fillMaxWidth()) { + measureTUOs.forEachIndexed { indexInMeasure, oneTUO -> + val globalIndex = (measureIndex * gridColumnCount) + indexInMeasure + val isActive = (globalIndex == activeRowIndex) + Box(modifier = Modifier.weight(1f) + .combinedClickable( + onClick = { + sharedScreenModel.updatePositionFromPartition(globalIndex, nbTotalDesRow) + // println("tempNum ${oneTUO.numBlock} mesasindex ${measureIndex} & indexinM $indexInMeasure gridCount $gridColumnCount") + } , + onDoubleClick = { + println("Double-Clicked: ${oneTUO.numBlock} / relative index: ") + } + )) { + TimeUnitComposable( + tuo = oneTUO, + stanzaNumber = currentStanza, + gridColumnCount = gridColumnCount, + gridActive = isActive + ) } - ) - ) { - TimeUnitComposable( - tuo = oneTUO, - currentStanza, - gridColumnCount, - gridActive = isActive - ) + } + } + + val allTemps = measureTUOs.map { it.getSingleSyllable(currentStanza) } + val maxLines = allTemps.maxOfOrNull { it.size } ?: 0 + + + val textMeasurer = rememberTextMeasurer() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Start + ) { + + allTemps.forEachIndexed { syls_i, syllables -> + Column( + modifier = Modifier + .weight(1f) + .widthIn(min = gridWidthDp / gridColumnCount) + ) { + val noteWidth = gridWidthDp / gridColumnCount + val density = LocalDensity.current + syllables.joinToString { " " } + syllables.mapIndexed { index, syl -> + var textWidth by remember { mutableStateOf(0.dp) } + + + val textLayoutResult = textMeasurer.measure( + text = syl, + maxLines = 1, + softWrap = false + ) + val textWidthDp = with(density) { textLayoutResult.size.width.toDp() } + val containerWidthDp = gridWidthDp / gridColumnCount + val isTooLong = textWidthDp > containerWidthDp + + val spacer = makeSpaceBetweenSyllables(textMeasurer, gridWidthDp / gridColumnCount) + val dynamicSpaceSyl = spacer(syl, allTemps, syls_i, index) + Text( + text = dynamicSpaceSyl, + modifier = Modifier + .fillMaxWidth() + .padding(end = 4.dp) + .wrapContentSize(unbounded = true, align = Alignment.Center,), + softWrap = false, + maxLines = 1, + overflow = TextOverflow.Visible, + style = TextStyle( + color = when { + (isTooLong) -> Color(0XFFBFAC00) + (index == 0) -> Color.Black + else -> Color(0XFF3B3A39) + }, + fontSize = 16.sp, + ), + onTextLayout = { txtLayoutRes -> + textWidth = with(density) { txtLayoutRes.size.width.toDp() } + val textWidthPx = txtLayoutRes.size.width + val noteWidthPx = with(density) { noteWidth.toPx() } + } + ) + } + } + } + } } } } } } +@Composable +fun makeSpaceBetweenSyllables( + textMeasurer: TextMeasurer, + noteWidthDp: Dp, + fontSize: TextUnit = 16.sp +): (String, List>, Int, Int) -> String { + val density = LocalDensity.current + val noteWidthPx = with(density) { noteWidthDp.toPx() } + val style = TextStyle(fontSize = fontSize) + val spaceWidthPx = textMeasurer.measure("\u00A0", style).size.width + return { currentSyl, allT, s_i, index -> + if (currentSyl.isEmpty()) "" + else { + val currentWidth = textMeasurer.measure(currentSyl, style).size.width + val next_syl = allT.getOrNull(s_i + 1)?.getOrNull(index) ?: "" + val prev_syl = if (s_i > 0) allT.getOrNull(s_i - 1)?.getOrNull(index) ?: "" else "" + + val excessPx = currentWidth - noteWidthPx + val neededSpaces = if (excessPx > 0) (excessPx / spaceWidthPx).toInt() + 1 else 0 + + val nextNextSyl = allT.getOrNull(s_i + 2)?.getOrNull(index) ?: "" + + when { + excessPx > 0 && next_syl.length < prev_syl.length -> { + // Ici, currentSyl se décale à gauche. + "\u00A0".repeat(neededSpaces) + currentSyl + } + excessPx > 0 && next_syl.length >= prev_syl.length -> { + currentSyl + "\u00A0".repeat(neededSpaces) + } + + next_syl.isNotEmpty() -> { + val nextWidth = textMeasurer.measure(next_syl, style).size.width + val nextExcess = nextWidth - noteWidthPx + + if (nextExcess > 0 && (allT.getOrNull(s_i + 2)?.getOrNull(index)?.length ?: 0) > next_syl.length) { + // Si le mot d'après va se décaler à gauche, moi je me décale aussi à gauche + val compensationSpaces = ((nextExcess / spaceWidthPx) / 2).toInt() + 1 + "\u00A0".repeat(compensationSpaces) + currentSyl + } else { + currentSyl + } + } + + else -> currentSyl + } + } + } +} \ No newline at end of file