Optimize midi control space & modernize volume slider

This commit is contained in:
hasinarak3@gmail.com 2026-02-03 16:06:30 +03:00
parent 337f1d9912
commit da1d130085
2 changed files with 177 additions and 121 deletions

View file

@ -46,6 +46,8 @@ import androidx.compose.material.icons.filled.Tonality
import androidx.compose.material.icons.filled.Tune import androidx.compose.material.icons.filled.Tune
import androidx.compose.material.icons.automirrored.filled.VolumeUp import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.automirrored.filled.VolumeOff import androidx.compose.material.icons.automirrored.filled.VolumeOff
import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -80,7 +82,9 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import mg.dot.feufaro.getPlatform
import mg.dot.feufaro.midi.MediaPlayer import mg.dot.feufaro.midi.MediaPlayer
import org.koin.core.component.getScopeId
import javax.swing.Icon import javax.swing.Icon
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -120,7 +124,7 @@ fun MidiControlPanel(
var showInstruTools by remember { mutableStateOf(false) } var showInstruTools by remember { mutableStateOf(false) }
var showSATBTools by remember { mutableStateOf(false) } var showSATBTools by remember { mutableStateOf(false) }
var showVolumeTools by remember { mutableStateOf(false) } var showVolumeTools by remember { mutableStateOf(false) }
var expandedCtl by remember { mutableStateOf(false) }
LaunchedEffect(tempo) { LaunchedEffect(tempo) {
currentBpm = mediaPlayer.getCurrentBPM() currentBpm = mediaPlayer.getCurrentBPM()
} }
@ -131,64 +135,48 @@ fun MidiControlPanel(
.background(color = Color.Gray.copy(alpha = 0.5f), shape = RoundedCornerShape(size = 5.dp)), .background(color = Color.Gray.copy(alpha = 0.5f), shape = RoundedCornerShape(size = 5.dp)),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Row ( AnimatedVisibility(
verticalAlignment = Alignment.CenterVertically, visible = !expandedCtl
horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Text("${currentPos.toInt() / 1000}s", color = Color.White) Row(
Slider( verticalAlignment = Alignment.CenterVertically,
value = currentPos, horizontalArrangement = Arrangement.spacedBy(8.dp)
onValueChange = onSeek,
valueRange = 0f..(if (duration > 0) duration else 1f),
modifier = Modifier.weight(1f),
colors = SliderDefaults.colors(
thumbColor = Color.Red,
activeTrackColor = Color.Green,
inactiveTrackColor = Color.Gray,
inactiveTickColor = Color(0xffb0BEC5),
disabledThumbColor = Color(0xff78909C),
disabledActiveTickColor = Color(0xff757575),
disabledActiveTrackColor = Color(0xffBDBDBD),
disabledInactiveTickColor = Color(0xff616161),
disabledInactiveTrackColor = Color(0xffBCAAA4),
),
thumb = {
Box (
modifier = Modifier
.size(15.dp)
.background(Color.Gray, CircleShape)
)
},
track = { sliderState ->
SliderDefaults.Track(
sliderState = sliderState,
modifier = Modifier.height(5.dp)
)
}
)
Text("${momo / 1000}s", color = Color.White)
}
Row(
verticalAlignment = Alignment.CenterVertically
){
IconButton(
onClick = onPlayPauseClick,
modifier = Modifier.size(48.dp)
) { ) {
Icon( Text("${currentPos.toInt() / 1000}s", color = Color.White)
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause, Slider(
contentDescription = "Pla", value = currentPos,
tint = MaterialTheme.colorScheme.primary onValueChange = onSeek,
valueRange = 0f..(if (duration > 0) duration else 1f),
modifier = Modifier.weight(1f),
colors = SliderDefaults.colors(
thumbColor = Color.Red,
activeTrackColor = Color.Green,
inactiveTrackColor = Color.Gray,
inactiveTickColor = Color(0xffb0BEC5),
disabledThumbColor = Color(0xff78909C),
disabledActiveTickColor = Color(0xff757575),
disabledActiveTrackColor = Color(0xffBDBDBD),
disabledInactiveTickColor = Color(0xff616161),
disabledInactiveTrackColor = Color(0xffBCAAA4),
),
thumb = {
Box(
modifier = Modifier
.size(15.dp)
.background(Color.Gray, CircleShape)
)
},
track = { sliderState ->
SliderDefaults.Track(
sliderState = sliderState,
modifier = Modifier.height(5.dp)
)
}
) )
Text("${momo / 1000}s", color = Color.White)
} }
} }
AnimatedVisibility( AnimatedVisibility(
visible = showSATBTools, visible = showSATBTools,
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 }, enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
@ -302,10 +290,22 @@ fun MidiControlPanel(
} }
} }
} }
Row( Row(
verticalAlignment = Alignment.CenterVertically modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround
){ ){
Column(horizontalAlignment = Alignment.CenterHorizontally) { Box(contentAlignment = Alignment.CenterStart) {
IconButton(
onClick = {
showBPMTools = !showBPMTools
}
) {
Icon(imageVector = Icons.Default.AvTimer, contentDescription = "Tempo")
}
}
Box( contentAlignment = Alignment.CenterStart) {
val isAtStart = loopState.first == -1L val isAtStart = loopState.first == -1L
val isWaitingForB = loopState.first != -1L && !loopState.third val isWaitingForB = loopState.first != -1L && !loopState.third
val isLooping = loopState.third val isLooping = loopState.third
@ -326,8 +326,15 @@ fun MidiControlPanel(
else -> Color.LightGray else -> Color.LightGray
} }
), ),
shape = RoundedCornerShape(5.dp) , contentPadding = PaddingValues(horizontal = 8.dp, vertical = 0.dp),
modifier = Modifier.padding(2.dp) shape = RoundedCornerShape(5.dp) ,
modifier = Modifier
.height(34.dp)
.padding(horizontal = 4.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
Text( Text(
text = "A", text = "A",
@ -349,80 +356,56 @@ fun MidiControlPanel(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 18.sp fontSize = 18.sp
) )
}
} }
} }
Box(contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
IconButton(
onClick = {
showBPMTools = !showBPMTools
}) {
Icon(imageVector = Icons.Default.AvTimer, contentDescription = "Tempo")
}
AnimatedVisibility(visible = showBPMTools){
Box(
modifier = Modifier
.width(20.dp)
.height(3.dp)
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp))
)}
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
IconButton(
onClick = {
showInstruTools = !showInstruTools
}) {
Icon(imageVector = Icons.Default.Tune, contentDescription = "Instru")
}
AnimatedVisibility(visible = showInstruTools){
Box(
modifier = Modifier
.width(20.dp)
.height(3.dp)
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp))
)}
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
IconButton( IconButton(
onClick = { onClick = {
showSATBTools = !showSATBTools showSATBTools = !showSATBTools
}) { }) {
Icon(imageVector = Icons.Default.SettingsVoice, contentDescription = "SATB") Icon(imageVector = Icons.Default.SettingsVoice, contentDescription = "SATB")
} }
AnimatedVisibility(visible = showSATBTools){
Box(
modifier = Modifier
.width(20.dp)
.height(3.dp)
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp))
)}
} }
Box(contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { IconButton(
Icon( onClick = onPlayPauseClick,
imageVector = if (volume > 0) Icons.AutoMirrored.Filled.VolumeUp else Icons.AutoMirrored.Filled.VolumeOff, modifier = Modifier.size(48.dp)
contentDescription = null, ) {
modifier = Modifier.size(20.dp) Icon(
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause,
contentDescription = "Pla",
tint = MaterialTheme.colorScheme.primary
)
}
}
Box(contentAlignment = Alignment.CenterEnd) {
IconButton(
onClick = {
showInstruTools = !showInstruTools
}
) {
Icon(imageVector = Icons.Default.Tune, contentDescription = "Instru")
}
}
val platform = getPlatform()
if (platform.name.startsWith("Java")) {
ModernVolumeSlider(
volume, onVolumeChange = onVolumeChange
) )
} }
Column(horizontalAlignment = Alignment.CenterHorizontally) { Row(
Slider( horizontalArrangement = Arrangement.End,
value = volume, verticalAlignment = Alignment.CenterVertically
onValueChange = onVolumeChange, ) {
valueRange = 0f..1f, Spacer(modifier = Modifier.width(8.dp))
modifier = Modifier.width(100.dp), IconButton(onClick = { expandedCtl = !expandedCtl }) {
thumb = { Icon(
Box( imageVector = if (expandedCtl) Icons.Default.MoreHoriz else Icons.Default.MoreVert,
modifier = Modifier.size(15.dp).background(Color.Blue, CircleShape) contentDescription = "More",
) tint = Color.White
}, )
track = { sliderState -> }
SliderDefaults.Track(
sliderState = sliderState, modifier = Modifier.height(5.dp)
)
})
} }
} }
} }

View file

@ -0,0 +1,73 @@
package mg.dot.feufaro.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.VolumeDown
import androidx.compose.material.icons.automirrored.filled.VolumeOff
import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ModernVolumeSlider(
volume: Float,
onVolumeChange: (Float) -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.height(42.dp)
.background(Color.White.copy(alpha = 0.1f), CircleShape)
.padding(horizontal = 12.dp, vertical = 4.dp)
) {
Icon(
imageVector = when {
volume == 0f -> Icons.AutoMirrored.Filled.VolumeOff
volume < 0.5f -> Icons.AutoMirrored.Filled.VolumeDown
else -> Icons.AutoMirrored.Filled.VolumeUp
},
contentDescription = null,
tint = if (volume > 0) Color.Black else Color.Gray,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Slider(
value = volume,
onValueChange = onVolumeChange,
modifier = Modifier.fillMaxWidth(0.30f),
colors = SliderDefaults.colors(
activeTrackColor = Color(0xFFF59E0B),
inactiveTrackColor = Color.White.copy(alpha = 0.2f),
thumbColor = Color.Transparent
),
track = { sliderState ->
SliderDefaults.Track(
sliderState = sliderState,
modifier = Modifier.height(15.dp),
thumbTrackGapSize = 0.dp
)
},
thumb = {
Box(Modifier.size(0.dp))
}
)
}
}