Skip to content

Commit 64bf0b5

Browse files
committed
library: Fix dropdown popup offset
1 parent 0167e79 commit 64bf0b5

File tree

2 files changed

+191
-92
lines changed

2 files changed

+191
-92
lines changed

composeApp/src/commonMain/kotlin/MainPage.kt

Lines changed: 173 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
import androidx.compose.foundation.Image
22
import androidx.compose.foundation.clickable
3+
import androidx.compose.foundation.layout.BoxWithConstraints
34
import androidx.compose.foundation.layout.Column
45
import androidx.compose.foundation.layout.PaddingValues
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.WindowInsets
9+
import androidx.compose.foundation.layout.WindowInsetsSides
10+
import androidx.compose.foundation.layout.displayCutout
511
import androidx.compose.foundation.layout.fillMaxSize
612
import androidx.compose.foundation.layout.fillMaxWidth
13+
import androidx.compose.foundation.layout.height
14+
import androidx.compose.foundation.layout.navigationBars
15+
import androidx.compose.foundation.layout.only
716
import androidx.compose.foundation.layout.padding
17+
import androidx.compose.foundation.layout.windowInsetsPadding
818
import androidx.compose.runtime.Composable
919
import androidx.compose.runtime.getValue
1020
import androidx.compose.runtime.mutableStateOf
@@ -35,80 +45,177 @@ fun MainPage(
3545
var miuixSearchValue by remember { mutableStateOf("") }
3646
var expanded by rememberSaveable { mutableStateOf(false) }
3747

38-
LazyColumn(
39-
contentPadding = PaddingValues(top = padding.calculateTopPadding()),
40-
topAppBarScrollBehavior = topAppBarScrollBehavior,
48+
BoxWithConstraints(
49+
modifier = Modifier.fillMaxSize()
4150
) {
42-
item {
43-
SearchBar(
44-
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
45-
inputField = {
46-
InputField(
47-
query = miuixSearchValue,
48-
onQueryChange = { miuixSearchValue = it },
49-
onSearch = { expanded = false },
50-
expanded = expanded,
51-
onExpandedChange = { expanded = it },
52-
label = "Search",
53-
leadingIcon = {
54-
Image(
55-
modifier = Modifier.padding(horizontal = 16.dp),
56-
imageVector = MiuixIcons.Search,
57-
colorFilter = BlendModeColorFilter(
58-
MiuixTheme.colorScheme.onSurfaceContainer,
59-
BlendMode.SrcIn
60-
),
61-
contentDescription = "Search"
51+
if (maxWidth < 840.dp) {
52+
LazyColumn(
53+
contentPadding = PaddingValues(top = padding.calculateTopPadding()),
54+
topAppBarScrollBehavior = topAppBarScrollBehavior,
55+
) {
56+
item {
57+
SearchBar(
58+
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
59+
inputField = {
60+
InputField(
61+
query = miuixSearchValue,
62+
onQueryChange = { miuixSearchValue = it },
63+
onSearch = { expanded = false },
64+
expanded = expanded,
65+
onExpandedChange = { expanded = it },
66+
label = "Search",
67+
leadingIcon = {
68+
Image(
69+
modifier = Modifier.padding(horizontal = 16.dp),
70+
imageVector = MiuixIcons.Search,
71+
colorFilter = BlendModeColorFilter(
72+
MiuixTheme.colorScheme.onSurfaceContainer,
73+
BlendMode.SrcIn
74+
),
75+
contentDescription = "Search"
76+
)
77+
},
6278
)
6379
},
64-
)
65-
},
66-
outsideRightAction = {
67-
Text(
68-
modifier = Modifier
69-
.padding(start = 12.dp)
70-
.clickable(
71-
interactionSource = null,
72-
indication = null
73-
) {
74-
expanded = false
75-
miuixSearchValue = ""
76-
},
77-
text = "Cancel",
78-
color = MiuixTheme.colorScheme.primary
79-
)
80-
},
81-
expanded = expanded,
82-
onExpandedChange = { expanded = it }
83-
) {
84-
Column(
85-
Modifier.fillMaxSize()
86-
) {
87-
repeat(4) { idx ->
88-
val resultText = "Suggestion $idx"
89-
BasicComponent(
90-
title = resultText,
91-
modifier = Modifier
92-
.fillMaxWidth(),
93-
onClick = {
94-
miuixSearchValue = resultText
95-
expanded = false
80+
outsideRightAction = {
81+
Text(
82+
modifier = Modifier
83+
.padding(start = 12.dp)
84+
.clickable(
85+
interactionSource = null,
86+
indication = null
87+
) {
88+
expanded = false
89+
miuixSearchValue = ""
90+
},
91+
text = "Cancel",
92+
color = MiuixTheme.colorScheme.primary
93+
)
94+
},
95+
expanded = expanded,
96+
onExpandedChange = { expanded = it }
97+
) {
98+
Column(
99+
Modifier.fillMaxSize()
100+
) {
101+
repeat(4) { idx ->
102+
val resultText = "Suggestion $idx"
103+
BasicComponent(
104+
title = resultText,
105+
modifier = Modifier
106+
.fillMaxWidth(),
107+
onClick = {
108+
miuixSearchValue = resultText
109+
expanded = false
110+
}
111+
)
96112
}
97-
)
113+
}
114+
}
115+
}
116+
if (!expanded) {
117+
item(
118+
key = "text"
119+
) {
120+
TextComponent()
121+
}
122+
item(
123+
key = "other"
124+
) {
125+
OtherComponent(padding)
98126
}
99127
}
100128
}
101-
}
102-
if (!expanded) {
103-
item(
104-
key = "text"
105-
) {
106-
TextComponent()
107-
}
108-
item(
109-
key = "other"
129+
} else {
130+
Row(
131+
modifier = Modifier
132+
.windowInsetsPadding(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal))
133+
.windowInsetsPadding(WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal))
110134
) {
111-
OtherComponent(padding)
135+
LazyColumn(
136+
modifier = Modifier.weight(1f),
137+
contentPadding = PaddingValues(top = padding.calculateTopPadding()),
138+
topAppBarScrollBehavior = topAppBarScrollBehavior,
139+
) {
140+
item {
141+
SearchBar(
142+
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
143+
inputField = {
144+
InputField(
145+
query = miuixSearchValue,
146+
onQueryChange = { miuixSearchValue = it },
147+
onSearch = { expanded = false },
148+
expanded = expanded,
149+
onExpandedChange = { expanded = it },
150+
label = "Search",
151+
leadingIcon = {
152+
Image(
153+
modifier = Modifier.padding(horizontal = 16.dp),
154+
imageVector = MiuixIcons.Search,
155+
colorFilter = BlendModeColorFilter(
156+
MiuixTheme.colorScheme.onSurfaceContainer,
157+
BlendMode.SrcIn
158+
),
159+
contentDescription = "Search"
160+
)
161+
},
162+
)
163+
},
164+
outsideRightAction = {
165+
Text(
166+
modifier = Modifier
167+
.padding(start = 12.dp)
168+
.clickable(
169+
interactionSource = null,
170+
indication = null
171+
) {
172+
expanded = false
173+
miuixSearchValue = ""
174+
},
175+
text = "Cancel",
176+
color = MiuixTheme.colorScheme.primary
177+
)
178+
},
179+
expanded = expanded,
180+
onExpandedChange = { expanded = it }
181+
) {
182+
Column(
183+
Modifier.fillMaxSize()
184+
) {
185+
repeat(4) { idx ->
186+
val resultText = "Suggestion $idx"
187+
BasicComponent(
188+
title = resultText,
189+
modifier = Modifier
190+
.fillMaxWidth(),
191+
onClick = {
192+
miuixSearchValue = resultText
193+
expanded = false
194+
}
195+
)
196+
}
197+
}
198+
}
199+
}
200+
if (!expanded) {
201+
item(
202+
key = "text"
203+
) {
204+
OtherComponent(padding)
205+
206+
}
207+
}
208+
}
209+
LazyColumn(
210+
modifier = Modifier.padding(end = 12.dp).weight(1f),
211+
contentPadding = PaddingValues(top = padding.calculateTopPadding()),
212+
topAppBarScrollBehavior = topAppBarScrollBehavior
213+
) {
214+
item {
215+
TextComponent()
216+
Spacer(modifier = Modifier.height(12.dp + padding.calculateBottomPadding()))
217+
}
218+
}
112219
}
113220
}
114221
}

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

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
2525
import androidx.compose.runtime.Composable
2626
import androidx.compose.runtime.LaunchedEffect
2727
import androidx.compose.runtime.MutableState
28-
import androidx.compose.runtime.derivedStateOf
2928
import androidx.compose.runtime.getValue
3029
import androidx.compose.runtime.mutableStateListOf
3130
import androidx.compose.runtime.mutableStateOf
@@ -54,9 +53,7 @@ import androidx.compose.ui.text.rememberTextMeasurer
5453
import androidx.compose.ui.text.style.TextAlign
5554
import androidx.compose.ui.unit.Dp
5655
import androidx.compose.ui.unit.DpSize
57-
import androidx.compose.ui.unit.LayoutDirection
5856
import androidx.compose.ui.unit.dp
59-
import androidx.compose.ui.unit.max
6057
import androidx.compose.ui.unit.sp
6158
import top.yukonga.miuix.kmp.basic.BasicComponent
6259
import top.yukonga.miuix.kmp.basic.Box
@@ -110,7 +107,8 @@ fun SuperDropdown(
110107
val hapticFeedback = LocalHapticFeedback.current
111108
val actionColor = if (enabled) MiuixTheme.colorScheme.onSurfaceVariantActions else MiuixTheme.colorScheme.disabledOnSecondaryVariant
112109
var alignLeft by rememberSaveable { mutableStateOf(true) }
113-
var dropdownOffsetPx by remember { mutableStateOf(0) }
110+
var dropdownOffsetXPx by remember { mutableStateOf(0) }
111+
var dropdownOffsetYPx by remember { mutableStateOf(0) }
114112
var componentHeightPx by remember { mutableStateOf(0) }
115113
var componentWidthPx by remember { mutableStateOf(0) }
116114

@@ -130,7 +128,8 @@ fun SuperDropdown(
130128
.onGloballyPositioned { coordinates ->
131129
if (isDropdownExpanded.value) {
132130
val positionInWindow = coordinates.positionInWindow()
133-
dropdownOffsetPx = positionInWindow.y.toInt()
131+
dropdownOffsetXPx = positionInWindow.x.toInt()
132+
dropdownOffsetYPx = positionInWindow.y.toInt()
134133
componentHeightPx = coordinates.size.height
135134
componentWidthPx = coordinates.size.width
136135
}
@@ -174,12 +173,12 @@ fun SuperDropdown(
174173
}
175174

176175
val density = LocalDensity.current
177-
var offsetPx by remember { mutableStateOf(0) }
176+
var offsetXPx by remember { mutableStateOf(0) }
177+
var offsetYPx by remember { mutableStateOf(0) }
178178
val textMeasurer = rememberTextMeasurer()
179179
val textStyle = remember { TextStyle(fontWeight = FontWeight.Medium, fontSize = 17.sp) }
180180
val textWidthDp = remember(items) { items.maxOfOrNull { with(density) { textMeasurer.measure(text = it, style = textStyle).size.width.toDp() } } }
181181
val getWindowSize = rememberUpdatedState(getWindowSize())
182-
val windowWeightPx by rememberUpdatedState(getWindowSize.value.width)
183182
val windowHeightPx by rememberUpdatedState(getWindowSize.value.height)
184183
val statusBarPx by rememberUpdatedState(
185184
with(density) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() }.roundToInt()
@@ -191,17 +190,7 @@ fun SuperDropdown(
191190
with(density) { WindowInsets.captionBar.asPaddingValues().calculateBottomPadding().toPx() }.roundToInt()
192191
)
193192
val insideHeightPx by rememberUpdatedState(with(density) { insideMargin.height.toPx() }.roundToInt())
194-
val displayCutoutSize =
195-
WindowInsets.displayCutout.asPaddingValues(density).calculateLeftPadding(LayoutDirection.Ltr) + WindowInsets.displayCutout.asPaddingValues(density)
196-
.calculateRightPadding(LayoutDirection.Ltr)
197-
val popupPadding by rememberUpdatedState {
198-
derivedStateOf {
199-
max(
200-
horizontalPadding + (windowWeightPx.dp - componentWidthPx.dp) / 2 / density.density
201-
- if (defaultWindowInsetsPadding) displayCutoutSize / 2 else 0.dp, 0.dp
202-
)
203-
}
204-
}
193+
val paddingPx by rememberUpdatedState(with(density) { horizontalPadding.toPx() }.roundToInt())
205194

206195
BackHandler(enabled = isDropdownExpanded.value) { dismissPopup(isDropdownExpanded) }
207196

@@ -222,17 +211,19 @@ fun SuperDropdown(
222211
}
223212
)
224213
}
225-
.offset(y = offsetPx.dp / density.density)
214+
.offset(x = offsetXPx.dp / density.density, y = offsetYPx.dp / density.density)
226215
) {
227216
LazyColumn(
228217
modifier = Modifier
229-
.padding(
230-
horizontal = popupPadding.invoke().value
231-
)
232218
.onGloballyPositioned { layoutCoordinates ->
233-
offsetPx = calculateOffsetPx(
219+
offsetXPx = if (alwaysRight || !alignLeft) {
220+
dropdownOffsetXPx + componentWidthPx - layoutCoordinates.size.width - paddingPx
221+
} else {
222+
dropdownOffsetXPx + paddingPx
223+
}
224+
offsetYPx = calculateOffsetYPx(
234225
windowHeightPx,
235-
dropdownOffsetPx,
226+
dropdownOffsetYPx,
236227
layoutCoordinates.size.height,
237228
componentHeightPx,
238229
insideHeightPx,
@@ -241,7 +232,7 @@ fun SuperDropdown(
241232
captionBarPx
242233
)
243234
}
244-
.align(if (alignLeft && !alwaysRight) AbsoluteAlignment.TopLeft else AbsoluteAlignment.TopRight)
235+
.align(AbsoluteAlignment.TopLeft)
245236
.graphicsLayer(
246237
shadowElevation = 18f,
247238
shape = RoundedCornerShape(18.dp)
@@ -348,7 +339,7 @@ fun DropdownImpl(
348339
* @param captionBarPx The height of the caption bar padding.
349340
* @return The offset of the current dropdown.
350341
*/
351-
fun calculateOffsetPx(
342+
fun calculateOffsetYPx(
352343
windowHeightPx: Int,
353344
dropdownOffsetPx: Int,
354345
dropdownHeightPx: Int,
@@ -364,6 +355,7 @@ fun calculateOffsetPx(
364355
val offset = dropdownOffsetPx - dropdownHeightPx / 2 + componentHeightPx / 2
365356
if (offset > insideHeightPx + statusBarPx) offset else insideHeightPx + statusBarPx
366357
}
358+
367359
}
368360

369361
/**

0 commit comments

Comments
 (0)