Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(324)

Side by Side Diff: ui/message_center/views/notification_view_md.cc

Issue 2892893002: Initial implementation of new-style notification (Closed)
Patch Set: Fixed build failure Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 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 "ui/message_center/views/notification_view_md.h"
6
7 #include <stddef.h>
8
9 #include "base/strings/string_util.h"
10 #include "ui/base/cursor/cursor.h"
11 #include "ui/base/l10n/l10n_util.h"
12 #include "ui/gfx/geometry/size.h"
13 #include "ui/gfx/image/image_skia_operations.h"
14 #include "ui/gfx/paint_vector_icon.h"
15 #include "ui/gfx/skia_util.h"
16 #include "ui/gfx/text_elider.h"
17 #include "ui/message_center/message_center.h"
18 #include "ui/message_center/message_center_style.h"
19 #include "ui/message_center/notification.h"
20 #include "ui/message_center/notification_types.h"
21 #include "ui/message_center/vector_icons.h"
22 #include "ui/message_center/views/bounded_label.h"
23 #include "ui/message_center/views/constants.h"
24 #include "ui/message_center/views/message_center_controller.h"
25 #include "ui/message_center/views/notification_button.h"
26 #include "ui/message_center/views/padded_button.h"
27 #include "ui/strings/grit/ui_strings.h"
28 #include "ui/views/background.h"
29 #include "ui/views/border.h"
30 #include "ui/views/controls/image_view.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/focus/focus_manager.h"
33 #include "ui/views/layout/box_layout.h"
34 #include "ui/views/native_cursor.h"
35 #include "ui/views/view_targeter.h"
36
37 namespace message_center {
38
39 namespace {
40
41 // Dimensions.
42 constexpr int kNotificationRightPadding = 13;
43 constexpr int kNotificationLeftPadding = 13;
44 constexpr int kNotificationTopPadding = 6;
45 constexpr int kNotificationBottomPadding = 10;
46 constexpr gfx::Insets kNotificationPadding(kNotificationTopPadding,
47 kNotificationLeftPadding,
48 kNotificationBottomPadding,
49 kNotificationRightPadding);
50
51 constexpr int kMaxContextTitleLines = 1;
52
53 // Foreground of small icon image.
54 const SkColor kSmallImageBackgroundColor = SK_ColorWHITE;
fukino 2017/05/25 07:35:10 nit: constexpr for SkColor.
yoshiki 2017/05/29 03:52:38 Done.
55 // Background of small icon image.
56 const SkColor kSmallImageColor = SkColorSetRGB(0x43, 0x43, 0x43);
fukino 2017/05/25 07:35:10 ditto
yoshiki 2017/05/29 03:52:39 We can't make it constexpr since SkColorSetRGB is
fukino 2017/05/30 07:32:33 I see. Acknowledged.
57
58 const gfx::ImageSkia CreateSolidColorImage(int width,
59 int height,
60 SkColor color) {
61 SkBitmap bitmap;
62 bitmap.allocN32Pixels(width, height);
63 bitmap.eraseColor(color);
64 return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
65 }
66
67 // Take the alpha channel of icon, mask it with the foreground,
68 // then add the masked foreground on top of the background
69 const gfx::ImageSkia GetMaskedIcon(const gfx::ImageSkia& icon) {
70 int width = icon.width();
71 int height = icon.height();
72
73 // Background color grey
74 const gfx::ImageSkia background = CreateSolidColorImage(
75 width, height, message_center::kSmallImageBackgroundColor);
76 // Foreground color white
77 const gfx::ImageSkia foreground =
78 CreateSolidColorImage(width, height, message_center::kSmallImageColor);
79 const gfx::ImageSkia masked_small_image =
80 gfx::ImageSkiaOperations::CreateMaskedImage(foreground, icon);
81 return gfx::ImageSkiaOperations::CreateSuperimposedImage(background,
82 masked_small_image);
83 }
84
85 const gfx::ImageSkia GetProductIcon() {
86 return gfx::CreateVectorIcon(kProductIcon, kSmallImageColor);
87 }
88
89 } // anonymous namespace
90
91 // ////////////////////////////////////////////////////////////
92 // NotificationViewMD
93 // ////////////////////////////////////////////////////////////
94
95 views::View* NotificationViewMD::TargetForRect(views::View* root,
96 const gfx::Rect& rect) {
97 CHECK_EQ(root, this);
98
99 // TODO(tdanderson): Modify this function to support rect-based event
100 // targeting. Using the center point of |rect| preserves this function's
101 // expected behavior for the time being.
102 gfx::Point point = rect.CenterPoint();
103
104 // Want to return this for underlying views, otherwise GetCursor is not
105 // called. But buttons are exceptions, they'll have their own event handlings.
106 std::vector<views::View*> buttons(action_buttons_.begin(),
107 action_buttons_.end());
108 if (settings_button_)
109 buttons.push_back(settings_button_.get());
110 if (close_button_)
111 buttons.push_back(close_button_.get());
112
113 for (size_t i = 0; i < buttons.size(); ++i) {
114 gfx::Point point_in_child = point;
115 ConvertPointToTarget(this, buttons[i], &point_in_child);
116 if (buttons[i]->HitTestPoint(point_in_child))
117 return buttons[i]->GetEventHandlerForPoint(point_in_child);
118 }
119
120 return root;
121 }
122
123 void NotificationViewMD::CreateOrUpdateViews(const Notification& notification) {
124 CreateOrUpdateContextTitleView(notification);
125 CreateOrUpdateTitleView(notification);
126 CreateOrUpdateMessageView(notification);
127 CreateOrUpdateProgressBarView(notification);
128 CreateOrUpdateListItemViews(notification);
129 CreateOrUpdateIconView(notification);
130 CreateOrUpdateSmallIconView(notification);
131 CreateOrUpdateImageView(notification);
132 CreateOrUpdateActionButtonViews(notification);
133 }
134
135 NotificationViewMD::NotificationViewMD(MessageCenterController* controller,
136 const Notification& notification)
137 : MessageView(controller, notification),
138 clickable_(notification.clickable()) {
139 layout_ = new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 2);
140 layout_->set_inside_border_insets(kNotificationPadding);
141 SetLayoutManager(layout_);
142
143 // Create the top_view_, which collects into a vertical box all content
144 // at the top of the notification (to the right of the icon) except for the
145 // close button.
146 top_view_ = new views::View();
147 views::BoxLayout* top_box_layout =
148 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 1, 5);
149 top_box_layout->set_cross_axis_alignment(
150 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
151 top_view_->SetLayoutManager(top_box_layout);
152 AddChildView(top_view_);
153
154 main_view_ = new views::View();
155 views::BoxLayout* main_box_layout =
156 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
157 main_view_->SetLayoutManager(main_box_layout);
fukino 2017/05/25 07:35:10 nit: Instead of having |main_box_layout|, calling
yoshiki 2017/05/29 03:52:38 Done.
158 AddChildView(main_view_);
159
160 // Create the bottom_view_, which collects notification icon.
161 bottom_view_ = new views::View();
162 bottom_view_->SetLayoutManager(
163 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
164 AddChildView(bottom_view_);
165
166 views::ImageView* small_image_view = new views::ImageView();
167 small_image_view->SetImageSize(gfx::Size(kSmallImageSize, kSmallImageSize));
168 small_image_view->set_owned_by_client();
169 small_image_view_.reset(small_image_view);
170 top_view_->AddChildView(small_image_view_.get());
171
172 CreateOrUpdateViews(notification);
173 CreateOrUpdateCloseButtonView(notification);
fukino 2017/05/25 07:35:10 Shouldn't we do CreateOrUpdateCloseButtonView() an
yoshiki 2017/05/29 03:52:39 Done.
174 CreateOrUpdateSettingsButtonView(notification);
175
176 SetEventTargeter(
177 std::unique_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
178 }
179
180 NotificationViewMD::~NotificationViewMD() {}
181
182 void NotificationViewMD::Layout() {
183 // View::Layout() is not called in MessageView::Layout() for some reason.
fukino 2017/05/25 07:35:11 I'm a bit concerned about calling both views::View
yoshiki 2017/05/29 03:52:38 That's ideal but breaks NotificationView actually.
fukino 2017/05/30 07:32:32 Oh, it sounds scary that calling View::Layout() in
yoshiki 2017/05/31 03:53:01 As I checked, all wrong values which were set by V
184 views::View::Layout();
185
186 MessageView::Layout();
187
188 // Before any resizing, set or adjust the number of message lines.
189 int title_lines = 0;
190 if (title_view_) {
191 title_lines = title_view_->GetLinesForWidthAndLimit(title_view_->width(),
192 kMaxTitleLines);
193 }
194 if (message_view_) {
195 message_view_->SetLineLimit(
196 std::max(0, message_center::kMessageExpandedLineLimit - title_lines));
197 }
198
199 // Settings & Bottom views.
200 if (settings_button_) {
201 gfx::Rect content_bounds = GetContentsBounds();
202 const gfx::Size settings_size(settings_button_->GetPreferredSize());
203 int marginFromRight = settings_size.width() + kControlButtonPadding;
204 if (close_button_)
205 marginFromRight += close_button_->GetPreferredSize().width();
206 gfx::Rect settings_rect(content_bounds.right() - marginFromRight,
207 GetContentsBounds().y() + kControlButtonPadding,
208 settings_size.width(), settings_size.height());
209 settings_button_->SetBoundsRect(settings_rect);
210 }
211
212 // Close button.
213 if (close_button_) {
214 gfx::Rect content_bounds = GetContentsBounds();
215 gfx::Size close_size(close_button_->GetPreferredSize());
216 gfx::Rect close_rect(
217 content_bounds.right() - close_size.width() - kControlButtonPadding,
218 content_bounds.y() + kControlButtonPadding, close_size.width(),
219 close_size.height());
220 close_button_->SetBoundsRect(close_rect);
221 }
222 }
223
224 void NotificationViewMD::OnFocus() {
225 MessageView::OnFocus();
226 ScrollRectToVisible(GetLocalBounds());
227 }
228
229 void NotificationViewMD::ScrollRectToVisible(const gfx::Rect& rect) {
230 // Notification want to show the whole notification when a part of it (like
231 // a button) gets focused.
232 views::View::ScrollRectToVisible(GetLocalBounds());
233 }
234
235 gfx::NativeCursor NotificationViewMD::GetCursor(const ui::MouseEvent& event) {
236 if (!clickable_ || !controller()->HasClickedListener(notification_id()))
237 return views::View::GetCursor(event);
238
239 return views::GetNativeHandCursor();
240 }
241
242 void NotificationViewMD::OnMouseEntered(const ui::MouseEvent& event) {
243 MessageView::OnMouseEntered(event);
244 UpdateControlButtonsVisibility();
245 }
246
247 void NotificationViewMD::OnMouseExited(const ui::MouseEvent& event) {
248 MessageView::OnMouseExited(event);
249 UpdateControlButtonsVisibility();
250 }
251
252 void NotificationViewMD::UpdateWithNotification(
253 const Notification& notification) {
254 MessageView::UpdateWithNotification(notification);
255
256 CreateOrUpdateViews(notification);
257 CreateOrUpdateCloseButtonView(notification);
258 CreateOrUpdateSettingsButtonView(notification);
259 Layout();
260 SchedulePaint();
261 }
262
263 void NotificationViewMD::ButtonPressed(views::Button* sender,
264 const ui::Event& event) {
265 // Certain operations can cause |this| to be destructed, so copy the members
266 // we send to other parts of the code.
267 // TODO(dewittj): Remove this hack.
268 std::string id(notification_id());
269
270 if (close_button_ && sender == close_button_.get()) {
271 // Warning: This causes the NotificationViewMD itself to be deleted, so
272 // don't do anything afterwards.
273 OnCloseButtonPressed();
274 return;
275 }
276
277 if (sender == settings_button_.get()) {
278 controller()->ClickOnSettingsButton(id);
279 return;
280 }
281
282 // See if the button pressed was an action button.
283 for (size_t i = 0; i < action_buttons_.size(); ++i) {
284 if (sender == action_buttons_[i]) {
285 controller()->ClickOnNotificationButton(id, i);
286 return;
287 }
288 }
289 }
290
291 bool NotificationViewMD::IsCloseButtonFocused() const {
292 if (!close_button_)
293 return false;
294
295 const views::FocusManager* focus_manager = GetFocusManager();
296 return focus_manager &&
297 focus_manager->GetFocusedView() == close_button_.get();
298 }
299
300 void NotificationViewMD::RequestFocusOnCloseButton() {
301 if (close_button_)
302 close_button_->RequestFocus();
303 }
304
305 void NotificationViewMD::CreateOrUpdateContextTitleView(
306 const Notification& notification) {
307 DCHECK(top_view_);
308
309 const gfx::FontList& font_list = views::Label().font_list().Derive(
310 -2, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL);
311
312 base::string16 sub_title = notification.display_source();
313 if (!context_title_view_) {
314 context_title_view_ = new BoundedLabel(sub_title, font_list);
315 context_title_view_->SetLineHeight(kTitleLineHeight);
316 context_title_view_->SetLineLimit(kMaxTitleLines);
fukino 2017/05/25 07:35:11 This line should be removed? (as kMaxContextTitleL
yoshiki 2017/05/29 03:52:38 Done.
317 context_title_view_->SetLineLimit(kMaxContextTitleLines);
318 top_view_->AddChildView(context_title_view_);
319 } else {
320 context_title_view_->SetText(sub_title);
321 }
322 }
323
324 void NotificationViewMD::CreateOrUpdateTitleView(
325 const Notification& notification) {
326 DCHECK(top_view_ != NULL);
327
328 const gfx::FontList& font_list = views::Label().font_list().Derive(
329 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL);
330
331 int title_character_limit =
332 kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter;
333
334 base::string16 title = gfx::TruncateString(
335 notification.title(), title_character_limit, gfx::WORD_BREAK);
336 if (!title_view_) {
337 title_view_ = new BoundedLabel(title, font_list);
338 title_view_->SetLineHeight(kMessageLineHeight);
339 title_view_->SetColors(message_center::kRegularTextColor,
340 kDimTextBackgroundColor);
341 main_view_->AddChildView(title_view_);
342 } else {
343 title_view_->SetText(title);
344 }
345 }
346
347 void NotificationViewMD::CreateOrUpdateMessageView(
348 const Notification& notification) {
349 if (notification.message().empty()) {
350 // Deletion will also remove |context_message_view_| from its parent.
351 delete message_view_;
352 message_view_ = nullptr;
353 return;
354 }
355
356 DCHECK(top_view_ != NULL);
357
358 base::string16 text = gfx::TruncateString(
359 notification.message(), kMessageCharacterLimit, gfx::WORD_BREAK);
360
361 const gfx::FontList& font_list = views::Label().font_list().Derive(
362 1, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL);
363
364 if (!message_view_) {
365 message_view_ = new BoundedLabel(text, font_list);
366 message_view_->SetLineLimit(message_center::kMessageExpandedLineLimit);
367 message_view_->SetColors(message_center::kDimTextColor,
368 kContextTextBackgroundColor);
369 main_view_->AddChildView(message_view_);
370 } else {
371 message_view_->SetText(text);
372 }
373 }
374
375 void NotificationViewMD::CreateOrUpdateProgressBarView(
376 const Notification& notification) {
377 // TODO(yoshiki): Implement this.
378 }
379
380 void NotificationViewMD::CreateOrUpdateListItemViews(
381 const Notification& notification) {
382 // TODO(yoshiki): Implement this.
383 }
384
385 void NotificationViewMD::CreateOrUpdateIconView(
386 const Notification& notification) {
387 // TODO(yoshiki): Implement this.
388 }
389
390 void NotificationViewMD::CreateOrUpdateSmallIconView(
391 const Notification& notification) {
392 gfx::ImageSkia icon =
393 notification.small_image().IsEmpty()
394 ? GetProductIcon()
395 : GetMaskedIcon(notification.small_image().AsImageSkia());
396
397 small_image_view_->SetImage(icon);
398 }
399
400 void NotificationViewMD::CreateOrUpdateImageView(
401 const Notification& notification) {
402 // TODO(yoshiki): Implement this.
403 }
404
405 void NotificationViewMD::CreateOrUpdateActionButtonViews(
406 const Notification& notification) {
407 // TODO(yoshiki): Implement this.
408 }
409
410 void NotificationViewMD::CreateOrUpdateCloseButtonView(
411 const Notification& notification) {
412 if (!notification.pinned() && !close_button_) {
413 close_button_ = base::MakeUnique<PaddedButton>(this);
414 close_button_->SetImage(views::Button::STATE_NORMAL, GetCloseIcon());
415 close_button_->SetAccessibleName(l10n_util::GetStringUTF16(
416 IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME));
417 close_button_->SetTooltipText(l10n_util::GetStringUTF16(
418 IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_TOOLTIP));
419 close_button_->set_owned_by_client();
420 AddChildView(close_button_.get());
421 UpdateControlButtonsVisibility();
422 } else if (notification.pinned() && close_button_) {
423 close_button_.reset();
424 }
425 }
426
427 void NotificationViewMD::CreateOrUpdateSettingsButtonView(
428 const Notification& notification) {
429 if (!settings_button_ && notification.delegate() &&
430 notification.delegate()->ShouldDisplaySettingsButton()) {
431 settings_button_ = base::MakeUnique<PaddedButton>(this);
432 settings_button_->SetImage(views::Button::STATE_NORMAL, GetSettingsIcon());
433 settings_button_->SetAccessibleName(l10n_util::GetStringUTF16(
434 IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME));
435 settings_button_->SetTooltipText(l10n_util::GetStringUTF16(
436 IDS_MESSAGE_NOTIFICATION_SETTINGS_BUTTON_ACCESSIBLE_NAME));
437 settings_button_->set_owned_by_client();
438 AddChildView(settings_button_.get());
439 } else {
440 settings_button_.reset();
441 }
442 UpdateControlButtonsVisibility();
443 }
444
445 void NotificationViewMD::UpdateControlButtonsVisibility() {
446 const bool target_visibility =
447 IsMouseHovered() || HasFocus() ||
448 (close_button_ && close_button_->HasFocus()) ||
449 (settings_button_ && settings_button_->HasFocus());
450
451 if (close_button_) {
452 if (target_visibility != close_button_->visible())
453 close_button_->SetVisible(target_visibility);
454 }
455
456 if (settings_button_) {
457 if (target_visibility != settings_button_->visible())
458 settings_button_->SetVisible(target_visibility);
459 }
460 }
461
462 } // namespace message_center
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698