1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ash/style/ash_color_provider.h"
6 
7 #include <math.h>
8 
9 #include "ash/public/cpp/ash_constants.h"
10 #include "ash/public/cpp/ash_features.h"
11 #include "ash/public/cpp/ash_pref_names.h"
12 #include "ash/session/session_controller_impl.h"
13 #include "ash/shell.h"
14 #include "ash/system/dark_mode/color_mode_observer.h"
15 #include "ash/wallpaper/wallpaper_controller_impl.h"
16 #include "base/bind.h"
17 #include "base/check_op.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "components/prefs/pref_change_registrar.h"
21 #include "components/prefs/pref_registry_simple.h"
22 #include "components/prefs/pref_service.h"
23 #include "ui/chromeos/colors/cros_colors.h"
24 #include "ui/gfx/color_analysis.h"
25 #include "ui/gfx/color_palette.h"
26 #include "ui/gfx/color_utils.h"
27 #include "ui/gfx/paint_vector_icon.h"
28 #include "ui/views/background.h"
29 #include "ui/views/controls/button/image_button.h"
30 #include "ui/views/controls/button/label_button.h"
31 
32 namespace ash {
33 
34 using ColorName = cros_colors::ColorName;
35 
36 namespace {
37 
38 // Opacity of the light/dark ink ripple.
39 constexpr float kLightInkRippleOpacity = 0.08f;
40 constexpr float kDarkInkRippleOpacity = 0.06f;
41 
42 // The disabled color is always 38% opacity of the enabled color.
43 constexpr float kDisabledColorOpacity = 0.38f;
44 
45 // Color of second tone is always 30% opacity of the color of first tone.
46 constexpr float kSecondToneOpacity = 0.3f;
47 
48 // Different alpha values that can be used by Shield and Base layers.
49 constexpr int kAlpha20 = 51;   // 20%
50 constexpr int kAlpha40 = 102;  // 40%
51 constexpr int kAlpha60 = 153;  // 60%
52 constexpr int kAlpha80 = 204;  // 80%
53 constexpr int kAlpha90 = 230;  // 90%
54 
55 // Alpha value that is used to calculate themed color. Please see function
56 // GetBackgroundThemedColor() about how the themed color is calculated.
57 constexpr int kDarkBackgroundBlendAlpha = 127;   // 50%
58 constexpr int kLightBackgroundBlendAlpha = 191;  // 75%
59 
60 // The default background color that can be applied on any layer.
61 constexpr SkColor kBackgroundColorDefaultLight = SK_ColorWHITE;
62 constexpr SkColor kBackgroundColorDefaultDark = gfx::kGoogleGrey900;
63 
64 // The spacing between a pill button's icon and label, if it has both.
65 constexpr int kPillButtonImageLabelSpacingDp = 8;
66 
67 // AshColorProvider is kind of NativeTheme of ChromeOS. This will notify the
68 // View::OnThemeChanged to live update the colors on color mode/theme changes.
NotifyThemeChanges()69 void NotifyThemeChanges() {
70   ui::NativeTheme::GetInstanceForNativeUi()->NotifyObservers();
71 }
72 
73 // Get the corresponding ColorName for |type|. ColorName is an enum in
74 // cros_colors.h file that is generated from cros_colors.json5, which includes
75 // the color IDs and colors that will be used by ChromeOS WebUI.
TypeToColorName(AshColorProvider::ContentLayerType type)76 ColorName TypeToColorName(AshColorProvider::ContentLayerType type) {
77   switch (type) {
78     case AshColorProvider::ContentLayerType::kTextColorPrimary:
79       return ColorName::kTextColorPrimary;
80     case AshColorProvider::ContentLayerType::kTextColorSecondary:
81       return ColorName::kTextColorSecondary;
82     case AshColorProvider::ContentLayerType::kTextColorAlert:
83       return ColorName::kTextColorAlert;
84     case AshColorProvider::ContentLayerType::kTextColorWarning:
85       return ColorName::kTextColorWarning;
86     case AshColorProvider::ContentLayerType::kTextColorPositive:
87       return ColorName::kTextColorPositive;
88     case AshColorProvider::ContentLayerType::kIconColorPrimary:
89       return ColorName::kIconColorPrimary;
90     case AshColorProvider::ContentLayerType::kIconColorAlert:
91       return ColorName::kIconColorAlert;
92     case AshColorProvider::ContentLayerType::kIconColorWarning:
93       return ColorName::kIconColorWarning;
94     case AshColorProvider::ContentLayerType::kIconColorPositive:
95       return ColorName::kIconColorPositive;
96     default:
97       DCHECK_EQ(AshColorProvider::ContentLayerType::kIconColorProminent, type);
98       return ColorName::kIconColorProminent;
99   }
100 }
101 
102 // Get the color from cros_colors.h header file that is generated from
103 // cros_colors.json5. Colors there will also be used by ChromeOS WebUI.
ResolveColor(AshColorProvider::ContentLayerType type,bool is_dark_mode)104 SkColor ResolveColor(AshColorProvider::ContentLayerType type,
105                      bool is_dark_mode) {
106   return cros_colors::ResolveColor(TypeToColorName(type), is_dark_mode);
107 }
108 
109 }  // namespace
110 
AshColorProvider()111 AshColorProvider::AshColorProvider() {
112   Shell::Get()->session_controller()->AddObserver(this);
113 }
114 
~AshColorProvider()115 AshColorProvider::~AshColorProvider() {
116   Shell::Get()->session_controller()->RemoveObserver(this);
117 }
118 
119 // static
Get()120 AshColorProvider* AshColorProvider::Get() {
121   return Shell::Get()->ash_color_provider();
122 }
123 
124 // static
GetDisabledColor(SkColor enabled_color)125 SkColor AshColorProvider::GetDisabledColor(SkColor enabled_color) {
126   return SkColorSetA(enabled_color, std::round(SkColorGetA(enabled_color) *
127                                                kDisabledColorOpacity));
128 }
129 
130 // static
GetSecondToneColor(SkColor color_of_first_tone)131 SkColor AshColorProvider::GetSecondToneColor(SkColor color_of_first_tone) {
132   return SkColorSetA(
133       color_of_first_tone,
134       std::round(SkColorGetA(color_of_first_tone) * kSecondToneOpacity));
135 }
136 
137 // static
RegisterProfilePrefs(PrefRegistrySimple * registry)138 void AshColorProvider::RegisterProfilePrefs(PrefRegistrySimple* registry) {
139   registry->RegisterBooleanPref(prefs::kDarkModeEnabled,
140                                 kDefaultDarkModeEnabled);
141   registry->RegisterBooleanPref(prefs::kColorModeThemed,
142                                 kDefaultColorModeThemed);
143 }
144 
OnActiveUserPrefServiceChanged(PrefService * prefs)145 void AshColorProvider::OnActiveUserPrefServiceChanged(PrefService* prefs) {
146   active_user_pref_service_ = prefs;
147   pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
148   pref_change_registrar_->Init(prefs);
149 
150   pref_change_registrar_->Add(
151       prefs::kDarkModeEnabled,
152       base::BindRepeating(&AshColorProvider::NotifyDarkModeEnabledPrefChange,
153                           base::Unretained(this)));
154   pref_change_registrar_->Add(
155       prefs::kColorModeThemed,
156       base::BindRepeating(&AshColorProvider::NotifyColorModeThemedPrefChange,
157                           base::Unretained(this)));
158 
159   // Immediately tell all the observers to load this user's saved preferences.
160   NotifyDarkModeEnabledPrefChange();
161   NotifyColorModeThemedPrefChange();
162 }
163 
OnSessionStateChanged(session_manager::SessionState state)164 void AshColorProvider::OnSessionStateChanged(
165     session_manager::SessionState state) {
166   if (!features::IsDarkLightModeEnabled())
167     return;
168   ui::NativeTheme::GetInstanceForNativeUi()->set_use_dark_colors(
169       IsDarkModeEnabled());
170 }
171 
GetShieldLayerColor(ShieldLayerType type) const172 SkColor AshColorProvider::GetShieldLayerColor(ShieldLayerType type) const {
173   constexpr int kAlphas[] = {kAlpha20, kAlpha40, kAlpha60, kAlpha80, kAlpha90};
174   DCHECK_LT(static_cast<size_t>(type), base::size(kAlphas));
175   return SkColorSetA(GetBackgroundColor(), kAlphas[static_cast<int>(type)]);
176 }
177 
GetBaseLayerColor(BaseLayerType type) const178 SkColor AshColorProvider::GetBaseLayerColor(BaseLayerType type) const {
179   constexpr int kAlphas[] = {kAlpha20, kAlpha40, kAlpha60,
180                              kAlpha80, kAlpha90, 0xFF};
181   DCHECK_LT(static_cast<size_t>(type), base::size(kAlphas));
182   return SkColorSetA(GetBackgroundColor(), kAlphas[static_cast<int>(type)]);
183 }
184 
GetControlsLayerColor(ControlsLayerType type) const185 SkColor AshColorProvider::GetControlsLayerColor(ControlsLayerType type) const {
186   constexpr SkColor kLightColors[] = {SkColorSetA(SK_ColorBLACK, 0x24),
187                                       gfx::kGoogleBlue600,
188                                       SkColorSetA(SK_ColorBLACK, 0x0D),
189                                       gfx::kGoogleRed600,
190                                       gfx::kGoogleYellow600,
191                                       gfx::kGoogleGreen600,
192                                       SkColorSetA(gfx::kGoogleBlue600, 0x3D),
193                                       gfx::kGoogleBlue600};
194   constexpr SkColor kDarkColors[] = {SkColorSetA(SK_ColorWHITE, 0x24),
195                                      gfx::kGoogleBlue300,
196                                      SkColorSetA(SK_ColorWHITE, 0x1A),
197                                      gfx::kGoogleRed300,
198                                      gfx::kGoogleYellow300,
199                                      gfx::kGoogleGreen300,
200                                      SkColorSetA(gfx::kGoogleBlue300, 0x3D),
201                                      gfx::kGoogleBlue300};
202   DCHECK(base::size(kLightColors) == base::size(kDarkColors));
203   static_assert(
204       base::size(kLightColors) == base::size(kDarkColors),
205       "Size of kLightColors should equal to the size of kDarkColors.");
206   const size_t index = static_cast<size_t>(type);
207   DCHECK_LT(index, base::size(kLightColors));
208   return IsDarkModeEnabled() ? kDarkColors[index] : kLightColors[index];
209 }
210 
GetContentLayerColor(ContentLayerType type) const211 SkColor AshColorProvider::GetContentLayerColor(ContentLayerType type) const {
212   const bool is_dark_mode = IsDarkModeEnabled();
213   switch (type) {
214     case ContentLayerType::kSeparatorColor:
215     case ContentLayerType::kShelfHandleColor:
216       return is_dark_mode ? SkColorSetA(SK_ColorWHITE, 0x24)
217                           : SkColorSetA(SK_ColorBLACK, 0x24);
218     case ContentLayerType::kIconColorSecondary:
219       return gfx::kGoogleGrey500;
220     case ContentLayerType::kButtonLabelColor:
221     case ContentLayerType::kButtonIconColor:
222     case ContentLayerType::kAppStateIndicatorColor:
223     case ContentLayerType::kSliderColorInactive:
224     case ContentLayerType::kRadioColorInactive:
225       return is_dark_mode ? gfx::kGoogleGrey200 : gfx::kGoogleGrey700;
226     case ContentLayerType::kButtonLabelColorBlue:
227     case ContentLayerType::kSliderColorActive:
228     case ContentLayerType::kRadioColorActive:
229       return is_dark_mode ? gfx::kGoogleBlue300 : gfx::kGoogleBlue600;
230     case ContentLayerType::kButtonLabelColorPrimary:
231     case ContentLayerType::kButtonIconColorPrimary:
232       return is_dark_mode ? gfx::kGoogleGrey900 : gfx::kGoogleGrey200;
233     case ContentLayerType::kAppStateIndicatorColorInactive:
234       return GetDisabledColor(
235           GetContentLayerColor(ContentLayerType::kAppStateIndicatorColor));
236     case ContentLayerType::kCurrentDeskColor:
237       return is_dark_mode ? SK_ColorWHITE : SK_ColorBLACK;
238     default:
239       return ResolveColor(type, is_dark_mode);
240   }
241 }
242 
GetRippleAttributes(SkColor bg_color) const243 AshColorProvider::RippleAttributes AshColorProvider::GetRippleAttributes(
244     SkColor bg_color) const {
245   if (bg_color == gfx::kPlaceholderColor)
246     bg_color = GetBackgroundColor();
247 
248   const bool is_dark = color_utils::IsDark(bg_color);
249   const SkColor base_color = is_dark ? SK_ColorWHITE : SK_ColorBLACK;
250   const float opacity =
251       is_dark ? kLightInkRippleOpacity : kDarkInkRippleOpacity;
252   return RippleAttributes(base_color, opacity, opacity);
253 }
254 
GetBackgroundColor() const255 SkColor AshColorProvider::GetBackgroundColor() const {
256   return IsThemed() ? GetBackgroundThemedColor() : GetBackgroundDefaultColor();
257 }
258 
DecoratePillButton(views::LabelButton * button,const gfx::VectorIcon * icon)259 void AshColorProvider::DecoratePillButton(views::LabelButton* button,
260                                           const gfx::VectorIcon* icon) {
261   if (icon) {
262     SkColor enabled_icon_color =
263         GetContentLayerColor(ContentLayerType::kButtonIconColor);
264     button->SetImage(views::Button::STATE_NORMAL,
265                      gfx::CreateVectorIcon(*icon, enabled_icon_color));
266     button->SetImage(
267         views::Button::STATE_DISABLED,
268         gfx::CreateVectorIcon(*icon, GetDisabledColor(enabled_icon_color)));
269     button->SetImageLabelSpacing(kPillButtonImageLabelSpacingDp);
270   }
271 
272   SkColor enabled_text_color =
273       GetContentLayerColor(ContentLayerType::kButtonLabelColor);
274   button->SetEnabledTextColors(enabled_text_color);
275   button->SetTextColor(views::Button::STATE_DISABLED,
276                        GetDisabledColor(enabled_text_color));
277 
278   // TODO(sammiequon): Add a default rounded rect background. It should probably
279   // be optional as some buttons still require customization. At that point we
280   // should package the parameters of this function into a struct.
281 }
282 
DecorateCloseButton(views::ImageButton * button,int button_size,const gfx::VectorIcon & icon)283 void AshColorProvider::DecorateCloseButton(views::ImageButton* button,
284                                            int button_size,
285                                            const gfx::VectorIcon& icon) {
286   DCHECK(!icon.is_empty());
287   SkColor enabled_icon_color =
288       GetContentLayerColor(ContentLayerType::kButtonIconColor);
289   button->SetImage(views::Button::STATE_NORMAL,
290                    gfx::CreateVectorIcon(icon, enabled_icon_color));
291 
292   // Add a rounded rect background. The rounding will be half the button size so
293   // it is a circle.
294   SkColor icon_background_color = AshColorProvider::Get()->GetBaseLayerColor(
295       AshColorProvider::BaseLayerType::kTransparent80);
296   button->SetBackground(views::CreateRoundedRectBackground(
297       icon_background_color, button_size / 2));
298 
299   // TODO(sammiequon): Add background blur as per spec. Background blur is quite
300   // heavy, and we may have many close buttons showing at a time. They'll be
301   // added separately so its easier to monitor performance.
302 }
303 
DecorateFloatingIconButton(views::ImageButton * button,const gfx::VectorIcon & icon)304 void AshColorProvider::DecorateFloatingIconButton(views::ImageButton* button,
305                                                   const gfx::VectorIcon& icon) {
306   DecorateIconButton(button, icon, /*toggled=*/false,
307                      GetDefaultSizeOfVectorIcon(icon));
308 }
309 
DecorateIconButton(views::ImageButton * button,const gfx::VectorIcon & icon,bool toggled,int icon_size)310 void AshColorProvider::DecorateIconButton(views::ImageButton* button,
311                                           const gfx::VectorIcon& icon,
312                                           bool toggled,
313                                           int icon_size) {
314   DCHECK(!icon.is_empty());
315   const SkColor normal_color =
316       GetContentLayerColor(ContentLayerType::kButtonIconColor);
317   const SkColor toggled_icon_color =
318       GetContentLayerColor(ContentLayerType::kButtonIconColorPrimary);
319   const SkColor icon_color = toggled ? toggled_icon_color : normal_color;
320 
321   // Skip repainting if the incoming icon is the same as the current icon. If
322   // the icon has been painted before, |gfx::CreateVectorIcon()| will simply
323   // grab the ImageSkia from a cache, so it will be cheap. Note that this
324   // assumes that toggled/disabled images changes at the same time as the normal
325   // image, which it currently does.
326   const gfx::ImageSkia new_normal_image =
327       gfx::CreateVectorIcon(icon, icon_size, icon_color);
328   const gfx::ImageSkia& old_normal_image =
329       button->GetImage(views::Button::STATE_NORMAL);
330   if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
331       new_normal_image.BackedBySameObjectAs(old_normal_image)) {
332     return;
333   }
334 
335   button->SetImage(views::Button::STATE_NORMAL, new_normal_image);
336   button->SetImage(
337       views::Button::STATE_DISABLED,
338       gfx::CreateVectorIcon(icon, icon_size, GetDisabledColor(icon_color)));
339 }
340 
AddObserver(ColorModeObserver * observer)341 void AshColorProvider::AddObserver(ColorModeObserver* observer) {
342   observers_.AddObserver(observer);
343 }
344 
RemoveObserver(ColorModeObserver * observer)345 void AshColorProvider::RemoveObserver(ColorModeObserver* observer) {
346   observers_.RemoveObserver(observer);
347 }
348 
IsDarkModeEnabled() const349 bool AshColorProvider::IsDarkModeEnabled() const {
350   if (!features::IsDarkLightModeEnabled() && override_light_mode_as_default_)
351     return false;
352 
353   if (!active_user_pref_service_ || !features::IsDarkLightModeEnabled())
354     return kDefaultDarkModeEnabled;
355   return active_user_pref_service_->GetBoolean(prefs::kDarkModeEnabled);
356 }
357 
IsThemed() const358 bool AshColorProvider::IsThemed() const {
359   if (!active_user_pref_service_)
360     return kDefaultColorModeThemed;
361   return active_user_pref_service_->GetBoolean(prefs::kColorModeThemed);
362 }
363 
ToggleColorMode()364 void AshColorProvider::ToggleColorMode() {
365   DCHECK(active_user_pref_service_);
366   active_user_pref_service_->SetBoolean(prefs::kDarkModeEnabled,
367                                         !IsDarkModeEnabled());
368   active_user_pref_service_->CommitPendingWrite();
369 
370   ui::NativeTheme::GetInstanceForNativeUi()->set_use_dark_colors(
371       IsDarkModeEnabled());
372 }
373 
UpdateColorModeThemed(bool is_themed)374 void AshColorProvider::UpdateColorModeThemed(bool is_themed) {
375   if (is_themed == IsThemed())
376     return;
377 
378   DCHECK(active_user_pref_service_);
379   active_user_pref_service_->SetBoolean(prefs::kColorModeThemed, is_themed);
380   active_user_pref_service_->CommitPendingWrite();
381 }
382 
GetBackgroundDefaultColor() const383 SkColor AshColorProvider::GetBackgroundDefaultColor() const {
384   return IsDarkModeEnabled() ? kBackgroundColorDefaultDark
385                              : kBackgroundColorDefaultLight;
386 }
387 
GetBackgroundThemedColor() const388 SkColor AshColorProvider::GetBackgroundThemedColor() const {
389   const SkColor default_color = GetBackgroundDefaultColor();
390   WallpaperControllerImpl* wallpaper_controller =
391       Shell::Get()->wallpaper_controller();
392   if (!wallpaper_controller)
393     return default_color;
394 
395   const bool is_dark_mode = IsDarkModeEnabled();
396   color_utils::LumaRange luma_range = is_dark_mode
397                                           ? color_utils::LumaRange::DARK
398                                           : color_utils::LumaRange::LIGHT;
399   SkColor muted_color =
400       wallpaper_controller->GetProminentColor(color_utils::ColorProfile(
401           luma_range, color_utils::SaturationRange::MUTED));
402   if (muted_color == kInvalidWallpaperColor)
403     return default_color;
404 
405   return color_utils::GetResultingPaintColor(
406       SkColorSetA(is_dark_mode ? SK_ColorBLACK : SK_ColorWHITE,
407                   is_dark_mode ? kDarkBackgroundBlendAlpha
408                                : kLightBackgroundBlendAlpha),
409       muted_color);
410 }
411 
NotifyDarkModeEnabledPrefChange()412 void AshColorProvider::NotifyDarkModeEnabledPrefChange() {
413   const bool is_enabled = IsDarkModeEnabled();
414   for (auto& observer : observers_)
415     observer.OnColorModeChanged(is_enabled);
416 
417   NotifyThemeChanges();
418 }
419 
NotifyColorModeThemedPrefChange()420 void AshColorProvider::NotifyColorModeThemedPrefChange() {
421   const bool is_themed = IsThemed();
422   for (auto& observer : observers_)
423     observer.OnColorModeThemed(is_themed);
424 
425   NotifyThemeChanges();
426 }
427 
428 }  // namespace ash
429