1 /*
2  * Copyright 2020 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 androidx.compose.foundation.shape
18 
19 import androidx.annotation.IntRange
20 import androidx.compose.ui.geometry.Size
21 import androidx.compose.ui.geometry.toRect
22 import androidx.compose.ui.graphics.Outline
23 import androidx.compose.ui.graphics.Path
24 import androidx.compose.ui.unit.Dp
25 import androidx.compose.ui.unit.LayoutDirection
26 import androidx.compose.ui.unit.dp
27 
28 /**
29  * A shape describing the rectangle with cut corners. Corner size is representing the cut length -
30  * the size of both legs of the cut's right triangle.
31  *
32  * This shape will not automatically mirror the corner sizes in [LayoutDirection.Rtl], use
33  * [CutCornerShape] for the layout direction aware version of this shape.
34  *
35  * @param topLeft a size of the top left corner
36  * @param topRight a size of the top right corner
37  * @param bottomRight a size of the bottom right corner
38  * @param bottomLeft a size of the bottom left corner
39  */
40 class AbsoluteCutCornerShape(
41     topLeft: CornerSize,
42     topRight: CornerSize,
43     bottomRight: CornerSize,
44     bottomLeft: CornerSize
45 ) :
46     CornerBasedShape(
47         topStart = topLeft,
48         topEnd = topRight,
49         bottomEnd = bottomRight,
50         bottomStart = bottomLeft
51     ) {
52 
createOutlinenull53     override fun createOutline(
54         size: Size,
55         topStart: Float,
56         topEnd: Float,
57         bottomEnd: Float,
58         bottomStart: Float,
59         layoutDirection: LayoutDirection
60     ) =
61         if (topStart + topEnd + bottomStart + bottomEnd == 0.0f) {
62             Outline.Rectangle(size.toRect())
63         } else
64             Outline.Generic(
<lambda>null65                 Path().apply {
66                     var cornerSize = topStart
67                     moveTo(0f, cornerSize)
68                     lineTo(cornerSize, 0f)
69                     cornerSize = topEnd
70                     lineTo(size.width - cornerSize, 0f)
71                     lineTo(size.width, cornerSize)
72                     cornerSize = bottomEnd
73                     lineTo(size.width, size.height - cornerSize)
74                     lineTo(size.width - cornerSize, size.height)
75                     cornerSize = bottomStart
76                     lineTo(cornerSize, size.height)
77                     lineTo(0f, size.height - cornerSize)
78                     close()
79                 }
80             )
81 
copynull82     override fun copy(
83         topStart: CornerSize,
84         topEnd: CornerSize,
85         bottomEnd: CornerSize,
86         bottomStart: CornerSize
87     ) =
88         AbsoluteCutCornerShape(
89             topLeft = topStart,
90             topRight = topEnd,
91             bottomRight = bottomEnd,
92             bottomLeft = bottomStart
93         )
94 
95     override fun toString(): String {
96         return "AbsoluteCutCornerShape(topLeft = $topStart, topRight = $topEnd, bottomRight = " +
97             "$bottomEnd, bottomLeft = $bottomStart)"
98     }
99 
equalsnull100     override fun equals(other: Any?): Boolean {
101         if (this === other) return true
102         if (other !is AbsoluteCutCornerShape) return false
103 
104         if (topStart != other.topStart) return false
105         if (topEnd != other.topEnd) return false
106         if (bottomEnd != other.bottomEnd) return false
107         if (bottomStart != other.bottomStart) return false
108 
109         return true
110     }
111 
hashCodenull112     override fun hashCode(): Int {
113         var result = topStart.hashCode()
114         result = 31 * result + topEnd.hashCode()
115         result = 31 * result + bottomEnd.hashCode()
116         result = 31 * result + bottomStart.hashCode()
117         return result
118     }
119 }
120 
121 /**
122  * Creates [AbsoluteCutCornerShape] with the same size applied for all four corners.
123  *
124  * @param corner [CornerSize] to apply.
125  */
AbsoluteCutCornerShapenull126 fun AbsoluteCutCornerShape(corner: CornerSize) =
127     AbsoluteCutCornerShape(corner, corner, corner, corner)
128 
129 /**
130  * Creates [AbsoluteCutCornerShape] with the same size applied for all four corners.
131  *
132  * @param size Size in [Dp] to apply.
133  */
134 fun AbsoluteCutCornerShape(size: Dp) = AbsoluteCutCornerShape(CornerSize(size))
135 
136 /**
137  * Creates [AbsoluteCutCornerShape] with the same size applied for all four corners.
138  *
139  * @param size Size in pixels to apply.
140  */
141 fun AbsoluteCutCornerShape(size: Float) = AbsoluteCutCornerShape(CornerSize(size))
142 
143 /**
144  * Creates [AbsoluteCutCornerShape] with the same size applied for all four corners.
145  *
146  * @param percent Size in percents to apply.
147  */
148 fun AbsoluteCutCornerShape(percent: Int) = AbsoluteCutCornerShape(CornerSize(percent))
149 
150 /** Creates [AbsoluteCutCornerShape] with sizes defined in [Dp]. */
151 fun AbsoluteCutCornerShape(
152     topLeft: Dp = 0.dp,
153     topRight: Dp = 0.dp,
154     bottomRight: Dp = 0.dp,
155     bottomLeft: Dp = 0.dp
156 ) =
157     AbsoluteCutCornerShape(
158         topLeft = CornerSize(topLeft),
159         topRight = CornerSize(topRight),
160         bottomRight = CornerSize(bottomRight),
161         bottomLeft = CornerSize(bottomLeft)
162     )
163 
164 /** Creates [AbsoluteCutCornerShape] with sizes defined in float. */
165 fun AbsoluteCutCornerShape(
166     topLeft: Float = 0.0f,
167     topRight: Float = 0.0f,
168     bottomRight: Float = 0.0f,
169     bottomLeft: Float = 0.0f
170 ) =
171     AbsoluteCutCornerShape(
172         topLeft = CornerSize(topLeft),
173         topRight = CornerSize(topRight),
174         bottomRight = CornerSize(bottomRight),
175         bottomLeft = CornerSize(bottomLeft)
176     )
177 
178 /**
179  * Creates [AbsoluteCutCornerShape] with sizes defined in percents of the shape's smaller side.
180  *
181  * @param topLeftPercent The top left corner clip size as a percentage of the smaller side, with a
182  *   range of 0 - 100.
183  * @param topRightPercent The top right corner clip size as a percentage of the smaller side, with a
184  *   range of 0 - 100.
185  * @param bottomRightPercent The bottom right clip size radius as a percentage of the smaller side,
186  *   with a range of 0 - 100.
187  * @param bottomLeftPercent The bottom left clip size radius as a percentage of the smaller side,
188  *   with a range of 0 - 100.
189  */
190 fun AbsoluteCutCornerShape(
191     @IntRange(from = 0, to = 100) topLeftPercent: Int = 0,
192     @IntRange(from = 0, to = 100) topRightPercent: Int = 0,
193     @IntRange(from = 0, to = 100) bottomRightPercent: Int = 0,
194     @IntRange(from = 0, to = 100) bottomLeftPercent: Int = 0
195 ) =
196     AbsoluteCutCornerShape(
197         topLeft = CornerSize(topLeftPercent),
198         topRight = CornerSize(topRightPercent),
199         bottomRight = CornerSize(bottomRightPercent),
200         bottomLeft = CornerSize(bottomLeftPercent)
201     )
202