Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,8 @@ internal fun rememberComposeSceneLayer(
internal fun ComposeSceneLayer.Content(content: @Composable () -> Unit) {
val currentContent by rememberUpdatedState(content)
DisposableEffect(this) {
setContent {
currentContent()
}
onDispose { }
setContent(currentContent)
onDispose { }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ package androidx.compose.ui.window

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
Expand All @@ -35,7 +35,7 @@ import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.layout.EmptyLayout
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalPlatformWindowInsets
Expand Down Expand Up @@ -419,17 +419,28 @@ private fun PopupLayout(
onOutsidePointerEvent: ((eventType: PointerEventType, button: PointerButton?) -> Unit)? = null,
content: @Composable () -> Unit
) {
// Use a MutableState directly to avoid recomposing when the value changes
val parentBoundsInWindow: MutableState<IntRect> = remember { mutableStateOf(IntRect.Zero) }
EmptyLayout(Modifier.onPlaced { childCoordinates ->
// This will be called before the popup measure policy is actually asked to calculate
// the popup's position, so it will never see the initial value of IntRect.Zero
childCoordinates.parentCoordinates?.let {
// Nodes which read layout coordinates (including, e.g., positionInWindow) in
// layout/placement get invalidated when these coordinates change
val layoutPosition = it.positionInWindow().round()
val layoutSize = it.size
parentBoundsInWindow.value = IntRect(layoutPosition, layoutSize)
}
})

val currentContent by rememberUpdatedState(content)
var layoutParentBoundsInWindow: IntRect? by remember { mutableStateOf(null) }
EmptyLayout(Modifier.parentBoundsInWindow { layoutParentBoundsInWindow = it })
val layer = rememberComposeSceneLayer(
focusable = properties.focusable
)
layer.setKeyEventListener(onPreviewKeyEvent, onKeyEvent)
layer.setOutsidePointerEventListener(onOutsidePointerEvent)
layer.Content {
val platformInsets = properties.platformInsets
val parentBoundsInWindow = layoutParentBoundsInWindow ?: return@Content
val containerSize = LocalWindowInfo.current.containerSize
val layoutDirection = LocalLayoutDirection.current
val measurePolicy = rememberPopupMeasurePolicy(
Expand Down Expand Up @@ -463,16 +474,6 @@ private val PopupProperties.platformInsets: PlatformInsets
}
}

private fun Modifier.parentBoundsInWindow(
onBoundsChanged: (IntRect) -> Unit
) = this.onGloballyPositioned { childCoordinates ->
childCoordinates.parentCoordinates?.let {
val layoutPosition = it.positionInWindow().round()
val layoutSize = it.size
onBoundsChanged(IntRect(layoutPosition, layoutSize))
}
}

@Composable
private fun rememberPopupMeasurePolicy(
layer: ComposeSceneLayer,
Expand All @@ -481,15 +482,16 @@ private fun rememberPopupMeasurePolicy(
containerSize: IntSize,
platformInsets: PlatformInsets,
layoutDirection: LayoutDirection,
parentBoundsInWindow: IntRect,
parentBoundsInWindow: MutableState<IntRect>
) = remember(layer, popupPositionProvider, properties, containerSize, platformInsets, layoutDirection, parentBoundsInWindow) {
RootMeasurePolicy(
platformInsets = platformInsets,
usePlatformDefaultWidth = properties.usePlatformDefaultWidth
) { contentSize ->
val parentRectInWindow = parentBoundsInWindow.value
val positionWithInsets = positionWithInsets(platformInsets, containerSize) { sizeWithoutInsets ->
// Position provider works in coordinates without insets.
val boundsWithoutInsets = parentBoundsInWindow.translate(
val boundsWithoutInsets = parentRectInWindow.translate(
-platformInsets.left,
-platformInsets.top
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ internal fun RootMeasurePolicy(
constraints, platformInsets, usePlatformDefaultWidth
)
val placeables = measurables.fastMap { it.measure(platformConstraints) }
val contentSize = IntSize(
width = placeables.fastMaxBy { it.width }?.width ?: constraints.minWidth,
height = placeables.fastMaxBy { it.height }?.height ?: constraints.minHeight
)
val position = calculatePosition(contentSize)
layout(constraints.maxWidth, constraints.maxHeight) {
val contentSize = IntSize(
width = placeables.fastMaxBy { it.width }?.width ?: constraints.minWidth,
height = placeables.fastMaxBy { it.height }?.height ?: constraints.minHeight
)
val position = calculatePosition(contentSize)
placeables.fastForEach {
it.place(position.x, position.y)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ import androidx.compose.ui.assertReceived
import androidx.compose.ui.assertReceivedLast
import androidx.compose.ui.assertReceivedNoEvents
import androidx.compose.ui.assertThat
import androidx.compose.ui.containsExactly
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.asComposeCanvas
import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.input.pointer.PointerButtons
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.isEqualTo
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalPlatformWindowInsets
Expand Down Expand Up @@ -742,6 +745,44 @@ class PopupTest {
scene.close()
}
}

@Test
fun popupShownAtCorrectCoordinatesImmediately() = runSkikoComposeUiTest {
val positionProvider = object : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset {
return anchorBounds.topLeft
}
}

var showPopup by mutableStateOf(false)
val popupPositions = mutableListOf<Offset>()
setContent {
Box(Modifier.size(300.dp)) {
Box(Modifier.size(100.dp).offset(100.dp, 100.dp)) {
if (showPopup) {
Popup(popupPositionProvider = positionProvider) {
Box(
Modifier
.size(50.dp)
.onPlaced {
popupPositions.add(it.positionInRoot())
}
)
}
}
}
}
}

showPopup = true
waitForIdle()
assertThat(popupPositions).containsExactly(Offset(100f, 100f))
}
}

private fun WindowInsetsConfigSystemBars(
Expand Down