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