[go: nahoru, domu]

blob: 858842f5ee53735a87f6ee26b7e8af6fb5b25d6c [file] [log] [blame]
Mihai Popaea553e82019-05-23 18:19:22 +01001/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Louis Pullen-Freilich623e4052020-07-19 20:24:03 +010017package androidx.compose.foundation.layout
Mihai Popaea553e82019-05-23 18:19:22 +010018
Leland Richardson47df77f2020-05-21 09:15:40 -070019import androidx.compose.Stable
Mihai Popaea553e82019-05-23 18:19:22 +010020import androidx.ui.core.Constraints
Mihai Popa832df7d2020-04-20 21:05:40 +010021import androidx.ui.core.IntrinsicMeasurable
22import androidx.ui.core.IntrinsicMeasureScope
Anastasia Soboleva9474ff82020-02-19 19:02:15 +000023import androidx.ui.core.LayoutDirection
Mihai Popa5f9e7532020-04-21 01:19:23 +010024import androidx.ui.core.LayoutModifier
Anastasia Soboleva1de1d102019-09-30 16:48:31 +010025import androidx.ui.core.Measurable
Mihai Popa832df7d2020-04-20 21:05:40 +010026import androidx.ui.core.MeasureScope
Adam Powell999a89b2020-03-11 09:08:07 -070027import androidx.ui.core.Modifier
Mihai Popaea553e82019-05-23 18:19:22 +010028import androidx.ui.core.satisfiedBy
George Mount8f237572020-04-30 12:08:30 -070029import androidx.ui.unit.IntSize
Nikolay Igotti63c6e122020-07-06 20:21:59 +030030import androidx.ui.util.annotation.FloatRange
George Mount8f237572020-04-30 12:08:30 -070031import kotlin.math.roundToInt
Mihai Popaea553e82019-05-23 18:19:22 +010032
33/**
Adam Powell999a89b2020-03-11 09:08:07 -070034 * Attempts to size the content to match a specified aspect ratio by trying to match one of the
35 * incoming constraints in the following order:
36 * [Constraints.maxWidth], [Constraints.maxHeight], [Constraints.minWidth], [Constraints.minHeight].
37 * The size in the other dimension is determined by the aspect ratio.
38 *
Mihai Popa832df7d2020-04-20 21:05:40 +010039 * Example usage:
Louis Pullen-Freilich623e4052020-07-19 20:24:03 +010040 * @sample androidx.compose.foundation.layout.samples.SimpleAspectRatio
Mihai Popa832df7d2020-04-20 21:05:40 +010041 *
42 * @param ratio the desired width/height positive ratio
Adam Powell999a89b2020-03-11 09:08:07 -070043 */
Leland Richardson47df77f2020-05-21 09:15:40 -070044@Stable
Adam Powell999a89b2020-03-11 09:08:07 -070045fun Modifier.aspectRatio(
Nikolay Igotti63c6e122020-07-06 20:21:59 +030046 @FloatRange(from = 0.0, to = 3.4e38 /* POSITIVE_INFINITY */, fromInclusive = false)
Adam Powell999a89b2020-03-11 09:08:07 -070047 ratio: Float
Mihai Popa832df7d2020-04-20 21:05:40 +010048) = this + AspectRatioModifier(ratio)
Adam Powell999a89b2020-03-11 09:08:07 -070049
Mihai Popa5f9e7532020-04-21 01:19:23 +010050private data class AspectRatioModifier(val aspectRatio: Float) : LayoutModifier {
Anastasia Soboleva1de1d102019-09-30 16:48:31 +010051 init {
Adam Powell712dc992019-12-04 12:48:30 -080052 require(aspectRatio > 0) { "aspectRatio $aspectRatio must be > 0" }
Anastasia Soboleva1de1d102019-09-30 16:48:31 +010053 }
54
Mihai Popa832df7d2020-04-20 21:05:40 +010055 override fun MeasureScope.measure(
56 measurable: Measurable,
Anastasia Soboleva9474ff82020-02-19 19:02:15 +000057 constraints: Constraints,
58 layoutDirection: LayoutDirection
Mihai Popa832df7d2020-04-20 21:05:40 +010059 ): MeasureScope.MeasureResult {
Anastasia Soboleva1de1d102019-09-30 16:48:31 +010060 val size = constraints.findSizeWith(aspectRatio)
Mihai Popa832df7d2020-04-20 21:05:40 +010061 val wrappedConstraints = if (size != null) {
Mihai Popad9d53502020-01-15 12:58:15 +000062 Constraints.fixed(size.width, size.height)
Mihai Popa832df7d2020-04-20 21:05:40 +010063 } else {
Anastasia Soboleva1de1d102019-09-30 16:48:31 +010064 constraints
Mihai Popa832df7d2020-04-20 21:05:40 +010065 }
66 val placeable = measurable.measure(wrappedConstraints)
67 return layout(placeable.width, placeable.height) {
George Mount8f237572020-04-30 12:08:30 -070068 placeable.place(0, 0)
Mihai Popa832df7d2020-04-20 21:05:40 +010069 }
Anastasia Soboleva1de1d102019-09-30 16:48:31 +010070 }
71
Mihai Popa832df7d2020-04-20 21:05:40 +010072 override fun IntrinsicMeasureScope.minIntrinsicWidth(
73 measurable: IntrinsicMeasurable,
George Mount8f237572020-04-30 12:08:30 -070074 height: Int,
Anastasia Soboleva9474ff82020-02-19 19:02:15 +000075 layoutDirection: LayoutDirection
George Mount8f237572020-04-30 12:08:30 -070076 ) = if (height != Constraints.Infinity) {
77 (height * aspectRatio).roundToInt()
Mihai Popa832df7d2020-04-20 21:05:40 +010078 } else {
79 measurable.minIntrinsicWidth(height)
Anastasia Soboleva1de1d102019-09-30 16:48:31 +010080 }
81
Mihai Popa832df7d2020-04-20 21:05:40 +010082 override fun IntrinsicMeasureScope.maxIntrinsicWidth(
83 measurable: IntrinsicMeasurable,
George Mount8f237572020-04-30 12:08:30 -070084 height: Int,
Anastasia Soboleva9474ff82020-02-19 19:02:15 +000085 layoutDirection: LayoutDirection
George Mount8f237572020-04-30 12:08:30 -070086 ) = if (height != Constraints.Infinity) {
87 (height * aspectRatio).roundToInt()
Mihai Popa832df7d2020-04-20 21:05:40 +010088 } else {
89 measurable.maxIntrinsicWidth(height)
Anastasia Soboleva1de1d102019-09-30 16:48:31 +010090 }
91
Mihai Popa832df7d2020-04-20 21:05:40 +010092 override fun IntrinsicMeasureScope.minIntrinsicHeight(
93 measurable: IntrinsicMeasurable,
George Mount8f237572020-04-30 12:08:30 -070094 width: Int,
Anastasia Soboleva9474ff82020-02-19 19:02:15 +000095 layoutDirection: LayoutDirection
George Mount8f237572020-04-30 12:08:30 -070096 ) = if (width != Constraints.Infinity) {
97 (width / aspectRatio).roundToInt()
Mihai Popa832df7d2020-04-20 21:05:40 +010098 } else {
99 measurable.minIntrinsicHeight(width)
Anastasia Soboleva1de1d102019-09-30 16:48:31 +0100100 }
101
Mihai Popa832df7d2020-04-20 21:05:40 +0100102 override fun IntrinsicMeasureScope.maxIntrinsicHeight(
103 measurable: IntrinsicMeasurable,
George Mount8f237572020-04-30 12:08:30 -0700104 width: Int,
Anastasia Soboleva9474ff82020-02-19 19:02:15 +0000105 layoutDirection: LayoutDirection
George Mount8f237572020-04-30 12:08:30 -0700106 ) = if (width != Constraints.Infinity) {
107 (width / aspectRatio).roundToInt()
Mihai Popa832df7d2020-04-20 21:05:40 +0100108 } else {
109 measurable.maxIntrinsicHeight(width)
110 }
111
George Mount8f237572020-04-30 12:08:30 -0700112 private fun Constraints.findSizeWith(aspectRatio: Float): IntSize? {
113 val maxWidth = this.maxWidth
114 if (maxWidth != Constraints.Infinity) {
115 val height = (maxWidth / aspectRatio).roundToInt()
116 if (height > 0) {
117 val size = IntSize(maxWidth, height)
118 if (satisfiedBy(size)) {
119 return size
120 }
121 }
Mihai Popa832df7d2020-04-20 21:05:40 +0100122 }
George Mount8f237572020-04-30 12:08:30 -0700123 val maxHeight = this.maxHeight
124 if (maxHeight != Constraints.Infinity) {
125 val width = (maxHeight * aspectRatio).roundToInt()
126 if (width > 0) {
127 val size = IntSize(width, maxHeight)
128 if (satisfiedBy(size)) {
129 return size
130 }
131 }
132 }
133 val minWidth = this.minWidth
134 val height = (minWidth / aspectRatio).roundToInt()
135 if (height > 0) {
136 val size = IntSize(minWidth, height)
137 if (satisfiedBy(size)) {
138 return size
139 }
140 }
141 val minHeight = this.minHeight
142 val width = (minHeight * aspectRatio).roundToInt()
143 if (width > 0) {
144 val size = IntSize(width, minHeight)
145 if (satisfiedBy(size)) {
146 return size
147 }
148 }
149 return null
Anastasia Soboleva1de1d102019-09-30 16:48:31 +0100150 }
Anastasia Soboleva1de1d102019-09-30 16:48:31 +0100151}