From 83a26110e6a7075c05db3b4599c1fcc1a7a5dd59 Mon Sep 17 00:00:00 2001 From: Hasinjato Date: Wed, 7 Oct 2026 15:32:32 +0300 Subject: [PATCH] Update ui & Dynamic factor slider - Settings --- .../kotlin/mg/dot/feufaro/midi/Dynamic.kt | 14 +- .../kotlin/mg/dot/feufaro/ui/Settings.kt | 1180 +++++++++-------- 2 files changed, 623 insertions(+), 571 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/Dynamic.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/Dynamic.kt index ffce286..c2d6e04 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/Dynamic.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/Dynamic.kt @@ -5,14 +5,14 @@ import mg.dot.feufaro.provideSettings import java.util.prefs.Preferences enum class Dynamic(val velocity: Int, val label: String, val defaultFactor: Float) { - PPP(16, "ppp", 0.55f), - PP (33, "pp", 0.66f), - P (49, "p", 0.77f), - MP (64, "mp", 0.88f), + PPP(16, "ppp", 0.80f), + PP (32, "pp", 0.85f), + P (48, "p", 0.90f), + MP (64, "mp", 0.95f), MF (80, "mf", 1.00f), - F (96, "f", 1.15f), - FF (112, "ff", 1.35f), - FFF(127, "fff", 1.60f); + F (96, "f", 1.05f), + FF (112, "ff", 1.10f), + FFF(127, "fff", 1.15f); private val prefs: Settings = provideSettings() var savedFactor: Float = prefs.getFloat("dynamic_${name.lowercase()}", defaultFactor) set(value) { diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt index 063813e..d5ac74f 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/Settings.kt @@ -95,7 +95,7 @@ fun Settings( ?: MidiInstrument(program = globalInstrumentProgram, name = "Instrument $globalInstrumentProgram") val menuScrollState = rememberScrollState() - var expandedDropdown by remember { mutableStateOf(false) } + var expandedDropdown by remember { mutableStateOf(true) } LaunchedEffect(expandedDropdown, globalInstrumentProgram) { if (expandedDropdown) { @@ -128,629 +128,681 @@ fun Settings( ) }, text = { - Column( + val settingsScrollState = rememberScrollState() + + Box( modifier = Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(8.dp) + .fillMaxHeight() ) { - HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant) - - SettingsAccordion( - title = "Paramètres Général", - isExpanded = expandedGeneral, - onToggle = { - openExclusive("General") - } + MyVerticalScrollbar( + scrollState = settingsScrollState, + modifier = Modifier + .align(Alignment.CenterEnd) + .fillMaxHeight() + .width(8.dp) ) { Column( - modifier = Modifier.padding(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(end = 11.dp) + .verticalScroll(settingsScrollState), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - if(isAndroid) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Mode Plein Écran", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface - ) - Switch( - checked = isFullScreenEnabled, - onCheckedChange = { isChecked -> - sharedScreenModel.toggleFullScreen(isChecked) - } - ) + HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant) + + SettingsAccordion( + title = "Paramètres Général", + isExpanded = expandedGeneral, + onToggle = { + openExclusive("General") } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 4.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically ) { - Text( - text = "Thème Sombre", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface - ) - - Switch( - checked = tempDarkMode, - onCheckedChange = { checked -> - val newMode = if (checked) "DARK" else "LIGHT" - displayConfigManager.updateThemeMode(newMode) - }, - thumbContent = { - Icon( - imageVector = if (tempDarkMode) Icons.Default.DarkMode else Icons.Default.LightMode, - contentDescription = "Btn dark mode", - tint = MaterialTheme.colorScheme.onSecondary - ) - }, - colors = SwitchDefaults.colors( - checkedThumbColor = MaterialTheme.colorScheme.primary, - checkedTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.4f), - uncheckedThumbColor = MaterialTheme.colorScheme.error, - uncheckedTrackColor = MaterialTheme.colorScheme.error.copy(alpha = 0.2f), - checkedBorderColor = Color.Transparent, - uncheckedBorderColor = Color.Transparent, - checkedIconColor = Color.White, - uncheckedIconColor = MaterialTheme.colorScheme.onTertiary, - ) - ) - } - - HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = "Taille de la police", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface - ) - - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - IconButton( - onClick = { - if (currentDisplayConfig.fontSize > 8f) { - displayConfigManager.updateFontSize(currentDisplayConfig.fontSize - 0.5f) - } - }, - modifier = Modifier.size(32.dp).background(MaterialTheme.colorScheme.primary, CircleShape) - ) { - Text("-", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = Color.White) - } - - Spacer(modifier = Modifier.width(16.dp)) - - OutlinedTextField( - value = tempFontSize.toString(), - onValueChange = {}, - readOnly = true, - textStyle = MaterialTheme.typography.titleMedium.copy(textAlign = TextAlign.Center), - modifier = Modifier.width(80.dp), - shape = RoundedCornerShape(12.dp) - ) - - Spacer(modifier = Modifier.width(16.dp)) - - IconButton( - onClick = { - if (currentDisplayConfig.fontSize < 30f) { - displayConfigManager.updateFontSize(currentDisplayConfig.fontSize + 0.5f) - } - }, - modifier = Modifier.size(32.dp).background(MaterialTheme.colorScheme.primary, CircleShape) - ) { - Text("+", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = Color.White) - } - } - } - - HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "Personnalisation des couleurs", - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - fontWeight = FontWeight.Bold - ) - - val colorPickers = listOf( - Triple("Couleur Primaire", "primaryBlueHex", currentDisplayConfig.primaryBlueHex), - Triple("Couleur Secondaire", "accentGreenHex", currentDisplayConfig.accentGreenHex), - Triple("Couleur Tertiaire", "accentYellowHex", currentDisplayConfig.accentYellowHex), - Triple("Autre couleur", "accentPinkHex", currentDisplayConfig.accentPinkHex) - ) - - val availableColors = ThemeDefaults.availableColors - - colorPickers.forEach { (label, configKey, currentHex) -> - var showLocalPicker by remember { mutableStateOf(false) } - - Column(modifier = Modifier.fillMaxWidth()) { + if (isAndroid) { Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Text(label, style = MaterialTheme.typography.bodyMedium) - - Box( - modifier = Modifier - .size(36.dp) - .clip(CircleShape) - .background(currentHex.toColorOrDefault(Color.Gray)) - .border(2.dp, MaterialTheme.colorScheme.outline, CircleShape) - .clickable { showLocalPicker = !showLocalPicker } + Text( + text = "Mode Plein Écran", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface + ) + Switch( + checked = isFullScreenEnabled, + onCheckedChange = { isChecked -> + sharedScreenModel.toggleFullScreen(isChecked) + } ) } - - AnimatedVisibility(visible = showLocalPicker) { - FlowRow( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f), RoundedCornerShape(8.dp)) - .padding(12.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - availableColors.forEach { colorHex -> - Box( - modifier = Modifier - .size(32.dp) - .clip(CircleShape) - .background(colorHex.toColorOrDefault(Color.Gray)) - .border( - width = if (currentHex.uppercase() == colorHex.uppercase()) 2.5.dp else 0.dp, - color = MaterialTheme.colorScheme.onSurface, - shape = CircleShape - ) - .clickable { - displayConfigManager.updateCustomColor(configKey, colorHex) - } - ) - } - } - } } - } - } - Spacer(modifier = Modifier.weight(1f)) - OutlinedButton( - onClick = { displayConfigManager.resetToDefault() }, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp), - colors = ButtonDefaults.outlinedButtonColors( - contentColor = MaterialTheme.colorScheme.error - ) - ) { - Text("Réinitialiser", style = MaterialTheme.typography.labelLarge) - } - } - } - - SettingsAccordion( - title = "Paramètres Audio & Nuances", - isExpanded = expandedAudio, - onToggle = { - openExclusive("Audio") - } - ) { - Column(modifier = Modifier.padding(top = 8.dp), - verticalArrangement = Arrangement.spacedBy(2.dp) - ) { - Text( - text = "Réglage de la nuance", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface - ) - Surface( - shape = RoundedCornerShape(12.dp), - color = MaterialTheme.colorScheme.surfaceContainerLow, - modifier = Modifier.fillMaxWidth() - ) { - Row( - modifier = Modifier.fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(5.dp) + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - Switch( - checked = isDynamicEnabled, - onCheckedChange = { enabled -> - isDynamicEnabled = enabled - Dynamic.isGloballyEnabled = enabled - refreshTrigger++ - } - ) Text( - text = if (isDynamicEnabled) "Désactiver" else "Activer", + text = "Thème Sombre", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface ) + + Switch( + checked = tempDarkMode, + onCheckedChange = { checked -> + val newMode = if (checked) "DARK" else "LIGHT" + displayConfigManager.updateThemeMode(newMode) + }, + thumbContent = { + Icon( + imageVector = if (tempDarkMode) Icons.Default.DarkMode else Icons.Default.LightMode, + contentDescription = "Btn dark mode", + tint = MaterialTheme.colorScheme.onSecondary + ) + }, + colors = SwitchDefaults.colors( + checkedThumbColor = MaterialTheme.colorScheme.primary, + checkedTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.4f), + uncheckedThumbColor = MaterialTheme.colorScheme.error, + uncheckedTrackColor = MaterialTheme.colorScheme.error.copy(alpha = 0.2f), + checkedBorderColor = Color.Transparent, + uncheckedBorderColor = Color.Transparent, + checkedIconColor = Color.White, + uncheckedIconColor = MaterialTheme.colorScheme.onTertiary, + ) + ) } + HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "Taille de la police", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface + ) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + IconButton( + onClick = { + if (currentDisplayConfig.fontSize > 8f) { + displayConfigManager.updateFontSize(currentDisplayConfig.fontSize - 0.5f) + } + }, + modifier = Modifier.size(32.dp).background(MaterialTheme.colorScheme.primary, CircleShape) + ) { + Text( + "-", + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + } + + Spacer(modifier = Modifier.width(16.dp)) + + OutlinedTextField( + value = tempFontSize.toString(), + onValueChange = {}, + readOnly = true, + textStyle = MaterialTheme.typography.titleMedium.copy(textAlign = TextAlign.Center), + modifier = Modifier.width(80.dp), + shape = RoundedCornerShape(12.dp) + ) + + Spacer(modifier = Modifier.width(16.dp)) + + IconButton( + onClick = { + if (currentDisplayConfig.fontSize < 30f) { + displayConfigManager.updateFontSize(currentDisplayConfig.fontSize + 0.5f) + } + }, + modifier = Modifier.size(32.dp) + .background(MaterialTheme.colorScheme.primary, CircleShape) + ) { + Text( + "+", + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + } + } + } + + HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Personnalisation des couleurs", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold + ) + + val colorPickers = listOf( + Triple( + "Couleur Primaire", + "primaryBlueHex", + currentDisplayConfig.primaryBlueHex + ), + Triple( + "Couleur Secondaire", + "accentGreenHex", + currentDisplayConfig.accentGreenHex + ), + Triple( + "Couleur Tertiaire", + "accentYellowHex", + currentDisplayConfig.accentYellowHex + ), + Triple("Autre couleur", "accentPinkHex", currentDisplayConfig.accentPinkHex) + ) + + val availableColors = ThemeDefaults.availableColors + + colorPickers.forEach { (label, configKey, currentHex) -> + var showLocalPicker by remember { mutableStateOf(false) } + + Column(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(label, style = MaterialTheme.typography.bodyMedium) + + Box( + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(currentHex.toColorOrDefault(Color.Gray)) + .border(2.dp, MaterialTheme.colorScheme.outline, CircleShape) + .clickable { showLocalPicker = !showLocalPicker } + ) + } + + AnimatedVisibility(visible = showLocalPicker) { + FlowRow( + modifier = Modifier + .fillMaxWidth() + .background( + MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f), + RoundedCornerShape(8.dp) + ) + .padding(12.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + availableColors.forEach { colorHex -> + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(colorHex.toColorOrDefault(Color.Gray)) + .border( + width = if (currentHex.uppercase() == colorHex.uppercase()) 2.5.dp else 0.dp, + color = MaterialTheme.colorScheme.onSurface, + shape = CircleShape + ) + .clickable { + displayConfigManager.updateCustomColor( + configKey, + colorHex + ) + } + ) + } + } + } + } + } + } + + Spacer(modifier = Modifier.weight(1f)) OutlinedButton( - onClick = { - Dynamic.resetToDefaults() - isDynamicEnabled = true - refreshTrigger++ - }, - shape = RoundedCornerShape(8.dp), + onClick = { displayConfigManager.resetToDefault() }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), colors = ButtonDefaults.outlinedButtonColors( contentColor = MaterialTheme.colorScheme.error - ), - contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp) - ) { - Icon( - imageVector = Icons.AutoMirrored.Default.Undo, - contentDescription = "Btn reset dynamic volumes", - tint = MaterialTheme.colorScheme.error ) + ) { + Text("Réinitialiser", style = MaterialTheme.typography.labelLarge) } } } - - key(refreshTrigger) { - val dynamicEntries = Dynamic.entries - val minF = 0.22f - val maxF = 2.33f - val step = 0.05f - - Box( - modifier = Modifier - .fillMaxWidth() - .background(Color(0xFF121212).copy(alpha = 0.6f), RoundedCornerShape(16.dp)) - .padding(8.dp), - contentAlignment = Alignment.Center + SettingsAccordion( + title = "Paramètres Audio & Nuances", + isExpanded = expandedAudio, + onToggle = { + openExclusive("Audio") + } + ) { + Column( + modifier = Modifier.padding(top = 8.dp), + verticalArrangement = Arrangement.spacedBy(2.dp) ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(0.dp) + Text( + text = "Réglage de la nuance", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + Surface( + shape = RoundedCornerShape(12.dp), + color = MaterialTheme.colorScheme.surfaceContainerLow, + modifier = Modifier.fillMaxWidth() ) { - dynamicEntries.forEachIndexed { index, dynamicItem -> + Row( + modifier = Modifier.fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(1.dp), - modifier = Modifier.padding(vertical = 0.dp) + horizontalArrangement = Arrangement.spacedBy(5.dp) ) { - // (-) - if(index == 0) { - FilledIconButton( - onClick = { - if (isDynamicEnabled) { - val delta = - step - var insideBounds = true - dynamicEntries.forEach { item -> - val projectedValue = item.savedFactor + delta - if (projectedValue < minF || projectedValue > maxF) { - insideBounds = false - } - } - if (insideBounds) { - dynamicEntries.forEach { item -> - item.savedFactor = (item.savedFactor + delta).coerceIn(minF, maxF) - } - refreshTrigger++ - } - } - }, - enabled = isDynamicEnabled, - colors = IconButtonDefaults.filledIconButtonColors( - containerColor = Color(0xFF2C2C2C), - contentColor = Color.White - ), - modifier = Modifier.size(32.dp) - ) { - Text("-", fontWeight = FontWeight.Bold, fontSize = 16.sp) + Switch( + checked = isDynamicEnabled, + onCheckedChange = { enabled -> + isDynamicEnabled = enabled + Dynamic.isGloballyEnabled = enabled + refreshTrigger++ } - } else { - Spacer(modifier = Modifier.width(32.dp)) - } - Text( - text = dynamicItem.label.lowercase(), - style = MaterialTheme.typography.displaySmall.copy( - fontFamily = emmentaler - ), - fontWeight = FontWeight.Bold, - color = if (isDynamicEnabled) MaterialTheme.colorScheme.onSecondary else Color.Gray, - textAlign = TextAlign.Center, - modifier = Modifier.width(30.dp) ) - - Slider( - value = dynamicItem.savedFactor, - onValueChange = { newValue -> - if (isDynamicEnabled) { - val oldValue = dynamicItem.savedFactor - val delta = newValue - oldValue - var insideBounds = true - - dynamicEntries.forEach { item -> - val projectedValue = item.savedFactor + delta - if (projectedValue < minF || projectedValue > maxF) { - insideBounds = false - } - } - - if (insideBounds) { - dynamicEntries.forEach { item -> - item.savedFactor = (item.savedFactor + delta).coerceIn(minF, maxF) - } - refreshTrigger++ - } - } - }, - valueRange = minF..maxF, - enabled = isDynamicEnabled, - track = { sliderState -> - SliderDefaults.Track( - sliderState = sliderState, - enabled = isDynamicEnabled, - colors = SliderDefaults.colors( - activeTrackColor = if (isDynamicEnabled) Color.Blue else Color.Gray, - inactiveTrackColor = Color.LightGray - ), - modifier = Modifier.height(6.dp) - ) - }, - thumb = { - SliderDefaults.Thumb( - interactionSource = remember { MutableInteractionSource() }, - colors = SliderDefaults.colors(thumbColor = if(isDynamicEnabled) Color(0xFFFF4081) else Color.LightGray), - modifier = Modifier.size(12.dp), - ) - }, - colors = SliderDefaults.colors( - activeTrackColor = Color(0xFF7C4DFF), - inactiveTrackColor = Color(0xFF2C2C2C) - ), - modifier = Modifier - .weight(1f) - .padding(horizontal = 8.dp) - .height(16.dp) - ) - Text( - text = String.format("%.2f", if (isDynamicEnabled) dynamicItem.savedFactor else 1.00f), - fontSize = 11.sp, - fontWeight = FontWeight.Bold, - color = if (isDynamicEnabled) Color(0xFFDEFF9A) else Color.DarkGray, - textAlign = TextAlign.Center, - modifier = Modifier.width(30.dp) - ) - - // (+) - if(index == 0) { - FilledIconButton( - onClick = { - if (isDynamicEnabled) { - val delta = step - var insideBounds = true - dynamicEntries.forEach { item -> - val projectedValue = item.savedFactor + delta - if (projectedValue < minF || projectedValue > maxF) { - insideBounds = false - } - } - if (insideBounds) { - dynamicEntries.forEach { item -> - item.savedFactor = - (item.savedFactor + delta).coerceIn(minF, maxF) - } - refreshTrigger++ - } - } - }, - enabled = isDynamicEnabled, - colors = IconButtonDefaults.filledIconButtonColors( - containerColor = Color(0xFF2C2C2C), - contentColor = Color.White - ), - modifier = Modifier.size(32.dp) - ) { - Text("+", fontWeight = FontWeight.Bold, fontSize = 16.sp) - } - } else { - Spacer(modifier = Modifier.width(32.dp)) - } - } - } - } - } - } - - HorizontalDivider( - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.2f), - modifier = Modifier.padding(vertical = 12.dp) - ) - - var showInstrumentDialog by remember { mutableStateOf(false) } - Text( - text = "Règlage des Instruments", - style = MaterialTheme.typography.bodyLarge, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(bottom = 8.dp) - ) - Surface( - onClick = { showInstrumentDialog = true }, - shape = RoundedCornerShape(12.dp), - color = MaterialTheme.colorScheme.surfaceContainerHigh, - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)), - modifier = Modifier.fillMaxWidth() - ) { - Row( - modifier = Modifier.padding(14.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text( - text = "${selectedInstrument.program} - ${selectedInstrument.name}", - style = MaterialTheme.typography.bodyLarge, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary - ) - } - Icon( - imageVector = Icons.Default.KeyboardArrowDown, - contentDescription = "Changer l'instrument", - tint = MaterialTheme.colorScheme.primary - ) - } - } - - if (showInstrumentDialog) { - InstrumentPickerDialog( - instruments = instruments, - selectedProgram = globalInstrumentProgram, - onInstrumentSelected = { instrument -> - globalInstrumentProgram = instrument.program - sharedScreenModel.saveVoiceInstrument(instrument.program) - showInstrumentDialog = false - }, - onDismissRequest = { showInstrumentDialog = false } - ) - } - - HorizontalDivider( - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.2f), - modifier = Modifier.padding(vertical = 12.dp) - ) - - Text( - text = "Règlage du lecteur", - style = MaterialTheme.typography.bodyLarge, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(bottom = 8.dp) - ) - - Surface( - shape = RoundedCornerShape(12.dp), - color = MaterialTheme.colorScheme.surfaceContainerLow, - modifier = Modifier.fillMaxWidth() - ) { - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Décompte avant la lecture ", - style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(bottom = 8.dp) - ) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) - ) { - if (!isAndroid) { - val countInStat = if (hasCountIn) "Désactiver" else "Activer" - Text( - text = countInStat, - style = MaterialTheme.typography.bodyMedium + text = if (isDynamicEnabled) "Désactiver" else "Activer", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface ) } - Switch( - checked = hasCountIn, - onCheckedChange = { enabled -> - hasCountIn = enabled - prefs.putBoolean("isCountIn", enabled) + + OutlinedButton( + onClick = { + Dynamic.resetToDefaults() + isDynamicEnabled = true + refreshTrigger++ }, - colors = SwitchDefaults.colors( - checkedThumbColor = MaterialTheme.colorScheme.onPrimary, - checkedTrackColor = MaterialTheme.colorScheme.primary, - uncheckedThumbColor = MaterialTheme.colorScheme.outline, - uncheckedTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, - uncheckedBorderColor = MaterialTheme.colorScheme.outline + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.error ), - modifier = Modifier.size(45.dp) - ) + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp) + ) { + Icon( + imageVector = Icons.AutoMirrored.Default.Undo, + contentDescription = "Btn reset dynamic volumes", + tint = MaterialTheme.colorScheme.error + ) + } } } + + + key(refreshTrigger) { + val dynamicEntries = Dynamic.entries + val minF = 0.20f + val maxF = 1.59f + val gapStep = 0.01f + val mfIndex = dynamicEntries.indexOfFirst { it == Dynamic.MF }.let { if (it == -1) 4 else it } + + Box( + modifier = Modifier + .fillMaxWidth() + .background(Color(0xFF121212).copy(alpha = 0.6f), RoundedCornerShape(16.dp)) + .padding(8.dp), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(0.dp) + ) { + dynamicEntries.forEachIndexed { index, dynamicItem -> + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(1.dp), + modifier = Modifier.padding(vertical = 0.dp) + ) { + // (-) + if (index == 0) { + FilledIconButton( + onClick = { + if (isDynamicEnabled) { + val deltaGap = gapStep + val projected = dynamicEntries.mapIndexed { i, item -> + item.savedFactor + ((i - mfIndex) * deltaGap) + } + val insideBounds = projected.all { it in minF..maxF } + val maintainsOrder = projected.zipWithNext().all { (a, b) -> a < b } + + if (insideBounds && maintainsOrder) { + dynamicEntries.forEachIndexed { i, item -> + item.savedFactor = projected[i].coerceIn(minF, maxF) + } + refreshTrigger++ + } + } + }, + enabled = isDynamicEnabled, + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = Color(0xFF2C2C2C), + contentColor = Color.White + ), + modifier = Modifier.size(32.dp) + ) { + Text("-", fontWeight = FontWeight.Bold, fontSize = 16.sp) + } + } else { + Spacer(modifier = Modifier.width(32.dp)) + } + + Text( + text = dynamicItem.label.lowercase(), + style = MaterialTheme.typography.displaySmall.copy( + fontFamily = emmentaler + ), + fontWeight = FontWeight.Bold, + color = if (isDynamicEnabled) MaterialTheme.colorScheme.onSecondary else Color.Gray, + textAlign = TextAlign.Center, + modifier = Modifier.width(30.dp) + ) + + Slider( + value = dynamicItem.savedFactor, + onValueChange = { newValue -> + if (isDynamicEnabled) { + val oldValue = dynamicItem.savedFactor + val delta = newValue - oldValue + + if (delta != 0f) { + val deltaGap = delta + val projected = dynamicEntries.mapIndexed { i, item -> + item.savedFactor + ((i - mfIndex) * deltaGap) + } + val insideBounds = projected.all { it in minF..maxF } + val maintainsOrder = projected.zipWithNext().all { (a, b) -> a < b } + + if (insideBounds && maintainsOrder) { + dynamicEntries.forEachIndexed { i, item -> + item.savedFactor = projected[i].coerceIn(minF, maxF) + } + refreshTrigger++ + } + } + } + }, + valueRange = minF..maxF, + enabled = isDynamicEnabled, + track = { sliderState -> + SliderDefaults.Track( + sliderState = sliderState, + enabled = isDynamicEnabled, + colors = SliderDefaults.colors( + activeTrackColor = if (isDynamicEnabled) Color.Blue else Color.Gray, + inactiveTrackColor = Color.LightGray + ), + modifier = Modifier.height(6.dp) + ) + }, + thumb = { + SliderDefaults.Thumb( + interactionSource = remember { MutableInteractionSource() }, + colors = SliderDefaults.colors( + thumbColor = if (isDynamicEnabled) Color(0xFFFF4081) else Color.LightGray + ), + modifier = Modifier.size(12.dp), + ) + }, + colors = SliderDefaults.colors( + activeTrackColor = Color(0xFF7C4DFF), + inactiveTrackColor = Color(0xFF2C2C2C) + ), + modifier = Modifier + .weight(1f) + .padding(horizontal = 8.dp) + .height(16.dp) + ) + + Text( + text = String.format( + "%.2f", + if (isDynamicEnabled) dynamicItem.savedFactor else 1.00f + ), + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = if (isDynamicEnabled) Color(0xFFDEFF9A) else Color.DarkGray, + textAlign = TextAlign.Center, + modifier = Modifier.width(30.dp) + ) + + // (+) + if (index == 0) { + FilledIconButton( + onClick = { + if (isDynamicEnabled) { + val deltaGap = -gapStep + val projected = dynamicEntries.mapIndexed { i, item -> + item.savedFactor + ((i - mfIndex) * deltaGap) + } + val insideBounds = projected.all { it in minF..maxF } + val maintainsOrder = projected.zipWithNext().all { (a, b) -> a < b } + + if (insideBounds && maintainsOrder) { + dynamicEntries.forEachIndexed { i, item -> + item.savedFactor = projected[i].coerceIn(minF, maxF) + } + refreshTrigger++ + } + } + }, + enabled = isDynamicEnabled, + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = Color(0xFF2C2C2C), + contentColor = Color.White + ), + modifier = Modifier.size(32.dp) + ) { + Text("+", fontWeight = FontWeight.Bold, fontSize = 16.sp) + } + } else { + Spacer(modifier = Modifier.width(32.dp)) + } + } + } + } + } + } + HorizontalDivider( color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.2f), - modifier = Modifier.padding(vertical = 4.dp) + modifier = Modifier.padding(vertical = 12.dp) ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + + var showInstrumentDialog by remember { mutableStateOf(false) } + Text( + text = "Règlage des Instruments", + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 8.dp) + ) + Surface( + onClick = { showInstrumentDialog = true }, + shape = RoundedCornerShape(12.dp), + color = MaterialTheme.colorScheme.surfaceContainerHigh, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)), + modifier = Modifier.fillMaxWidth() ) { - Text( - text = "A-B looping intelligent", - style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(bottom = 8.dp) - ) Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) + modifier = Modifier.padding(14.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - if(!isAndroid) { - val stat = if (isSmartPointBLoop) "Désactiver" else "Activer" + Column { Text( - text = stat, - style = MaterialTheme.typography.bodyMedium + text = "${selectedInstrument.program} - ${selectedInstrument.name}", + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary ) } - Switch( - checked = isSmartPointBLoop, - onCheckedChange = { enabled -> - isSmartPointBLoop = enabled - prefs.putBoolean("smartBpoint", enabled) - }, - colors = SwitchDefaults.colors( - checkedThumbColor = MaterialTheme.colorScheme.onPrimary, - checkedTrackColor = MaterialTheme.colorScheme.primary, - uncheckedThumbColor = MaterialTheme.colorScheme.outline, - uncheckedTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, - uncheckedBorderColor = MaterialTheme.colorScheme.outline - ), - modifier = Modifier.size(45.dp) + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = "Changer l'instrument", + tint = MaterialTheme.colorScheme.primary ) } } + + if (showInstrumentDialog) { + InstrumentPickerDialog( + instruments = instruments, + selectedProgram = globalInstrumentProgram, + onInstrumentSelected = { instrument -> + globalInstrumentProgram = instrument.program + sharedScreenModel.saveVoiceInstrument(instrument.program) + showInstrumentDialog = false + }, + onDismissRequest = { showInstrumentDialog = false } + ) + } + + HorizontalDivider( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.2f), + modifier = Modifier.padding(vertical = 12.dp) + ) + + Text( + text = "Règlage du lecteur", + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 8.dp) + ) + + Surface( + shape = RoundedCornerShape(12.dp), + color = MaterialTheme.colorScheme.surfaceContainerLow, + modifier = Modifier.fillMaxWidth() + ) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Décompte avant la lecture ", + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 8.dp) + ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + if (!isAndroid) { + val countInStat = if (hasCountIn) "Désactiver" else "Activer" + Text( + text = countInStat, + style = MaterialTheme.typography.bodyMedium + ) + } + Switch( + checked = hasCountIn, + onCheckedChange = { enabled -> + hasCountIn = enabled + prefs.putBoolean("isCountIn", enabled) + }, + colors = SwitchDefaults.colors( + checkedThumbColor = MaterialTheme.colorScheme.onPrimary, + checkedTrackColor = MaterialTheme.colorScheme.primary, + uncheckedThumbColor = MaterialTheme.colorScheme.outline, + uncheckedTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, + uncheckedBorderColor = MaterialTheme.colorScheme.outline + ), + modifier = Modifier.size(45.dp) + ) + } + } + HorizontalDivider( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.2f), + modifier = Modifier.padding(vertical = 4.dp) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "A-B looping intelligent", + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 8.dp) + ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + if (!isAndroid) { + val stat = if (isSmartPointBLoop) "Désactiver" else "Activer" + Text( + text = stat, + style = MaterialTheme.typography.bodyMedium + ) + } + Switch( + checked = isSmartPointBLoop, + onCheckedChange = { enabled -> + isSmartPointBLoop = enabled + prefs.putBoolean("smartBpoint", enabled) + }, + colors = SwitchDefaults.colors( + checkedThumbColor = MaterialTheme.colorScheme.onPrimary, + checkedTrackColor = MaterialTheme.colorScheme.primary, + uncheckedThumbColor = MaterialTheme.colorScheme.outline, + uncheckedTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, + uncheckedBorderColor = MaterialTheme.colorScheme.outline + ), + modifier = Modifier.size(45.dp) + ) + } + } + } + } } } }