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 * 
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