OLD | NEW |
---|---|
(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 | |
OLD | NEW |