https://kotlinlang.org logo
#compose-desktop
Title
# compose-desktop
t

Tom Truyen

11/11/2023, 4:25 PM
Hi, I am working on an AutoCompleteTextView/ExposedDropdownMenuBox in Jetpack compose for Desktop. For some reason with my current implementation whenever I get a lot of items from my API returned the DropdownMenu overlaps with my TextField, how can I prevent this behaviour? I have already tried to calculate the height of the screen and subtract the TextField height but without much success. Is there a way I can force my Dropdown to always be below the TextField, maybe with some kinds of menuAnchor() behaviour like Android has?
Copy code
@Composable
fun TextFieldWithDropdown(
    modifier: Modifier = Modifier,
    value: String,
    onValueChange: (String) -> Unit,
    options: List<String>,
    onOptionClick: (String) -> Unit,
) {
    val density = LocalDensity.current

    var text by remember {
        mutableStateOf(value)
    }

    var expanded by remember(options) { mutableStateOf(options.isNotEmpty()) }

    var anchorSize by remember {
        mutableStateOf(DpSize.Zero)
    }

    Box(modifier) {
        TextField(
            modifier = Modifier
                .fillMaxWidth()
                .onGloballyPositioned {
                    anchorSize = with(density) {
                        DpSize(
                            width = it.size.width.toDp(),
                            height = it.size.height.toDp()
                        )
                    }
                }
                .onFocusChanged { focusState ->
                    if (!focusState.isFocused) {
                        expanded = false
                    }
                },
            singleLine = true,
            value = text,
            onValueChange = {
                text = it
                onValueChange(it)
            },
            colors = TextFieldDefaults.textFieldColors(
                backgroundColor = MaterialTheme.colors.surface,
                textColor = MaterialTheme.colors.onSurface,
                cursorColor = MaterialTheme.colors.onSurface,
            ),
        )


        DropdownMenu(
            modifier = Modifier
                .width(anchorSize.width),
            offset = DpOffset(0.dp, Dimens.normal),
            focusable = false,
            expanded = expanded,
            onDismissRequest = { expanded = false }
        ) {
            options.forEach { text ->
                DropdownMenuItem(
                    onClick = {
                        onOptionClick(text)
                    }
                ) {
                    Text(text = text)
                }
            }
        }
    }
}
a

Alexander Maryanovsky

11/11/2023, 4:38 PM
You can look at the implementation of
ExposedDropdownMenuBox
and see how it does that.
This is how it computes the menu height:
Copy code
val windowInfo = LocalWindowInfo.current
    Box(
        modifier.onGloballyPositioned {
            width = it.size.width
            val boundsInWindow = it.boundsInWindow()
            val visibleWindowBounds = windowInfo.containerSize.toIntRect()
            val heightAbove = boundsInWindow.top - visibleWindowBounds.top
            val heightBelow = visibleWindowBounds.height - boundsInWindow.bottom
            menuHeight = max(heightAbove, heightBelow).toInt() - verticalMarginInPx
        }
    )
t

Tom Truyen

11/11/2023, 4:42 PM
Thank you. I will look into that a bit more
a

Alexander Maryanovsky

11/11/2023, 4:42 PM
I implemented this with my own Popup, though.
DropdownMenu
will refuse to position too close to the top/bottom of the window, which will look weird if your textfield is smaller than the standard material one.
image.png
and yeah looks like it will overlap the textfield if it’s too tall
There’s a bug in it.
The desired height is computed as
max(heightAbove, heightBelow).toInt() - verticalMarginInPx
but the popup positioning requires that it doesn’t extend into the vertical margin both at the top and the bottom.
Hmm, somehow it’s not happening on Android. I’ll look into it.
t

Tom Truyen

11/11/2023, 5:24 PM
If you find anything then please let me know because its quite annoying as I have tried forcing its height and even that is being ignored somehow, making it impossible for me to properly calculate it. The only way that I can limit it is by hardcoding the value but that is not something I intent on doing
a

Alexander Maryanovsky

11/11/2023, 5:24 PM
The height computation simply needs to be done differently.
Should be
Copy code
val visibleWindowBounds = IntRect(
                left = 0,
                top = verticalMarginInPx,
                right = windowInfo.containerSize.width,
                bottom = windowInfo.containerSize.height - 2*verticalMarginInPx
            )
Other than that, the above code should work.
Use the value of
menuHeight
above, convert it to
dp
and pass it to
Modifier.heightIn(max = menuHeightDp)
on the
DropdownMenu
t

Tom Truyen

11/11/2023, 5:34 PM
Incredible. That does work indeed.
final code.kt
Just in case somebody ever needs it 🙂
Thanks for your help Alexander!
Do you maybe also know where I can read more about this
boundsInWindow
extension function? Looks quite interesting
a

Alexander Maryanovsky

11/11/2023, 5:36 PM
cmd-click it
t

Tom Truyen

11/11/2023, 5:36 PM
I have, of course 🙂 But I was looking for more of a written guide maybe?
a

Alexander Maryanovsky

11/11/2023, 5:49 PM
It just converts local coordinates to window bounds
But anyway, why are you not simply using
ExposedDropdownMenuBox
?
t

Tom Truyen

11/11/2023, 5:50 PM
I thought that didn't exist yet for Desktop?
a

Alexander Maryanovsky

11/11/2023, 5:50 PM
It does, since latest release.
(but it will have that bug, until we fix it)
t

Tom Truyen

11/11/2023, 5:53 PM
The fact that I missed it 🤦‍♂️
Though for now I'll probably be better of using this custom implementation until it is fixed 🙂
3 Views