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