diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 7c2ce523..906ac9cd 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -5,7 +5,6 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream import java.util.Properties plugins { @@ -171,14 +170,9 @@ compose.desktop { } } - fun getGitCommitCount(): Int { - val out = ByteArrayOutputStream() - exec { - commandLine("git", "rev-list", "--count", "HEAD") - standardOutput = out - } - return out.toString().trim().toInt() + val process = Runtime.getRuntime().exec("git rev-list --count HEAD") + return process.inputStream.bufferedReader().use { it.readText().trim().toInt() } } fun getVersionCode(): Int { diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt index bca034e5..a455016a 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt @@ -30,6 +30,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -45,6 +46,7 @@ import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.BlendModeColorFilter import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.pointer.PointerEventType @@ -125,10 +127,15 @@ fun SuperDropdown( val hapticFeedback = LocalHapticFeedback.current val actionColor = if (enabled) MiuixTheme.colorScheme.onSurfaceVariantActions else MiuixTheme.colorScheme.disabledOnSecondaryVariant var alignLeft by rememberSaveable { mutableStateOf(true) } - var dropdownOffsetXPx by remember { mutableStateOf(0) } - var dropdownOffsetYPx by remember { mutableStateOf(0) } - var componentHeightPx by remember { mutableStateOf(0) } - var componentWidthPx by remember { mutableStateOf(0) } + var dropdownOffsetXPx by remember { mutableIntStateOf(0) } + var dropdownOffsetYPx by remember { mutableIntStateOf(0) } + var componentHeightPx by remember { mutableIntStateOf(0) } + var componentWidthPx by remember { mutableIntStateOf(0) } + + val getWindowSize = rememberUpdatedState(getWindowSize()) + val windowHeightPx by rememberUpdatedState(getWindowSize.value.height) + val windowWidthPx by rememberUpdatedState(getWindowSize.value.width) + var transformOrigin by mutableStateOf(TransformOrigin.Center) DisposableEffect(Unit) { onDispose { @@ -153,7 +160,9 @@ fun SuperDropdown( val event = awaitPointerEvent() if (event.type != PointerEventType.Move) { val eventChange = event.changes.first() - alignLeft = eventChange.position.x < (size.width / 2) + if (eventChange.pressed) { + alignLeft = eventChange.position.x < (size.width / 2) + } } } } @@ -165,6 +174,12 @@ fun SuperDropdown( dropdownOffsetYPx = positionInWindow.y.toInt() componentHeightPx = coordinates.size.height componentWidthPx = coordinates.size.width + val xInWindow = dropdownOffsetXPx + if (mode == DropDownMode.AlwaysOnRight || !alignLeft) componentWidthPx else 0 + val yInWindow = dropdownOffsetYPx + componentHeightPx / 2 + transformOrigin = TransformOrigin( + xInWindow / windowWidthPx.toFloat(), + yInWindow / windowHeightPx.toFloat() + ) } }, interactionSource = interactionSource, @@ -222,8 +237,6 @@ fun SuperDropdown( val textMeasurer = rememberTextMeasurer() val textStyle = remember { TextStyle(fontWeight = FontWeight.Medium, fontSize = 16.sp) } val textWidthDp = remember(items) { items.maxOfOrNull { with(density) { textMeasurer.measure(text = it, style = textStyle).size.width.toDp() } } } - val getWindowSize = rememberUpdatedState(getWindowSize()) - val windowHeightPx by rememberUpdatedState(getWindowSize.value.height) val statusBarPx by rememberUpdatedState( with(density) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() }.roundToInt() ) @@ -261,6 +274,7 @@ fun SuperDropdown( } showPopup( + transformOrigin = { transformOrigin }, content = { Box( modifier = if (defaultWindowInsetsPadding) { diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperSpinner.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperSpinner.kt index 05f5c127..3def9c4a 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperSpinner.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperSpinner.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.BlendModeColorFilter import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.pointer.PointerEventType @@ -134,6 +135,11 @@ fun SuperSpinner( var componentHeightPx by remember { mutableIntStateOf(0) } var componentWidthPx by remember { mutableIntStateOf(0) } + val getWindowSize = rememberUpdatedState(getWindowSize()) + val windowHeightPx by rememberUpdatedState(getWindowSize.value.height) + val windowWidthPx by rememberUpdatedState(getWindowSize.value.width) + var transformOrigin by mutableStateOf(TransformOrigin.Center) + DisposableEffect(Unit) { onDispose { dismissPopup(isDropdownExpanded) @@ -157,7 +163,9 @@ fun SuperSpinner( val event = awaitPointerEvent() if (event.type != PointerEventType.Move) { val eventChange = event.changes.first() - alignLeft = eventChange.position.x < (size.width / 2) + if (eventChange.pressed) { + alignLeft = eventChange.position.x < (size.width / 2) + } } } } @@ -169,6 +177,12 @@ fun SuperSpinner( dropdownOffsetYPx = positionInWindow.y.toInt() componentHeightPx = coordinates.size.height componentWidthPx = coordinates.size.width + val xInWindow = dropdownOffsetXPx + if (mode == SpinnerMode.AlwaysOnRight || !alignLeft) componentWidthPx else 0 + val yInWindow = dropdownOffsetYPx + componentHeightPx / 2 + transformOrigin = TransformOrigin( + xInWindow / windowWidthPx.toFloat(), + yInWindow / windowHeightPx.toFloat() + ) } }, interactionSource = interactionSource, @@ -228,8 +242,6 @@ fun SuperSpinner( val density = LocalDensity.current var offsetXPx by remember { mutableIntStateOf(0) } var offsetYPx by remember { mutableIntStateOf(0) } - val getWindowSize = rememberUpdatedState(getWindowSize()) - val windowHeightPx by rememberUpdatedState(getWindowSize.value.height) val statusBarPx by rememberUpdatedState( with(density) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() }.roundToInt() ) @@ -269,6 +281,7 @@ fun SuperSpinner( } showPopup( + transformOrigin = { transformOrigin }, content = { Box( modifier = if (defaultWindowInsetsPadding) { diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt index 03e41f6a..df5f7241 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt @@ -15,9 +15,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex @@ -37,6 +41,7 @@ class MiuixPopupUtil { private var isDialogShowing = mutableStateOf(false) private var popupContext = mutableStateOf<(@Composable () -> Unit)?>(null) private var dialogContext = mutableStateOf<(@Composable () -> Unit)?>(null) + private var popupTransformOrigin = mutableStateOf({ TransformOrigin.Center }) /** * Show a dialog. @@ -68,12 +73,15 @@ class MiuixPopupUtil { * Show a popup. * * @param content The [Composable] content of the popup. + * @param transformOrigin The pivot point in terms of fraction of the overall size, used for scale transformations. By default it's [TransformOrigin.Center]. */ @Composable fun showPopup( content: (@Composable () -> Unit)? = null, + transformOrigin: (() -> TransformOrigin) = { TransformOrigin.Center } ) { if (isPopupShowing.value) return + popupTransformOrigin.value = transformOrigin isPopupShowing.value = true popupContext.value = content } @@ -100,11 +108,20 @@ class MiuixPopupUtil { val windowWidth by rememberUpdatedState(getWindowSize.width.dp / density.density) val windowHeight by rememberUpdatedState(getWindowSize.height.dp / density.density) val largeScreen by rememberUpdatedState { derivedStateOf { (windowHeight >= 480.dp && windowWidth >= 840.dp) } } + var dimEnterDuration by remember { mutableIntStateOf(0) } + var dimExitDuration by remember { mutableIntStateOf(0) } + if (isDialogShowing.value) { + dimEnterDuration = 300 + dimExitDuration = 250 + } else if (isPopupShowing.value) { + dimEnterDuration = 150 + dimExitDuration = 150 + } AnimatedVisibility( visible = isDialogShowing.value || isPopupShowing.value, modifier = Modifier.zIndex(1f).fillMaxSize(), - enter = fadeIn(animationSpec = tween(300, easing = DecelerateEasing(1.5f))), - exit = fadeOut(animationSpec = tween(250, easing = DecelerateEasing(1.5f))) + enter = fadeIn(animationSpec = tween(dimEnterDuration, easing = DecelerateEasing(1.5f))), + exit = fadeOut(animationSpec = tween(dimExitDuration, easing = DecelerateEasing(1.5f))) ) { Box( modifier = Modifier @@ -162,16 +179,18 @@ class MiuixPopupUtil { visible = isPopupShowing.value, modifier = Modifier.zIndex(2f).fillMaxSize(), enter = fadeIn( - animationSpec = tween(150, easing = DecelerateEasing(1.5f)) + animationSpec = tween(150, easing = AccelerateEasing(1.5f)) ) + scaleIn( - initialScale = 0.8f, - animationSpec = tween(150, easing = DecelerateEasing(1.5f)) + initialScale = 0.4f, + animationSpec = tween(150, easing = DecelerateEasing(1.5f)), + transformOrigin = popupTransformOrigin.value.invoke() ), exit = fadeOut( animationSpec = tween(150, easing = AccelerateEasing(3.0f)) ) + scaleOut( targetScale = 0.8f, - animationSpec = tween(150, easing = AccelerateEasing(3.0f)) + animationSpec = tween(150, easing = AccelerateEasing(3.0f)), + transformOrigin = popupTransformOrigin.value.invoke() ) ) { Box(