1 /*
2  * Copyright 2022 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.glance.material3
18 
19 import androidx.compose.material3.ColorScheme
20 import androidx.compose.ui.graphics.Color
21 import androidx.compose.ui.graphics.toArgb
22 import androidx.core.graphics.ColorUtils.M3HCTToColor
23 import androidx.core.graphics.ColorUtils.colorToM3HCT
24 import androidx.glance.color.ColorProvider
25 import androidx.glance.color.ColorProviders
26 import androidx.glance.color.colorProviders
27 import androidx.glance.unit.ColorProvider
28 
29 /**
30  * Creates a Material 3 [ColorProviders] given a light and dark [ColorScheme]. Each color in the
31  * theme will have a day and night mode.
32  */
ColorProvidersnull33 fun ColorProviders(light: ColorScheme, dark: ColorScheme): ColorProviders {
34     return colorProviders(
35         primary = ColorProvider(day = light.primary, night = dark.primary),
36         onPrimary = ColorProvider(day = light.onPrimary, night = dark.onPrimary),
37         primaryContainer =
38             ColorProvider(day = light.primaryContainer, night = dark.primaryContainer),
39         onPrimaryContainer =
40             ColorProvider(day = light.onPrimaryContainer, night = dark.onPrimaryContainer),
41         secondary = ColorProvider(day = light.secondary, night = dark.secondary),
42         onSecondary = ColorProvider(day = light.onSecondary, night = dark.onSecondary),
43         secondaryContainer =
44             ColorProvider(day = light.secondaryContainer, night = dark.secondaryContainer),
45         onSecondaryContainer =
46             ColorProvider(day = light.onSecondaryContainer, night = dark.onSecondaryContainer),
47         tertiary = ColorProvider(day = light.tertiary, night = dark.tertiary),
48         onTertiary = ColorProvider(day = light.onTertiary, night = dark.onTertiary),
49         tertiaryContainer =
50             ColorProvider(day = light.tertiaryContainer, night = dark.tertiaryContainer),
51         onTertiaryContainer =
52             ColorProvider(day = light.onTertiaryContainer, night = dark.onTertiaryContainer),
53         error = ColorProvider(day = light.error, night = dark.error),
54         errorContainer = ColorProvider(day = light.errorContainer, night = dark.errorContainer),
55         onError = ColorProvider(day = light.onError, night = dark.onError),
56         onErrorContainer =
57             ColorProvider(day = light.onErrorContainer, night = dark.onErrorContainer),
58         background = ColorProvider(day = light.background, night = dark.background),
59         onBackground = ColorProvider(day = light.onBackground, night = dark.onBackground),
60         surface = ColorProvider(day = light.surface, night = dark.surface),
61         onSurface = ColorProvider(day = light.onSurface, night = dark.onSurface),
62         surfaceVariant = ColorProvider(day = light.surfaceVariant, night = dark.surfaceVariant),
63         onSurfaceVariant =
64             ColorProvider(day = light.onSurfaceVariant, night = dark.onSurfaceVariant),
65         outline = ColorProvider(day = light.outline, night = dark.outline),
66         inverseOnSurface =
67             ColorProvider(day = light.inverseOnSurface, night = dark.inverseOnSurface),
68         inverseSurface = ColorProvider(day = light.inverseSurface, night = dark.inverseSurface),
69         inversePrimary = ColorProvider(day = light.inversePrimary, night = dark.inversePrimary),
70         // Widget background is a widget / glace specific token it is generally derived from the
71         // secondary container color.
72         widgetBackground =
73             ColorProvider(
74                 day = adjustColorToneForWidgetBackground(light.secondaryContainer),
75                 night = adjustColorToneForWidgetBackground(dark.secondaryContainer)
76             ),
77     )
78 }
79 
80 /**
81  * Creates a Material 3 [ColorProviders] given a [ColorScheme]. This is a fixed scheme and does not
82  * have day/night modes.
83  */
ColorProvidersnull84 fun ColorProviders(scheme: ColorScheme): ColorProviders {
85     return colorProviders(
86         primary = ColorProvider(color = scheme.primary),
87         onPrimary = ColorProvider(scheme.onPrimary),
88         primaryContainer = ColorProvider(color = scheme.primaryContainer),
89         onPrimaryContainer = ColorProvider(color = scheme.onPrimaryContainer),
90         secondary = ColorProvider(color = scheme.secondary),
91         onSecondary = ColorProvider(color = scheme.onSecondary),
92         secondaryContainer = ColorProvider(color = scheme.secondaryContainer),
93         onSecondaryContainer = ColorProvider(color = scheme.onSecondaryContainer),
94         tertiary = ColorProvider(color = scheme.tertiary),
95         onTertiary = ColorProvider(color = scheme.onTertiary),
96         tertiaryContainer = ColorProvider(color = scheme.tertiaryContainer),
97         onTertiaryContainer = ColorProvider(color = scheme.onTertiaryContainer),
98         error = ColorProvider(color = scheme.error),
99         onError = ColorProvider(color = scheme.onError),
100         errorContainer = ColorProvider(color = scheme.errorContainer),
101         onErrorContainer = ColorProvider(color = scheme.onErrorContainer),
102         background = ColorProvider(color = scheme.background),
103         onBackground = ColorProvider(color = scheme.onBackground),
104         surface = ColorProvider(color = scheme.surface),
105         onSurface = ColorProvider(color = scheme.onSurface),
106         surfaceVariant = ColorProvider(color = scheme.surfaceVariant),
107         onSurfaceVariant = ColorProvider(color = scheme.onSurfaceVariant),
108         outline = ColorProvider(color = scheme.outline),
109         inverseOnSurface = ColorProvider(color = scheme.inverseOnSurface),
110         inverseSurface = ColorProvider(color = scheme.inverseSurface),
111         inversePrimary = ColorProvider(color = scheme.inversePrimary),
112 
113         // Widget background is a widget / glace specific token it is generally derived from the
114         // secondary container color.
115         widgetBackground =
116             ColorProvider(color = adjustColorToneForWidgetBackground(scheme.secondaryContainer))
117     )
118 }
119 
120 private const val WIDGET_BG_TONE_ADJUSTMENT_LIGHT = 5f
121 private const val WIDGET_BG_TONE_ADJUSTMENT_DARK = -10f
122 
123 /**
124  * Adjusts the input color to work as a widgetBackground token.
125  *
126  * widgetBackground is a Widgets / Glance specific role so won't be present in the original Scheme.
127  * In the system it is defined as being a variation on the secondaryContainer, lighter for light
128  * themes and darker for dark themes.
129  */
adjustColorToneForWidgetBackgroundnull130 private fun adjustColorToneForWidgetBackground(input: Color): Color {
131     val hctColor = floatArrayOf(0f, 0f, 0f)
132     colorToM3HCT(input.toArgb(), hctColor)
133     // Check the Tone of the input color, if it is "light" (greater than 50) lighten it, otherwise
134     // darken it.
135     val adjustment =
136         if (hctColor[2] > 50) WIDGET_BG_TONE_ADJUSTMENT_LIGHT else WIDGET_BG_TONE_ADJUSTMENT_DARK
137 
138     // Tone should be defined in the 0 - 100 range, ok to clamp here.
139     val tone = (hctColor[2] + adjustment).coerceIn(0f, 100f)
140     return Color(M3HCTToColor(hctColor[0], hctColor[1], tone))
141 }
142