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