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