Skip to content

Commit b8d1f17

Browse files
committed
library: Refactor BasicComponent to use SubcomposeLayout
1 parent aec2c52 commit b8d1f17

File tree

7 files changed

+175
-134
lines changed

7 files changed

+175
-134
lines changed

iosApp/iosApp/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<key>CFBundleShortVersionString</key>
1818
<string>1.0.4</string>
1919
<key>CFBundleVersion</key>
20-
<string>485</string>
20+
<string>486</string>
2121
<key>LSRequiresIPhoneOS</key>
2222
<true/>
2323
<key>CADisableMinimumFrameDurationOnPhone</key>

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

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33

44
package top.yukonga.miuix.kmp.basic
55

6-
import androidx.compose.foundation.Canvas
76
import androidx.compose.foundation.background
87
import androidx.compose.foundation.border
98
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
109
import androidx.compose.foundation.layout.Arrangement
1110
import androidx.compose.foundation.layout.Box
1211
import androidx.compose.foundation.layout.Column
13-
import androidx.compose.foundation.layout.fillMaxSize
1412
import androidx.compose.foundation.layout.fillMaxWidth
1513
import androidx.compose.foundation.layout.height
1614
import androidx.compose.foundation.layout.offset
@@ -25,6 +23,8 @@ import androidx.compose.runtime.setValue
2523
import androidx.compose.ui.Alignment
2624
import androidx.compose.ui.Modifier
2725
import androidx.compose.ui.draw.clip
26+
import androidx.compose.ui.draw.drawBehind
27+
import androidx.compose.ui.geometry.CornerRadius
2828
import androidx.compose.ui.graphics.Brush
2929
import androidx.compose.ui.graphics.Color
3030
import androidx.compose.ui.graphics.ImageBitmap
@@ -33,10 +33,12 @@ import androidx.compose.ui.graphics.Paint
3333
import androidx.compose.ui.graphics.ShaderBrush
3434
import androidx.compose.ui.graphics.TileMode
3535
import androidx.compose.ui.graphics.drawscope.DrawScope
36+
import androidx.compose.ui.graphics.drawscope.Stroke
3637
import androidx.compose.ui.input.pointer.pointerInput
3738
import androidx.compose.ui.layout.onGloballyPositioned
3839
import androidx.compose.ui.platform.LocalDensity
3940
import androidx.compose.ui.platform.LocalHapticFeedback
41+
import androidx.compose.ui.unit.Density
4042
import androidx.compose.ui.unit.Dp
4143
import androidx.compose.ui.unit.dp
4244
import top.yukonga.miuix.kmp.utils.ColorUtils
@@ -207,7 +209,10 @@ fun SaturationSlider(
207209
hapticEffect: SliderDefaults.SliderHapticEffect = SliderDefaults.DefaultHapticEffect
208210
) {
209211
val saturationColors = remember(currentHue) {
210-
listOf(Color.hsv(currentHue, 0f, 1f, 1f), Color.hsv(currentHue, 1f, 1f, 1f))
212+
listOf(
213+
Color.hsv(currentHue, 0f, 1f, 1f),
214+
Color.hsv(currentHue, 1f, 1f, 1f)
215+
)
211216
}
212217
ColorSlider(
213218
value = currentSaturation,
@@ -284,11 +289,16 @@ fun AlphaSlider(
284289
drawBrushColors = alphaColors,
285290
modifier = Modifier.fillMaxWidth(),
286291
hapticEffect = hapticEffect,
287-
drawBackground = { _, _ -> drawRect(brush = checkerBrush) }
292+
drawBackground = { _, _ ->
293+
drawRoundRect(
294+
brush = checkerBrush,
295+
cornerRadius = CornerRadius(size.height / 2, size.height / 2)
296+
)
297+
}
288298
)
289299
}
290300

291-
private fun createCheckerboardBrush(density: androidx.compose.ui.unit.Density): ShaderBrush {
301+
private fun createCheckerboardBrush(density: Density): ShaderBrush {
292302
val lightColor = Color(0xFFCCCCCC)
293303
val darkColor = Color(0xFFAAAAAA)
294304
val checkerSizeDp = 3.dp
@@ -327,52 +337,61 @@ private fun ColorSlider(
327337
val indicatorSizeDp = 20.dp
328338
val sliderHeightDp = 26.dp
329339
val sliderHeightPx = with(density) { sliderHeightDp.toPx() }
330-
val sliderWidthPx = with(density) { sliderWidth.toPx() }
331-
val halfSliderHeightPx = sliderHeightPx / 2f
332340
val hapticFeedback = LocalHapticFeedback.current
333341
val hapticState = remember { SliderHapticState() }
334342

335-
val gradientBrush = remember(drawBrushColors, sliderWidthPx, halfSliderHeightPx) {
343+
val gradientBrush = remember(drawBrushColors, sliderWidth) {
344+
val widthPx = with(density) { sliderWidth.toPx() }
345+
val halfSliderHeightPx = with(density) { sliderHeightDp.toPx() } / 2f
336346
Brush.horizontalGradient(
337347
colors = drawBrushColors,
338348
startX = halfSliderHeightPx,
339-
endX = sliderWidthPx - halfSliderHeightPx,
349+
endX = widthPx - halfSliderHeightPx,
340350
tileMode = TileMode.Clamp
341351
)
342352
}
343353

344354
Box(
345355
modifier = modifier
346356
.height(sliderHeightDp)
347-
.clip(SmoothRoundedCornerShape(50.dp))
348-
.border(0.5.dp, Color.Gray.copy(0.1f), SmoothRoundedCornerShape(50.dp))
357+
.onGloballyPositioned { coordinates ->
358+
sliderWidth = with(density) { coordinates.size.width.toDp() }
359+
}
360+
.drawBehind {
361+
drawBackground?.invoke(this, size.width, size.height)
362+
drawRoundRect(
363+
brush = gradientBrush,
364+
cornerRadius = CornerRadius(size.height / 2, size.height / 2)
365+
)
366+
drawRoundRect(
367+
color = Color.Gray.copy(0.1f),
368+
style = Stroke(width = with(density) { 0.5.dp.toPx() }),
369+
cornerRadius = CornerRadius(size.height / 2, size.height / 2)
370+
)
371+
}
372+
.pointerInput(Unit) {
373+
detectHorizontalDragGestures(
374+
onDragStart = { offset ->
375+
val newValue =
376+
handleSliderInteraction(offset.x, size.width.toFloat(), with(density) { sliderHeightDp.toPx() }).coerceIn(
377+
0f,
378+
1f
379+
)
380+
onValueChanged(newValue)
381+
hapticState.reset(newValue)
382+
},
383+
onHorizontalDrag = { change, _ ->
384+
change.consume()
385+
val newValue = handleSliderInteraction(
386+
change.position.x,
387+
size.width.toFloat(),
388+
with(density) { sliderHeightDp.toPx() }).coerceIn(0f, 1f)
389+
onValueChanged(newValue)
390+
hapticState.handleHapticFeedback(newValue, hapticEffect, hapticFeedback)
391+
}
392+
)
393+
}
349394
) {
350-
Canvas(
351-
modifier = Modifier
352-
.fillMaxSize()
353-
.onGloballyPositioned { coordinates ->
354-
sliderWidth = with(density) { coordinates.size.width.toDp() }
355-
}
356-
.pointerInput(Unit) {
357-
detectHorizontalDragGestures(
358-
onDragStart = { offset ->
359-
val newValue = handleSliderInteraction(offset.x, size.width.toFloat(), sliderHeightPx).coerceIn(0f, 1f)
360-
onValueChanged(newValue)
361-
hapticState.reset(newValue)
362-
},
363-
onHorizontalDrag = { change, _ ->
364-
change.consume()
365-
val newValue = handleSliderInteraction(change.position.x, size.width.toFloat(), sliderHeightPx).coerceIn(0f, 1f)
366-
onValueChanged(newValue)
367-
hapticState.handleHapticFeedback(newValue, hapticEffect, hapticFeedback)
368-
}
369-
)
370-
}
371-
) {
372-
drawBackground?.invoke(this, size.width, size.height)
373-
drawRect(gradientBrush)
374-
}
375-
376395
SliderIndicator(
377396
modifier = Modifier.align(Alignment.CenterStart),
378397
value = value,

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

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import androidx.compose.foundation.LocalIndication
77
import androidx.compose.foundation.clickable
88
import androidx.compose.foundation.interaction.MutableInteractionSource
99
import androidx.compose.foundation.layout.Arrangement
10-
import androidx.compose.foundation.layout.Box
11-
import androidx.compose.foundation.layout.Column
1210
import androidx.compose.foundation.layout.PaddingValues
1311
import androidx.compose.foundation.layout.Row
1412
import androidx.compose.foundation.layout.RowScope
@@ -26,6 +24,7 @@ import androidx.compose.runtime.rememberUpdatedState
2624
import androidx.compose.ui.Alignment
2725
import androidx.compose.ui.Modifier
2826
import androidx.compose.ui.graphics.Color
27+
import androidx.compose.ui.layout.SubcomposeLayout
2928
import androidx.compose.ui.text.font.FontWeight
3029
import androidx.compose.ui.unit.dp
3130
import top.yukonga.miuix.kmp.interfaces.HoldDownInteraction
@@ -78,55 +77,82 @@ fun BasicComponent(
7877
}
7978
}
8079

81-
val rowModifier = modifier
82-
.heightIn(min = 56.dp)
83-
.fillMaxWidth()
84-
.then(
85-
if (currentOnClick != null && enabled) {
86-
Modifier.clickable(
87-
indication = LocalIndication.current,
88-
interactionSource = interactionSource,
89-
onClick = { currentOnClick?.invoke() }
90-
)
91-
} else Modifier
92-
)
93-
.padding(insideMargin)
94-
95-
Row(
96-
modifier = rowModifier,
97-
verticalAlignment = Alignment.CenterVertically,
98-
horizontalArrangement = Arrangement.SpaceBetween
99-
) {
100-
leftAction?.invoke()
101-
102-
Column(
103-
modifier = Modifier.weight(1f)
104-
) {
105-
title?.let {
80+
SubcomposeLayout(
81+
modifier = modifier
82+
.heightIn(min = 56.dp)
83+
.fillMaxWidth()
84+
.then(
85+
if (currentOnClick != null && enabled) {
86+
Modifier.clickable(
87+
indication = LocalIndication.current,
88+
interactionSource = interactionSource,
89+
onClick = { currentOnClick?.invoke() }
90+
)
91+
} else Modifier
92+
)
93+
.padding(insideMargin)
94+
) { constraints ->
95+
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
96+
// 1. leftAction
97+
val leftPlaceables = leftAction?.let {
98+
subcompose("leftAction") { it() }.map { it -> it.measure(looseConstraints) }
99+
} ?: emptyList()
100+
val leftWidth = leftPlaceables.maxOfOrNull { it.width } ?: 0
101+
val leftHeight = leftPlaceables.maxOfOrNull { it.height } ?: 0
102+
// 2. rightActions
103+
val rightPlaceables = subcompose("rightActions") {
104+
Row(
105+
horizontalArrangement = Arrangement.End,
106+
verticalAlignment = Alignment.CenterVertically,
107+
content = rightActions
108+
)
109+
}.map { it.measure(looseConstraints) }
110+
val rightWidth = rightPlaceables.maxOfOrNull { it.width } ?: 0
111+
val rightHeight = rightPlaceables.maxOfOrNull { it.height } ?: 0
112+
// 3. content
113+
val contentMaxWidth = maxOf(0, constraints.maxWidth - leftWidth - rightWidth - 16.dp.roundToPx())
114+
val titlePlaceable = title?.let {
115+
subcompose("title") {
106116
Text(
107117
text = it,
108118
fontSize = MiuixTheme.textStyles.headline1.fontSize,
109119
fontWeight = FontWeight.Medium,
110120
color = titleColor.color(enabled)
111121
)
112-
}
113-
summary?.let {
122+
}.first().measure(looseConstraints.copy(maxWidth = contentMaxWidth))
123+
}
124+
val summaryPlaceable = summary?.let {
125+
subcompose("summary") {
114126
Text(
115127
text = it,
116128
fontSize = MiuixTheme.textStyles.body2.fontSize,
117129
color = summaryColor.color(enabled)
118130
)
119-
}
131+
}.first().measure(looseConstraints.copy(maxWidth = contentMaxWidth))
120132
}
133+
listOfNotNull(titlePlaceable?.width, summaryPlaceable?.width).maxOrNull() ?: 0
134+
val contentHeight = (titlePlaceable?.height ?: 0) + (summaryPlaceable?.height ?: 0)
121135

122-
Box(
123-
modifier = Modifier.padding(start = 16.dp)
124-
) {
125-
Row(
126-
horizontalArrangement = Arrangement.End,
127-
verticalAlignment = Alignment.CenterVertically,
128-
content = rightActions
129-
)
136+
val layoutHeight = maxOf(leftHeight, rightHeight, contentHeight)
137+
layout(constraints.maxWidth, layoutHeight) {
138+
var x = 0
139+
// leftAction
140+
leftPlaceables.forEach {
141+
it.placeRelative(x, (layoutHeight - it.height) / 2)
142+
x += it.width
143+
}
144+
// content
145+
var contentY = (layoutHeight - contentHeight) / 2
146+
titlePlaceable?.let {
147+
it.placeRelative(x, contentY)
148+
contentY += it.height
149+
}
150+
summaryPlaceable?.placeRelative(x, contentY)
151+
// rightActions
152+
val rightX = constraints.maxWidth - rightWidth
153+
rightPlaceables.forEach {
154+
it.placeRelative(rightX, (layoutHeight - it.height) / 2)
155+
}
130156
}
131157
}
132158
}

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

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,27 +83,27 @@ fun ListPopup(
8383
var parentBounds by remember { mutableStateOf(IntRect.Zero) }
8484

8585
Layout(
86-
modifier = Modifier.onGloballyPositioned { childCoordinates ->
87-
childCoordinates.parentLayoutCoordinates?.let { parentLayoutCoordinates ->
88-
val positionInWindow = parentLayoutCoordinates.positionInWindow()
89-
parentBounds = IntRect(
90-
left = positionInWindow.x.toInt(),
91-
top = positionInWindow.y.toInt(),
92-
right = positionInWindow.x.toInt() + parentLayoutCoordinates.size.width,
93-
bottom = positionInWindow.y.toInt() + parentLayoutCoordinates.size.height
94-
)
86+
modifier = Modifier
87+
.onGloballyPositioned { childCoordinates ->
88+
childCoordinates.parentLayoutCoordinates?.let { parentLayoutCoordinates ->
89+
val positionInWindow = parentLayoutCoordinates.positionInWindow()
90+
parentBounds = IntRect(
91+
left = positionInWindow.x.toInt(),
92+
top = positionInWindow.y.toInt(),
93+
right = positionInWindow.x.toInt() + parentLayoutCoordinates.size.width,
94+
bottom = positionInWindow.y.toInt() + parentLayoutCoordinates.size.height
95+
)
96+
}
9597
}
96-
}
9798
) { _, _ -> layout(0, 0) {} }
9899

99-
val popupMargin = remember(popupPositionProvider, layoutDirection, density) {
100-
val pd = popupPositionProvider.getMargins()
100+
val popupMargin = remember {
101101
with(density) {
102102
IntRect(
103-
left = pd.calculateLeftPadding(layoutDirection).roundToPx(),
104-
top = pd.calculateTopPadding().roundToPx(),
105-
right = pd.calculateRightPadding(layoutDirection).roundToPx(),
106-
bottom = pd.calculateBottomPadding().roundToPx()
103+
left = popupPositionProvider.getMargins().calculateLeftPadding(layoutDirection).roundToPx(),
104+
top = popupPositionProvider.getMargins().calculateTopPadding().roundToPx(),
105+
right = popupPositionProvider.getMargins().calculateRightPadding(layoutDirection).roundToPx(),
106+
bottom = popupPositionProvider.getMargins().calculateBottomPadding().roundToPx()
107107
)
108108
}
109109
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,9 @@ private fun RefreshHeader(
557557
}
558558

559559
Column(
560-
modifier = modifier.fillMaxWidth().height(heightInfo.second),
560+
modifier = modifier
561+
.fillMaxWidth()
562+
.height(heightInfo.second),
561563
horizontalAlignment = Alignment.CenterHorizontally,
562564
verticalArrangement = Arrangement.Top
563565
) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ fun TabRow(
6969
BoxWithConstraints(
7070
modifier = Modifier
7171
.fillMaxWidth()
72-
.then(modifier)
7372
.height(height)
73+
.then(modifier)
7474
) {
7575
val config = rememberTabRowConfig(tabs, minWidth, maxWidth, cornerRadius, 9.dp, this.maxWidth)
7676

@@ -130,8 +130,8 @@ fun TabRowWithContour(
130130
BoxWithConstraints(
131131
modifier = Modifier
132132
.fillMaxWidth()
133-
.then(modifier)
134133
.height(height)
134+
.then(modifier)
135135
) {
136136
val lazyRowAvailableWidth = this.maxWidth - (contourPadding * 2)
137137
val config = rememberTabRowConfig(tabs, minWidth, maxWidth, cornerRadius, contourPadding, lazyRowAvailableWidth)

0 commit comments

Comments
 (0)