1 /*
2  * Copyright (C) 2022 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 
17 package com.android.compose.modifiers
18 
19 import androidx.compose.ui.Modifier
20 import androidx.compose.ui.layout.LayoutModifier
21 import androidx.compose.ui.layout.Measurable
22 import androidx.compose.ui.layout.MeasureResult
23 import androidx.compose.ui.layout.MeasureScope
24 import androidx.compose.ui.platform.InspectorInfo
25 import androidx.compose.ui.platform.InspectorValueInfo
26 import androidx.compose.ui.platform.debugInspectorInfo
27 import androidx.compose.ui.unit.Constraints
28 import androidx.compose.ui.unit.Density
29 import androidx.compose.ui.unit.constrainHeight
30 import androidx.compose.ui.unit.constrainWidth
31 import androidx.compose.ui.unit.offset
32 
33 // This file was mostly copy/pasted from by androidx.compose.foundation.layout.Padding.kt and
34 // contains modifiers with lambda parameters to change the padding of a Composable without
35 // triggering recomposition when the paddings change.
36 //
37 // These should be used instead of the traditional size modifiers when the size changes often, for
38 // instance when it is animated.
39 //
40 // TODO(b/247473910): Remove these modifiers once they can be fully replaced by layout animations
41 // APIs.
42 
43 /** @see androidx.compose.foundation.layout.padding */
44 fun Modifier.padding(
45     start: Density.() -> Int = PaddingUnspecified,
46     top: Density.() -> Int = PaddingUnspecified,
47     end: Density.() -> Int = PaddingUnspecified,
48     bottom: Density.() -> Int = PaddingUnspecified,
49 ) =
50     this.then(
51         PaddingModifier(
52             start,
53             top,
54             end,
55             bottom,
56             rtlAware = true,
57             inspectorInfo =
58                 debugInspectorInfo {
59                     name = "padding"
60                     properties["start"] = start
61                     properties["top"] = top
62                     properties["end"] = end
63                     properties["bottom"] = bottom
64                 }
65         )
66     )
67 
68 /** @see androidx.compose.foundation.layout.padding */
69 fun Modifier.padding(
70     horizontal: Density.() -> Int = PaddingUnspecified,
71     vertical: Density.() -> Int = PaddingUnspecified,
72 ): Modifier {
73     return this.then(
74         PaddingModifier(
75             start = horizontal,
76             top = vertical,
77             end = horizontal,
78             bottom = vertical,
79             rtlAware = true,
80             inspectorInfo =
81                 debugInspectorInfo {
82                     name = "padding"
83                     properties["horizontal"] = horizontal
84                     properties["vertical"] = vertical
85                 }
86         )
87     )
88 }
89 
90 private val PaddingUnspecified: Density.() -> Int = { 0 }
91 
92 private class PaddingModifier(
93     val start: Density.() -> Int,
94     val top: Density.() -> Int,
95     val end: Density.() -> Int,
96     val bottom: Density.() -> Int,
97     val rtlAware: Boolean,
98     inspectorInfo: InspectorInfo.() -> Unit
99 ) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
100     override fun MeasureScope.measure(
101         measurable: Measurable,
102         constraints: Constraints
103     ): MeasureResult {
104         val start = start()
105         val top = top()
106         val end = end()
107         val bottom = bottom()
108 
109         val horizontal = start + end
110         val vertical = top + bottom
111 
112         val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))
113 
114         val width = constraints.constrainWidth(placeable.width + horizontal)
115         val height = constraints.constrainHeight(placeable.height + vertical)
116         return layout(width, height) {
117             if (rtlAware) {
118                 placeable.placeRelative(start, top)
119             } else {
120                 placeable.place(start, top)
121             }
122         }
123     }
124 
125     override fun hashCode(): Int {
126         var result = start.hashCode()
127         result = 31 * result + top.hashCode()
128         result = 31 * result + end.hashCode()
129         result = 31 * result + bottom.hashCode()
130         result = 31 * result + rtlAware.hashCode()
131         return result
132     }
133 
134     override fun equals(other: Any?): Boolean {
135         val otherModifier = other as? PaddingModifier ?: return false
136         return start == otherModifier.start &&
137             top == otherModifier.top &&
138             end == otherModifier.end &&
139             bottom == otherModifier.bottom &&
140             rtlAware == otherModifier.rtlAware
141     }
142 }
143