1 /*
2  * Copyright 2019 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.material
18 
19 import androidx.compose.runtime.Composable
20 import androidx.compose.runtime.ReadOnlyComposable
21 import androidx.compose.runtime.Stable
22 import androidx.compose.runtime.getValue
23 import androidx.compose.runtime.mutableStateOf
24 import androidx.compose.runtime.setValue
25 import androidx.compose.runtime.staticCompositionLocalOf
26 import androidx.compose.runtime.structuralEqualityPolicy
27 import androidx.compose.ui.graphics.Color
28 import androidx.compose.ui.graphics.takeOrElse
29 
30 /**
31  * [Material Design color system](https://material.io/design/color/the-color-system.html)
32  *
33  * The Material Design color system can help you create a color theme that reflects your brand or
34  * style.
35  *
36  * ![Color
37  * image](https://developer.android.com/images/reference/androidx/compose/material/color.png)
38  *
39  * To create a light set of colors using the baseline values, use [lightColors] To create a dark set
40  * of colors using the baseline values, use [darkColors]
41  *
42  * @property primary The primary color is the color displayed most frequently across your app’s
43  *   screens and components.
44  * @property primaryVariant The primary variant color is used to distinguish two elements of the app
45  *   using the primary color, such as the top app bar and the system bar.
46  * @property secondary The secondary color provides more ways to accent and distinguish your
47  *   product. Secondary colors are best for:
48  * - Floating action buttons
49  * - Selection controls, like checkboxes and radio buttons
50  * - Highlighting selected text
51  * - Links and headlines
52  *
53  * @property secondaryVariant The secondary variant color is used to distinguish two elements of the
54  *   app using the secondary color.
55  * @property background The background color appears behind scrollable content.
56  * @property surface The surface color is used on surfaces of components, such as cards, sheets and
57  *   menus.
58  * @property error The error color is used to indicate error within components, such as text fields.
59  * @property onPrimary Color used for text and icons displayed on top of the primary color.
60  * @property onSecondary Color used for text and icons displayed on top of the secondary color.
61  * @property onBackground Color used for text and icons displayed on top of the background color.
62  * @property onSurface Color used for text and icons displayed on top of the surface color.
63  * @property onError Color used for text and icons displayed on top of the error color.
64  * @property isLight Whether this Colors is considered as a 'light' or 'dark' set of colors. This
65  *   affects default behavior for some components: for example, in a light theme a [TopAppBar] will
66  *   use [primary] by default for its background color, when in a dark theme it will use [surface].
67  */
68 @Stable
69 class Colors(
70     primary: Color,
71     primaryVariant: Color,
72     secondary: Color,
73     secondaryVariant: Color,
74     background: Color,
75     surface: Color,
76     error: Color,
77     onPrimary: Color,
78     onSecondary: Color,
79     onBackground: Color,
80     onSurface: Color,
81     onError: Color,
82     isLight: Boolean
83 ) {
84     var primary by mutableStateOf(primary, structuralEqualityPolicy())
85         internal set
86 
87     var primaryVariant by mutableStateOf(primaryVariant, structuralEqualityPolicy())
88         internal set
89 
90     var secondary by mutableStateOf(secondary, structuralEqualityPolicy())
91         internal set
92 
93     var secondaryVariant by mutableStateOf(secondaryVariant, structuralEqualityPolicy())
94         internal set
95 
96     var background by mutableStateOf(background, structuralEqualityPolicy())
97         internal set
98 
99     var surface by mutableStateOf(surface, structuralEqualityPolicy())
100         internal set
101 
102     var error by mutableStateOf(error, structuralEqualityPolicy())
103         internal set
104 
105     var onPrimary by mutableStateOf(onPrimary, structuralEqualityPolicy())
106         internal set
107 
108     var onSecondary by mutableStateOf(onSecondary, structuralEqualityPolicy())
109         internal set
110 
111     var onBackground by mutableStateOf(onBackground, structuralEqualityPolicy())
112         internal set
113 
114     var onSurface by mutableStateOf(onSurface, structuralEqualityPolicy())
115         internal set
116 
117     var onError by mutableStateOf(onError, structuralEqualityPolicy())
118         internal set
119 
120     var isLight by mutableStateOf(isLight, structuralEqualityPolicy())
121         internal set
122 
123     /** Returns a copy of this Colors, optionally overriding some of the values. */
copynull124     fun copy(
125         primary: Color = this.primary,
126         primaryVariant: Color = this.primaryVariant,
127         secondary: Color = this.secondary,
128         secondaryVariant: Color = this.secondaryVariant,
129         background: Color = this.background,
130         surface: Color = this.surface,
131         error: Color = this.error,
132         onPrimary: Color = this.onPrimary,
133         onSecondary: Color = this.onSecondary,
134         onBackground: Color = this.onBackground,
135         onSurface: Color = this.onSurface,
136         onError: Color = this.onError,
137         isLight: Boolean = this.isLight
138     ): Colors =
139         Colors(
140             primary,
141             primaryVariant,
142             secondary,
143             secondaryVariant,
144             background,
145             surface,
146             error,
147             onPrimary,
148             onSecondary,
149             onBackground,
150             onSurface,
151             onError,
152             isLight
153         )
154 
155     override fun toString(): String {
156         return "Colors(" +
157             "primary=$primary, " +
158             "primaryVariant=$primaryVariant, " +
159             "secondary=$secondary, " +
160             "secondaryVariant=$secondaryVariant, " +
161             "background=$background, " +
162             "surface=$surface, " +
163             "error=$error, " +
164             "onPrimary=$onPrimary, " +
165             "onSecondary=$onSecondary, " +
166             "onBackground=$onBackground, " +
167             "onSurface=$onSurface, " +
168             "onError=$onError, " +
169             "isLight=$isLight" +
170             ")"
171     }
172 }
173 
174 /**
175  * Creates a complete color definition for the
176  * [Material color specification](https://material.io/design/color/the-color-system.html#color-theme-creation)
177  * using the default light theme values.
178  *
179  * @see darkColors
180  */
lightColorsnull181 fun lightColors(
182     primary: Color = Color(0xFF6200EE),
183     primaryVariant: Color = Color(0xFF3700B3),
184     secondary: Color = Color(0xFF03DAC6),
185     secondaryVariant: Color = Color(0xFF018786),
186     background: Color = Color.White,
187     surface: Color = Color.White,
188     error: Color = Color(0xFFB00020),
189     onPrimary: Color = Color.White,
190     onSecondary: Color = Color.Black,
191     onBackground: Color = Color.Black,
192     onSurface: Color = Color.Black,
193     onError: Color = Color.White
194 ): Colors =
195     Colors(
196         primary,
197         primaryVariant,
198         secondary,
199         secondaryVariant,
200         background,
201         surface,
202         error,
203         onPrimary,
204         onSecondary,
205         onBackground,
206         onSurface,
207         onError,
208         true
209     )
210 
211 /**
212  * Creates a complete color definition for the
213  * [Material color specification](https://material.io/design/color/the-color-system.html#color-theme-creation)
214  * using the default dark theme values.
215  *
216  * Note: [secondaryVariant] is typically the same as [secondary] in dark theme since contrast levels
217  * are higher, and hence there is less need for a separate secondary color.
218  *
219  * @see lightColors
220  */
221 fun darkColors(
222     primary: Color = Color(0xFFBB86FC),
223     primaryVariant: Color = Color(0xFF3700B3),
224     secondary: Color = Color(0xFF03DAC6),
225     secondaryVariant: Color = secondary,
226     background: Color = Color(0xFF121212),
227     surface: Color = Color(0xFF121212),
228     error: Color = Color(0xFFCF6679),
229     onPrimary: Color = Color.Black,
230     onSecondary: Color = Color.Black,
231     onBackground: Color = Color.White,
232     onSurface: Color = Color.White,
233     onError: Color = Color.Black
234 ): Colors =
235     Colors(
236         primary,
237         primaryVariant,
238         secondary,
239         secondaryVariant,
240         background,
241         surface,
242         error,
243         onPrimary,
244         onSecondary,
245         onBackground,
246         onSurface,
247         onError,
248         false
249     )
250 
251 /**
252  * primarySurface represents the background color of components that are [Colors.primary] in light
253  * theme, and [Colors.surface] in dark theme, such as [androidx.compose.material.TabRow] and
254  * [androidx.compose.material.TopAppBar]. This is to reduce brightness of large surfaces in dark
255  * theme, aiding contrast and readability. See
256  * [Dark Theme](https://material.io/design/color/dark-theme.html#custom-application).
257  *
258  * @return [Colors.primary] if in light theme, else [Colors.surface]
259  */
260 val Colors.primarySurface: Color
261     get() = if (isLight) primary else surface
262 
263 /**
264  * The Material color system contains pairs of colors that are typically used for the background and
265  * content color inside a component. For example, a [Button] typically uses `primary` for its
266  * background, and `onPrimary` for the color of its content (usually text or iconography).
267  *
268  * This function tries to match the provided [backgroundColor] to a 'background' color in this
269  * [Colors], and then will return the corresponding color used for content. For example, when
270  * [backgroundColor] is [Colors.primary], this will return [Colors.onPrimary].
271  *
272  * If [backgroundColor] does not match a background color in the theme, this will return
273  * [Color.Unspecified].
274  *
275  * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
276  *   the theme's [Colors], then returns [Color.Unspecified].
277  * @see contentColorFor
278  */
279 fun Colors.contentColorFor(backgroundColor: Color): Color {
280     return when (backgroundColor) {
281         primary -> onPrimary
282         primaryVariant -> onPrimary
283         secondary -> onSecondary
284         secondaryVariant -> onSecondary
285         background -> onBackground
286         surface -> onSurface
287         error -> onError
288         else -> Color.Unspecified
289     }
290 }
291 
292 /**
293  * The Material color system contains pairs of colors that are typically used for the background and
294  * content color inside a component. For example, a [Button] typically uses `primary` for its
295  * background, and `onPrimary` for the color of its content (usually text or iconography).
296  *
297  * This function tries to match the provided [backgroundColor] to a 'background' color in this
298  * [Colors], and then will return the corresponding color used for content. For example, when
299  * [backgroundColor] is [Colors.primary], this will return [Colors.onPrimary].
300  *
301  * If [backgroundColor] does not match a background color in the theme, this will return the current
302  * value of [LocalContentColor] as a best-effort color.
303  *
304  * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
305  *   the theme's [Colors], then returns the current value of [LocalContentColor].
306  * @see Colors.contentColorFor
307  */
308 @Composable
309 @ReadOnlyComposable
contentColorFornull310 fun contentColorFor(backgroundColor: Color) =
311     MaterialTheme.colors.contentColorFor(backgroundColor).takeOrElse { LocalContentColor.current }
312 
313 /**
314  * Updates the internal values of the given [Colors] with values from the [other] [Colors]. This
315  * allows efficiently updating a subset of [Colors], without recomposing every composable that
316  * consumes values from [LocalColors].
317  *
318  * Because [Colors] is very wide-reaching, and used by many expensive composables in the hierarchy,
319  * providing a new value to [LocalColors] causes every composable consuming [LocalColors] to
320  * recompose, which is prohibitively expensive in cases such as animating one color in the theme.
321  * Instead, [Colors] is internally backed by [mutableStateOf], and this function mutates the
322  * internal state of [this] to match values in [other]. This means that any changes will mutate the
323  * internal state of [this], and only cause composables that are reading the specific changed value
324  * to recompose.
325  */
updateColorsFromnull326 internal fun Colors.updateColorsFrom(other: Colors) {
327     primary = other.primary
328     primaryVariant = other.primaryVariant
329     secondary = other.secondary
330     secondaryVariant = other.secondaryVariant
331     background = other.background
332     surface = other.surface
333     error = other.error
334     onPrimary = other.onPrimary
335     onSecondary = other.onSecondary
336     onBackground = other.onBackground
337     onSurface = other.onSurface
338     onError = other.onError
339     isLight = other.isLight
340 }
341 
342 /**
343  * CompositionLocal used to pass [Colors] down the tree.
344  *
345  * Setting the value here is typically done as part of [MaterialTheme], which will automatically
346  * handle efficiently updating any changed colors without causing unnecessary recompositions, using
347  * [Colors.updateColorsFrom]. To retrieve the current value of this CompositionLocal, use
348  * [MaterialTheme.colors].
349  */
<lambda>null350 internal val LocalColors = staticCompositionLocalOf { lightColors() }
351