1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
ash / system / notification_center / notification_style_utils.cc [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/notification_center/notification_style_utils.h"
#include <memory>
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/ash_color_provider.h"
#include "ash/style/dark_light_mode_controller_impl.h"
#include "ash/style/pill_button.h"
#include "ash/system/notification_center/message_center_constants.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_manager.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/vector_icons.h"
#include "ui/message_center/views/message_view.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/highlight_border.h"
#include "ui/views/layout/flex_layout_view.h"
namespace ash::notification_style_utils {
gfx::ImageSkia CreateNotificationAppIcon(
const message_center::Notification* notification) {
SkColor icon_color = GetColorProviderForNativeTheme()->GetColor(
cros_tokens::kCrosSysOnPrimary);
SkColor icon_background_color = CalculateIconBackgroundColor(notification);
// TODO(crbug.com/40541732): figure out if this has a performance impact and
// cache images if so.
gfx::Image masked_small_icon = notification->GenerateMaskedSmallIcon(
kNotificationAppIconImageSize, icon_color, icon_background_color,
icon_color);
gfx::ImageSkia app_icon =
masked_small_icon.IsEmpty()
? gfx::CreateVectorIcon(message_center::kProductIcon,
kNotificationAppIconImageSize, icon_color)
: masked_small_icon.AsImageSkia();
return gfx::ImageSkiaOperations::CreateImageWithCircleBackground(
kNotificationAppIconViewSize / 2, icon_background_color, app_icon);
}
gfx::ImageSkia CreateNotificationItemIcon(
const message_center::NotificationItem* item) {
if (item->icon().has_value()) {
gfx::ImageSkia resized = gfx::ImageSkiaOperations::CreateResizedImage(
item->icon().value().GetImage().AsImageSkia(),
skia::ImageOperations::ResizeMethod::RESIZE_BEST,
gfx::Size(kNotificationAppIconViewSize, kNotificationAppIconViewSize));
return resized;
}
// TODO(b/284512022): Remove the temporary implementation returning a
// hardcoded chrome icon as a default icon.
return gfx::ImageSkiaOperations::CreateImageWithCircleBackground(
kNotificationAppIconViewSize / 2, SK_ColorRED,
gfx::CreateVectorIcon(message_center::kProductIcon,
kNotificationAppIconImageSize, SK_ColorBLACK));
}
SkColor CalculateIconBackgroundColor(
const message_center::Notification* notification) {
SkColor default_color = AshColorProvider::Get()->GetControlsLayerColor(
AshColorProvider::ControlsLayerType::kControlBackgroundColorActive);
if (!notification) {
return default_color;
}
auto color_id = notification->accent_color_id();
std::optional<SkColor> accent_color = notification->accent_color();
if (!color_id || !accent_color.has_value()) {
return default_color;
}
SkColor fg_color;
// ColorProvider needs widget to be created.
if (color_id) {
fg_color = GetColorProviderForNativeTheme()->GetColor(color_id.value());
} else {
fg_color = accent_color.value();
}
// TODO(crbug/1351205): move color calculation logic to color mixer.
// TODO(crbug/1294459): re-evaluate contrast, maybe increase or use fixed HSL
float minContrastRatio =
DarkLightModeControllerImpl::Get()->IsDarkModeEnabled()
? minContrastRatio = kDarkModeMinContrastRatio
: color_utils::kMinimumReadableContrastRatio;
// Actual color is kTransparent80, but BlendForMinContrast requires opaque.
// GetColorProvider might be nullptr in tests.
const auto* color_provider = GetColorProviderForNativeTheme();
const SkColor bg_color =
color_provider ? color_provider->GetColor(kColorAshShieldAndBaseOpaque)
: gfx::kPlaceholderColor;
return color_utils::BlendForMinContrast(
fg_color, bg_color,
/*high_contrast_foreground=*/std::nullopt, minContrastRatio)
.color;
}
void ConfigureLabelStyle(views::Label* label,
int size,
bool is_color_primary,
gfx::Font::Weight font_weight) {
label->SetAutoColorReadabilityEnabled(false);
label->SetFontList(
gfx::FontList({kGoogleSansFont}, gfx::Font::NORMAL, size, font_weight));
auto layer_type =
is_color_primary
? ash::AshColorProvider::ContentLayerType::kTextColorPrimary
: ash::AshColorProvider::ContentLayerType::kTextColorSecondary;
label->SetEnabledColor(
ash::AshColorProvider::Get()->GetContentLayerColor(layer_type));
}
ui::ColorProvider* GetColorProviderForNativeTheme() {
auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
return ui::ColorProviderManager::Get().GetColorProviderFor(
native_theme->GetColorProviderKey(nullptr));
}
std::unique_ptr<views::Background> CreateNotificationBackground(
int top_radius,
int bottom_radius,
bool is_popup_notification,
bool is_grouped_child_notification) {
ui::ColorId background_color_id =
is_popup_notification ? cros_tokens::kCrosSysSystemBaseElevatedOpaque
: cros_tokens::kCrosSysSystemOnBaseOpaque;
if (chromeos::features::IsSystemBlurEnabled()) {
background_color_id =
is_popup_notification
? static_cast<ui::ColorId>(kColorAshShieldAndBase80)
: cros_tokens::kCrosSysSystemOnBase;
}
const gfx::RoundedCornersF background_radii(top_radius, top_radius,
bottom_radius, bottom_radius);
if (is_grouped_child_notification) {
// Grouped children are always transparent. Handle them separately.
return views::CreateRoundedRectBackground(SK_ColorTRANSPARENT,
background_radii);
}
return views::CreateThemedRoundedRectBackground(background_color_id,
background_radii);
}
void StyleNotificationPopup(message_center::MessageView* notification_view) {
notification_view->SetPaintToLayer();
auto* layer = notification_view->layer();
if (chromeos::features::IsSystemBlurEnabled()) {
layer->SetFillsBoundsOpaquely(false);
layer->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
layer->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
}
layer->SetRoundedCornerRadius(
gfx::RoundedCornersF{kMessagePopupCornerRadius});
layer->SetIsFastRoundedCorner(true);
notification_view->SetBackground(CreateNotificationBackground(
kMessagePopupCornerRadius, kMessagePopupCornerRadius,
/*is_popup_notification=*/true, /*is_grouped_child_notification=*/false));
notification_view->SetBorder(std::make_unique<views::HighlightBorder>(
kMessagePopupCornerRadius,
views::HighlightBorder::Type::kHighlightBorderOnShadow));
notification_view->UpdateCornerRadius(kMessagePopupCornerRadius,
kMessagePopupCornerRadius);
}
std::unique_ptr<views::LabelButton> GenerateNotificationLabelButton(
views::Button::PressedCallback callback,
const std::u16string& label) {
std::unique_ptr<PillButton> actions_button = std::make_unique<PillButton>(
std::move(callback), label, PillButton::Type::kFloatingWithoutIcon,
/*icon=*/nullptr, kNotificationPillButtonHorizontalSpacing);
actions_button->SetButtonTextColorId(cros_tokens::kCrosSysOnSurface);
return actions_button;
}
std::unique_ptr<views::FlexLayoutView> CreateInlineSettingsViewForMessageView(
message_center::MessageView* message_view) {
auto inline_settings_view = std::make_unique<views::FlexLayoutView>();
inline_settings_view->SetOrientation(views::LayoutOrientation::kHorizontal);
// base::Unretained(message_view) is safe here because `inline_settings_view`
// and it's children must be owned by the provided `message_view`
auto turn_off_notifications_button = GenerateNotificationLabelButton(
base::BindRepeating(&message_center::MessageView::DisableNotification,
base::Unretained(message_view)),
l10n_util::GetStringUTF16(
IDS_ASH_NOTIFICATION_INLINE_SETTINGS_TURN_OFF_BUTTON_TEXT));
turn_off_notifications_button->SetID(kNotificationTurnOffNotificationsButton);
inline_settings_view->AddChildView(std::move(turn_off_notifications_button));
auto cancel_button =
notification_style_utils::GenerateNotificationLabelButton(
base::BindRepeating(
&message_center::MessageView::ToggleInlineSettings,
base::Unretained(message_view)),
l10n_util::GetStringUTF16(
IDS_ASH_NOTIFICATION_INLINE_SETTINGS_CANCEL_BUTTON_TEXT));
cancel_button->SetID(kNotificationInlineSettingsCancelButton);
inline_settings_view->AddChildView(std::move(cancel_button));
return inline_settings_view;
}
} // namespace ash::notification_style_utils