1 /*
2 * Copyright 2023 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.tv.material3
18
19 import androidx.compose.foundation.shape.CircleShape
20 import androidx.compose.foundation.shape.CornerBasedShape
21 import androidx.compose.foundation.shape.CornerSize
22 import androidx.compose.runtime.Composable
23 import androidx.compose.runtime.Immutable
24 import androidx.compose.runtime.staticCompositionLocalOf
25 import androidx.compose.ui.graphics.RectangleShape
26 import androidx.compose.ui.graphics.Shape
27 import androidx.compose.ui.unit.dp
28 import androidx.tv.material3.tokens.ShapeKeyTokens
29 import androidx.tv.material3.tokens.ShapeTokens
30
31 /**
32 * Material surfaces can be displayed in different shapes. Shapes direct attention, identify
33 * components, communicate state, and express brand.
34 *
35 * The shape scale defines the style of container corners, offering a range of roundedness from
36 * square to fully circular.
37 *
38 * There are different sizes of shapes:
39 * - Extra Small
40 * - Small
41 * - Medium
42 * - Large
43 * - Extra Large
44 *
45 * You can customize the shape system for all components in the [MaterialTheme] or you can do it on
46 * a per component basis.
47 *
48 * You can change the shape that a component has by overriding the shape parameter for that
49 * component. For example, by default, buttons use the shape style “full.” If your product requires
50 * a smaller amount of roundedness, you can override the shape parameter with a different shape
51 * value like MaterialTheme.shapes.small.
52 *
53 * To learn more about shapes, see
54 * [Material Design shapes](https://m3.material.io/styles/shape/overview).
55 *
56 * @param extraSmall A shape style with 4 same-sized corners whose size are bigger than
57 * [RectangleShape] and smaller than [Shapes.small]. By default autocomplete menu, select menu,
58 * snackbars, standard menu, and text fields use this shape.
59 * @param small A shape style with 4 same-sized corners whose size are bigger than
60 * [Shapes.extraSmall] and smaller than [Shapes.medium]. By default chips use this shape.
61 * @param medium A shape style with 4 same-sized corners whose size are bigger than [Shapes.small]
62 * and smaller than [Shapes.large]. By default cards and small FABs use this shape.
63 * @param large A shape style with 4 same-sized corners whose size are bigger than [Shapes.medium]
64 * and smaller than [Shapes.extraLarge]. By default extended FABs, FABs, and navigation drawers
65 * use this shape.
66 * @param extraLarge A shape style with 4 same-sized corners whose size are bigger than
67 * [Shapes.large] and smaller than [CircleShape]. By default large FABs use this shape.
68 */
69 @Immutable
70 class Shapes(
71 // Shapes None and Full are omitted as None is a RectangleShape and Full is a CircleShape.
72 val extraSmall: CornerBasedShape = ShapeDefaults.ExtraSmall,
73 val small: CornerBasedShape = ShapeDefaults.Small,
74 val medium: CornerBasedShape = ShapeDefaults.Medium,
75 val large: CornerBasedShape = ShapeDefaults.Large,
76 val extraLarge: CornerBasedShape = ShapeDefaults.ExtraLarge
77 ) {
78 /** Returns a copy of this Shapes, optionally overriding some of the values. */
copynull79 fun copy(
80 extraSmall: CornerBasedShape = this.extraSmall,
81 small: CornerBasedShape = this.small,
82 medium: CornerBasedShape = this.medium,
83 large: CornerBasedShape = this.large,
84 extraLarge: CornerBasedShape = this.extraLarge
85 ): Shapes =
86 Shapes(
87 extraSmall = extraSmall,
88 small = small,
89 medium = medium,
90 large = large,
91 extraLarge = extraLarge
92 )
93
94 override fun equals(other: Any?): Boolean {
95 if (this === other) return true
96 if (other !is Shapes) return false
97 if (extraSmall != other.extraSmall) return false
98 if (small != other.small) return false
99 if (medium != other.medium) return false
100 if (large != other.large) return false
101 if (extraLarge != other.extraLarge) return false
102 return true
103 }
104
hashCodenull105 override fun hashCode(): Int {
106 var result = extraSmall.hashCode()
107 result = 31 * result + small.hashCode()
108 result = 31 * result + medium.hashCode()
109 result = 31 * result + large.hashCode()
110 result = 31 * result + extraLarge.hashCode()
111 return result
112 }
113
toStringnull114 override fun toString(): String {
115 return "Shapes(" +
116 "extraSmall=$extraSmall, " +
117 "small=$small, " +
118 "medium=$medium, " +
119 "large=$large, " +
120 "extraLarge=$extraLarge)"
121 }
122 }
123
124 /** Contains the default values used by [Shapes] */
125 object ShapeDefaults {
126 /** Extra small sized corner shape */
127 val ExtraSmall: CornerBasedShape = ShapeTokens.CornerExtraSmall
128
129 /** Small sized corner shape */
130 val Small: CornerBasedShape = ShapeTokens.CornerSmall
131
132 /** Medium sized corner shape */
133 val Medium: CornerBasedShape = ShapeTokens.CornerMedium
134
135 /** Large sized corner shape */
136 val Large: CornerBasedShape = ShapeTokens.CornerLarge
137
138 /** Extra large sized corner shape */
139 val ExtraLarge: CornerBasedShape = ShapeTokens.CornerExtraLarge
140 }
141
142 /** Helper function for component shape tokens. Used to grab the top values of a shape parameter. */
topnull143 internal fun CornerBasedShape.top(): CornerBasedShape {
144 return copy(bottomStart = CornerSize(0.0.dp), bottomEnd = CornerSize(0.0.dp))
145 }
146
147 /** Helper function for component shape tokens. Used to grab the end values of a shape parameter. */
endnull148 internal fun CornerBasedShape.end(): CornerBasedShape {
149 return copy(topStart = CornerSize(0.0.dp), bottomStart = CornerSize(0.0.dp))
150 }
151
152 /**
153 * Helper function for component shape tokens. Here is an example on how to use component color
154 * tokens: ``MaterialTheme.shapes.fromToken(FabPrimarySmallTokens.ContainerShape)``
155 */
fromTokennull156 internal fun Shapes.fromToken(value: ShapeKeyTokens): Shape {
157 return when (value) {
158 ShapeKeyTokens.CornerExtraLarge -> extraLarge
159 ShapeKeyTokens.CornerExtraLargeTop -> extraLarge.top()
160 ShapeKeyTokens.CornerExtraSmall -> extraSmall
161 ShapeKeyTokens.CornerExtraSmallTop -> extraSmall.top()
162 ShapeKeyTokens.CornerFull -> CircleShape
163 ShapeKeyTokens.CornerLarge -> large
164 ShapeKeyTokens.CornerLargeEnd -> large.end()
165 ShapeKeyTokens.CornerLargeTop -> large.top()
166 ShapeKeyTokens.CornerMedium -> medium
167 ShapeKeyTokens.CornerNone -> RectangleShape
168 ShapeKeyTokens.CornerSmall -> small
169 }
170 }
171
172 /** Converts a shape token key to the local shape provided by the theme */
173 @Composable
toShapenull174 internal fun ShapeKeyTokens.toShape(): Shape {
175 return MaterialTheme.shapes.fromToken(this)
176 }
177
178 /** CompositionLocal used to specify the default shapes for the surfaces. */
<lambda>null179 internal val LocalShapes = staticCompositionLocalOf { Shapes() }
180