Skip to content

Commit ce8f644

Browse files
committed
feat(Dropdown/Spinner): change the transformation origin of the scaling animation to the component's relative position
1 parent 749f14d commit ce8f644

File tree

3 files changed

+77
-22
lines changed

3 files changed

+77
-22
lines changed

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import androidx.compose.ui.graphics.BlendMode
4545
import androidx.compose.ui.graphics.BlendModeColorFilter
4646
import androidx.compose.ui.graphics.Color
4747
import androidx.compose.ui.graphics.ColorFilter
48+
import androidx.compose.ui.graphics.TransformOrigin
4849
import androidx.compose.ui.graphics.graphicsLayer
4950
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
5051
import androidx.compose.ui.input.pointer.PointerEventType
@@ -130,6 +131,21 @@ fun SuperDropdown(
130131
var componentHeightPx by remember { mutableStateOf(0) }
131132
var componentWidthPx by remember { mutableStateOf(0) }
132133

134+
val density = LocalDensity.current
135+
val getWindowSize = rememberUpdatedState(getWindowSize())
136+
val windowHeightPx by rememberUpdatedState(getWindowSize.value.height)
137+
val windowWidthPx by rememberUpdatedState(getWindowSize.value.width)
138+
val insideLeftPx by rememberUpdatedState(with(density) {
139+
insideMargin.calculateLeftPadding(LayoutDirection.Ltr).toPx()
140+
}.roundToInt())
141+
val insideRightPx by rememberUpdatedState(with(density) {
142+
insideMargin.calculateRightPadding(LayoutDirection.Ltr).toPx()
143+
}.roundToInt())
144+
val transformOriginXPadding by rememberUpdatedState(with(density) {
145+
64.dp.toPx()
146+
})
147+
var transformOrigin by mutableStateOf(TransformOrigin.Center)
148+
133149
DisposableEffect(Unit) {
134150
onDispose {
135151
dismissPopup(isDropdownExpanded)
@@ -165,6 +181,16 @@ fun SuperDropdown(
165181
dropdownOffsetYPx = positionInWindow.y.toInt()
166182
componentHeightPx = coordinates.size.height
167183
componentWidthPx = coordinates.size.width
184+
val xInWindow = dropdownOffsetXPx + if (mode == DropDownMode.AlwaysOnRight || !alignLeft) {
185+
componentWidthPx - insideRightPx - transformOriginXPadding
186+
} else {
187+
insideLeftPx + transformOriginXPadding
188+
}
189+
val yInWindow = dropdownOffsetYPx - componentHeightPx / 3
190+
transformOrigin = TransformOrigin(
191+
xInWindow / windowWidthPx.toFloat(),
192+
yInWindow / windowHeightPx.toFloat()
193+
)
168194
}
169195
},
170196
interactionSource = interactionSource,
@@ -216,14 +242,11 @@ fun SuperDropdown(
216242
}
217243
}
218244

219-
val density = LocalDensity.current
220245
var offsetXPx by remember { mutableStateOf(0) }
221246
var offsetYPx by remember { mutableStateOf(0) }
222247
val textMeasurer = rememberTextMeasurer()
223248
val textStyle = remember { TextStyle(fontWeight = FontWeight.Medium, fontSize = 16.sp) }
224249
val textWidthDp = remember(items) { items.maxOfOrNull { with(density) { textMeasurer.measure(text = it, style = textStyle).size.width.toDp() } } }
225-
val getWindowSize = rememberUpdatedState(getWindowSize())
226-
val windowHeightPx by rememberUpdatedState(getWindowSize.value.height)
227250
val statusBarPx by rememberUpdatedState(
228251
with(density) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() }.roundToInt()
229252
)
@@ -239,12 +262,6 @@ fun SuperDropdown(
239262
val dropdownElevation by rememberUpdatedState(with(density) {
240263
11.dp.toPx()
241264
})
242-
val insideLeftPx by rememberUpdatedState(with(density) {
243-
insideMargin.calculateLeftPadding(LayoutDirection.Ltr).toPx()
244-
}.roundToInt())
245-
val insideRightPx by rememberUpdatedState(with(density) {
246-
insideMargin.calculateRightPadding(LayoutDirection.Ltr).toPx()
247-
}.roundToInt())
248265
val insideTopPx by rememberUpdatedState(with(density) {
249266
insideMargin.calculateTopPadding().toPx()
250267
}.roundToInt())
@@ -261,6 +278,7 @@ fun SuperDropdown(
261278
}
262279

263280
showPopup(
281+
transformOrigin = { transformOrigin },
264282
content = {
265283
Box(
266284
modifier = if (defaultWindowInsetsPadding) {

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperSpinner.kt

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import androidx.compose.ui.graphics.BlendMode
4646
import androidx.compose.ui.graphics.BlendModeColorFilter
4747
import androidx.compose.ui.graphics.Color
4848
import androidx.compose.ui.graphics.ColorFilter
49+
import androidx.compose.ui.graphics.TransformOrigin
4950
import androidx.compose.ui.graphics.graphicsLayer
5051
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
5152
import androidx.compose.ui.input.pointer.PointerEventType
@@ -134,6 +135,21 @@ fun SuperSpinner(
134135
var componentHeightPx by remember { mutableIntStateOf(0) }
135136
var componentWidthPx by remember { mutableIntStateOf(0) }
136137

138+
val density = LocalDensity.current
139+
val getWindowSize = rememberUpdatedState(getWindowSize())
140+
val windowHeightPx by rememberUpdatedState(getWindowSize.value.height)
141+
val windowWidthPx by rememberUpdatedState(getWindowSize.value.width)
142+
val insideLeftPx by rememberUpdatedState(with(density) {
143+
insideMargin.calculateLeftPadding(LayoutDirection.Ltr).toPx()
144+
}.roundToInt())
145+
val insideRightPx by rememberUpdatedState(with(density) {
146+
insideMargin.calculateRightPadding(LayoutDirection.Ltr).toPx()
147+
}.roundToInt())
148+
val transformOriginXPadding by rememberUpdatedState(with(density) {
149+
64.dp.toPx()
150+
})
151+
var transformOrigin by mutableStateOf(TransformOrigin.Center)
152+
137153
DisposableEffect(Unit) {
138154
onDispose {
139155
dismissPopup(isDropdownExpanded)
@@ -169,6 +185,16 @@ fun SuperSpinner(
169185
dropdownOffsetYPx = positionInWindow.y.toInt()
170186
componentHeightPx = coordinates.size.height
171187
componentWidthPx = coordinates.size.width
188+
val xInWindow = dropdownOffsetXPx + if (mode == SpinnerMode.AlwaysOnRight || !alignLeft) {
189+
componentWidthPx - insideRightPx - transformOriginXPadding
190+
} else {
191+
insideLeftPx + transformOriginXPadding
192+
}
193+
val yInWindow = dropdownOffsetYPx - componentHeightPx / 3
194+
transformOrigin = TransformOrigin(
195+
xInWindow / windowWidthPx.toFloat(),
196+
yInWindow / windowHeightPx.toFloat()
197+
)
172198
}
173199
},
174200
interactionSource = interactionSource,
@@ -225,11 +251,8 @@ fun SuperSpinner(
225251
}
226252
}
227253

228-
val density = LocalDensity.current
229254
var offsetXPx by remember { mutableIntStateOf(0) }
230255
var offsetYPx by remember { mutableIntStateOf(0) }
231-
val getWindowSize = rememberUpdatedState(getWindowSize())
232-
val windowHeightPx by rememberUpdatedState(getWindowSize.value.height)
233256
val statusBarPx by rememberUpdatedState(
234257
with(density) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() }.roundToInt()
235258
)
@@ -245,12 +268,6 @@ fun SuperSpinner(
245268
val dropdownElevation by rememberUpdatedState(with(density) {
246269
11.dp.toPx()
247270
})
248-
val insideLeftPx by rememberUpdatedState(with(density) {
249-
insideMargin.calculateLeftPadding(LayoutDirection.Ltr).toPx()
250-
}.roundToInt())
251-
val insideRightPx by rememberUpdatedState(with(density) {
252-
insideMargin.calculateRightPadding(LayoutDirection.Ltr).toPx()
253-
}.roundToInt())
254271
val insideTopPx by rememberUpdatedState(with(density) {
255272
insideMargin.calculateTopPadding().toPx()
256273
}.roundToInt())
@@ -269,6 +286,7 @@ fun SuperSpinner(
269286
}
270287

271288
showPopup(
289+
transformOrigin = { transformOrigin },
272290
content = {
273291
Box(
274292
modifier = if (defaultWindowInsetsPadding) {

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ import androidx.compose.runtime.Composable
1515
import androidx.compose.runtime.MutableState
1616
import androidx.compose.runtime.derivedStateOf
1717
import androidx.compose.runtime.getValue
18+
import androidx.compose.runtime.mutableIntStateOf
1819
import androidx.compose.runtime.mutableStateOf
20+
import androidx.compose.runtime.remember
1921
import androidx.compose.runtime.rememberUpdatedState
22+
import androidx.compose.runtime.setValue
2023
import androidx.compose.ui.Modifier
24+
import androidx.compose.ui.graphics.TransformOrigin
2125
import androidx.compose.ui.platform.LocalDensity
2226
import androidx.compose.ui.unit.dp
2327
import androidx.compose.ui.zIndex
@@ -37,6 +41,7 @@ class MiuixPopupUtil {
3741
private var isDialogShowing = mutableStateOf(false)
3842
private var popupContext = mutableStateOf<(@Composable () -> Unit)?>(null)
3943
private var dialogContext = mutableStateOf<(@Composable () -> Unit)?>(null)
44+
private var popupTransformOrigin = mutableStateOf({ TransformOrigin.Center })
4045

4146
/**
4247
* Show a dialog.
@@ -68,12 +73,15 @@ class MiuixPopupUtil {
6873
* Show a popup.
6974
*
7075
* @param content The [Composable] content of the popup.
76+
* @param transformOrigin The pivot point in terms of fraction of the overall size, used for scale transformations. By default it's [TransformOrigin.Center].
7177
*/
7278
@Composable
7379
fun showPopup(
7480
content: (@Composable () -> Unit)? = null,
81+
transformOrigin: (() -> TransformOrigin) = { TransformOrigin.Center }
7582
) {
7683
if (isPopupShowing.value) return
84+
popupTransformOrigin.value = transformOrigin
7785
isPopupShowing.value = true
7886
popupContext.value = content
7987
}
@@ -100,11 +108,20 @@ class MiuixPopupUtil {
100108
val windowWidth by rememberUpdatedState(getWindowSize.width.dp / density.density)
101109
val windowHeight by rememberUpdatedState(getWindowSize.height.dp / density.density)
102110
val largeScreen by rememberUpdatedState { derivedStateOf { (windowHeight >= 480.dp && windowWidth >= 840.dp) } }
111+
var dimEnterDuration by remember { mutableIntStateOf(0) }
112+
var dimExitDuration by remember { mutableIntStateOf(0) }
113+
if (isDialogShowing.value) {
114+
dimEnterDuration = 300
115+
dimExitDuration = 250
116+
} else if (isPopupShowing.value) {
117+
dimEnterDuration = 150
118+
dimExitDuration = 150
119+
}
103120
AnimatedVisibility(
104121
visible = isDialogShowing.value || isPopupShowing.value,
105122
modifier = Modifier.zIndex(1f).fillMaxSize(),
106-
enter = fadeIn(animationSpec = tween(300, easing = DecelerateEasing(1.5f))),
107-
exit = fadeOut(animationSpec = tween(250, easing = DecelerateEasing(1.5f)))
123+
enter = fadeIn(animationSpec = tween(dimEnterDuration, easing = DecelerateEasing(1.5f))),
124+
exit = fadeOut(animationSpec = tween(dimExitDuration, easing = DecelerateEasing(1.5f)))
108125
) {
109126
Box(
110127
modifier = Modifier
@@ -165,13 +182,15 @@ class MiuixPopupUtil {
165182
animationSpec = tween(150, easing = DecelerateEasing(1.5f))
166183
) + scaleIn(
167184
initialScale = 0.8f,
168-
animationSpec = tween(150, easing = DecelerateEasing(1.5f))
185+
animationSpec = tween(150, easing = DecelerateEasing(1.5f)),
186+
transformOrigin = popupTransformOrigin.value.invoke()
169187
),
170188
exit = fadeOut(
171189
animationSpec = tween(150, easing = AccelerateEasing(3.0f))
172190
) + scaleOut(
173191
targetScale = 0.8f,
174-
animationSpec = tween(150, easing = AccelerateEasing(3.0f))
192+
animationSpec = tween(150, easing = AccelerateEasing(3.0f)),
193+
transformOrigin = popupTransformOrigin.value.invoke()
175194
)
176195
) {
177196
Box(

0 commit comments

Comments
 (0)