| /* |
| * 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.ui.layout |
| |
| import androidx.compose.Composable |
| import androidx.compose.composer |
| import androidx.ui.core.AlignmentLine |
| import androidx.ui.core.Constraints |
| import androidx.ui.core.DensityScope |
| import androidx.ui.core.IntPx |
| import androidx.ui.core.IntPxPosition |
| import androidx.ui.core.IntPxSize |
| import androidx.ui.core.Layout |
| import androidx.ui.core.LayoutModifier |
| import androidx.ui.core.Measurable |
| import androidx.ui.core.ipx |
| import androidx.ui.core.isFinite |
| import androidx.ui.core.satisfiedBy |
| |
| /** |
| * A layout modifier that attempts to size a layout to match a specified aspect ratio. The layout |
| * modifier will try to match one of the incoming constraints, in the following order: maxWidth, |
| * maxHeight, minWidth, minHeight. The size in the other dimension will then be computed |
| * according to the aspect ratio. Note that the provided aspectRatio will always correspond to |
| * the width/height ratio. |
| * |
| * If a valid size that satisfies the constraints is found this way, the modifier will size the |
| * target layout to it: the layout will be measured with the tight constraints to match the size. |
| * If a child is present, it will be measured with tight constraints to match the size. |
| * If no valid size is found, the aspect ratio will not be satisfied, and the target layout will |
| * be measured with the original constraints. |
| * |
| * Example usage: |
| * @sample androidx.ui.layout.samples.SimpleAspectRatio |
| * |
| * @param value Must be positive non-zero Float. |
| */ |
| fun AspectRatio(value: Float): LayoutModifier = AspectRatioModifier(value) |
| |
| /** |
| * A [LayoutModifier] that applies an aspect ratio to the wrapped UI element's size. |
| */ |
| private data class AspectRatioModifier(val aspectRatio: Float) : LayoutModifier { |
| init { |
| require(aspectRatio > 0) { |
| "Received aspect ratio value $aspectRatio is expected to be positive non-zero." |
| } |
| } |
| |
| override fun DensityScope.modifyConstraints(constraints: Constraints): Constraints { |
| val size = constraints.findSizeWith(aspectRatio) |
| return if (size != null) |
| Constraints.tightConstraints(size.width, size.height) |
| else |
| constraints |
| } |
| |
| override fun DensityScope.modifySize( |
| constraints: Constraints, |
| childSize: IntPxSize |
| ): IntPxSize { |
| val size = constraints.findSizeWith(aspectRatio) |
| return IntPxSize( |
| size?.width ?: childSize.width, |
| size?.height ?: childSize.height |
| ) |
| } |
| |
| override fun DensityScope.minIntrinsicWidthOf(measurable: Measurable, height: IntPx): IntPx { |
| return if (height == IntPx.Infinity) measurable.minIntrinsicWidth(height) |
| else height * aspectRatio |
| } |
| |
| override fun DensityScope.maxIntrinsicWidthOf(measurable: Measurable, height: IntPx): IntPx { |
| return if (height == IntPx.Infinity) measurable.maxIntrinsicWidth(height) |
| else height * aspectRatio |
| } |
| |
| override fun DensityScope.minIntrinsicHeightOf(measurable: Measurable, width: IntPx): IntPx { |
| return if (width == IntPx.Infinity) measurable.minIntrinsicHeight(width) |
| else width / aspectRatio |
| } |
| |
| override fun DensityScope.maxIntrinsicHeightOf(measurable: Measurable, width: IntPx): IntPx { |
| return if (width == IntPx.Infinity) measurable.maxIntrinsicHeight(width) |
| else width / aspectRatio |
| } |
| |
| override fun DensityScope.modifyPosition( |
| childPosition: IntPxPosition, |
| childSize: IntPxSize, |
| containerSize: IntPxSize |
| ): IntPxPosition = childPosition |
| |
| override fun DensityScope.modifyAlignmentLine(line: AlignmentLine, value: IntPx?): IntPx? { |
| return value |
| } |
| } |
| |
| /** |
| * Layout widget that attempts to size itself and a potential layout child to match a specified |
| * aspect ratio. The widget will try to match one of the incoming constraints, in the following |
| * order: maxWidth, maxHeight, minWidth, minHeight. The size in the other dimension will then be |
| * computed according to the aspect ratio. Note that the provided aspectRatio will always |
| * correspond to the width/height ratio. |
| * |
| * If a valid size that satisfies the constraints is found this way, the widget will size itself to |
| * this size. If a child is present, this will be measured with tight constraints to match the size. |
| * If no valid size is found, the aspect ratio will not be satisfied, and the widget will |
| * wrap its child, which is measured with the same constraints. If there is no child, the widget |
| * will size itself to the incoming min constraints. |
| * |
| * Example usage: |
| * AspectRatio(2f) { |
| * DrawRectangle(color = Color.Blue) |
| * Container(padding = EdgeInsets(20.dp)) { |
| * SizedRectangle(color = Color.Black) |
| * } |
| * } |
| * The AspectRatio widget will make the Container have the width twice its height. |
| */ |
| @Deprecated( |
| "Use AspectRatio layout modifier instead.", |
| ReplaceWith("modifier = AspectRatio(aspectRatio)"), |
| DeprecationLevel.WARNING |
| ) |
| @Composable |
| fun AspectRatio( |
| aspectRatio: Float, |
| children: @Composable() () -> Unit |
| ) { |
| require(aspectRatio > 0) { |
| "Received aspect ratio value $aspectRatio is expected to be positive non-zero." |
| } |
| Layout(children) { measurables, constraints -> |
| val size = constraints.findSizeWith(aspectRatio) |
| |
| val measurable = measurables.firstOrNull() |
| val childConstraints = if (size != null) { |
| Constraints.tightConstraints(size.width, size.height) |
| } else { |
| constraints |
| } |
| val placeable = measurable?.measure(childConstraints) |
| |
| layout( |
| size?.width ?: placeable?.width ?: constraints.minWidth, |
| size?.height ?: placeable?.height ?: constraints.minHeight |
| ) { |
| placeable?.place(0.ipx, 0.ipx) |
| } |
| } |
| } |
| |
| private fun Constraints.findSizeWith(aspectRatio: Float): IntPxSize? { |
| return listOf( |
| IntPxSize(this.maxWidth, this.maxWidth / aspectRatio), |
| IntPxSize(this.maxHeight * aspectRatio, this.maxHeight), |
| IntPxSize(this.minWidth, this.minWidth / aspectRatio), |
| IntPxSize(this.minHeight * aspectRatio, this.minHeight) |
| ).find { |
| this.satisfiedBy(it) && |
| it.width != 0.ipx && it.height != 0.ipx && |
| it.width.isFinite() && it.height.isFinite() |
| } |
| } |