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