Skip to content

Commit 0433c3c

Browse files
YunZiAYuKongA
andcommitted
library: Support custom dialog/popup layout anim (#71)
--------- Co-Authored-By: YuKongA <[email protected]>
1 parent 1fb088b commit 0433c3c

File tree

11 files changed

+305
-234
lines changed

11 files changed

+305
-234
lines changed

docs/components/listpopup.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ var showPopup = remember { mutableStateOf(false) }
7979
ListPopup(
8080
show = showPopup,
8181
onDismissRequest = { showPopup.value = false } // Close the popup menu
82-
windowDimming = false
82+
enableWindowDim = false // Disable dimming layer
8383
) {
8484
ListPopupColumn {
8585
// Custom content
@@ -97,7 +97,7 @@ ListPopup(
9797
| popupModifier | Modifier | Modifier applied to the popup list | Modifier | No |
9898
| popupPositionProvider | PopupPositionProvider | Position provider for the popup | ListPopupDefaults.DropdownPositionProvider | No |
9999
| alignment | PopupPositionProvider.Align | Alignment of the popup list | PopupPositionProvider.Align.Right | No |
100-
| windowDimming | Boolean | Enable window dimming when showing popup | true | No |
100+
| enableWindowDim | Boolean | Whether to enable dimming layer | true | No |
101101
| shadowElevation | Dp | Shadow elevation of the popup | 11.dp | No |
102102
| onDismissRequest | (() -> Unit)? | Callback when popup is dismissed | null | No |
103103
| maxHeight | Dp? | Maximum height of the popup list | null (auto-calculated) | No |

docs/components/superdialog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Scaffold {
5353
| summary | String? | Dialog summary text | null | No |
5454
| summaryColor | Color | Summary text color | SuperDialogDefaults.summaryColor() | No |
5555
| backgroundColor | Color | Dialog background color | SuperDialogDefaults.backgroundColor() | No |
56+
| enableWindowDim | Boolean | Whether to enable dimming layer | true | No |
5657
| onDismissRequest | (() -> Unit)? | Callback when dialog is closed | null | No |
5758
| outsideMargin | DpSize | Dialog external margin | SuperDialogDefaults.outsideMargin | No |
5859
| insideMargin | DpSize | Dialog internal content margin | SuperDialogDefaults.insideMargin | No |

docs/guide/utils.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ If you use multiple Scaffolds, you need to set the `popupHost` parameter in the
1515
val showDialogState = remember { mutableStateOf(false) }
1616

1717
DialogLayout(
18-
visible = showDialogState
18+
visible = showDialogState, // MutableState<Boolean> to control dialog visibility
19+
enterTransition = fadeIn(), // Optional, custom enter animation for dialog content
20+
exitTransition = fadeOut(), // Optional, custom exit animation for dialog content
21+
enableWindowDim = true, // Optional, whether to enable dimming layer
22+
dimEnterTransition = fadeIn(), // Optional, custom enter animation for dim layer
23+
dimExitTransition = fadeOut() // Optional, custom exit animation for dim layer
1924
) {
2025
// Dialog content
2126
}
@@ -29,9 +34,13 @@ Normally, you don't need to use it actively. See the [SuperDialog](../components
2934
val showPopupState = remember { mutableStateOf(false) }
3035

3136
PopupLayout(
32-
visible = showPopupState,
37+
visible = showPopupState, // MutableState<Boolean> to control popup visibility
38+
enterTransition = fadeIn(), // Optional, custom enter animation for dialog content
39+
exitTransition = fadeOut(), // Optional, custom exit animation for dialog content
40+
enableWindowDim = true, // Optional, whether to enable dimming layer
41+
dimEnterTransition = fadeIn(), // Optional, custom enter animation for dim layer
42+
dimExitTransition = fadeOut() // Optional, custom exit animation for dim layer
3343
transformOrigin = { TransformOrigin.Center }, // Transform origin for the popup
34-
windowDimming = true // Dim the background
3544
) {
3645
// Popup content
3746
}

docs/zh_CN/components/listpopup.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ var showPopup = remember { mutableStateOf(false) }
8080
ListPopup(
8181
show = showPopup,
8282
onDismissRequest = { showPopup.value = false } // 关闭弹出菜单
83-
windowDimming = false
83+
enableWindowDim = false // 禁用遮罩层
8484
) {
8585
ListPopupColumn {
8686
// 自定义内容
@@ -98,7 +98,7 @@ ListPopup(
9898
| popupModifier | Modifier | 应用于弹出列表的修饰符 | Modifier ||
9999
| popupPositionProvider | PopupPositionProvider | 弹出列表的位置提供者 | ListPopupDefaults.DropdownPositionProvider ||
100100
| alignment | PopupPositionProvider.Align | 弹出列表的对齐方式 | PopupPositionProvider.Align.Right ||
101-
| windowDimming | Boolean | 是否在显示弹出列表时使窗口变暗 | true ||
101+
| enableWindowDim | Boolean | 是否启用遮罩层 | true ||
102102
| shadowElevation | Dp | 弹出列表的阴影高度 | 11.dp ||
103103
| onDismissRequest | (() -> Unit)? | 弹出列表消失时的回调函数 | null ||
104104
| maxHeight | Dp? | 弹出列表的最大高度 | null (自动计算) ||

docs/zh_CN/components/superdialog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Scaffold {
5353
| summary | String? | 对话框的摘要说明 | null ||
5454
| summaryColor | Color | 摘要文本的颜色 | SuperDialogDefaults.summaryColor() ||
5555
| backgroundColor | Color | 对话框背景色 | SuperDialogDefaults.backgroundColor() ||
56+
| enableWindowDim | Boolean | 是否启用遮罩层 | true ||
5657
| onDismissRequest | (() -> Unit)? | 对话框关闭时的回调函数 | null ||
5758
| outsideMargin | DpSize | 对话框外部边距 | SuperDialogDefaults.outsideMargin ||
5859
| insideMargin | DpSize | 对话框内部内容的边距 | SuperDialogDefaults.insideMargin ||

docs/zh_CN/guide/utils.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ Miuix 提供了一系列工具函数,帮助您更高效地开发应用程序
1515
val showDialogState = remember { mutableStateOf(false) }
1616

1717
DialogLayout(
18-
visible = showDialogState
18+
visible = showDialogState, // 控制对话框显示状态
19+
enterTransition = fadeIn(), // 可选,自定义对话框进入动画
20+
exitTransition = fadeOut(), // 可选,自定义对话框对话框退出动画
21+
enableWindowDim = true, // 可选,是否启用遮罩层
22+
dimEnterTransition = fadeIn(), // 可选,自定义遮罩层进入动画
23+
dimExitTransition = fadeOut() // 可选,自定义遮罩层退出动画
1924
) {
2025
// 对话框内容
2126
}
@@ -30,9 +35,13 @@ DialogLayout(
3035
val showPopupState = remember { mutableStateOf(false) }
3136

3237
PopupLayout(
33-
visible = showPopupState,
38+
visible = showPopupState, // 控制弹出窗口显示状态
39+
enterTransition = fadeIn(), // 可选,自定义对话框进入动画
40+
exitTransition = fadeOut(), // 可选,自定义对话框对话框退出动画
41+
enableWindowDim = true, // 可选,是否启用遮罩层
42+
dimEnterTransition = fadeIn(), // 可选,自定义遮罩层进入动画
43+
dimExitTransition = fadeOut() // 可选,自定义遮罩层退出动画
3444
transformOrigin = { TransformOrigin.Center }, // 弹出窗口的起始位置
35-
windowDimming = true // 背景压暗
3645
) {
3746
// 弹出窗口内容
3847
}

example/src/commonMain/kotlin/UITest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,10 @@ private fun TopAppBarActions(
407407
popupPositionProvider = ListPopupDefaults.ContextMenuPositionProvider,
408408
alignment = PopupPositionProvider.Align.TopRight,
409409
onDismissRequest = {
410+
showTopPopup.value = false
410411
onPopupExpandedChange(false)
411-
}
412+
},
413+
enableWindowDim = false
412414
) {
413415
ListPopupColumn {
414416
items.take(3).forEachIndexed { index, navigationItem ->

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/ListPopup.kt

Lines changed: 62 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import kotlin.math.min
5252
* @param popupModifier The modifier to be applied to the [ListPopup].
5353
* @param popupPositionProvider The [PopupPositionProvider] of the [ListPopup].
5454
* @param alignment The alignment of the [ListPopup].
55-
* @param windowDimming Whether to dim the window when the [ListPopup] is shown.
55+
* @param enableWindowDim Whether to enable window dimming when the [ListPopup] is shown.
5656
* @param shadowElevation The elevation of the shadow of the [ListPopup].
5757
* @param onDismissRequest The callback when the [ListPopup] is dismissed.
5858
* @param maxHeight The maximum height of the [ListPopup]. If null, the height will be calculated automatically.
@@ -65,7 +65,7 @@ fun ListPopup(
6565
popupModifier: Modifier = Modifier,
6666
popupPositionProvider: PopupPositionProvider = ListPopupDefaults.DropdownPositionProvider,
6767
alignment: PopupPositionProvider.Align = PopupPositionProvider.Align.Right,
68-
windowDimming: Boolean = true,
68+
enableWindowDim: Boolean = true,
6969
shadowElevation: Dp = 11.dp,
7070
onDismissRequest: (() -> Unit)? = null,
7171
maxHeight: Dp? = null,
@@ -93,10 +93,6 @@ fun ListPopup(
9393
var popupMargin by remember { mutableStateOf(IntRect.Zero) }
9494
var transformOrigin by remember { mutableStateOf(TransformOrigin.Center) }
9595

96-
BackHandler(enabled = show.value) {
97-
onDismissRequest?.invoke()
98-
}
99-
10096
DisposableEffect(popupPositionProvider, alignment) {
10197
val popupMargins = popupPositionProvider.getMargins()
10298
popupMargin = with(density) {
@@ -120,66 +116,7 @@ fun ListPopup(
120116
onDispose {}
121117
}
122118

123-
if (show.value) {
124-
PopupLayout(
125-
visible = show,
126-
transformOrigin = { transformOrigin },
127-
windowDimming = windowDimming
128-
) {
129-
val currentContent by rememberUpdatedState(content)
130-
val currentOffset by rememberUpdatedState(offset)
131-
val shape = remember { derivedStateOf { SmoothRoundedCornerShape(16.dp) } }
132-
val elevationPx = with(density) { shadowElevation.toPx() }
133-
Box(
134-
modifier = popupModifier
135-
.pointerInput(Unit) {
136-
detectTapGestures {
137-
onDismissRequest?.invoke()
138-
}
139-
}
140-
.layout { measurable, constraints ->
141-
val placeable = measurable.measure(
142-
constraints.copy(
143-
minWidth = if (minWidth.roundToPx() <= windowSize.width) minWidth.roundToPx() else windowSize.width,
144-
minHeight = if (50.dp.roundToPx() <= windowSize.height) 50.dp.roundToPx() else windowSize.height,
145-
maxHeight = maxHeight?.roundToPx()?.coerceAtLeast(50.dp.roundToPx())
146-
?: (windowBounds.height - popupMargin.top - popupMargin.bottom).coerceAtLeast(50.dp.roundToPx()),
147-
maxWidth = if (minWidth.roundToPx() <= windowSize.width) windowSize.width else minWidth.roundToPx()
148-
)
149-
)
150-
popupContentSize = IntSize(placeable.width, placeable.height)
151-
offset = popupPositionProvider.calculatePosition(
152-
parentBounds,
153-
windowBounds,
154-
layoutDirection,
155-
popupContentSize,
156-
popupMargin,
157-
alignment
158-
)
159-
layout(constraints.maxWidth, constraints.maxHeight) {
160-
placeable.place(currentOffset)
161-
}
162-
}
163-
) {
164-
Box(
165-
Modifier
166-
.graphicsLayer(
167-
clip = true,
168-
shape = shape.value,
169-
shadowElevation = elevationPx,
170-
ambientShadowColor = MiuixTheme.colorScheme.windowDimming,
171-
spotShadowColor = MiuixTheme.colorScheme.windowDimming
172-
)
173-
.background(MiuixTheme.colorScheme.surface)
174-
) {
175-
currentContent()
176-
}
177-
}
178-
}
179-
}
180-
181119
Layout(
182-
content = {},
183120
modifier = Modifier.onGloballyPositioned { childCoordinates ->
184121
val parentCoordinates = childCoordinates.parentLayoutCoordinates!!
185122
val positionInWindow = parentCoordinates.positionInWindow()
@@ -212,6 +149,66 @@ fun ListPopup(
212149
) { _, _ ->
213150
layout(0, 0) {}
214151
}
152+
153+
PopupLayout(
154+
visible = show,
155+
enableWindowDim = enableWindowDim,
156+
transformOrigin = { transformOrigin },
157+
) {
158+
val currentContent by rememberUpdatedState(content)
159+
val currentOffset by rememberUpdatedState(offset)
160+
val shape = remember { derivedStateOf { SmoothRoundedCornerShape(16.dp) } }
161+
val elevationPx = with(density) { shadowElevation.toPx() }
162+
Box(
163+
modifier = popupModifier
164+
.pointerInput(Unit) {
165+
detectTapGestures {
166+
onDismissRequest?.invoke()
167+
}
168+
}
169+
.layout { measurable, constraints ->
170+
val placeable = measurable.measure(
171+
constraints.copy(
172+
minWidth = if (minWidth.roundToPx() <= windowSize.width) minWidth.roundToPx() else windowSize.width,
173+
minHeight = if (50.dp.roundToPx() <= windowSize.height) 50.dp.roundToPx() else windowSize.height,
174+
maxHeight = maxHeight?.roundToPx()?.coerceAtLeast(50.dp.roundToPx())
175+
?: (windowBounds.height - popupMargin.top - popupMargin.bottom).coerceAtLeast(50.dp.roundToPx()),
176+
maxWidth = if (minWidth.roundToPx() <= windowSize.width) windowSize.width else minWidth.roundToPx()
177+
)
178+
)
179+
popupContentSize = IntSize(placeable.width, placeable.height)
180+
offset = popupPositionProvider.calculatePosition(
181+
parentBounds,
182+
windowBounds,
183+
layoutDirection,
184+
popupContentSize,
185+
popupMargin,
186+
alignment
187+
)
188+
layout(constraints.maxWidth, constraints.maxHeight) {
189+
placeable.place(currentOffset)
190+
}
191+
}
192+
) {
193+
Box(
194+
Modifier
195+
.graphicsLayer(
196+
clip = true,
197+
shape = shape.value,
198+
shadowElevation = elevationPx,
199+
ambientShadowColor = MiuixTheme.colorScheme.windowDimming,
200+
spotShadowColor = MiuixTheme.colorScheme.windowDimming
201+
)
202+
.background(MiuixTheme.colorScheme.surface)
203+
) {
204+
currentContent()
205+
}
206+
}
207+
}
208+
209+
BackHandler(enabled = show.value) {
210+
onDismissRequest?.invoke()
211+
}
215212
}
216213

217214
/**

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/PullToRefresh.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ fun RefreshHeader(
185185
when (pullToRefreshState.refreshState) {
186186
RefreshState.Idle -> 0f
187187
RefreshState.Pulling -> if (pullToRefreshState.pullProgress > 0.6f) (pullToRefreshState.pullProgress - 0.5f) * 2f else 0f
188-
RefreshState.RefreshComplete -> (1f - refreshCompleteAnimProgress * 1.8f).coerceAtLeast(0f)
188+
RefreshState.RefreshComplete -> (1f - refreshCompleteAnimProgress * 1.95f).coerceAtLeast(0f)
189189
else -> 1f
190190
}
191191
}
@@ -422,11 +422,12 @@ private fun DrawScope.drawRefreshCompleteState(
422422
refreshCompleteProgress: Float
423423
) {
424424
val animatedRadius = radius * ((1f - refreshCompleteProgress).coerceAtLeast(0.9f))
425-
val alphaColor = color.copy(alpha = (1f - refreshCompleteProgress - 0.2f).coerceAtLeast(0f))
425+
val alphaColor = color.copy(alpha = (1f - refreshCompleteProgress - 0.35f).coerceAtLeast(0f))
426+
val y = center.y - radius - strokeWidth + animatedRadius
426427
drawCircle(
427428
color = alphaColor,
428429
radius = animatedRadius,
429-
center = center,
430+
center = Offset(center.x,y),
430431
style = Stroke(strokeWidth, cap = StrokeCap.Round)
431432
)
432433
}

0 commit comments

Comments
 (0)