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

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

Issue 961993007: Pull MessageListView into its own file for easier testing. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix build Created 5 years, 7 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
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/message_center/views/message_center_view.h" 5 #include "ui/message_center/views/message_center_view.h"
6 6
7 #include <list> 7 #include <list>
8 #include <map> 8 #include <map>
9 9
10 #include "base/command_line.h"
11 #include "base/memory/weak_ptr.h" 10 #include "base/memory/weak_ptr.h"
12 #include "base/message_loop/message_loop.h" 11 #include "base/message_loop/message_loop.h"
13 #include "base/stl_util.h" 12 #include "base/stl_util.h"
14 #include "ui/base/l10n/l10n_util.h" 13 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/gfx/animation/multi_animation.h" 14 #include "ui/gfx/animation/multi_animation.h"
16 #include "ui/gfx/animation/slide_animation.h"
17 #include "ui/gfx/canvas.h" 15 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/geometry/insets.h" 16 #include "ui/gfx/geometry/insets.h"
19 #include "ui/gfx/geometry/rect.h" 17 #include "ui/gfx/geometry/rect.h"
20 #include "ui/gfx/geometry/size.h" 18 #include "ui/gfx/geometry/size.h"
21 #include "ui/message_center/message_center.h" 19 #include "ui/message_center/message_center.h"
22 #include "ui/message_center/message_center_style.h" 20 #include "ui/message_center/message_center_style.h"
23 #include "ui/message_center/message_center_switches.h"
24 #include "ui/message_center/message_center_tray.h" 21 #include "ui/message_center/message_center_tray.h"
25 #include "ui/message_center/message_center_types.h" 22 #include "ui/message_center/message_center_types.h"
26 #include "ui/message_center/views/message_center_button_bar.h" 23 #include "ui/message_center/views/message_center_button_bar.h"
24 #include "ui/message_center/views/message_list_view.h"
27 #include "ui/message_center/views/message_view.h" 25 #include "ui/message_center/views/message_view.h"
28 #include "ui/message_center/views/message_view_context_menu_controller.h" 26 #include "ui/message_center/views/message_view_context_menu_controller.h"
29 #include "ui/message_center/views/notification_view.h" 27 #include "ui/message_center/views/notification_view.h"
30 #include "ui/message_center/views/notifier_settings_view.h" 28 #include "ui/message_center/views/notifier_settings_view.h"
31 #include "ui/resources/grit/ui_resources.h" 29 #include "ui/resources/grit/ui_resources.h"
32 #include "ui/strings/grit/ui_strings.h" 30 #include "ui/strings/grit/ui_strings.h"
33 #include "ui/views/animation/bounds_animator.h"
34 #include "ui/views/animation/bounds_animator_observer.h"
35 #include "ui/views/background.h" 31 #include "ui/views/background.h"
36 #include "ui/views/border.h" 32 #include "ui/views/border.h"
37 #include "ui/views/controls/button/button.h" 33 #include "ui/views/controls/button/button.h"
38 #include "ui/views/controls/label.h" 34 #include "ui/views/controls/label.h"
39 #include "ui/views/controls/scroll_view.h" 35 #include "ui/views/controls/scroll_view.h"
40 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h" 36 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
41 #include "ui/views/layout/box_layout.h"
42 #include "ui/views/layout/fill_layout.h" 37 #include "ui/views/layout/fill_layout.h"
43 #include "ui/views/widget/widget.h" 38 #include "ui/views/widget/widget.h"
44 39
45 namespace message_center { 40 namespace message_center {
46 41
47 namespace { 42 namespace {
48 43
49 const SkColor kNoNotificationsTextColor = SkColorSetRGB(0xb4, 0xb4, 0xb4); 44 const SkColor kNoNotificationsTextColor = SkColorSetRGB(0xb4, 0xb4, 0xb4);
50 #if defined(OS_LINUX) && defined(OS_CHROMEOS) 45 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
51 const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0); 46 const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0);
52 #endif 47 #endif
53 const int kAnimateClearingNextNotificationDelayMS = 40;
54 48
55 const int kDefaultAnimationDurationMs = 120; 49 const int kDefaultAnimationDurationMs = 120;
56 const int kDefaultFrameRateHz = 60; 50 const int kDefaultFrameRateHz = 60;
57 51
58 void SetViewHierarchyEnabled(views::View* view, bool enabled) { 52 void SetViewHierarchyEnabled(views::View* view, bool enabled) {
59 for (int i = 0; i < view->child_count(); i++) 53 for (int i = 0; i < view->child_count(); i++)
60 SetViewHierarchyEnabled(view->child_at(i), enabled); 54 SetViewHierarchyEnabled(view->child_at(i), enabled);
61 view->SetEnabled(enabled); 55 view->SetEnabled(enabled);
62 } 56 }
63 57
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
102 int NoNotificationMessageView::GetHeightForWidth(int width) const { 96 int NoNotificationMessageView::GetHeightForWidth(int width) const {
103 return kMinScrollViewHeight; 97 return kMinScrollViewHeight;
104 } 98 }
105 99
106 void NoNotificationMessageView::Layout() { 100 void NoNotificationMessageView::Layout() {
107 int text_height = label_->GetHeightForWidth(width()); 101 int text_height = label_->GetHeightForWidth(width());
108 int margin = (height() - text_height) / 2; 102 int margin = (height() - text_height) / 2;
109 label_->SetBounds(0, margin, width(), text_height); 103 label_->SetBounds(0, margin, width(), text_height);
110 } 104 }
111 105
112 // Displays a list of messages for rich notifications. Functions as an array of
113 // MessageViews and animates them on transitions. It also supports
114 // repositioning.
115 class MessageListView : public views::View,
116 public views::BoundsAnimatorObserver {
117 public:
118 explicit MessageListView(MessageCenterView* message_center_view,
119 bool top_down);
120 ~MessageListView() override;
121
122 void AddNotificationAt(MessageView* view, int i);
123 void RemoveNotification(MessageView* view);
124 void UpdateNotification(MessageView* view, const Notification& notification);
125 void SetRepositionTarget(const gfx::Rect& target_rect);
126 void ResetRepositionSession();
127 void ClearAllNotifications(const gfx::Rect& visible_scroll_rect);
128
129 protected:
130 // Overridden from views::View.
131 void Layout() override;
132 gfx::Size GetPreferredSize() const override;
133 int GetHeightForWidth(int width) const override;
134 void PaintChildren(const ui::PaintContext& context) override;
135 void ReorderChildLayers(ui::Layer* parent_layer) override;
136
137 // Overridden from views::BoundsAnimatorObserver.
138 void OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) override;
139 void OnBoundsAnimatorDone(views::BoundsAnimator* animator) override;
140
141 private:
142 bool IsValidChild(const views::View* child) const;
143 void DoUpdateIfPossible();
144
145 // Animates all notifications below target upwards to align with the top of
146 // the last closed notification.
147 void AnimateNotificationsBelowTarget();
148 // Animates all notifications above target downwards to align with the top of
149 // the last closed notification.
150 void AnimateNotificationsAboveTarget();
151
152 // Schedules animation for a child to the specified position. Returns false
153 // if |child| will disappear after the animation.
154 bool AnimateChild(views::View* child, int top, int height);
155
156 // Animate clearing one notification.
157 void AnimateClearingOneNotification();
158 MessageCenterView* message_center_view() const {
159 return message_center_view_;
160 }
161
162 MessageCenterView* message_center_view_; // Weak reference.
163 // The top position of the reposition target rectangle.
164 int reposition_top_;
165 int fixed_height_;
166 bool has_deferred_task_;
167 bool clear_all_started_;
168 bool top_down_;
169 std::set<views::View*> adding_views_;
170 std::set<views::View*> deleting_views_;
171 std::set<views::View*> deleted_when_done_;
172 std::list<views::View*> clearing_all_views_;
173 scoped_ptr<views::BoundsAnimator> animator_;
174 base::WeakPtrFactory<MessageListView> weak_ptr_factory_;
175
176 DISALLOW_COPY_AND_ASSIGN(MessageListView);
177 };
178
179 MessageListView::MessageListView(MessageCenterView* message_center_view,
180 bool top_down)
181 : message_center_view_(message_center_view),
182 reposition_top_(-1),
183 fixed_height_(0),
184 has_deferred_task_(false),
185 clear_all_started_(false),
186 top_down_(top_down),
187 weak_ptr_factory_(this) {
188 views::BoxLayout* layout =
189 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
190 layout->SetDefaultFlex(1);
191 SetLayoutManager(layout);
192
193 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
194 // for top and bottom, but the bottom margin here should be smaller
195 // because of the shadow of message view. Use an empty border instead
196 // to provide this margin.
197 gfx::Insets shadow_insets = MessageView::GetShadowInsets();
198 set_background(views::Background::CreateSolidBackground(
199 kMessageCenterBackgroundColor));
200 SetBorder(views::Border::CreateEmptyBorder(
201 top_down ? 0 : kMarginBetweenItems - shadow_insets.top(), /* top */
202 kMarginBetweenItems - shadow_insets.left(), /* left */
203 top_down ? kMarginBetweenItems - shadow_insets.bottom() : 0, /* bottom */
204 kMarginBetweenItems - shadow_insets.right() /* right */));
205 }
206
207 MessageListView::~MessageListView() {
208 if (animator_.get())
209 animator_->RemoveObserver(this);
210 }
211
212 void MessageListView::Layout() {
213 if (animator_.get())
214 return;
215
216 gfx::Rect child_area = GetContentsBounds();
217 int top = child_area.y();
218 int between_items =
219 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
220
221 for (int i = 0; i < child_count(); ++i) {
222 views::View* child = child_at(i);
223 if (!child->visible())
224 continue;
225 int height = child->GetHeightForWidth(child_area.width());
226 child->SetBounds(child_area.x(), top, child_area.width(), height);
227 top += height + between_items;
228 }
229 }
230
231 void MessageListView::AddNotificationAt(MessageView* view, int index) {
232 // |index| refers to a position in a subset of valid children. |real_index|
233 // in a list includes the invalid children, so we compute the real index by
234 // walking the list until |index| number of valid children are encountered,
235 // or to the end of the list.
236 int real_index = 0;
237 while (real_index < child_count()) {
238 if (IsValidChild(child_at(real_index))) {
239 --index;
240 if (index < 0)
241 break;
242 }
243 ++real_index;
244 }
245
246 AddChildViewAt(view, real_index);
247 if (GetContentsBounds().IsEmpty())
248 return;
249
250 adding_views_.insert(view);
251 DoUpdateIfPossible();
252 }
253
254 void MessageListView::RemoveNotification(MessageView* view) {
255 DCHECK_EQ(view->parent(), this);
256 if (GetContentsBounds().IsEmpty()) {
257 delete view;
258 } else {
259 if (view->layer()) {
260 deleting_views_.insert(view);
261 } else {
262 if (animator_.get())
263 animator_->StopAnimatingView(view);
264 delete view;
265 }
266 DoUpdateIfPossible();
267 }
268 }
269
270 void MessageListView::UpdateNotification(MessageView* view,
271 const Notification& notification) {
272 int index = GetIndexOf(view);
273 DCHECK_LE(0, index); // GetIndexOf is negative if not a child.
274
275 if (animator_.get())
276 animator_->StopAnimatingView(view);
277 if (deleting_views_.find(view) != deleting_views_.end())
278 deleting_views_.erase(view);
279 if (deleted_when_done_.find(view) != deleted_when_done_.end())
280 deleted_when_done_.erase(view);
281 view->UpdateWithNotification(notification);
282 DoUpdateIfPossible();
283 }
284
285 gfx::Size MessageListView::GetPreferredSize() const {
286 int width = 0;
287 for (int i = 0; i < child_count(); i++) {
288 const views::View* child = child_at(i);
289 if (IsValidChild(child))
290 width = std::max(width, child->GetPreferredSize().width());
291 }
292
293 return gfx::Size(width + GetInsets().width(),
294 GetHeightForWidth(width + GetInsets().width()));
295 }
296
297 int MessageListView::GetHeightForWidth(int width) const {
298 if (fixed_height_ > 0)
299 return fixed_height_;
300
301 width -= GetInsets().width();
302 int height = 0;
303 int padding = 0;
304 for (int i = 0; i < child_count(); ++i) {
305 const views::View* child = child_at(i);
306 if (!IsValidChild(child))
307 continue;
308 height += child->GetHeightForWidth(width) + padding;
309 padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
310 }
311
312 return height + GetInsets().height();
313 }
314
315 void MessageListView::PaintChildren(const ui::PaintContext& context) {
316 // Paint in the inversed order. Otherwise upper notification may be
317 // hidden by the lower one.
318 for (int i = child_count() - 1; i >= 0; --i) {
319 if (!child_at(i)->layer())
320 child_at(i)->Paint(context);
321 }
322 }
323
324 void MessageListView::ReorderChildLayers(ui::Layer* parent_layer) {
325 // Reorder children to stack the last child layer at the top. Otherwise
326 // upper notification may be hidden by the lower one.
327 for (int i = 0; i < child_count(); ++i) {
328 if (child_at(i)->layer())
329 parent_layer->StackAtBottom(child_at(i)->layer());
330 }
331 }
332
333 void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
334 reposition_top_ = target.y();
335 fixed_height_ = GetHeightForWidth(width());
336 }
337
338 void MessageListView::ResetRepositionSession() {
339 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
340 // animation. Reset will cause the change of the bubble size itself, and
341 // animation from the old location will look weird.
342 if (reposition_top_ >= 0 && animator_.get()) {
343 has_deferred_task_ = false;
344 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
345 animator_->Cancel();
346 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
347 deleting_views_.clear();
348 adding_views_.clear();
349 animator_.reset();
350 }
351
352 reposition_top_ = -1;
353 fixed_height_ = 0;
354 }
355
356 void MessageListView::ClearAllNotifications(
357 const gfx::Rect& visible_scroll_rect) {
358 for (int i = 0; i < child_count(); ++i) {
359 views::View* child = child_at(i);
360 if (!child->visible())
361 continue;
362 if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
363 continue;
364 clearing_all_views_.push_back(child);
365 }
366 DoUpdateIfPossible();
367 }
368
369 void MessageListView::OnBoundsAnimatorProgressed(
370 views::BoundsAnimator* animator) {
371 DCHECK_EQ(animator_.get(), animator);
372 for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
373 iter != deleted_when_done_.end(); ++iter) {
374 const gfx::SlideAnimation* animation = animator->GetAnimationForView(*iter);
375 if (animation)
376 (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
377 }
378 }
379
380 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
381 STLDeleteContainerPointers(
382 deleted_when_done_.begin(), deleted_when_done_.end());
383 deleted_when_done_.clear();
384
385 if (clear_all_started_) {
386 clear_all_started_ = false;
387 message_center_view()->OnAllNotificationsCleared();
388 }
389
390 if (has_deferred_task_) {
391 has_deferred_task_ = false;
392 DoUpdateIfPossible();
393 }
394
395 if (GetWidget())
396 GetWidget()->SynthesizeMouseMoveEvent();
397 }
398
399 bool MessageListView::IsValidChild(const views::View* child) const {
400 return child->visible() &&
401 deleting_views_.find(const_cast<views::View*>(child)) ==
402 deleting_views_.end() &&
403 deleted_when_done_.find(const_cast<views::View*>(child)) ==
404 deleted_when_done_.end();
405 }
406
407 void MessageListView::DoUpdateIfPossible() {
408 gfx::Rect child_area = GetContentsBounds();
409 if (child_area.IsEmpty())
410 return;
411
412 if (animator_.get() && animator_->IsAnimating()) {
413 has_deferred_task_ = true;
414 return;
415 }
416
417 if (!animator_.get()) {
418 animator_.reset(new views::BoundsAnimator(this));
419 animator_->AddObserver(this);
420 }
421
422 if (!clearing_all_views_.empty()) {
423 AnimateClearingOneNotification();
424 return;
425 }
426
427 if (top_down_ ||
428 base::CommandLine::ForCurrentProcess()->HasSwitch(
429 switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval))
430 AnimateNotificationsBelowTarget();
431 else
432 AnimateNotificationsAboveTarget();
433
434 adding_views_.clear();
435 deleting_views_.clear();
436 }
437
438 void MessageListView::AnimateNotificationsBelowTarget() {
439 int last_index = -1;
440 for (int i = 0; i < child_count(); ++i) {
441 views::View* child = child_at(i);
442 if (!IsValidChild(child)) {
443 AnimateChild(child, child->y(), child->height());
444 } else if (reposition_top_ < 0 || child->y() > reposition_top_) {
445 // Find first notification below target (or all notifications if no
446 // target).
447 last_index = i;
448 break;
449 }
450 }
451 if (last_index > 0) {
452 int between_items =
453 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
454 int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top();
455
456 for (int i = last_index; i < child_count(); ++i) {
457 // Animate notifications below target upwards.
458 views::View* child = child_at(i);
459 if (AnimateChild(child, top, child->height()))
460 top += child->height() + between_items;
461 }
462 }
463 }
464
465 void MessageListView::AnimateNotificationsAboveTarget() {
466 int last_index = -1;
467 for (int i = child_count() - 1; i >= 0; --i) {
468 views::View* child = child_at(i);
469 if (!IsValidChild(child)) {
470 AnimateChild(child, child->y(), child->height());
471 } else if (reposition_top_ < 0 || child->y() < reposition_top_) {
472 // Find first notification above target (or all notifications if no
473 // target).
474 last_index = i;
475 break;
476 }
477 }
478 if (last_index >= 0) {
479 int between_items =
480 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
481 int bottom = (reposition_top_ > 0)
482 ? reposition_top_ + child_at(last_index)->height()
483 : GetHeightForWidth(width()) - GetInsets().bottom();
484 for (int i = last_index; i >= 0; --i) {
485 // Animate notifications above target downwards.
486 views::View* child = child_at(i);
487 if (AnimateChild(child, bottom - child->height(), child->height()))
488 bottom -= child->height() + between_items;
489 }
490 }
491 }
492
493 bool MessageListView::AnimateChild(views::View* child, int top, int height) {
494 gfx::Rect child_area = GetContentsBounds();
495 if (adding_views_.find(child) != adding_views_.end()) {
496 child->SetBounds(child_area.right(), top, child_area.width(), height);
497 animator_->AnimateViewTo(
498 child, gfx::Rect(child_area.x(), top, child_area.width(), height));
499 } else if (deleting_views_.find(child) != deleting_views_.end()) {
500 DCHECK(child->layer());
501 // No moves, but animate to fade-out.
502 animator_->AnimateViewTo(child, child->bounds());
503 deleted_when_done_.insert(child);
504 return false;
505 } else {
506 gfx::Rect target(child_area.x(), top, child_area.width(), height);
507 if (child->bounds().origin() != target.origin())
508 animator_->AnimateViewTo(child, target);
509 else
510 child->SetBoundsRect(target);
511 }
512 return true;
513 }
514
515 void MessageListView::AnimateClearingOneNotification() {
516 DCHECK(!clearing_all_views_.empty());
517
518 clear_all_started_ = true;
519
520 views::View* child = clearing_all_views_.front();
521 clearing_all_views_.pop_front();
522
523 // Slide from left to right.
524 gfx::Rect new_bounds = child->bounds();
525 new_bounds.set_x(new_bounds.right() + kMarginBetweenItems);
526 animator_->AnimateViewTo(child, new_bounds);
527
528 // Schedule to start sliding out next notification after a short delay.
529 if (!clearing_all_views_.empty()) {
530 base::MessageLoop::current()->PostDelayedTask(
531 FROM_HERE,
532 base::Bind(&MessageListView::AnimateClearingOneNotification,
533 weak_ptr_factory_.GetWeakPtr()),
534 base::TimeDelta::FromMilliseconds(
535 kAnimateClearingNextNotificationDelayMS));
536 }
537 }
538
539 // MessageCenterView /////////////////////////////////////////////////////////// 106 // MessageCenterView ///////////////////////////////////////////////////////////
540 107
541 MessageCenterView::MessageCenterView(MessageCenter* message_center, 108 MessageCenterView::MessageCenterView(MessageCenter* message_center,
542 MessageCenterTray* tray, 109 MessageCenterTray* tray,
543 int max_height, 110 int max_height,
544 bool initially_settings_visible, 111 bool initially_settings_visible,
545 bool top_down, 112 bool top_down,
546 const base::string16& title) 113 const base::string16& title)
547 : message_center_(message_center), 114 : message_center_(message_center),
548 tray_(tray), 115 tray_(tray),
(...skipping 457 matching lines...) Expand 10 before | Expand all | Expand 10 after
1006 scroller_->InvalidateLayout(); 573 scroller_->InvalidateLayout();
1007 PreferredSizeChanged(); 574 PreferredSizeChanged();
1008 Layout(); 575 Layout();
1009 } 576 }
1010 577
1011 void MessageCenterView::SetNotificationViewForTest(MessageView* view) { 578 void MessageCenterView::SetNotificationViewForTest(MessageView* view) {
1012 message_list_view_->AddNotificationAt(view, 0); 579 message_list_view_->AddNotificationAt(view, 0);
1013 } 580 }
1014 581
1015 } // namespace message_center 582 } // namespace message_center
OLDNEW
« no previous file with comments | « ui/message_center/views/message_center_view.h ('k') | ui/message_center/views/message_list_view.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698