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

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 @@ -18,13 +18,17 @@ package androidx.compose.foundation.layout

import androidx.compose.runtime.Composable
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.LocalLayoutMargins
import androidx.compose.ui.platform.LocalSafeArea
import androidx.compose.ui.platform.PlatformInsets
import androidx.compose.ui.uikit.*
import androidx.compose.ui.unit.dp

private val ZeroInsets = WindowInsets(0, 0, 0, 0)

@OptIn(InternalComposeApi::class)
private fun IOSInsets.toWindowInsets() = WindowInsets(
@OptIn(ExperimentalComposeUiApi::class)
private fun PlatformInsets.toWindowInsets() = WindowInsets(
top = top,
bottom = bottom,
left = left,
Expand All @@ -36,16 +40,16 @@ private fun IOSInsets.toWindowInsets() = WindowInsets(
*/
private val WindowInsets.Companion.iosSafeArea: WindowInsets
@Composable
@OptIn(InternalComposeApi::class)
get() = LocalSafeAreaState.current.value.toWindowInsets()
@OptIn(InternalComposeApi::class, ExperimentalComposeUiApi::class)
get() = LocalSafeArea.current.toWindowInsets()

/**
* This insets represents iOS layoutMargins.
*/
private val WindowInsets.Companion.layoutMargins: WindowInsets
@Composable
@OptIn(InternalComposeApi::class)
get() = LocalLayoutMarginsState.current.value.toWindowInsets()
@OptIn(InternalComposeApi::class, ExperimentalComposeUiApi::class)
get() = LocalLayoutMargins.current.toWindowInsets()

/**
* An insets type representing the window of a caption bar.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@
package androidx.compose.ui.window

import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Density
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.ui.platform.PlatformInsets

@OptIn(InternalComposeApi::class)
@Composable
internal actual fun Density.platformPadding(): RootLayoutPadding =
RootLayoutPadding.Zero
internal actual fun platformInsets(): PlatformInsets =
PlatformInsets.Zero

@Composable
internal actual fun platformOwnerContent(content: @Composable () -> Unit) {
content()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.ui.platform

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

/**
* This class represents platform insets.
*/
@ExperimentalComposeUiApi
@Immutable
class PlatformInsets(
@Stable
val top: Dp = 0.dp,
@Stable
val bottom: Dp = 0.dp,
@Stable
val left: Dp = 0.dp,
@Stable
val right: Dp = 0.dp,
) {
companion object {
val Zero = PlatformInsets(0.dp, 0.dp, 0.dp, 0.dp)
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is PlatformInsets) return false

if (top != other.top) return false
if (bottom != other.bottom) return false
if (left != other.left) return false
if (right != other.right) return false

return true
}

override fun hashCode(): Int {
var result = top.hashCode()
result = 31 * result + bottom.hashCode()
result = 31 * result + left.hashCode()
result = 31 * result + right.hashCode()
return result
}

override fun toString(): String {
return "PlatformInsets(top=$top, bottom=$bottom, left=$left, right=$right)"
}
}

internal fun PlatformInsets.exclude(insets: PlatformInsets) = PlatformInsets(
left = (left - insets.left).coerceAtLeast(0.dp),
top = (top - insets.top).coerceAtLeast(0.dp),
right = (right - insets.right).coerceAtLeast(0.dp),
bottom = (bottom - insets.bottom).coerceAtLeast(0.dp)
)
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerInputEvent
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.PlatformInsets
import androidx.compose.ui.requireCurrent
import androidx.compose.ui.semantics.dialog
import androidx.compose.ui.semantics.semantics
Expand Down Expand Up @@ -169,15 +170,15 @@ private fun DialogLayout(
onOutsidePointerEvent: ((PointerInputEvent) -> Unit)? = null,
content: @Composable () -> Unit
) {
val platformInsets = platformInsets()
RootLayout(
modifier = modifier,
focusable = true,
onOutsidePointerEvent = onOutsidePointerEvent
) { owner ->
val density = LocalDensity.current
val measurePolicy = rememberDialogMeasurePolicy(
properties = properties,
platformPadding = with(density) { platformPadding() }
platformInsets = platformInsets
) {
owner.bounds = it
}
Expand All @@ -191,14 +192,14 @@ private fun DialogLayout(
@Composable
private fun rememberDialogMeasurePolicy(
properties: DialogProperties,
platformPadding: RootLayoutPadding,
platformInsets: PlatformInsets,
onBoundsChanged: (IntRect) -> Unit
) = remember(properties, platformPadding, onBoundsChanged) {
) = remember(properties, platformInsets, onBoundsChanged) {
RootMeasurePolicy(
platformPadding = platformPadding,
platformInsets = platformInsets,
usePlatformDefaultWidth = properties.usePlatformDefaultWidth
) { windowSize, contentSize ->
val position = positionWithPadding(platformPadding, windowSize) {
val position = positionWithInsets(platformInsets, windowSize) {
it.center - contentSize.center
}
onBoundsChanged(IntRect(position, contentSize))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ import androidx.compose.ui.input.pointer.PointerInputEvent
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.PlatformInsets
import androidx.compose.ui.semantics.popup
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.IntOffset
Expand Down Expand Up @@ -421,6 +421,7 @@ private fun PopupLayout(
onOutsidePointerEvent: ((PointerInputEvent) -> Unit)? = null,
content: @Composable () -> Unit
) {
val platformInsets = platformInsets()
var layoutParentBoundsInWindow: IntRect? by remember { mutableStateOf(null) }
EmptyLayout(Modifier.parentBoundsInWindow { layoutParentBoundsInWindow = it })
RootLayout(
Expand All @@ -429,12 +430,11 @@ private fun PopupLayout(
onOutsidePointerEvent = onOutsidePointerEvent
) { owner ->
val parentBounds = layoutParentBoundsInWindow ?: return@RootLayout
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
val measurePolicy = rememberPopupMeasurePolicy(
popupPositionProvider = popupPositionProvider,
properties = properties,
platformPadding = with(density) { platformPadding() },
platformInsets = platformInsets,
layoutDirection = layoutDirection,
parentBounds = parentBounds
) {
Expand All @@ -461,16 +461,16 @@ private fun Modifier.parentBoundsInWindow(
private fun rememberPopupMeasurePolicy(
popupPositionProvider: PopupPositionProvider,
properties: PopupProperties,
platformPadding: RootLayoutPadding,
platformInsets: PlatformInsets,
layoutDirection: LayoutDirection,
parentBounds: IntRect,
onBoundsChanged: (IntRect) -> Unit
) = remember(popupPositionProvider, properties, platformPadding, layoutDirection, parentBounds, onBoundsChanged) {
) = remember(popupPositionProvider, properties, platformInsets, layoutDirection, parentBounds, onBoundsChanged) {
RootMeasurePolicy(
platformPadding = platformPadding,
platformInsets = platformInsets,
usePlatformDefaultWidth = properties.usePlatformDefaultWidth
) { windowSize, contentSize ->
var position = positionWithPadding(platformPadding, windowSize) {
var position = positionWithInsets(platformInsets, windowSize) {
popupPositionProvider.calculatePosition(
parentBounds, it, layoutDirection, contentSize
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerInputEvent
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.MeasurePolicy
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.PlatformInsets
import androidx.compose.ui.platform.SkiaBasedOwner
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.requireCurrent
Expand Down Expand Up @@ -71,7 +73,9 @@ internal fun RootLayout(
)
scene.attach(owner)
owner to owner.setContent(parent = parentComposition) {
content(owner)
platformOwnerContent {
content(owner)
}
}
}
DisposableEffect(Unit) {
Expand Down Expand Up @@ -99,12 +103,12 @@ internal fun EmptyLayout(
)

internal fun RootMeasurePolicy(
platformPadding: RootLayoutPadding,
platformInsets: PlatformInsets,
usePlatformDefaultWidth: Boolean,
calculatePosition: (windowSize: IntSize, contentSize: IntSize) -> IntOffset,
calculatePosition: MeasureScope.(windowSize: IntSize, contentSize: IntSize) -> IntOffset,
) = MeasurePolicy {measurables, constraints ->
val platformConstraints = applyPlatformConstrains(
constraints, platformPadding, usePlatformDefaultWidth
constraints, platformInsets, usePlatformDefaultWidth
)
val placeables = measurables.fastMap { it.measure(platformConstraints) }
val windowSize = IntSize(constraints.maxWidth, constraints.maxHeight)
Expand All @@ -122,13 +126,12 @@ internal fun RootMeasurePolicy(

private fun Density.applyPlatformConstrains(
constraints: Constraints,
platformPadding: RootLayoutPadding,
platformInsets: PlatformInsets,
usePlatformDefaultWidth: Boolean
): Constraints {
val platformConstraints = constraints.offset(
horizontal = -(platformPadding.left + platformPadding.right),
vertical = -(platformPadding.top + platformPadding.bottom)
)
val horizontal = platformInsets.left.roundToPx() + platformInsets.right.roundToPx()
val vertical = platformInsets.top.roundToPx() + platformInsets.bottom.roundToPx()
val platformConstraints = constraints.offset(-horizontal, -vertical)
return if (usePlatformDefaultWidth) {
platformConstraints.constrain(
platformDefaultConstrains(constraints)
Expand All @@ -138,32 +141,30 @@ private fun Density.applyPlatformConstrains(
}
}

internal data class RootLayoutPadding(
val left: Int,
val top: Int,
val right: Int,
val bottom: Int
) {
companion object {
val Zero = RootLayoutPadding(0, 0, 0, 0)
}
}

internal fun positionWithPadding(
padding: RootLayoutPadding,
internal fun MeasureScope.positionWithInsets(
insets: PlatformInsets,
size: IntSize,
calculatePosition: (size: IntSize) -> IntOffset,
): IntOffset {
val horizontal = insets.left.roundToPx() + insets.right.roundToPx()
val vertical = insets.top.roundToPx() + insets.bottom.roundToPx()
val sizeWithPadding = IntSize(
width = size.width - padding.left - padding.right,
height = size.height - padding.top - padding.bottom
width = size.width - horizontal,
height = size.height - vertical
)
val position = calculatePosition(sizeWithPadding)
return position + IntOffset(padding.left, padding.top)
val offset = IntOffset(
x = insets.left.roundToPx(),
y = insets.top.roundToPx()
)
return position + offset
}

@Composable
internal expect fun Density.platformPadding(): RootLayoutPadding
internal expect fun platformInsets(): PlatformInsets

@Composable
internal expect fun platformOwnerContent(content: @Composable () -> Unit)

private fun Density.platformDefaultConstrains(
constraints: Constraints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,23 @@
* limitations under the License.
*/

package androidx.compose.ui.uikit
package androidx.compose.ui.platform

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.State
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

/**
* Composition local for SafeArea of ComposeUIViewController
*/
@InternalComposeApi
val LocalSafeAreaState = staticCompositionLocalOf<State<IOSInsets>> {
error("CompositionLocal LocalSafeAreaTopState not present")
val LocalSafeArea = staticCompositionLocalOf<PlatformInsets> {
error("CompositionLocal LocalSafeArea not present")
}

/**
* Composition local for layoutMargins of ComposeUIViewController
*/
@InternalComposeApi
val LocalLayoutMarginsState = staticCompositionLocalOf<State<IOSInsets>> {
error("CompositionLocal LocalLayoutMarginsState not present")
val LocalLayoutMargins = staticCompositionLocalOf<PlatformInsets> {
error("CompositionLocal LocalLayoutMargins not present")
}

/**
* This class represents iOS Insets.
* It contains equals and hashcode and can be used as Compose State<IOSInsets>.
*/
@Immutable
@InternalComposeApi
data class IOSInsets(
val top: Dp = 0.dp,
val bottom: Dp = 0.dp,
val left: Dp = 0.dp,
val right: Dp = 0.dp,
)
Loading