1 /*
2  * Copyright 2024 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.animation.core.FiniteAnimationSpec
20 import androidx.compose.foundation.BorderStroke
21 import androidx.compose.foundation.interaction.Interaction
22 import androidx.compose.foundation.interaction.MutableInteractionSource
23 import androidx.compose.foundation.interaction.collectIsPressedAsState
24 import androidx.compose.foundation.layout.Arrangement
25 import androidx.compose.foundation.layout.PaddingValues
26 import androidx.compose.foundation.layout.Row
27 import androidx.compose.foundation.layout.RowScope
28 import androidx.compose.foundation.layout.defaultMinSize
29 import androidx.compose.foundation.layout.padding
30 import androidx.compose.foundation.shape.CornerBasedShape
31 import androidx.compose.foundation.shape.RoundedCornerShape
32 import androidx.compose.material3.internal.ProvideContentColorTextStyle
33 import androidx.compose.material3.internal.rememberAnimatedShape
34 import androidx.compose.material3.tokens.ButtonLargeTokens
35 import androidx.compose.material3.tokens.ButtonMediumTokens
36 import androidx.compose.material3.tokens.ButtonSmallTokens
37 import androidx.compose.material3.tokens.ButtonXLargeTokens
38 import androidx.compose.material3.tokens.ButtonXSmallTokens
39 import androidx.compose.material3.tokens.ElevatedButtonTokens
40 import androidx.compose.material3.tokens.FilledButtonTokens
41 import androidx.compose.material3.tokens.MotionSchemeKeyTokens
42 import androidx.compose.material3.tokens.OutlinedButtonTokens
43 import androidx.compose.material3.tokens.TonalButtonTokens
44 import androidx.compose.runtime.Composable
45 import androidx.compose.runtime.Immutable
46 import androidx.compose.runtime.Stable
47 import androidx.compose.runtime.getValue
48 import androidx.compose.runtime.key
49 import androidx.compose.runtime.remember
50 import androidx.compose.ui.Alignment
51 import androidx.compose.ui.Modifier
52 import androidx.compose.ui.graphics.Color
53 import androidx.compose.ui.graphics.Shape
54 import androidx.compose.ui.graphics.takeOrElse
55 import androidx.compose.ui.semantics.Role
56 import androidx.compose.ui.semantics.role
57 import androidx.compose.ui.semantics.semantics
58 import androidx.compose.ui.unit.Dp
59 import androidx.compose.ui.unit.dp
60 
61 /**
62  * TODO link to mio page when available.
63  *
64  * Toggle button is a toggleable button that switches between primary and tonal colors depending on
65  * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
66  * state of the interaction with the toggle button as long as the three shapes provided our
67  * [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
68  * toggle between the [ToggleButtonShapes] according to user interaction.
69  *
70  * TODO link to an image when available
71  *
72  * see [Button] for a static button that doesn't need to be toggled. see [IconToggleButton] for a
73  * toggleable button where the content is specifically an [Icon].
74  *
75  * @sample androidx.compose.material3.samples.ToggleButtonSample
76  * @sample androidx.compose.material3.samples.ToggleButtonWithIconSample
77  *
78  * For a [ToggleButton] that uses a round unchecked shape and morphs into a square checked shape:
79  *
80  * [ToggleButton] uses the small button design as default. For a [ToggleButton] that uses the design
81  * for extra small, medium, large, or extra large buttons:
82  *
83  * @sample androidx.compose.material3.samples.XSmallToggleButtonWithIconSample
84  * @sample androidx.compose.material3.samples.MediumToggleButtonWithIconSample
85  * @sample androidx.compose.material3.samples.LargeToggleButtonWithIconSample
86  * @sample androidx.compose.material3.samples.XLargeToggleButtonWithIconSample
87  * @sample androidx.compose.material3.samples.SquareToggleButtonSample
88  * @param checked whether the toggle button is toggled on or off.
89  * @param onCheckedChange called when the toggle button is clicked.
90  * @param modifier the [Modifier] to be applied to the toggle button.
91  * @param enabled controls the enabled state of this toggle button. When `false`, this component
92  *   will not respond to user input, and it will appear visually disabled and disabled to
93  *   accessibility services.
94  * @param shapes the [ToggleButtonShapes] that the toggle button will morph between depending on the
95  *   user's interaction with the toggle button.
96  * @param colors [ToggleButtonColors] that will be used to resolve the colors used for this toggle
97  *   button in different states. See [ToggleButtonDefaults.toggleButtonColors].
98  * @param elevation [ButtonElevation] used to resolve the elevation for this button in different
99  *   states. This controls the size of the shadow below the button. See
100  *   [ButtonElevation.shadowElevation]. Additionally, when the container color is
101  *   [ColorScheme.surface], this controls the amount of primary color applied as an overlay.
102  * @param border the border to draw around the container of this toggle button.
103  * @param contentPadding the spacing values to apply internally between the container and the
104  *   content
105  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
106  *   emitting [Interaction]s for this toggle button. You can use this to change the toggle button's
107  *   appearance or preview the toggle button in different states. Note that if `null` is provided,
108  *   interactions will still happen internally.
109  * @param content The content displayed on the toggle button, expected to be text, icon or image.
110  */
111 @Composable
112 @ExperimentalMaterial3ExpressiveApi
ToggleButtonnull113 fun ToggleButton(
114     checked: Boolean,
115     onCheckedChange: (Boolean) -> Unit,
116     modifier: Modifier = Modifier,
117     enabled: Boolean = true,
118     shapes: ToggleButtonShapes = ToggleButtonDefaults.shapesFor(ButtonDefaults.MinHeight),
119     colors: ToggleButtonColors = ToggleButtonDefaults.toggleButtonColors(),
120     elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
121     border: BorderStroke? = null,
122     contentPadding: PaddingValues = ButtonDefaults.contentPaddingFor(ButtonDefaults.MinHeight),
123     interactionSource: MutableInteractionSource? = null,
124     content: @Composable RowScope.() -> Unit
125 ) {
126     @Suppress("NAME_SHADOWING")
127     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
128     // TODO Load the motionScheme tokens from the component tokens file
129     val defaultAnimationSpec = MotionSchemeKeyTokens.FastSpatial.value<Float>()
130     val pressed by interactionSource.collectIsPressedAsState()
131     val containerColor = colors.containerColor(enabled, checked)
132     val contentColor = colors.contentColor(enabled, checked)
133     val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
134     val buttonShape = shapeByInteraction(shapes, pressed, checked, defaultAnimationSpec)
135 
136     Surface(
137         checked = checked,
138         onCheckedChange = onCheckedChange,
139         modifier = modifier.semantics { role = Role.Checkbox },
140         enabled = enabled,
141         shape = buttonShape,
142         color = containerColor,
143         contentColor = contentColor,
144         shadowElevation = shadowElevation,
145         border = border,
146         interactionSource = interactionSource
147     ) {
148         ProvideContentColorTextStyle(
149             contentColor = contentColor,
150             textStyle = MaterialTheme.typography.labelLarge
151         ) {
152             Row(
153                 Modifier.defaultMinSize(minHeight = ToggleButtonDefaults.MinHeight)
154                     .padding(contentPadding),
155                 horizontalArrangement = Arrangement.Center,
156                 verticalAlignment = Alignment.CenterVertically,
157                 content = content
158             )
159         }
160     }
161 }
162 
163 /**
164  * TODO link to mio page when available.
165  *
166  * Toggle button is a toggleable button that switches between primary and tonal colors depending on
167  * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
168  * state of the interaction with the toggle button as long as the three shapes provided our
169  * [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
170  * toggle between the [ToggleButtonShapes] according to user interaction.
171  *
172  * TODO link to an image when available
173  *
174  * Elevated toggle buttons are high-emphasis Toggle buttons. To prevent shadow creep, only use them
175  * when absolutely necessary, such as when the toggle button requires visual separation from
176  * patterned container.
177  *
178  * see [ElevatedButton] for a static button that doesn't need to be toggled.
179  *
180  * @sample androidx.compose.material3.samples.ElevatedToggleButtonSample
181  * @param checked whether the toggle button is toggled on or off.
182  * @param onCheckedChange called when the toggle button is clicked.
183  * @param modifier the [Modifier] to be applied to the toggle button.
184  * @param enabled controls the enabled state of this toggle button. When `false`, this component
185  *   will not respond to user input, and it will appear visually disabled and disabled to
186  *   accessibility services.
187  * @param shapes the [ToggleButtonShapes] that the toggle button will morph between depending on the
188  *   user's interaction with the toggle button.
189  * @param colors [ToggleButtonColors] that will be used to resolve the colors used for this toggle
190  *   button in different states. See [ToggleButtonDefaults.elevatedToggleButtonColors].
191  * @param elevation [ButtonElevation] used to resolve the elevation for this button in different
192  *   states. This controls the size of the shadow below the button. Additionally, when the container
193  *   color is [ColorScheme.surface], this controls the amount of primary color applied as an
194  *   overlay.
195  * @param border the border to draw around the container of this toggle button.
196  * @param contentPadding the spacing values to apply internally between the container and the
197  *   content
198  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
199  *   emitting [Interaction]s for this toggle button. You can use this to change the toggle button's
200  *   appearance or preview the toggle button in different states. Note that if `null` is provided,
201  *   interactions will still happen internally.
202  * @param content The content displayed on the toggle button, expected to be text, icon or image.
203  */
204 @Composable
205 @ExperimentalMaterial3ExpressiveApi
ElevatedToggleButtonnull206 fun ElevatedToggleButton(
207     checked: Boolean,
208     onCheckedChange: (Boolean) -> Unit,
209     modifier: Modifier = Modifier,
210     enabled: Boolean = true,
211     shapes: ToggleButtonShapes = ToggleButtonDefaults.shapesFor(ButtonDefaults.MinHeight),
212     colors: ToggleButtonColors = ToggleButtonDefaults.elevatedToggleButtonColors(),
213     elevation: ButtonElevation? = ButtonDefaults.elevatedButtonElevation(),
214     border: BorderStroke? = null,
215     contentPadding: PaddingValues = ButtonDefaults.contentPaddingFor(ButtonDefaults.MinHeight),
216     interactionSource: MutableInteractionSource? = null,
217     content: @Composable RowScope.() -> Unit
218 ) =
219     ToggleButton(
220         checked = checked,
221         onCheckedChange = onCheckedChange,
222         modifier = modifier,
223         enabled = enabled,
224         shapes = shapes,
225         colors = colors,
226         elevation = elevation,
227         border = border,
228         contentPadding = contentPadding,
229         interactionSource = interactionSource,
230         content = content
231     )
232 
233 /**
234  * TODO link to mio page when available.
235  *
236  * Toggle button is a toggleable button that switches between primary and tonal colors depending on
237  * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
238  * state of the interaction with the toggle button as long as the three shapes provided our
239  * [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
240  * toggle between the [ToggleButtonShapes] according to user interaction.
241  *
242  * TODO link to an image when available
243  *
244  * tonal toggle buttons are medium-emphasis buttons that is an alternative middle ground between
245  * default [ToggleButton]s (filled) and [OutlinedToggleButton]s. They can be used in contexts where
246  * lower-priority button requires slightly more emphasis than an outline would give. Tonal toggle
247  * buttons use the secondary color mapping.
248  *
249  * see [FilledTonalButton] for a static button that doesn't need to be toggled. see
250  * [FilledTonalIconToggleButton] for a toggleable button where the content is specifically an
251  * [Icon].
252  *
253  * @sample androidx.compose.material3.samples.TonalToggleButtonSample
254  * @param checked whether the toggle button is toggled on or off.
255  * @param onCheckedChange called when the toggle button is clicked.
256  * @param modifier the [Modifier] to be applied to the toggle button.
257  * @param enabled controls the enabled state of this toggle button. When `false`, this component
258  *   will not respond to user input, and it will appear visually disabled and disabled to
259  *   accessibility services.
260  * @param shapes the [ToggleButtonShapes] that the toggle button will morph between depending on the
261  *   user's interaction with the toggle button.
262  * @param colors [ToggleButtonColors] that will be used to resolve the colors used for this toggle
263  *   button in different states. See [ToggleButtonDefaults.tonalToggleButtonColors].
264  * @param elevation [ButtonElevation] used to resolve the elevation for this button in different
265  *   states. This controls the size of the shadow below the button. Additionally, when the container
266  *   color is [ColorScheme.surface], this controls the amount of primary color applied as an
267  *   overlay.
268  * @param border the border to draw around the container of this toggle button.
269  * @param contentPadding the spacing values to apply internally between the container and the
270  *   content
271  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
272  *   emitting [Interaction]s for this toggle button. You can use this to change the toggle button's
273  *   appearance or preview the toggle button in different states. Note that if `null` is provided,
274  *   interactions will still happen internally.
275  * @param content The content displayed on the toggle button, expected to be text, icon or image.
276  */
277 @Composable
278 @ExperimentalMaterial3ExpressiveApi
279 fun TonalToggleButton(
280     checked: Boolean,
281     onCheckedChange: (Boolean) -> Unit,
282     modifier: Modifier = Modifier,
283     enabled: Boolean = true,
284     shapes: ToggleButtonShapes = ToggleButtonDefaults.shapesFor(ButtonDefaults.MinHeight),
285     colors: ToggleButtonColors = ToggleButtonDefaults.tonalToggleButtonColors(),
286     elevation: ButtonElevation? = ButtonDefaults.filledTonalButtonElevation(),
287     border: BorderStroke? = null,
288     contentPadding: PaddingValues = ButtonDefaults.contentPaddingFor(ButtonDefaults.MinHeight),
289     interactionSource: MutableInteractionSource? = null,
290     content: @Composable RowScope.() -> Unit
291 ) =
292     ToggleButton(
293         checked = checked,
294         onCheckedChange = onCheckedChange,
295         modifier = modifier,
296         enabled = enabled,
297         shapes = shapes,
298         colors = colors,
299         elevation = elevation,
300         border = border,
301         contentPadding = contentPadding,
302         interactionSource = interactionSource,
303         content = content
304     )
305 
306 /**
307  * TODO link to mio page when available.
308  *
309  * Toggle button is a toggleable button that switches between primary and tonal colors depending on
310  * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
311  * state of the interaction with the toggle button as long as the three shapes provided our
312  * [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
313  * toggle between the [ToggleButtonShapes] according to user interaction.
314  *
315  * TODO link to an image when available
316  *
317  * Outlined toggle buttons are medium-emphasis buttons. They contain actions that are important, but
318  * are not the primary action in an app. Outlined buttons pair well with [ToggleButton]s to indicate
319  * an alternative, secondary action.
320  *
321  * see [OutlinedButton] for a static button that doesn't need to be toggled. see
322  * [OutlinedIconToggleButton] for a toggleable button where the content is specifically an [Icon].
323  *
324  * @sample androidx.compose.material3.samples.OutlinedToggleButtonSample
325  * @param checked whether the toggle button is toggled on or off.
326  * @param onCheckedChange called when the toggle button is clicked.
327  * @param modifier the [Modifier] to be applied to the toggle button.
328  * @param enabled controls the enabled state of this toggle button. When `false`, this component
329  *   will not respond to user input, and it will appear visually disabled and disabled to
330  *   accessibility services.
331  * @param shapes the [ToggleButtonShapes] that the toggle button will morph between depending on the
332  *   user's interaction with the toggle button.
333  * @param colors [ToggleButtonColors] that will be used to resolve the colors used for this toggle
334  *   button in different states. See [ToggleButtonDefaults.outlinedToggleButtonColors].
335  * @param elevation [ButtonElevation] used to resolve the elevation for this button in different
336  *   states. This controls the size of the shadow below the button. Additionally, when the container
337  *   color is [ColorScheme.surface], this controls the amount of primary color applied as an
338  *   overlay.
339  * @param border the border to draw around the container of this toggle button.
340  * @param contentPadding the spacing values to apply internally between the container and the
341  *   content
342  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
343  *   emitting [Interaction]s for this toggle button. You can use this to change the toggle button's
344  *   appearance or preview the toggle button in different states. Note that if `null` is provided,
345  *   interactions will still happen internally.
346  * @param content The content displayed on the toggle button, expected to be text, icon or image.
347  */
348 @Composable
349 @ExperimentalMaterial3ExpressiveApi
350 fun OutlinedToggleButton(
351     checked: Boolean,
352     onCheckedChange: (Boolean) -> Unit,
353     modifier: Modifier = Modifier,
354     enabled: Boolean = true,
355     shapes: ToggleButtonShapes = ToggleButtonDefaults.shapesFor(ButtonDefaults.MinHeight),
356     colors: ToggleButtonColors = ToggleButtonDefaults.outlinedToggleButtonColors(),
357     elevation: ButtonElevation? = null,
358     border: BorderStroke? = if (!checked) ButtonDefaults.outlinedButtonBorder(enabled) else null,
359     contentPadding: PaddingValues = ButtonDefaults.contentPaddingFor(ButtonDefaults.MinHeight),
360     interactionSource: MutableInteractionSource? = null,
361     content: @Composable RowScope.() -> Unit
362 ) =
363     ToggleButton(
364         checked = checked,
365         onCheckedChange = onCheckedChange,
366         modifier = modifier,
367         enabled = enabled,
368         shapes = shapes,
369         colors = colors,
370         elevation = elevation,
371         border = border,
372         contentPadding = contentPadding,
373         interactionSource = interactionSource,
374         content = content
375     )
376 
377 /** Contains the default values for all five toggle button types. */
378 @ExperimentalMaterial3ExpressiveApi
379 object ToggleButtonDefaults {
380     /**
381      * The default min height applied for all toggle buttons. Note that you can override it by
382      * applying Modifier.heightIn directly on the toggle button composable.
383      */
384     val MinHeight = ButtonSmallTokens.ContainerHeight
385 
386     private val ToggleButtonStartPadding = ButtonSmallTokens.LeadingSpace
387     private val ToggleButtonEndPadding = ButtonSmallTokens.TrailingSpace
388     private val ButtonVerticalPadding = 8.dp
389 
390     /**
391      * The default size of the spacing between an icon and a text when they used inside any toggle
392      * button.
393      */
394     val IconSpacing = ButtonSmallTokens.IconLabelSpace
395 
396     /**
397      * The default size of the spacing between an icon and a text when they used inside any toggle
398      * button.
399      */
400     val IconSize = ButtonSmallTokens.IconSize
401 
402     /** The default content padding used by all toggle buttons. */
403     val ContentPadding =
404         PaddingValues(
405             start = ToggleButtonStartPadding,
406             top = ButtonVerticalPadding,
407             end = ToggleButtonEndPadding,
408             bottom = ButtonVerticalPadding
409         )
410 
411     /**
412      * Creates a [ToggleButtonShapes] that represents the default shape, pressedShape, and
413      * checkedShape used in a [ToggleButton].
414      */
415     @Composable fun shapes() = MaterialTheme.shapes.defaultToggleButtonShapes
416 
417     /**
418      * Creates a [ToggleButtonShapes] that represents the default shape, pressedShape, and
419      * checkedShape used in a [ToggleButton] and its variants.
420      *
421      * @param shape the unchecked shape for [ToggleButtonShapes]
422      * @param pressedShape the unchecked shape for [ToggleButtonShapes]
423      * @param checkedShape the unchecked shape for [ToggleButtonShapes]
424      */
425     @Composable
426     fun shapes(
427         shape: Shape? = null,
428         pressedShape: Shape? = null,
429         checkedShape: Shape? = null
430     ): ToggleButtonShapes =
431         MaterialTheme.shapes.defaultToggleButtonShapes.copy(
432             shape = shape,
433             pressedShape = pressedShape,
434             checkedShape = checkedShape
435         )
436 
437     internal val Shapes.defaultToggleButtonShapes: ToggleButtonShapes
438         get() {
439             return defaultToggleButtonShapesCached
440                 ?: ToggleButtonShapes(
441                         shape = fromToken(ButtonSmallTokens.ContainerShapeRound),
442                         pressedShape = RoundedCornerShape(6.dp),
443                         checkedShape = fromToken(ButtonSmallTokens.SelectedContainerShapeSquare)
444                     )
445                     .also { defaultToggleButtonShapesCached = it }
446         }
447 
448     /** A round shape that can be used for all [ToggleButton]s and its variants */
449     val roundShape: Shape
450         @Composable get() = ButtonSmallTokens.ContainerShapeRound.value
451 
452     /** A square shape that can be used for all [ToggleButton]s and its variants */
453     val squareShape: Shape
454         @Composable get() = ButtonSmallTokens.ContainerShapeSquare.value
455 
456     /** The default unchecked shape for [ToggleButton] */
457     val shape: Shape
458         @Composable get() = ButtonSmallTokens.ContainerShapeRound.value
459 
460     /** The default pressed shape for [ToggleButton] */
461     val pressedShape: Shape
462         @Composable get() = RoundedCornerShape(6.dp)
463 
464     /** The default checked shape for [ToggleButton] */
465     val checkedShape: Shape
466         @Composable get() = ButtonSmallTokens.SelectedContainerShapeSquare.value
467 
468     /** The default square shape for a extra small toggle button */
469     val extraSmallSquareShape: Shape
470         @Composable get() = ButtonXSmallTokens.ContainerShapeSquare.value
471 
472     /** The default square shape for a medium toggle button */
473     val mediumSquareShape: Shape
474         @Composable get() = ButtonMediumTokens.ContainerShapeSquare.value
475 
476     /** The default square shape for a large toggle button */
477     val largeSquareShape: Shape
478         @Composable get() = ButtonLargeTokens.ContainerShapeSquare.value
479 
480     /** The default square shape for a extra large toggle button */
481     val extraLargeSquareShape: Shape
482         @Composable get() = ButtonXLargeTokens.ContainerShapeSquare.value
483 
484     /** The default pressed shape for a extra small toggle button */
485     val extraSmallPressedShape: Shape
486         @Composable get() = ButtonXSmallTokens.PressedContainerShape.value
487 
488     /** The default pressed shape for a medium toggle button */
489     val mediumPressedShape: Shape
490         @Composable get() = ButtonMediumTokens.PressedContainerShape.value
491 
492     /** The default pressed shape for a large toggle button */
493     val largePressedShape: Shape
494         @Composable get() = ButtonLargeTokens.PressedContainerShape.value
495 
496     /** The default pressed shape for a extra large toggle button */
497     val extraLargePressedShape: Shape
498         @Composable get() = ButtonXLargeTokens.PressedContainerShape.value
499 
500     /** The default checked square shape for a extra small toggle button */
501     val extraSmallCheckedSquareShape: Shape
502         @Composable get() = ButtonXSmallTokens.ContainerShapeSquare.value
503 
504     /** The default checked square shape for a medium toggle button */
505     val mediumCheckedSquareShape: Shape
506         @Composable get() = ButtonMediumTokens.ContainerShapeSquare.value
507 
508     /** The default checked square shape for a large toggle button */
509     val largeCheckedSquareShape: Shape
510         @Composable get() = ButtonLargeTokens.ContainerShapeSquare.value
511 
512     /** The default checked square shape for a extra large toggle button */
513     val extraLargeCheckedSquareShape: Shape
514         @Composable get() = ButtonXLargeTokens.ContainerShapeSquare.value
515 
516     /**
517      * Creates a [ToggleButtonColors] that represents the default container and content colors used
518      * in a [ToggleButton].
519      */
520     @Composable fun toggleButtonColors() = MaterialTheme.colorScheme.defaultToggleButtonColors
521 
522     /**
523      * Creates a [ToggleButtonColors] that represents the default container and content colors used
524      * in a [ToggleButton].
525      *
526      * @param containerColor the container color of this [ToggleButton] when enabled.
527      * @param contentColor the content color of this [ToggleButton] when enabled.
528      * @param disabledContainerColor the container color of this [ToggleButton] when not enabled.
529      * @param disabledContentColor the content color of this [ToggleButton] when not enabled.
530      * @param checkedContainerColor the container color of this [ToggleButton] when checked.
531      * @param checkedContentColor the content color of this [ToggleButton] when checked.
532      */
533     @Composable
534     fun toggleButtonColors(
535         containerColor: Color = Color.Unspecified,
536         contentColor: Color = Color.Unspecified,
537         disabledContainerColor: Color = Color.Unspecified,
538         disabledContentColor: Color = Color.Unspecified,
539         checkedContainerColor: Color = Color.Unspecified,
540         checkedContentColor: Color = Color.Unspecified
541     ): ToggleButtonColors =
542         MaterialTheme.colorScheme.defaultToggleButtonColors.copy(
543             containerColor = containerColor,
544             contentColor = contentColor,
545             disabledContainerColor = disabledContainerColor,
546             disabledContentColor = disabledContentColor,
547             checkedContainerColor = checkedContainerColor,
548             checkedContentColor = checkedContentColor
549         )
550 
551     internal val ColorScheme.defaultToggleButtonColors: ToggleButtonColors
552         get() {
553             return defaultToggleButtonColorsCached
554                 ?: ToggleButtonColors(
555                         containerColor = fromToken(FilledButtonTokens.UnselectedContainerColor),
556                         contentColor =
557                             fromToken(FilledButtonTokens.UnselectedPressedLabelTextColor),
558                         disabledContainerColor =
559                             fromToken(FilledButtonTokens.DisabledContainerColor)
560                                 .copy(alpha = FilledButtonTokens.DisabledContainerOpacity),
561                         disabledContentColor =
562                             fromToken(FilledButtonTokens.DisabledLabelTextColor)
563                                 .copy(alpha = FilledButtonTokens.DisabledLabelTextOpacity),
564                         checkedContainerColor =
565                             fromToken(FilledButtonTokens.SelectedContainerColor),
566                         checkedContentColor =
567                             fromToken(FilledButtonTokens.SelectedPressedLabelTextColor)
568                     )
569                     .also { defaultToggleButtonColorsCached = it }
570         }
571 
572     /**
573      * Creates a [ToggleButtonColors] that represents the default container and content colors used
574      * in a [ElevatedToggleButton].
575      */
576     @Composable
577     fun elevatedToggleButtonColors() = MaterialTheme.colorScheme.defaultElevatedToggleButtonColors
578 
579     /**
580      * Creates a [ToggleButtonColors] that represents the default container and content colors used
581      * in a [ElevatedToggleButton].
582      *
583      * @param containerColor the container color of this [ElevatedToggleButton] when enabled.
584      * @param contentColor the content color of this [ElevatedToggleButton] when enabled.
585      * @param disabledContainerColor the container color of this [ElevatedToggleButton] when not
586      *   enabled.
587      * @param disabledContentColor the content color of this [ElevatedToggleButton] when not
588      *   enabled.
589      * @param checkedContainerColor the container color of this [ElevatedToggleButton] when checked.
590      * @param checkedContentColor the content color of this [ElevatedToggleButton] when checked.
591      */
592     @Composable
593     fun elevatedToggleButtonColors(
594         containerColor: Color = Color.Unspecified,
595         contentColor: Color = Color.Unspecified,
596         disabledContainerColor: Color = Color.Unspecified,
597         disabledContentColor: Color = Color.Unspecified,
598         checkedContainerColor: Color = Color.Unspecified,
599         checkedContentColor: Color = Color.Unspecified
600     ): ToggleButtonColors =
601         MaterialTheme.colorScheme.defaultElevatedToggleButtonColors.copy(
602             containerColor = containerColor,
603             contentColor = contentColor,
604             disabledContainerColor = disabledContainerColor,
605             disabledContentColor = disabledContentColor,
606             checkedContainerColor = checkedContainerColor,
607             checkedContentColor = checkedContentColor
608         )
609 
610     internal val ColorScheme.defaultElevatedToggleButtonColors: ToggleButtonColors
611         get() {
612             return defaultElevatedToggleButtonColorsCached
613                 ?: ToggleButtonColors(
614                         containerColor = fromToken(ElevatedButtonTokens.UnselectedContainerColor),
615                         contentColor =
616                             fromToken(ElevatedButtonTokens.UnselectedPressedLabelTextColor),
617                         disabledContainerColor =
618                             fromToken(ElevatedButtonTokens.DisabledContainerColor)
619                                 .copy(alpha = ElevatedButtonTokens.DisabledContainerOpacity),
620                         disabledContentColor =
621                             fromToken(ElevatedButtonTokens.DisabledLabelTextColor)
622                                 .copy(alpha = ElevatedButtonTokens.DisabledLabelTextOpacity),
623                         checkedContainerColor =
624                             fromToken(ElevatedButtonTokens.SelectedContainerColor),
625                         checkedContentColor =
626                             fromToken(ElevatedButtonTokens.SelectedPressedLabelTextColor)
627                     )
628                     .also { defaultElevatedToggleButtonColorsCached = it }
629         }
630 
631     /**
632      * Creates a [ToggleButtonColors] that represents the default container and content colors used
633      * in a [TonalToggleButton].
634      */
635     @Composable
636     fun tonalToggleButtonColors() = MaterialTheme.colorScheme.defaultTonalToggleButtonColors
637 
638     /**
639      * Creates a [ToggleButtonColors] that represents the default container and content colors used
640      * in a [TonalToggleButton].
641      *
642      * @param containerColor the container color of this [TonalToggleButton] when enabled.
643      * @param contentColor the content color of this [TonalToggleButton] when enabled.
644      * @param disabledContainerColor the container color of this [TonalToggleButton] when not
645      *   enabled.
646      * @param disabledContentColor the content color of this [TonalToggleButton] when not enabled.
647      * @param checkedContainerColor the container color of this [TonalToggleButton] when checked.
648      * @param checkedContentColor the content color of this [TonalToggleButton] when checked.
649      */
650     @Composable
651     fun tonalToggleButtonColors(
652         containerColor: Color = Color.Unspecified,
653         contentColor: Color = Color.Unspecified,
654         disabledContainerColor: Color = Color.Unspecified,
655         disabledContentColor: Color = Color.Unspecified,
656         checkedContainerColor: Color = Color.Unspecified,
657         checkedContentColor: Color = Color.Unspecified
658     ): ToggleButtonColors =
659         MaterialTheme.colorScheme.defaultTonalToggleButtonColors.copy(
660             containerColor = containerColor,
661             contentColor = contentColor,
662             disabledContainerColor = disabledContainerColor,
663             disabledContentColor = disabledContentColor,
664             checkedContainerColor = checkedContainerColor,
665             checkedContentColor = checkedContentColor
666         )
667 
668     internal val ColorScheme.defaultTonalToggleButtonColors: ToggleButtonColors
669         get() {
670             return defaultTonalToggleButtonColorsCached
671                 ?: ToggleButtonColors(
672                         containerColor = fromToken(TonalButtonTokens.UnselectedContainerColor),
673                         contentColor = fromToken(TonalButtonTokens.UnselectedLabelTextColor),
674                         disabledContainerColor =
675                             fromToken(TonalButtonTokens.DisabledContainerColor)
676                                 .copy(alpha = TonalButtonTokens.DisabledContainerOpacity),
677                         disabledContentColor =
678                             fromToken(TonalButtonTokens.DisabledLabelTextColor)
679                                 .copy(alpha = TonalButtonTokens.DisabledLabelTextOpacity),
680                         checkedContainerColor = fromToken(TonalButtonTokens.SelectedContainerColor),
681                         checkedContentColor = fromToken(TonalButtonTokens.SelectedLabelTextColor)
682                     )
683                     .also { defaultTonalToggleButtonColorsCached = it }
684         }
685 
686     /**
687      * Creates a [ToggleButtonColors] that represents the default container and content colors used
688      * in a [OutlinedToggleButton].
689      */
690     @Composable
691     fun outlinedToggleButtonColors() = MaterialTheme.colorScheme.defaultOutlinedToggleButtonColors
692 
693     /**
694      * Creates a [ToggleButtonColors] that represents the default container and content colors used
695      * in a [OutlinedToggleButton].
696      *
697      * @param containerColor the container color of this [OutlinedToggleButton] when enabled.
698      * @param contentColor the content color of this [OutlinedToggleButton] when enabled.
699      * @param disabledContainerColor the container color of this [OutlinedToggleButton] when not
700      *   enabled.
701      * @param disabledContentColor the content color of this [OutlinedToggleButton] when not
702      *   enabled.
703      * @param checkedContainerColor the container color of this [OutlinedToggleButton] when checked.
704      * @param checkedContentColor the content color of this [OutlinedToggleButton] when checked.
705      */
706     @Composable
707     fun outlinedToggleButtonColors(
708         containerColor: Color = Color.Unspecified,
709         contentColor: Color = Color.Unspecified,
710         disabledContainerColor: Color = Color.Unspecified,
711         disabledContentColor: Color = Color.Unspecified,
712         checkedContainerColor: Color = Color.Unspecified,
713         checkedContentColor: Color = Color.Unspecified
714     ): ToggleButtonColors =
715         MaterialTheme.colorScheme.defaultOutlinedToggleButtonColors.copy(
716             containerColor = containerColor,
717             contentColor = contentColor,
718             disabledContainerColor = disabledContainerColor,
719             disabledContentColor = disabledContentColor,
720             checkedContainerColor = checkedContainerColor,
721             checkedContentColor = checkedContentColor
722         )
723 
724     internal val ColorScheme.defaultOutlinedToggleButtonColors: ToggleButtonColors
725         get() {
726             return defaultOutlinedToggleButtonColorsCached
727                 ?: ToggleButtonColors(
728                         containerColor = Color.Transparent,
729                         contentColor = fromToken(OutlinedButtonTokens.UnselectedLabelTextColor),
730                         disabledContainerColor =
731                             fromToken(OutlinedButtonTokens.DisabledOutlineColor)
732                                 .copy(alpha = OutlinedButtonTokens.DisabledContainerOpacity),
733                         disabledContentColor =
734                             fromToken(OutlinedButtonTokens.DisabledLabelTextColor)
735                                 .copy(alpha = OutlinedButtonTokens.DisabledLabelTextOpacity),
736                         checkedContainerColor =
737                             fromToken(OutlinedButtonTokens.SelectedContainerColor),
738                         checkedContentColor = fromToken(OutlinedButtonTokens.SelectedLabelTextColor)
739                     )
740                     .also { defaultOutlinedToggleButtonColorsCached = it }
741         }
742 
743     /**
744      * Recommended [ToggleButtonShapes] for a provided toggle button height.
745      *
746      * @param buttonHeight The height of the button
747      */
748     @Composable
749     fun shapesFor(buttonHeight: Dp): ToggleButtonShapes {
750         val xSmallHeight = ButtonDefaults.ExtraSmallContainerHeight
751         val smallHeight = ButtonDefaults.MinHeight
752         val mediumHeight = ButtonDefaults.MediumContainerHeight
753         val largeHeight = ButtonDefaults.LargeContainerHeight
754         val xLargeHeight = ButtonDefaults.ExtraLargeContainerHeight
755         return when {
756             buttonHeight <= (xSmallHeight + smallHeight) / 2 ->
757                 shapes(
758                     shape = shape,
759                     pressedShape = extraSmallPressedShape,
760                     checkedShape = extraSmallCheckedSquareShape
761                 )
762             buttonHeight <= (smallHeight + mediumHeight) / 2 -> shapes()
763             buttonHeight <= (mediumHeight + largeHeight) / 2 ->
764                 shapes(
765                     shape = shape,
766                     pressedShape = mediumPressedShape,
767                     checkedShape = mediumCheckedSquareShape
768                 )
769             buttonHeight <= (largeHeight + xLargeHeight) / 2 ->
770                 shapes(
771                     shape = shape,
772                     pressedShape = largePressedShape,
773                     checkedShape = largeCheckedSquareShape
774                 )
775             else ->
776                 shapes(
777                     shape = shape,
778                     pressedShape = extraLargePressedShape,
779                     checkedShape = extraLargeCheckedSquareShape
780                 )
781         }
782     }
783 }
784 
785 /**
786  * Represents the container and content colors used in a toggle button in different states.
787  *
788  * @param containerColor the container color of this [ToggleButton] when enabled.
789  * @param contentColor the content color of this [ToggleButton] when enabled.
790  * @param disabledContainerColor the container color of this [ToggleButton] when not enabled.
791  * @param disabledContentColor the content color of this [ToggleButton] when not enabled.
792  * @param checkedContainerColor the container color of this [ToggleButton] when checked.
793  * @param checkedContentColor the content color of this [ToggleButton] when checked.
794  * @constructor create an instance with arbitrary colors.
795  * - See [ToggleButtonDefaults.toggleButtonColors] for the default colors used in a [ToggleButton].
796  * - See [ToggleButtonDefaults.elevatedToggleButtonColors] for the default colors used in a
797  *   [ElevatedToggleButton].
798  * - See [ToggleButtonDefaults.tonalToggleButtonColors] for the default colors used in a
799  *   [TonalToggleButton].
800  * - See [ToggleButtonDefaults.outlinedToggleButtonColors] for the default colors used in a
801  *   [OutlinedToggleButton].
802  */
803 @Immutable
804 class ToggleButtonColors(
805     val containerColor: Color,
806     val contentColor: Color,
807     val disabledContainerColor: Color,
808     val disabledContentColor: Color,
809     val checkedContainerColor: Color,
810     val checkedContentColor: Color
811 ) {
812     /**
813      * Returns a copy of this ToggleButtonColors, optionally overriding some of the values. This
814      * uses the Color.Unspecified to mean “use the value from the source”
815      */
copynull816     fun copy(
817         containerColor: Color = this.containerColor,
818         contentColor: Color = this.contentColor,
819         disabledContainerColor: Color = this.disabledContainerColor,
820         disabledContentColor: Color = this.disabledContentColor,
821         checkedContainerColor: Color = this.checkedContainerColor,
822         checkedContentColor: Color = this.checkedContentColor
823     ) =
824         ToggleButtonColors(
825             containerColor.takeOrElse { this.containerColor },
<lambda>null826             contentColor.takeOrElse { this.contentColor },
<lambda>null827             disabledContainerColor.takeOrElse { this.disabledContainerColor },
<lambda>null828             disabledContentColor.takeOrElse { this.disabledContentColor },
<lambda>null829             checkedContainerColor.takeOrElse { this.checkedContainerColor },
<lambda>null830             checkedContentColor.takeOrElse { this.checkedContentColor }
831         )
832 
833     /**
834      * Represents the container color for this toggle button, depending on [enabled] and [checked].
835      *
836      * @param enabled whether the toggle button is enabled
837      * @param checked whether the toggle button is checked
838      */
839     @Stable
containerColornull840     internal fun containerColor(enabled: Boolean, checked: Boolean): Color {
841         return when {
842             enabled && checked -> checkedContainerColor
843             enabled && !checked -> containerColor
844             else -> disabledContainerColor
845         }
846     }
847 
848     /**
849      * Represents the content color for this toggle button, depending on [enabled] and [checked].
850      *
851      * @param enabled whether the toggle button is enabled
852      * @param checked whether the toggle button is checked
853      */
854     @Stable
contentColornull855     internal fun contentColor(enabled: Boolean, checked: Boolean): Color {
856         return when {
857             enabled && checked -> checkedContentColor
858             enabled && !checked -> contentColor
859             else -> disabledContentColor
860         }
861     }
862 
equalsnull863     override fun equals(other: Any?): Boolean {
864         if (this === other) return true
865         if (other == null || other !is ToggleButtonColors) return false
866 
867         if (containerColor != other.containerColor) return false
868         if (contentColor != other.contentColor) return false
869         if (disabledContainerColor != other.disabledContainerColor) return false
870         if (disabledContentColor != other.disabledContentColor) return false
871         if (checkedContainerColor != other.checkedContainerColor) return false
872         if (checkedContentColor != other.checkedContentColor) return false
873 
874         return true
875     }
876 
hashCodenull877     override fun hashCode(): Int {
878         var result = containerColor.hashCode()
879         result = 31 * result + contentColor.hashCode()
880         result = 31 * result + disabledContainerColor.hashCode()
881         result = 31 * result + disabledContentColor.hashCode()
882         result = 31 * result + checkedContainerColor.hashCode()
883         result = 31 * result + checkedContentColor.hashCode()
884 
885         return result
886     }
887 }
888 
889 /**
890  * The shapes that will be used in toggle buttons. Toggle button will morph between these three
891  * shapes depending on the interaction of the toggle button, assuming all of the shapes are
892  * [CornerBasedShape]s.
893  *
894  * @property shape is the unchecked shape.
895  * @property pressedShape is the pressed shape.
896  * @property checkedShape is the checked shape.
897  */
898 @ExperimentalMaterial3ExpressiveApi
899 @Immutable
900 class ToggleButtonShapes(val shape: Shape, val pressedShape: Shape, val checkedShape: Shape) {
901     /** Returns a copy of this ToggleButtonShapes, optionally overriding some of the values. */
copynull902     fun copy(
903         shape: Shape? = this.shape,
904         pressedShape: Shape? = this.pressedShape,
905         checkedShape: Shape? = this.checkedShape
906     ) =
907         ToggleButtonShapes(
908             shape = shape.takeOrElse { this.shape },
<lambda>null909             pressedShape = pressedShape.takeOrElse { this.pressedShape },
<lambda>null910             checkedShape = checkedShape.takeOrElse { this.checkedShape }
911         )
912 
takeOrElsenull913     internal fun Shape?.takeOrElse(block: () -> Shape): Shape = this ?: block()
914 
915     override fun equals(other: Any?): Boolean {
916         if (this === other) return true
917         if (other == null || other !is ToggleButtonShapes) return false
918 
919         if (shape != other.shape) return false
920         if (pressedShape != other.pressedShape) return false
921         if (checkedShape != other.checkedShape) return false
922 
923         return true
924     }
925 
hashCodenull926     override fun hashCode(): Int {
927         var result = shape.hashCode()
928         result = 31 * result + pressedShape.hashCode()
929         result = 31 * result + checkedShape.hashCode()
930 
931         return result
932     }
933 }
934 
935 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
936 internal val ToggleButtonShapes.hasRoundedCornerShapes: Boolean
937     get() =
938         shape is RoundedCornerShape &&
939             pressedShape is RoundedCornerShape &&
940             checkedShape is RoundedCornerShape
941 
942 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
943 @Composable
shapeByInteractionnull944 private fun shapeByInteraction(
945     shapes: ToggleButtonShapes,
946     pressed: Boolean,
947     checked: Boolean,
948     animationSpec: FiniteAnimationSpec<Float>
949 ): Shape {
950     val shape =
951         if (pressed) {
952             shapes.pressedShape
953         } else if (checked) {
954             shapes.checkedShape
955         } else {
956             shapes.shape
957         }
958 
959     if (shapes.hasRoundedCornerShapes)
960         return key(shapes) {
961             rememberAnimatedShape(
962                 shape as RoundedCornerShape,
963                 animationSpec,
964             )
965         }
966 
967     return shape
968 }
969