diff --git a/components/src/commonMain/kotlin/com/eimsound/daw/components/FloatingLayer.kt b/components/src/commonMain/kotlin/com/eimsound/daw/components/FloatingLayer.kt index 1a2bc7f3..467638e9 100644 --- a/components/src/commonMain/kotlin/com/eimsound/daw/components/FloatingLayer.kt +++ b/components/src/commonMain/kotlin/com/eimsound/daw/components/FloatingLayer.kt @@ -45,12 +45,15 @@ class FloatingLayerProvider { return k } - fun closeFloatingLayer(key: Any) { + fun closeFloatingLayer(key: Any): Boolean { + var closed = false floatingLayers.fastForEach { if (it.key != key) return@fastForEach if (it.isShow) it.isClosed = true else floatingLayers = floatingLayers.filter { f -> it != f } + closed = true } + return closed } fun setFloatingLayerShow(key: Any, show: Boolean) { @@ -119,7 +122,7 @@ val LocalFloatingLayerProvider = staticCompositionLocalOf { FloatingLayerProvide @Composable fun FloatingLayer( layerContent: @Composable (DpSize, () -> Unit) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, - hasOverlay: Boolean = false, isCentral: Boolean = false, content: @Composable BoxScope.() -> Unit + hasOverlay: Boolean = false, isCentral: Boolean = false, content: @Composable BoxScope.(Boolean) -> Unit ) { @OptIn(ExperimentalFoundationApi::class) FloatingLayer(layerContent, modifier, enabled, hasOverlay, isCentral, PointerMatcher.Primary, content = content) @@ -130,11 +133,15 @@ fun FloatingLayer( fun FloatingLayer( layerContent: @Composable (DpSize, () -> Unit) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, hasOverlay: Boolean = false, isCentral: Boolean = false, matcher: PointerMatcher = PointerMatcher.Primary, - pass: PointerEventPass = PointerEventPass.Main, content: @Composable BoxScope.() -> Unit + pass: PointerEventPass = PointerEventPass.Main, content: @Composable BoxScope.(Boolean) -> Unit ) { val id = remember { Any() } + var isOpened by remember { mutableStateOf(false) } val localFloatingLayerProvider = LocalFloatingLayerProvider.current - val closeLayer = remember { { localFloatingLayerProvider.closeFloatingLayer(id) } } + val closeLayer = remember { { + isOpened = false + localFloatingLayerProvider.closeFloatingLayer(id) + } } val offset = remember { arrayOf(Offset.Zero) } val size = remember { arrayOf(Size.Zero) } Box((if (isCentral) modifier else modifier.onGloballyPositioned { @@ -146,13 +153,14 @@ fun FloatingLayer( while (true) { val event = awaitPointerEvent(pass) if (event.type != PointerEventType.Press || !matcher.matches(event)) continue + isOpened = true localFloatingLayerProvider.openFloatingLayer({ closeLayer() }, if (isCentral) null else offset[0] + Offset(0f, size[0].height), id, hasOverlay ) { layerContent(size[0].toDpSize(), closeLayer) } } } } - ) { content() } + ) { content(isOpened) } } @Composable @@ -201,7 +209,7 @@ fun FloatingLayerProvider.openEditorMenu( lastMenuOpenedTime = t val key = Any() - val close = { closeFloatingLayer(key) } + val close: () -> Unit = { closeFloatingLayer(key) } openFloatingLayer({ close() }, position, key, overflow = true) { Dialog { diff --git a/components/src/commonMain/kotlin/com/eimsound/daw/components/Menu.kt b/components/src/commonMain/kotlin/com/eimsound/daw/components/Menu.kt index 777ac09f..c5cb8ac7 100644 --- a/components/src/commonMain/kotlin/com/eimsound/daw/components/Menu.kt +++ b/components/src/commonMain/kotlin/com/eimsound/daw/components/Menu.kt @@ -1,5 +1,6 @@ package com.eimsound.daw.components +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -18,12 +19,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.UiComposable import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.layout.Layout +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextDecoration @@ -90,7 +93,7 @@ fun DropdownMenu( boxModifier: Modifier = Modifier, enabled: Boolean = true, matcher: PointerMatcher = PointerMatcher.Primary, - content: @Composable BoxScope.() -> Unit, + content: @Composable BoxScope.(Boolean) -> Unit, ) { FloatingLayer({ size, close -> Surface( @@ -131,7 +134,7 @@ fun OutlinedDropdownSelector( label: String? = null, readonly: Boolean = false, colors: TextFieldColors? = null, - content: (@Composable () -> Unit)? = null, + content: (@Composable (Boolean) -> Unit)? = null, ) { @OptIn(ExperimentalFoundationApi::class) DropdownSelector( @@ -152,25 +155,25 @@ fun AutoWidthOutlinedDropdownSelector( label: String? = null, colors: TextFieldColors? = null ) { - var modifier = boxModifier - val textMeasurer = rememberTextMeasurer() - val textWidth = remember(items, items.size) { - (items.maxOfOrNull { textMeasurer.measure(it.displayName).size.width.dp } ?: 0.dp) + 32.dp + BoxWithConstraints { + val textMeasurer = rememberTextMeasurer() + val density = LocalDensity.current + OutlinedDropdownSelector( + onSelected, + items, + selected, + boxModifier.width(remember(items, items.size, constraints.maxWidth, density) { + density.run { ((items.maxOfOrNull { textMeasurer.measure(it.displayName).size.width } ?: 200) + 140) + .coerceAtMost(constraints.maxWidth).toDp() } + }), + enabled, + isSelected, + itemContent, + label, + readonly = true, + colors + ) } - val maxWidth = if (textWidth > 256.dp) 256.dp else textWidth - modifier = modifier.width(maxWidth) - OutlinedDropdownSelector( - onSelected, - items, - selected, - modifier, - enabled, - isSelected, - itemContent, - label, - readonly = true, - colors - ) } @Composable @@ -185,7 +188,7 @@ fun DropdownSelector( label: String? = null, readOnly: Boolean = false, colors: TextFieldColors? = null, - content: (@Composable () -> Unit)? = null, + content: (@Composable (Boolean) -> Unit)? = null, ) { @OptIn(ExperimentalFoundationApi::class) DropdownSelector( @@ -194,6 +197,15 @@ fun DropdownSelector( ) } +@Composable +fun ExpandIcon(isExpanded: Boolean = false) { + Icon( + Icons.Filled.ExpandMore, "Expand", + Modifier.size(20.dp).pointerHoverIcon(PointerIcon.Hand).clip(CircleShape) + .rotate(animateFloatAsState(if (isExpanded) 180F else 360F).value).clickable { } + ) +} + @Composable fun DropdownSelector( onSelected: (T) -> Unit, @@ -208,7 +220,7 @@ fun DropdownSelector( isOutlined: Boolean = false, readonly: Boolean = false, colors: TextFieldColors? = null, - content: (@Composable () -> Unit)? = null, + content: (@Composable (Boolean) -> Unit)? = null, ) { val filter = remember { mutableStateOf(null) } @OptIn(ExperimentalFoundationApi::class) FloatingLayer({ size, close -> @@ -241,34 +253,24 @@ fun DropdownSelector( if (content == null) { if (isOutlined) CustomOutlinedTextField( filter.value ?: selected?.displayName ?: "", - { filter.value = it.ifEmpty { null } }, + { v -> filter.value = v.ifEmpty { null } }, boxModifier.pointerHoverIcon(PointerIcon.Hand), label = if (label == null) null else ({ Text(label) }), singleLine = true, readOnly = readonly, colors = colors ?: TextFieldDefaults.colors(), - suffix = { - Icon( - Icons.Filled.ExpandMore, "Expand", - Modifier.size(20.dp).pointerHoverIcon(PointerIcon.Hand).clip(CircleShape).clickable { } - ) - } + suffix = { ExpandIcon(it) } ) else CustomTextField( filter.value ?: selected?.displayName ?: "", - { filter.value = it.ifEmpty { null } }, + { v -> filter.value = v.ifEmpty { null } }, boxModifier.pointerHoverIcon(PointerIcon.Hand), label = if (label == null) null else ({ Text(label) }), singleLine = true, readOnly = readonly, colors = colors ?: TextFieldDefaults.colors(), - suffix = { - Icon( - Icons.Filled.ExpandMore, "Expand", - Modifier.size(20.dp).pointerHoverIcon(PointerIcon.Hand).clip(CircleShape).clickable { } - ) - } + suffix = { ExpandIcon(it) } ) - } else content() + } else content(it) } } diff --git a/components/src/commonMain/kotlin/com/eimsound/daw/components/Settings.kt b/components/src/commonMain/kotlin/com/eimsound/daw/components/Settings.kt index cc6cb5bb..8c56ef0c 100644 --- a/components/src/commonMain/kotlin/com/eimsound/daw/components/Settings.kt +++ b/components/src/commonMain/kotlin/com/eimsound/daw/components/Settings.kt @@ -48,6 +48,7 @@ fun SettingsCard( .padding(16.dp, EXPANDER_PADDING_VERTICAL) ) { Text(header) + Gap(16) Filled() content() } diff --git a/daw/src/jvmMain/kotlin/com/eimsound/daw/components/BasicAudioParameterView.kt b/daw/src/jvmMain/kotlin/com/eimsound/daw/components/BasicAudioParameterView.kt index 5ab529f3..65d13579 100644 --- a/daw/src/jvmMain/kotlin/com/eimsound/daw/components/BasicAudioParameterView.kt +++ b/daw/src/jvmMain/kotlin/com/eimsound/daw/components/BasicAudioParameterView.kt @@ -31,7 +31,7 @@ fun BasicAudioParameterView(parameters: List, uuid: UUI private fun FloatingLayerProvider.openParameterSelector(processor: TrackAudioProcessorWrapper) { val key = Any() var tmpSelectedParameter = "" - val close = { closeFloatingLayer(key) } + val close: () -> Unit = { closeFloatingLayer(key) } openFloatingLayer(::closeFloatingLayer, key = key, hasOverlay = true) { Dialog({ close() diff --git a/daw/src/jvmMain/kotlin/com/eimsound/daw/components/app/AppBar.kt b/daw/src/jvmMain/kotlin/com/eimsound/daw/components/app/AppBar.kt index ac55df60..bd157e7f 100644 --- a/daw/src/jvmMain/kotlin/com/eimsound/daw/components/app/AppBar.kt +++ b/daw/src/jvmMain/kotlin/com/eimsound/daw/components/app/AppBar.kt @@ -1,8 +1,6 @@ package com.eimsound.daw.components.app -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* @@ -11,7 +9,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.layout.Layout @@ -63,11 +60,7 @@ private fun CurrentTime() { Modifier.width(110.dp), singleLine = true, textStyle = MaterialTheme.typography.labelLarge.copy(LocalContentColor.current), - suffix = { - Icon(Icons.Filled.ExpandMore, "Expand", - Modifier.size(20.dp).pointerHoverIcon(PointerIcon.Hand).clip(CircleShape).clickable { } - ) - }, + suffix = { ExpandIcon(it) }, colors = textFieldGrayColors(), paddingValues = TextFieldDefaults.contentPaddingWithLabel(8.dp, 4.dp, 3.dp, 4.dp) ) @@ -93,11 +86,7 @@ private fun Quantification() { prefix = { Icon(Magnet, "Quantification", modifier = Modifier.size(15.dp).offset((-2).dp, 1.dp)) }, - suffix = { - Icon(Icons.Filled.ExpandMore, "Expand", - Modifier.size(20.dp).pointerHoverIcon(PointerIcon.Hand).clip(CircleShape).clickable { } - ) - }, + suffix = { ExpandIcon(it) }, colors = textFieldGrayColors(), paddingValues = TextFieldDefaults.contentPaddingWithLabel(8.dp, 4.dp, 3.dp, 4.dp) ) diff --git a/daw/src/jvmMain/kotlin/com/eimsound/daw/impl/clips/midi/editor/EventEditor.kt b/daw/src/jvmMain/kotlin/com/eimsound/daw/impl/clips/midi/editor/EventEditor.kt index d702a6e1..77bbada4 100644 --- a/daw/src/jvmMain/kotlin/com/eimsound/daw/impl/clips/midi/editor/EventEditor.kt +++ b/daw/src/jvmMain/kotlin/com/eimsound/daw/impl/clips/midi/editor/EventEditor.kt @@ -161,7 +161,7 @@ private val defaultCCEvents = sortedMapOf( @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) private fun FloatingLayerProvider.openEventSelectorDialog(events: MutableMidiCCEvents) { val key = Any() - val close = { closeFloatingLayer(key) } + val close: () -> Unit = { closeFloatingLayer(key) } openFloatingLayer(::closeFloatingLayer, key = key, hasOverlay = true) { Dialog(close, modifier = Modifier.widthIn(max = 460.dp)) { Text("选择 CC 事件", style = MaterialTheme.typography.titleMedium) diff --git a/daw/src/jvmMain/kotlin/com/eimsound/daw/window/MainWindow.kt b/daw/src/jvmMain/kotlin/com/eimsound/daw/window/MainWindow.kt index 20af4fc2..3b2b5f6b 100644 --- a/daw/src/jvmMain/kotlin/com/eimsound/daw/window/MainWindow.kt +++ b/daw/src/jvmMain/kotlin/com/eimsound/daw/window/MainWindow.kt @@ -74,11 +74,11 @@ private fun MainWindowContent(window: ComposeWindow) { @Composable private fun SaveProjectWarningDialog() { - val windowState = rememberDialogState(width = 400.dp, height = 160.dp) + val windowState = rememberDialogState(width = 360.dp, height = 200.dp) val windowManager = EchoInMirror.windowManager if (windowManager.isSaveProjectWarningDialogOpened) DialogWindow({ windowManager.isSaveProjectWarningDialogOpened = false - }, windowState, resizable = false, title = "是否保存项目并退出?" + }, windowState, title = "是否保存项目并退出?" ) { Surface(Modifier.fillMaxSize(), tonalElevation = 4.dp) { Column(Modifier.padding(20.dp)) { diff --git a/daw/src/jvmMain/kotlin/com/eimsound/daw/window/dialogs/QuickLoadDialog.kt b/daw/src/jvmMain/kotlin/com/eimsound/daw/window/dialogs/QuickLoadDialog.kt index f6cc0bc8..a2f32899 100644 --- a/daw/src/jvmMain/kotlin/com/eimsound/daw/window/dialogs/QuickLoadDialog.kt +++ b/daw/src/jvmMain/kotlin/com/eimsound/daw/window/dialogs/QuickLoadDialog.kt @@ -84,18 +84,13 @@ private fun saveFavoriteAudioProcessors() { } } -private var isQuickLoadDialogOpen = false fun FloatingLayerProvider.openQuickLoadDialog(onClose: ((AudioProcessorDescriptionAndFactory?) -> Unit)? = null) { - if (isQuickLoadDialogOpen) { - closeFloatingLayer(KEY) - return - } + if (closeFloatingLayer(KEY)) return loadFavoriteAudioProcessors() - isQuickLoadDialogOpen = true openFloatingLayer({ closeFloatingLayer(KEY) onClose?.invoke(null) - }, key = KEY, afterClose = { isQuickLoadDialogOpen = false }) { + }, key = KEY) { val favoriteIconColors = IconButtonDefaults.iconToggleButtonColors( contentColor = MaterialTheme.colorScheme.outline, checkedContentColor = MaterialTheme.colorScheme.warning