1 /*
2  * Copyright 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 androidx.compose.material3
18 
19 import androidx.compose.foundation.shape.CircleShape
20 import androidx.compose.foundation.shape.CornerBasedShape
21 import androidx.compose.foundation.shape.CornerSize
22 import androidx.compose.material3.tokens.ShapeKeyTokens
23 import androidx.compose.material3.tokens.ShapeTokens
24 import androidx.compose.runtime.Composable
25 import androidx.compose.runtime.Immutable
26 import androidx.compose.runtime.ReadOnlyComposable
27 import androidx.compose.runtime.staticCompositionLocalOf
28 import androidx.compose.ui.graphics.RectangleShape
29 import androidx.compose.ui.graphics.Shape
30 
31 /**
32  * Material surfaces can be displayed in different shapes. Shapes direct attention, identify
33  * components, communicate state, and express brand.
34  *
35  * The shape scale defines the style of container corners, offering a range of roundedness from
36  * square to fully circular.
37  *
38  * There are different sizes of shapes:
39  * - Extra Small
40  * - Small
41  * - Medium
42  * - Large, Large Increased
43  * - Extra Large, Extra Large Increased
44  * - Extra Extra Large
45  *
46  * You can customize the shape system for all components in the [MaterialTheme] or you can do it on
47  * a per component basis.
48  *
49  * You can change the shape that a component has by overriding the shape parameter for that
50  * component. For example, by default, buttons use the shape style “full.” If your product requires
51  * a smaller amount of roundedness, you can override the shape parameter with a different shape
52  * value like [MaterialTheme.shapes.small].
53  *
54  * To learn more about shapes, see
55  * [Material Design shapes](https://m3.material.io/styles/shape/overview).
56  *
57  * @param extraSmall A shape style with 4 same-sized corners whose size are bigger than
58  *   [RectangleShape] and smaller than [Shapes.small]. By default autocomplete menu, select menu,
59  *   snackbars, standard menu, and text fields use this shape.
60  * @param small A shape style with 4 same-sized corners whose size are bigger than
61  *   [Shapes.extraSmall] and smaller than [Shapes.medium]. By default chips use this shape.
62  * @param medium A shape style with 4 same-sized corners whose size are bigger than [Shapes.small]
63  *   and smaller than [Shapes.large]. By default cards and small FABs use this shape.
64  * @param large A shape style with 4 same-sized corners whose size are bigger than [Shapes.medium]
65  *   and smaller than [Shapes.extraLarge]. By default extended FABs, FABs, and navigation drawers
66  *   use this shape.
67  * @param extraLarge A shape style with 4 same-sized corners whose size are bigger than
68  *   [Shapes.large] and smaller than [CircleShape]. By default large FABs use this shape.
69  * @param largeIncreased A shape style with 4 same-sized corners whose size are bigger than
70  *   [Shapes.medium] and smaller than [Shapes.extraLarge]. Slightly larger variant to
71  *   [Shapes.large].
72  * @param extraLargeIncreased A shape style with 4 same-sized corners whose size are bigger than
73  *   [Shapes.large] and smaller than [Shapes.extraExtraLarge]. Slightly larger variant to
74  *   [Shapes.extraLarge].
75  * @param extraExtraLarge A shape style with 4 same-sized corners whose size are bigger than
76  *   [Shapes.extraLarge] and smaller than [CircleShape].
77  */
78 // TODO: Update new shape descriptions to list what components leverage them by default.
79 // TODO(b/368578382): Update 'increased' variant kdocs to reference design documentation.
80 @Immutable
81 class Shapes
82 @ExperimentalMaterial3ExpressiveApi
83 constructor(
84     // Shapes None and Full are omitted as None is a RectangleShape and Full is a CircleShape.
85     val extraSmall: CornerBasedShape = ShapeDefaults.ExtraSmall,
86     val small: CornerBasedShape = ShapeDefaults.Small,
87     val medium: CornerBasedShape = ShapeDefaults.Medium,
88     val large: CornerBasedShape = ShapeDefaults.Large,
89     val extraLarge: CornerBasedShape = ShapeDefaults.ExtraLarge,
90     largeIncreased: CornerBasedShape = ShapeDefaults.LargeIncreased,
91     extraLargeIncreased: CornerBasedShape = ShapeDefaults.ExtraLargeIncreased,
92     extraExtraLarge: CornerBasedShape = ShapeDefaults.ExtraExtraLarge,
93 ) {
94     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
95     @get:ExperimentalMaterial3ExpressiveApi
96     @ExperimentalMaterial3ExpressiveApi
97     /**
98      * A shape style with 4 same-sized corners whose size are bigger than [Shapes.medium] and
99      * smaller than [Shapes.extraLarge]. Slightly larger variant to [Shapes.large].
100      */
101     val largeIncreased = largeIncreased
102 
103     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
104     @get:ExperimentalMaterial3ExpressiveApi
105     @ExperimentalMaterial3ExpressiveApi
106     /**
107      * A shape style with 4 same-sized corners whose size are bigger than [Shapes.large] and smaller
108      * than [Shapes.extraExtraLarge]. Slightly larger variant to [Shapes.extraLarge].
109      */
110     val extraLargeIncreased = extraLargeIncreased
111 
112     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
113     @get:ExperimentalMaterial3ExpressiveApi
114     @ExperimentalMaterial3ExpressiveApi
115     /**
116      * A shape style with 4 same-sized corners whose size are bigger than [Shapes.extraLarge] and
117      * smaller than [CircleShape].
118      */
119     val extraExtraLarge = extraExtraLarge
120 
121     /**
122      * Material surfaces can be displayed in different shapes. Shapes direct attention, identify
123      * components, communicate state, and express brand.
124      *
125      * The shape scale defines the style of container corners, offering a range of roundedness from
126      * square to fully circular.
127      *
128      * There are different sizes of shapes:
129      * - Extra Small
130      * - Small
131      * - Medium
132      * - Large, Large Increased
133      * - Extra Large, Extra Large Increased
134      * - Extra Extra Large
135      *
136      * You can customize the shape system for all components in the [MaterialTheme] or you can do it
137      * on a per component basis.
138      *
139      * You can change the shape that a component has by overriding the shape parameter for that
140      * component. For example, by default, buttons use the shape style “full.” If your product
141      * requires a smaller amount of roundedness, you can override the shape parameter with a
142      * different shape value like [MaterialTheme.shapes.small].
143      *
144      * To learn more about shapes, see
145      * [Material Design shapes](https://m3.material.io/styles/shape/overview).
146      *
147      * @param extraSmall A shape style with 4 same-sized corners whose size are bigger than
148      *   [RectangleShape] and smaller than [Shapes.small]. By default autocomplete menu, select
149      *   menu, snackbars, standard menu, and text fields use this shape.
150      * @param small A shape style with 4 same-sized corners whose size are bigger than
151      *   [Shapes.extraSmall] and smaller than [Shapes.medium]. By default chips use this shape.
152      * @param medium A shape style with 4 same-sized corners whose size are bigger than
153      *   [Shapes.small] and smaller than [Shapes.large]. By default cards and small FABs use this
154      *   shape.
155      * @param large A shape style with 4 same-sized corners whose size are bigger than
156      *   [Shapes.medium] and smaller than [Shapes.extraLarge]. By default extended FABs, FABs, and
157      *   navigation drawers use this shape.
158      * @param extraLarge A shape style with 4 same-sized corners whose size are bigger than
159      *   [Shapes.large] and smaller than [CircleShape]. By default large FABs use this shape.
160      */
161     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
162     constructor(
163         extraSmall: CornerBasedShape = ShapeDefaults.ExtraSmall,
164         small: CornerBasedShape = ShapeDefaults.Small,
165         medium: CornerBasedShape = ShapeDefaults.Medium,
166         large: CornerBasedShape = ShapeDefaults.Large,
167         extraLarge: CornerBasedShape = ShapeDefaults.ExtraLarge,
168     ) : this(
169         extraSmall = extraSmall,
170         small = small,
171         medium = medium,
172         large = large,
173         extraLarge = extraLarge,
174         largeIncreased = ShapeDefaults.LargeIncreased,
175         extraLargeIncreased = ShapeDefaults.ExtraLargeIncreased,
176         extraExtraLarge = ShapeDefaults.ExtraExtraLarge,
177     )
178 
179     /** Returns a copy of this Shapes, optionally overriding some of the values. */
180     @ExperimentalMaterial3ExpressiveApi
copynull181     fun copy(
182         extraSmall: CornerBasedShape = this.extraSmall,
183         small: CornerBasedShape = this.small,
184         medium: CornerBasedShape = this.medium,
185         large: CornerBasedShape = this.large,
186         extraLarge: CornerBasedShape = this.extraLarge,
187         largeIncreased: CornerBasedShape = this.largeIncreased,
188         extraLargeIncreased: CornerBasedShape = this.extraLargeIncreased,
189         extraExtraLarge: CornerBasedShape = this.extraExtraLarge,
190     ): Shapes =
191         Shapes(
192             extraSmall = extraSmall,
193             small = small,
194             medium = medium,
195             large = large,
196             extraLarge = extraLarge,
197             largeIncreased = largeIncreased,
198             extraLargeIncreased = extraLargeIncreased,
199             extraExtraLarge = extraExtraLarge,
200         )
201 
202     /** Returns a copy of this Shapes, optionally overriding some of the values. */
203     fun copy(
204         extraSmall: CornerBasedShape = this.extraSmall,
205         small: CornerBasedShape = this.small,
206         medium: CornerBasedShape = this.medium,
207         large: CornerBasedShape = this.large,
208         extraLarge: CornerBasedShape = this.extraLarge,
209     ): Shapes =
210         Shapes(
211             extraSmall = extraSmall,
212             small = small,
213             medium = medium,
214             large = large,
215             extraLarge = extraLarge,
216         )
217 
218     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
219     override fun equals(other: Any?): Boolean {
220         if (this === other) return true
221         if (other !is Shapes) return false
222         if (extraSmall != other.extraSmall) return false
223         if (small != other.small) return false
224         if (medium != other.medium) return false
225         if (large != other.large) return false
226         if (extraLarge != other.extraLarge) return false
227         if (largeIncreased != other.largeIncreased) return false
228         if (extraLargeIncreased != other.extraLargeIncreased) return false
229         if (extraExtraLarge != other.extraExtraLarge) return false
230         return true
231     }
232 
233     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
hashCodenull234     override fun hashCode(): Int {
235         var result = extraSmall.hashCode()
236         result = 31 * result + small.hashCode()
237         result = 31 * result + medium.hashCode()
238         result = 31 * result + large.hashCode()
239         result = 31 * result + extraLarge.hashCode()
240         result = 31 * result + largeIncreased.hashCode()
241         result = 31 * result + extraLargeIncreased.hashCode()
242         result = 31 * result + extraExtraLarge.hashCode()
243         return result
244     }
245 
246     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
toStringnull247     override fun toString(): String {
248         return "Shapes(" +
249             "extraSmall=$extraSmall, " +
250             "small=$small, " +
251             "medium=$medium, " +
252             "large=$large, " +
253             "largeIncreased=$largeIncreased, " +
254             "extraLarge=$extraLarge, " +
255             "extralargeIncreased=$extraLargeIncreased, " +
256             "extraExtraLarge=$extraExtraLarge)"
257     }
258 
259     /** Cached shapes used in components */
260     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
261     internal var defaultButtonShapesCached: ButtonShapes? = null
262     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
263     internal var defaultToggleButtonShapesCached: ToggleButtonShapes? = null
264     internal var defaultVerticalDragHandleShapesCached: DragHandleShapes? = null
265     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
266     internal var defaultIconToggleButtonShapesCached: IconToggleButtonShapes? = null
267     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
268     internal var defaultIconButtonShapesCached: IconButtonShapes? = null
269 }
270 
271 /** Contains the default values used by [Shapes] */
272 object ShapeDefaults {
273     /** Extra small sized corner shape */
274     val ExtraSmall: CornerBasedShape = ShapeTokens.CornerExtraSmall
275 
276     /** Small sized corner shape */
277     val Small: CornerBasedShape = ShapeTokens.CornerSmall
278 
279     /** Medium sized corner shape */
280     val Medium: CornerBasedShape = ShapeTokens.CornerMedium
281 
282     /** Large sized corner shape */
283     val Large: CornerBasedShape = ShapeTokens.CornerLarge
284 
285     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
286     @get:ExperimentalMaterial3ExpressiveApi
287     @ExperimentalMaterial3ExpressiveApi
288     /** Large sized corner shape, slightly larger than [Large] */
289     val LargeIncreased: CornerBasedShape = ShapeTokens.CornerLargeIncreased
290 
291     /** Extra large sized corner shape */
292     val ExtraLarge: CornerBasedShape = ShapeTokens.CornerExtraLarge
293 
294     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
295     @get:ExperimentalMaterial3ExpressiveApi
296     @ExperimentalMaterial3ExpressiveApi
297     /** Extra large sized corner shape, slightly larger than [ExtraLarge] */
298     val ExtraLargeIncreased: CornerBasedShape = ShapeTokens.CornerExtraLargeIncreased
299 
300     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
301     @get:ExperimentalMaterial3ExpressiveApi
302     @ExperimentalMaterial3ExpressiveApi
303     /** An extra extra large (XXL) sized corner shape */
304     val ExtraExtraLarge: CornerBasedShape = ShapeTokens.CornerExtraExtraLarge
305 
306     // TODO(b/368578382): Update 'increased' variant kdocs to reference design documentation.
307     /** A non-rounded corner size */
308     internal val CornerNone: CornerSize = ShapeTokens.CornerValueNone
309 
310     /** An extra small rounded corner size */
311     internal val CornerExtraSmall: CornerSize = ShapeTokens.CornerValueExtraSmall
312 
313     /** A small rounded corner size */
314     internal val CornerSmall: CornerSize = ShapeTokens.CornerValueSmall
315 
316     /** A medium rounded corner size */
317     internal val CornerMedium: CornerSize = ShapeTokens.CornerValueMedium
318 
319     /** A large rounded corner size */
320     internal val CornerLarge: CornerSize = ShapeTokens.CornerValueLarge
321 
322     /** A large rounded corner size, slightly larger than [CornerLarge] */
323     internal val CornerLargeIncreased: CornerSize = ShapeTokens.CornerValueLargeIncreased
324 
325     /** An extra large rounded corner size */
326     internal val CornerExtraLarge: CornerSize = ShapeTokens.CornerValueExtraLarge
327 
328     /** An extra large rounded corner size, slightly larger than [CornerExtraLarge] */
329     internal val CornerExtraLargeIncreased: CornerSize = ShapeTokens.CornerValueExtraLargeIncreased
330 
331     /** An extra extra large (XXL) rounded corner size */
332     internal val CornerExtraExtraLarge: CornerSize = ShapeTokens.CornerValueExtraExtraLarge
333 
334     /** A fully rounded corner size */
335     internal val CornerFull: CornerSize = CornerSize(100)
336 }
337 
338 /** Helper function for component shape tokens. Used to grab the top values of a shape parameter. */
topnull339 internal fun CornerBasedShape.top(
340     bottomSize: CornerSize = ShapeDefaults.CornerNone
341 ): CornerBasedShape {
342     return copy(bottomStart = bottomSize, bottomEnd = bottomSize)
343 }
344 
345 /**
346  * Helper function for component shape tokens. Used to grab the bottom values of a shape parameter.
347  */
bottomnull348 internal fun CornerBasedShape.bottom(
349     topSize: CornerSize = ShapeDefaults.CornerNone
350 ): CornerBasedShape {
351     return copy(topStart = topSize, topEnd = topSize)
352 }
353 
354 /**
355  * Helper function for component shape tokens. Used to grab the start values of a shape parameter.
356  */
startnull357 internal fun CornerBasedShape.start(
358     endSize: CornerSize = ShapeDefaults.CornerNone
359 ): CornerBasedShape {
360     return copy(topEnd = endSize, bottomEnd = endSize)
361 }
362 
363 /** Helper function for component shape tokens. Used to grab the end values of a shape parameter. */
endnull364 internal fun CornerBasedShape.end(
365     startSize: CornerSize = ShapeDefaults.CornerNone
366 ): CornerBasedShape {
367     return copy(topStart = startSize, bottomStart = startSize)
368 }
369 
370 /**
371  * Helper function for component shape tokens. Here is an example on how to use component color
372  * tokens: ``MaterialTheme.shapes.fromToken(FabPrimarySmallTokens.ContainerShape)``
373  */
374 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
fromTokennull375 internal fun Shapes.fromToken(value: ShapeKeyTokens): Shape {
376     return when (value) {
377         ShapeKeyTokens.CornerExtraLarge -> extraLarge
378         ShapeKeyTokens.CornerExtraLargeIncreased -> extraLargeIncreased
379         ShapeKeyTokens.CornerExtraExtraLarge -> extraExtraLarge
380         ShapeKeyTokens.CornerExtraLargeTop -> extraLarge.top()
381         ShapeKeyTokens.CornerExtraSmall -> extraSmall
382         ShapeKeyTokens.CornerExtraSmallTop -> extraSmall.top()
383         ShapeKeyTokens.CornerFull -> CircleShape
384         ShapeKeyTokens.CornerLarge -> large
385         ShapeKeyTokens.CornerLargeIncreased -> largeIncreased
386         ShapeKeyTokens.CornerLargeEnd -> large.end()
387         ShapeKeyTokens.CornerLargeTop -> large.top()
388         ShapeKeyTokens.CornerMedium -> medium
389         ShapeKeyTokens.CornerNone -> RectangleShape
390         ShapeKeyTokens.CornerSmall -> small
391         ShapeKeyTokens.CornerLargeStart -> large.start()
392     }
393 }
394 
395 /**
396  * Converts a shape token key to the local shape provided by the theme The color is subscribed to
397  * [LocalShapes] changes
398  */
399 internal val ShapeKeyTokens.value: Shape
400     @Composable @ReadOnlyComposable get() = MaterialTheme.shapes.fromToken(this)
401 
402 /** CompositionLocal used to specify the default shapes for the surfaces. */
<lambda>null403 internal val LocalShapes = staticCompositionLocalOf { Shapes() }
404