blob: 89a134f3f011f6307ce1ef19da584b1a271046d5 [file]
/*
* Copyright 2019 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.foundation.layout
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.IntrinsicMeasurable
import androidx.compose.ui.layout.IntrinsicMeasureScope
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.constrain
/**
* Declare the preferred width of the content to be the same as the min or max intrinsic width of
* the content. The incoming measurement [Constraints] may override this value, forcing the content
* to be either smaller or larger.
*
* See [height] for options of sizing to intrinsic height. Also see [width] and [widthIn] for other
* options to set the preferred width.
*
* Example usage for min intrinsic:
*
* @sample androidx.compose.foundation.layout.samples.SameWidthBoxes
*
* Example usage for max intrinsic:
*
* @sample androidx.compose.foundation.layout.samples.SameWidthTextBoxes
*/
@Stable
fun Modifier.width(intrinsicSize: IntrinsicSize) =
this then
IntrinsicWidthElement(
width = intrinsicSize,
enforceIncoming = true,
inspectorInfo =
debugInspectorInfo {
name = "width"
properties["intrinsicSize"] = intrinsicSize
},
)
/**
* Declare the preferred height of the content to be the same as the min or max intrinsic height of
* the content. The incoming measurement [Constraints] may override this value, forcing the content
* to be either smaller or larger.
*
* See [width] for other options of sizing to intrinsic width. Also see [height] and [heightIn] for
* other options to set the preferred height.
*
* Example usage for min intrinsic:
*
* @sample androidx.compose.foundation.layout.samples.MatchParentDividerForText
*
* Example usage for max intrinsic:
*
* @sample androidx.compose.foundation.layout.samples.MatchParentDividerForAspectRatio
*/
@Stable
fun Modifier.height(intrinsicSize: IntrinsicSize) =
this then
IntrinsicHeightElement(
height = intrinsicSize,
enforceIncoming = true,
inspectorInfo =
debugInspectorInfo {
name = "height"
properties["intrinsicSize"] = intrinsicSize
},
)
/**
* Declare the width of the content to be exactly the same as the min or max intrinsic width of the
* content. The incoming measurement [Constraints] will not override this value. If the content
* intrinsic width does not satisfy the incoming [Constraints], the parent layout will be reported a
* size coerced in the [Constraints], and the position of the content will be automatically offset
* to be centered on the space assigned to the child by the parent layout under the assumption that
* [Constraints] were respected.
*
* See [height] for options of sizing to intrinsic height. See [width] and [widthIn] for options to
* set the preferred width. See [requiredWidth] and [requiredWidthIn] for other options to set the
* required width.
*/
@Stable
fun Modifier.requiredWidth(intrinsicSize: IntrinsicSize) =
this then
IntrinsicWidthElement(
width = intrinsicSize,
enforceIncoming = false,
inspectorInfo =
debugInspectorInfo {
name = "requiredWidth"
properties["intrinsicSize"] = intrinsicSize
},
)
/**
* Declare the height of the content to be exactly the same as the min or max intrinsic height of
* the content. The incoming measurement [Constraints] will not override this value. If the content
* intrinsic height does not satisfy the incoming [Constraints], the parent layout will be reported
* a size coerced in the [Constraints], and the position of the content will be automatically offset
* to be centered on the space assigned to the child by the parent layout under the assumption that
* [Constraints] were respected.
*
* See [width] for options of sizing to intrinsic width. See [height] and [heightIn] for options to
* set the preferred height. See [requiredHeight] and [requiredHeightIn] for other options to set
* the required height.
*/
@Stable
fun Modifier.requiredHeight(intrinsicSize: IntrinsicSize) =
this then
IntrinsicHeightElement(
height = intrinsicSize,
enforceIncoming = false,
inspectorInfo =
debugInspectorInfo {
name = "requiredHeight"
properties["intrinsicSize"] = intrinsicSize
},
)
/** Intrinsic size used in [width] or [height] which can refer to width or height. */
enum class IntrinsicSize {
Min,
Max,
}
private class IntrinsicWidthElement(
val width: IntrinsicSize,
val enforceIncoming: Boolean,
val inspectorInfo: InspectorInfo.() -> Unit,
) : ModifierNodeElement<IntrinsicWidthNode>() {
override fun create() = IntrinsicWidthNode(width, enforceIncoming)
override fun update(node: IntrinsicWidthNode) {
node.width = width
node.enforceIncoming = enforceIncoming
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
val otherModifierElement = other as? IntrinsicWidthElement ?: return false
return width == otherModifierElement.width &&
enforceIncoming == otherModifierElement.enforceIncoming
}
override fun hashCode() = 31 * width.hashCode() + enforceIncoming.hashCode()
override fun InspectorInfo.inspectableProperties() {
inspectorInfo()
}
}
private class IntrinsicWidthNode(var width: IntrinsicSize, override var enforceIncoming: Boolean) :
IntrinsicSizeModifier() {
override fun MeasureScope.calculateContentConstraints(
measurable: Measurable,
constraints: Constraints,
): Constraints {
var measuredWidth =
if (width == IntrinsicSize.Min) {
measurable.minIntrinsicWidth(constraints.maxHeight)
} else {
measurable.maxIntrinsicWidth(constraints.maxHeight)
}
if (measuredWidth < 0) {
measuredWidth = 0
}
return Constraints.fixedWidth(measuredWidth)
}
override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int,
) =
if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height)
else measurable.maxIntrinsicWidth(height)
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int,
) =
if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height)
else measurable.maxIntrinsicWidth(height)
}
private class IntrinsicHeightElement(
val height: IntrinsicSize,
val enforceIncoming: Boolean,
val inspectorInfo: InspectorInfo.() -> Unit,
) : ModifierNodeElement<IntrinsicHeightNode>() {
override fun create() = IntrinsicHeightNode(height, enforceIncoming)
override fun update(node: IntrinsicHeightNode) {
node.height = height
node.enforceIncoming = enforceIncoming
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
val otherModifierElement = other as? IntrinsicHeightElement ?: return false
return height == otherModifierElement.height &&
enforceIncoming == otherModifierElement.enforceIncoming
}
override fun hashCode() = 31 * height.hashCode() + enforceIncoming.hashCode()
override fun InspectorInfo.inspectableProperties() {
inspectorInfo()
}
}
private class IntrinsicHeightNode(
var height: IntrinsicSize,
override var enforceIncoming: Boolean,
) : IntrinsicSizeModifier() {
override fun MeasureScope.calculateContentConstraints(
measurable: Measurable,
constraints: Constraints,
): Constraints {
var measuredHeight =
if (height == IntrinsicSize.Min) {
measurable.minIntrinsicHeight(constraints.maxWidth)
} else {
measurable.maxIntrinsicHeight(constraints.maxWidth)
}
if (measuredHeight < 0) {
measuredHeight = 0
}
return Constraints.fixedHeight(measuredHeight)
}
override fun IntrinsicMeasureScope.minIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int,
) =
if (height == IntrinsicSize.Min) measurable.minIntrinsicHeight(width)
else measurable.maxIntrinsicHeight(width)
override fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int,
) =
if (height == IntrinsicSize.Min) measurable.minIntrinsicHeight(width)
else measurable.maxIntrinsicHeight(width)
}
private abstract class IntrinsicSizeModifier : LayoutModifierNode, Modifier.Node() {
abstract val enforceIncoming: Boolean
abstract fun MeasureScope.calculateContentConstraints(
measurable: Measurable,
constraints: Constraints,
): Constraints
final override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints,
): MeasureResult {
val contentConstraints = calculateContentConstraints(measurable, constraints)
val placeable =
measurable.measure(
if (enforceIncoming) constraints.constrain(contentConstraints)
else contentConstraints
)
return layout(placeable.width, placeable.height) { placeable.placeRelative(IntOffset.Zero) }
}
override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int,
) = measurable.minIntrinsicWidth(height)
override fun IntrinsicMeasureScope.minIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int,
) = measurable.minIntrinsicHeight(width)
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int,
) = measurable.maxIntrinsicWidth(height)
override fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int,
) = measurable.maxIntrinsicHeight(width)
}