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
3 // found in the LICENSE file.
5 #include "ui/message_center/views/message_center_view.h"
10 #include "base/memory/weak_ptr.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/stl_util.h"
13 #include "grit/ui_strings.h"
14 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/gfx/animation/multi_animation.h"
16 #include "ui/gfx/animation/slide_animation.h"
17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/insets.h"
19 #include "ui/gfx/rect.h"
20 #include "ui/gfx/size.h"
21 #include "ui/message_center/message_center.h"
22 #include "ui/message_center/message_center_style.h"
23 #include "ui/message_center/message_center_types.h"
24 #include "ui/message_center/views/message_center_button_bar.h"
25 #include "ui/message_center/views/message_view.h"
26 #include "ui/message_center/views/notification_view.h"
27 #include "ui/message_center/views/notifier_settings_view.h"
28 #include "ui/views/animation/bounds_animator.h"
29 #include "ui/views/animation/bounds_animator_observer.h"
30 #include "ui/views/background.h"
31 #include "ui/views/border.h"
32 #include "ui/views/controls/button/button.h"
33 #include "ui/views/controls/label.h"
34 #include "ui/views/controls/scroll_view.h"
35 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
36 #include "ui/views/layout/box_layout.h"
37 #include "ui/views/widget/widget.h"
39 namespace message_center
{
43 const SkColor kNoNotificationsTextColor
= SkColorSetRGB(0xb4, 0xb4, 0xb4);
44 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
45 const SkColor kTransparentColor
= SkColorSetARGB(0, 0, 0, 0);
47 const int kAnimateClearingNextNotificationDelayMS
= 40;
48 const int kMinScrollViewHeight
= 100;
50 const int kDefaultAnimationDurationMs
= 120;
51 const int kDefaultFrameRateHz
= 60;
55 // BoundedScrollView ///////////////////////////////////////////////////////////
57 // A custom scroll view whose height has a minimum and maximum value and whose
58 // scroll bar disappears when not needed.
59 class BoundedScrollView
: public views::ScrollView
{
61 BoundedScrollView(int min_height
, int max_height
);
63 // Overridden from views::View:
64 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
65 virtual int GetHeightForWidth(int width
) OVERRIDE
;
66 virtual void Layout() OVERRIDE
;
72 DISALLOW_COPY_AND_ASSIGN(BoundedScrollView
);
75 BoundedScrollView::BoundedScrollView(int min_height
, int max_height
)
76 : min_height_(min_height
),
77 max_height_(max_height
) {
78 set_notify_enter_exit_on_child(true);
79 // Cancels the default dashed focus border.
80 set_focus_border(NULL
);
82 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor
));
83 SetVerticalScrollBar(new views::OverlayScrollBar(false));
86 gfx::Size
BoundedScrollView::GetPreferredSize() {
87 gfx::Size size
= contents()->GetPreferredSize();
88 size
.SetToMax(gfx::Size(size
.width(), min_height_
));
89 size
.SetToMin(gfx::Size(size
.width(), max_height_
));
90 gfx::Insets insets
= GetInsets();
91 size
.Enlarge(insets
.width(), insets
.height());
95 int BoundedScrollView::GetHeightForWidth(int width
) {
96 gfx::Insets insets
= GetInsets();
97 width
= std::max(0, width
- insets
.width());
98 int height
= contents()->GetHeightForWidth(width
) + insets
.height();
99 return std::min(std::max(height
, min_height_
), max_height_
);
102 void BoundedScrollView::Layout() {
103 int content_width
= width();
104 int content_height
= contents()->GetHeightForWidth(content_width
);
105 if (content_height
> height()) {
106 content_width
= std::max(content_width
- GetScrollBarWidth(), 0);
107 content_height
= contents()->GetHeightForWidth(content_width
);
109 if (contents()->bounds().size() != gfx::Size(content_width
, content_height
))
110 contents()->SetBounds(0, 0, content_width
, content_height
);
111 views::ScrollView::Layout();
114 class NoNotificationMessageView
: public views::View
{
116 NoNotificationMessageView();
117 virtual ~NoNotificationMessageView();
119 // Overridden from views::View.
120 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
121 virtual int GetHeightForWidth(int width
) OVERRIDE
;
122 virtual void Layout() OVERRIDE
;
125 views::Label
* label_
;
127 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView
);
130 NoNotificationMessageView::NoNotificationMessageView() {
131 label_
= new views::Label(l10n_util::GetStringUTF16(
132 IDS_MESSAGE_CENTER_NO_MESSAGES
));
133 label_
->SetAutoColorReadabilityEnabled(false);
134 label_
->SetEnabledColor(kNoNotificationsTextColor
);
135 // Set transparent background to ensure that subpixel rendering
136 // is disabled. See crbug.com/169056
137 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
138 label_
->SetBackgroundColor(kTransparentColor
);
140 AddChildView(label_
);
143 NoNotificationMessageView::~NoNotificationMessageView() {
146 gfx::Size
NoNotificationMessageView::GetPreferredSize() {
147 return gfx::Size(kMinScrollViewHeight
, label_
->GetPreferredSize().width());
150 int NoNotificationMessageView::GetHeightForWidth(int width
) {
151 return kMinScrollViewHeight
;
154 void NoNotificationMessageView::Layout() {
155 int text_height
= label_
->GetHeightForWidth(width());
156 int margin
= (height() - text_height
) / 2;
157 label_
->SetBounds(0, margin
, width(), text_height
);
160 // Displays a list of messages for rich notifications. It also supports
162 class MessageListView
: public views::View
,
163 public views::BoundsAnimatorObserver
{
165 explicit MessageListView(MessageCenterView
* message_center_view
,
167 virtual ~MessageListView();
169 void AddNotificationAt(views::View
* view
, int i
);
170 void RemoveNotificationAt(int i
);
171 void UpdateNotificationAt(views::View
* view
, int i
);
172 void SetRepositionTarget(const gfx::Rect
& target_rect
);
173 void ResetRepositionSession();
174 void ClearAllNotifications(const gfx::Rect
& visible_scroll_rect
);
177 // Overridden from views::View.
178 virtual void Layout() OVERRIDE
;
179 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
180 virtual int GetHeightForWidth(int width
) OVERRIDE
;
181 virtual void PaintChildren(gfx::Canvas
* canvas
) OVERRIDE
;
182 virtual void ReorderChildLayers(ui::Layer
* parent_layer
) OVERRIDE
;
184 // Overridden from views::BoundsAnimatorObserver.
185 virtual void OnBoundsAnimatorProgressed(
186 views::BoundsAnimator
* animator
) OVERRIDE
;
187 virtual void OnBoundsAnimatorDone(views::BoundsAnimator
* animator
) OVERRIDE
;
190 // Returns the actual index for child of |index|.
191 // MessageListView allows to slide down upper notifications, which means
192 // that the upper ones should come above the lower ones if top_down is not
193 // enabled. To achieve this, inversed order is adopted. The top most
194 // notification is the last child, and the bottom most notification is the
196 int GetActualIndex(int index
);
197 bool IsValidChild(views::View
* child
);
198 void DoUpdateIfPossible();
200 // Animates all notifications below target upwards to align with the top of
201 // the last closed notification.
202 void AnimateNotificationsBelowTarget();
203 // Animates all notifications above target downwards to align with the top of
204 // the last closed notification.
205 void AnimateNotificationsAboveTarget();
207 // Schedules animation for a child to the specified position. Returns false
208 // if |child| will disappear after the animation.
209 bool AnimateChild(views::View
* child
, int top
, int height
);
211 // Animate clearing one notification.
212 void AnimateClearingOneNotification();
213 MessageCenterView
* message_center_view() const {
214 return message_center_view_
;
217 MessageCenterView
* message_center_view_
; // Weak reference.
218 // The top position of the reposition target rectangle.
221 bool has_deferred_task_
;
222 bool clear_all_started_
;
224 std::set
<views::View
*> adding_views_
;
225 std::set
<views::View
*> deleting_views_
;
226 std::set
<views::View
*> deleted_when_done_
;
227 std::list
<views::View
*> clearing_all_views_
;
228 scoped_ptr
<views::BoundsAnimator
> animator_
;
229 base::WeakPtrFactory
<MessageListView
> weak_ptr_factory_
;
231 DISALLOW_COPY_AND_ASSIGN(MessageListView
);
234 MessageListView::MessageListView(MessageCenterView
* message_center_view
,
236 : message_center_view_(message_center_view
),
239 has_deferred_task_(false),
240 clear_all_started_(false),
242 weak_ptr_factory_(this) {
243 views::BoxLayout
* layout
=
244 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 1);
245 layout
->set_spread_blank_space(true);
246 SetLayoutManager(layout
);
248 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
249 // for top and bottom, but the bottom margin here should be smaller
250 // because of the shadow of message view. Use an empty border instead
251 // to provide this margin.
252 gfx::Insets shadow_insets
= MessageView::GetShadowInsets();
253 set_background(views::Background::CreateSolidBackground(
254 kMessageCenterBackgroundColor
));
255 set_border(views::Border::CreateEmptyBorder(
256 top_down
? 0 : kMarginBetweenItems
- shadow_insets
.top(), /* top */
257 kMarginBetweenItems
- shadow_insets
.left(), /* left */
258 top_down
? kMarginBetweenItems
- shadow_insets
.bottom() : 0, /* bottom */
259 kMarginBetweenItems
- shadow_insets
.right() /* right */));
262 MessageListView::~MessageListView() {
264 animator_
->RemoveObserver(this);
267 void MessageListView::Layout() {
271 gfx::Rect child_area
= GetContentsBounds();
272 int top
= child_area
.y();
274 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
276 for (int i
= 0; i
< child_count(); ++i
) {
277 views::View
* child
= child_at(i
);
278 if (!child
->visible())
280 int height
= child
->GetHeightForWidth(child_area
.width());
281 child
->SetBounds(child_area
.x(), top
, child_area
.width(), height
);
282 top
+= height
+ between_items
;
286 void MessageListView::AddNotificationAt(views::View
* view
, int i
) {
287 AddChildViewAt(view
, GetActualIndex(i
));
288 if (GetContentsBounds().IsEmpty())
291 adding_views_
.insert(view
);
292 DoUpdateIfPossible();
295 void MessageListView::RemoveNotificationAt(int i
) {
296 views::View
* child
= child_at(GetActualIndex(i
));
297 if (GetContentsBounds().IsEmpty()) {
300 if (child
->layer()) {
301 deleting_views_
.insert(child
);
304 animator_
->StopAnimatingView(child
);
307 DoUpdateIfPossible();
311 void MessageListView::UpdateNotificationAt(views::View
* view
, int i
) {
312 int actual_index
= GetActualIndex(i
);
313 views::View
* child
= child_at(actual_index
);
315 animator_
->StopAnimatingView(child
);
316 gfx::Rect old_bounds
= child
->bounds();
317 if (deleting_views_
.find(child
) != deleting_views_
.end())
318 deleting_views_
.erase(child
);
319 if (deleted_when_done_
.find(child
) != deleted_when_done_
.end())
320 deleted_when_done_
.erase(child
);
322 AddChildViewAt(view
, actual_index
);
323 view
->SetBounds(old_bounds
.x(), old_bounds
.y(), old_bounds
.width(),
324 view
->GetHeightForWidth(old_bounds
.width()));
325 DoUpdateIfPossible();
328 gfx::Size
MessageListView::GetPreferredSize() {
330 for (int i
= 0; i
< child_count(); i
++) {
331 views::View
* child
= child_at(i
);
332 if (IsValidChild(child
))
333 width
= std::max(width
, child
->GetPreferredSize().width());
336 return gfx::Size(width
+ GetInsets().width(),
337 GetHeightForWidth(width
+ GetInsets().width()));
340 int MessageListView::GetHeightForWidth(int width
) {
341 if (fixed_height_
> 0)
342 return fixed_height_
;
344 width
-= GetInsets().width();
347 for (int i
= 0; i
< child_count(); ++i
) {
348 views::View
* child
= child_at(i
);
349 if (!IsValidChild(child
))
351 height
+= child
->GetHeightForWidth(width
) + padding
;
352 padding
= kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
355 return height
+ GetInsets().height();
358 void MessageListView::PaintChildren(gfx::Canvas
* canvas
) {
359 // Paint in the inversed order. Otherwise upper notification may be
360 // hidden by the lower one.
361 for (int i
= child_count() - 1; i
>= 0; --i
) {
362 if (!child_at(i
)->layer())
363 child_at(i
)->Paint(canvas
);
367 void MessageListView::ReorderChildLayers(ui::Layer
* parent_layer
) {
368 // Reorder children to stack the last child layer at the top. Otherwise
369 // upper notification may be hidden by the lower one.
370 for (int i
= 0; i
< child_count(); ++i
) {
371 if (child_at(i
)->layer())
372 parent_layer
->StackAtBottom(child_at(i
)->layer());
376 void MessageListView::SetRepositionTarget(const gfx::Rect
& target
) {
377 reposition_top_
= target
.y();
378 fixed_height_
= GetHeightForWidth(width());
381 void MessageListView::ResetRepositionSession() {
382 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
383 // animation. Reset will cause the change of the bubble size itself, and
384 // animation from the old location will look weird.
385 if (reposition_top_
>= 0 && animator_
.get()) {
386 has_deferred_task_
= false;
387 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
389 STLDeleteContainerPointers(deleting_views_
.begin(), deleting_views_
.end());
390 deleting_views_
.clear();
391 adding_views_
.clear();
395 reposition_top_
= -1;
399 void MessageListView::ClearAllNotifications(
400 const gfx::Rect
& visible_scroll_rect
) {
401 for (int i
= 0; i
< child_count(); ++i
) {
402 views::View
* child
= child_at(i
);
403 if (!child
->visible())
405 if (gfx::IntersectRects(child
->bounds(), visible_scroll_rect
).IsEmpty())
407 clearing_all_views_
.push_back(child
);
409 DoUpdateIfPossible();
412 void MessageListView::OnBoundsAnimatorProgressed(
413 views::BoundsAnimator
* animator
) {
414 DCHECK_EQ(animator_
.get(), animator
);
415 for (std::set
<views::View
*>::iterator iter
= deleted_when_done_
.begin();
416 iter
!= deleted_when_done_
.end(); ++iter
) {
417 const gfx::SlideAnimation
* animation
= animator
->GetAnimationForView(*iter
);
419 (*iter
)->layer()->SetOpacity(animation
->CurrentValueBetween(1.0, 0.0));
423 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator
* animator
) {
424 STLDeleteContainerPointers(
425 deleted_when_done_
.begin(), deleted_when_done_
.end());
426 deleted_when_done_
.clear();
428 if (clear_all_started_
) {
429 clear_all_started_
= false;
430 message_center_view()->OnAllNotificationsCleared();
433 if (has_deferred_task_
) {
434 has_deferred_task_
= false;
435 DoUpdateIfPossible();
439 GetWidget()->SynthesizeMouseMoveEvent();
442 int MessageListView::GetActualIndex(int index
) {
443 for (int i
= 0; i
< child_count() && i
<= index
; ++i
)
444 index
+= IsValidChild(child_at(i
)) ? 0 : 1;
445 return std::min(index
, child_count());
448 bool MessageListView::IsValidChild(views::View
* child
) {
449 return child
->visible() &&
450 deleting_views_
.find(child
) == deleting_views_
.end() &&
451 deleted_when_done_
.find(child
) == deleted_when_done_
.end();
454 void MessageListView::DoUpdateIfPossible() {
455 gfx::Rect child_area
= GetContentsBounds();
456 if (child_area
.IsEmpty())
459 if (animator_
.get() && animator_
->IsAnimating()) {
460 has_deferred_task_
= true;
464 if (!animator_
.get()) {
465 animator_
.reset(new views::BoundsAnimator(this));
466 animator_
->AddObserver(this);
469 if (!clearing_all_views_
.empty()) {
470 AnimateClearingOneNotification();
475 AnimateNotificationsBelowTarget();
477 AnimateNotificationsAboveTarget();
479 adding_views_
.clear();
480 deleting_views_
.clear();
483 void MessageListView::AnimateNotificationsBelowTarget() {
485 for (int i
= 0; i
< child_count(); ++i
) {
486 views::View
* child
= child_at(i
);
487 if (!IsValidChild(child
)) {
488 AnimateChild(child
, child
->y(), child
->height());
489 } else if (reposition_top_
< 0 || child
->y() > reposition_top_
) {
490 // Find first notification below target (or all notifications if no
496 if (last_index
> 0) {
498 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
499 int top
= (reposition_top_
> 0) ? reposition_top_
: GetInsets().top();
501 for (int i
= last_index
; i
< child_count(); ++i
) {
502 // Animate notifications below target upwards.
503 views::View
* child
= child_at(i
);
504 if (AnimateChild(child
, top
, child
->height()))
505 top
+= child
->height() + between_items
;
510 void MessageListView::AnimateNotificationsAboveTarget() {
512 for (int i
= child_count() - 1; i
>= 0; --i
) {
513 views::View
* child
= child_at(i
);
514 if (!IsValidChild(child
)) {
515 AnimateChild(child
, child
->y(), child
->height());
516 } else if (reposition_top_
< 0 || child
->y() < reposition_top_
) {
517 // Find first notification above target (or all notifications if no
523 if (last_index
> 0) {
525 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
526 int bottom
= (reposition_top_
> 0)
527 ? reposition_top_
+ child_at(last_index
)->height()
528 : GetHeightForWidth(width()) - GetInsets().bottom();
529 for (int i
= last_index
; i
>= 0; --i
) {
530 // Animate notifications above target downwards.
531 views::View
* child
= child_at(i
);
532 if (AnimateChild(child
, bottom
- child
->height(), child
->height()))
533 bottom
-= child
->height() + between_items
;
538 bool MessageListView::AnimateChild(views::View
* child
, int top
, int height
) {
539 gfx::Rect child_area
= GetContentsBounds();
540 if (adding_views_
.find(child
) != adding_views_
.end()) {
541 child
->SetBounds(child_area
.right(), top
, child_area
.width(), height
);
542 animator_
->AnimateViewTo(
543 child
, gfx::Rect(child_area
.x(), top
, child_area
.width(), height
));
544 } else if (deleting_views_
.find(child
) != deleting_views_
.end()) {
545 DCHECK(child
->layer());
546 // No moves, but animate to fade-out.
547 animator_
->AnimateViewTo(child
, child
->bounds());
548 deleted_when_done_
.insert(child
);
551 gfx::Rect
target(child_area
.x(), top
, child_area
.width(), height
);
552 if (child
->bounds().origin() != target
.origin())
553 animator_
->AnimateViewTo(child
, target
);
555 child
->SetBoundsRect(target
);
560 void MessageListView::AnimateClearingOneNotification() {
561 DCHECK(!clearing_all_views_
.empty());
563 clear_all_started_
= true;
565 views::View
* child
= clearing_all_views_
.front();
566 clearing_all_views_
.pop_front();
568 // Slide from left to right.
569 gfx::Rect new_bounds
= child
->bounds();
570 new_bounds
.set_x(new_bounds
.right() + kMarginBetweenItems
);
571 animator_
->AnimateViewTo(child
, new_bounds
);
573 // Schedule to start sliding out next notification after a short delay.
574 if (!clearing_all_views_
.empty()) {
575 base::MessageLoop::current()->PostDelayedTask(
577 base::Bind(&MessageListView::AnimateClearingOneNotification
,
578 weak_ptr_factory_
.GetWeakPtr()),
579 base::TimeDelta::FromMilliseconds(
580 kAnimateClearingNextNotificationDelayMS
));
584 // MessageCenterView ///////////////////////////////////////////////////////////
586 MessageCenterView::MessageCenterView(MessageCenter
* message_center
,
587 MessageCenterTray
* tray
,
589 bool initially_settings_visible
,
591 : message_center_(message_center
),
594 settings_visible_(initially_settings_visible
),
600 message_center_
->AddObserver(this);
601 set_notify_enter_exit_on_child(true);
602 set_background(views::Background::CreateSolidBackground(
603 kMessageCenterBackgroundColor
));
605 NotifierSettingsProvider
* notifier_settings_provider
=
606 message_center_
->GetNotifierSettingsProvider();
607 button_bar_
= new MessageCenterButtonBar(this,
609 notifier_settings_provider
,
610 initially_settings_visible
);
612 const int button_height
= button_bar_
->GetPreferredSize().height();
615 new BoundedScrollView(kMinScrollViewHeight
, max_height
- button_height
);
617 if (get_use_acceleration_when_possible()) {
618 scroller_
->SetPaintToLayer(true);
619 scroller_
->SetFillsBoundsOpaquely(false);
620 scroller_
->layer()->SetMasksToBounds(true);
623 message_list_view_
= new MessageListView(this, top_down
);
624 no_notifications_message_view_
= new NoNotificationMessageView();
625 // Set the default visibility to false, otherwise the notification has slide
626 // in animation when the center is shown.
627 no_notifications_message_view_
->SetVisible(false);
628 message_list_view_
->AddChildView(no_notifications_message_view_
);
629 scroller_
->SetContents(message_list_view_
);
631 settings_view_
= new NotifierSettingsView(notifier_settings_provider
);
633 if (initially_settings_visible
)
634 scroller_
->SetVisible(false);
636 settings_view_
->SetVisible(false);
638 AddChildView(scroller_
);
639 AddChildView(settings_view_
);
640 AddChildView(button_bar_
);
643 MessageCenterView::~MessageCenterView() {
645 message_center_
->RemoveObserver(this);
648 void MessageCenterView::SetNotifications(
649 const NotificationList::Notifications
& notifications
) {
653 message_views_
.clear();
655 for (NotificationList::Notifications::const_iterator iter
=
656 notifications
.begin(); iter
!= notifications
.end();
658 AddNotificationAt(*(*iter
), index
);
659 if (message_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
662 NotificationsChanged();
663 scroller_
->RequestFocus();
666 void MessageCenterView::SetSettingsVisible(bool visible
) {
670 if (visible
== settings_visible_
)
673 settings_visible_
= visible
;
676 source_view_
= scroller_
;
677 target_view_
= settings_view_
;
679 source_view_
= settings_view_
;
680 target_view_
= scroller_
;
682 source_height_
= source_view_
->GetHeightForWidth(width());
683 target_height_
= target_view_
->GetHeightForWidth(width());
685 gfx::MultiAnimation::Parts parts
;
686 // First part: slide resize animation.
687 parts
.push_back(gfx::MultiAnimation::Part(
688 (source_height_
== target_height_
) ? 0 : kDefaultAnimationDurationMs
,
689 gfx::Tween::EASE_OUT
));
690 // Second part: fade-out the source_view.
691 if (source_view_
->layer()) {
692 parts
.push_back(gfx::MultiAnimation::Part(
693 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
695 parts
.push_back(gfx::MultiAnimation::Part());
697 // Third part: fade-in the target_view.
698 if (target_view_
->layer()) {
699 parts
.push_back(gfx::MultiAnimation::Part(
700 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
701 target_view_
->layer()->SetOpacity(0);
702 target_view_
->SetVisible(true);
704 parts
.push_back(gfx::MultiAnimation::Part());
706 settings_transition_animation_
.reset(new gfx::MultiAnimation(
707 parts
, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz
)));
708 settings_transition_animation_
->set_delegate(this);
709 settings_transition_animation_
->set_continuous(false);
710 settings_transition_animation_
->Start();
712 button_bar_
->SetBackArrowVisible(visible
);
715 void MessageCenterView::ClearAllNotifications() {
719 scroller_
->SetEnabled(false);
720 button_bar_
->SetAllButtonsEnabled(false);
721 message_list_view_
->ClearAllNotifications(scroller_
->GetVisibleRect());
724 void MessageCenterView::OnAllNotificationsCleared() {
725 scroller_
->SetEnabled(true);
726 button_bar_
->SetAllButtonsEnabled(true);
727 button_bar_
->SetCloseAllButtonEnabled(false);
728 message_center_
->RemoveAllNotifications(true); // Action by user.
731 size_t MessageCenterView::NumMessageViewsForTest() const {
732 return message_list_view_
->child_count();
735 void MessageCenterView::OnSettingsChanged() {
736 scroller_
->InvalidateLayout();
737 PreferredSizeChanged();
741 void MessageCenterView::SetIsClosing(bool is_closing
) {
742 is_closing_
= is_closing
;
744 message_center_
->RemoveObserver(this);
746 message_center_
->AddObserver(this);
749 void MessageCenterView::Layout() {
753 int button_height
= button_bar_
->GetHeightForWidth(width()) +
754 button_bar_
->GetInsets().height();
755 // Skip unnecessary re-layout of contents during the resize animation.
756 bool animating
= settings_transition_animation_
&&
757 settings_transition_animation_
->is_animating();
758 if (animating
&& settings_transition_animation_
->current_part_index() == 0) {
760 button_bar_
->SetBounds(
761 0, height() - button_height
, width(), button_height
);
766 scroller_
->SetBounds(0,
767 top_down_
? button_height
: 0,
769 height() - button_height
);
770 settings_view_
->SetBounds(0,
771 top_down_
? button_height
: 0,
773 height() - button_height
);
775 bool is_scrollable
= false;
776 if (scroller_
->visible())
777 is_scrollable
= scroller_
->height() < message_list_view_
->height();
779 is_scrollable
= settings_view_
->IsScrollable();
783 // Draw separator line on the top of the button bar if it is on the bottom
784 // or draw it at the bottom if the bar is on the top.
785 button_bar_
->set_border(views::Border::CreateSolidSidedBorder(
786 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0, kFooterDelimiterColor
));
788 button_bar_
->set_border(views::Border::CreateEmptyBorder(
789 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0));
791 button_bar_
->SchedulePaint();
793 button_bar_
->SetBounds(0,
794 top_down_
? 0 : height() - button_height
,
798 GetWidget()->GetRootView()->SchedulePaint();
801 gfx::Size
MessageCenterView::GetPreferredSize() {
802 if (settings_transition_animation_
&&
803 settings_transition_animation_
->is_animating()) {
804 int content_width
= std::max(source_view_
->GetPreferredSize().width(),
805 target_view_
->GetPreferredSize().width());
806 int width
= std::max(content_width
,
807 button_bar_
->GetPreferredSize().width());
808 return gfx::Size(width
, GetHeightForWidth(width
));
812 for (int i
= 0; i
< child_count(); ++i
) {
813 views::View
* child
= child_at(0);
814 if (child
->visible())
815 width
= std::max(width
, child
->GetPreferredSize().width());
817 return gfx::Size(width
, GetHeightForWidth(width
));
820 int MessageCenterView::GetHeightForWidth(int width
) {
821 if (settings_transition_animation_
&&
822 settings_transition_animation_
->is_animating()) {
823 int content_height
= target_height_
;
824 if (settings_transition_animation_
->current_part_index() == 0) {
825 content_height
= settings_transition_animation_
->CurrentValueBetween(
826 source_height_
, target_height_
);
828 return button_bar_
->GetHeightForWidth(width
) + content_height
;
831 int content_height
= 0;
832 if (scroller_
->visible())
833 content_height
+= scroller_
->GetHeightForWidth(width
);
835 content_height
+= settings_view_
->GetHeightForWidth(width
);
836 return button_bar_
->GetHeightForWidth(width
) +
837 button_bar_
->GetInsets().height() + content_height
;
840 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
841 // Do not rely on the default scroll event handler of ScrollView because
842 // the scroll happens only when the focus is on the ScrollView. The
843 // notification center will allow the scrolling even when the focus is on
845 if (scroller_
->bounds().Contains(event
.location()))
846 return scroller_
->OnMouseWheel(event
);
847 return views::View::OnMouseWheel(event
);
850 void MessageCenterView::OnMouseExited(const ui::MouseEvent
& event
) {
854 message_list_view_
->ResetRepositionSession();
855 NotificationsChanged();
858 void MessageCenterView::OnNotificationAdded(const std::string
& id
) {
860 const NotificationList::Notifications
& notifications
=
861 message_center_
->GetVisibleNotifications();
862 for (NotificationList::Notifications::const_iterator iter
=
863 notifications
.begin(); iter
!= notifications
.end();
865 if ((*iter
)->id() == id
) {
866 AddNotificationAt(*(*iter
), index
);
869 if (message_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
872 NotificationsChanged();
875 void MessageCenterView::OnNotificationRemoved(const std::string
& id
,
877 for (size_t i
= 0; i
< message_views_
.size(); ++i
) {
878 if (message_views_
[i
]->notification_id() == id
) {
880 message_list_view_
->SetRepositionTarget(message_views_
[i
]->bounds());
881 // Moves the keyboard focus to the next notification if the removed
882 // notification is focused so that the user can dismiss notifications
883 // without re-focusing by tab key.
884 if (message_views_
.size() > 1) {
885 views::View
* focused_view
= GetFocusManager()->GetFocusedView();
886 if (message_views_
[i
]->IsCloseButtonFocused() ||
887 focused_view
== message_views_
[i
]) {
888 size_t next_index
= i
+ 1;
889 if (next_index
>= message_views_
.size())
890 next_index
= message_views_
.size() - 2;
891 if (focused_view
== message_views_
[i
])
892 message_views_
[next_index
]->RequestFocus();
894 message_views_
[next_index
]->RequestFocusOnCloseButton();
898 message_list_view_
->RemoveNotificationAt(i
);
899 message_views_
.erase(message_views_
.begin() + i
);
900 NotificationsChanged();
906 void MessageCenterView::OnNotificationUpdated(const std::string
& id
) {
907 const NotificationList::Notifications
& notifications
=
908 message_center_
->GetVisibleNotifications();
910 for (NotificationList::Notifications::const_iterator iter
=
911 notifications
.begin();
912 iter
!= notifications
.end() && index
< message_views_
.size();
914 DCHECK((*iter
)->id() == message_views_
[index
]->notification_id());
915 if ((*iter
)->id() == id
) {
917 NotificationView::Create(*(*iter
),
920 true, // Create expanded.
921 false); // Not creating a top-level
923 view
->set_scroller(scroller_
);
924 message_list_view_
->UpdateNotificationAt(view
, index
);
925 message_views_
[index
] = view
;
926 NotificationsChanged();
932 void MessageCenterView::AnimationEnded(const gfx::Animation
* animation
) {
933 DCHECK_EQ(animation
, settings_transition_animation_
.get());
935 Visibility visibility
= target_view_
== settings_view_
936 ? VISIBILITY_SETTINGS
937 : VISIBILITY_MESSAGE_CENTER
;
938 message_center_
->SetVisibility(visibility
);
940 source_view_
->SetVisible(false);
941 target_view_
->SetVisible(true);
942 if (source_view_
->layer())
943 source_view_
->layer()->SetOpacity(1.0);
944 if (target_view_
->layer())
945 target_view_
->layer()->SetOpacity(1.0);
946 settings_transition_animation_
.reset();
947 PreferredSizeChanged();
951 void MessageCenterView::AnimationProgressed(const gfx::Animation
* animation
) {
952 DCHECK_EQ(animation
, settings_transition_animation_
.get());
953 PreferredSizeChanged();
954 if (settings_transition_animation_
->current_part_index() == 1 &&
955 source_view_
->layer()) {
956 source_view_
->layer()->SetOpacity(
957 1.0 - settings_transition_animation_
->GetCurrentValue());
959 } else if (settings_transition_animation_
->current_part_index() == 2 &&
960 target_view_
->layer()) {
961 target_view_
->layer()->SetOpacity(
962 settings_transition_animation_
->GetCurrentValue());
967 void MessageCenterView::AnimationCanceled(const gfx::Animation
* animation
) {
968 DCHECK_EQ(animation
, settings_transition_animation_
.get());
969 AnimationEnded(animation
);
972 void MessageCenterView::AddNotificationAt(const Notification
& notification
,
974 // NotificationViews are expanded by default here until
975 // http://crbug.com/217902 is fixed. TODO(dharcourt): Fix.
977 NotificationView::Create(notification
,
980 true, // Create expanded.
981 false); // Not creating a top-level
983 view
->set_scroller(scroller_
);
984 message_views_
.insert(message_views_
.begin() + index
, view
);
985 message_list_view_
->AddNotificationAt(view
, index
);
986 message_center_
->DisplayedNotification(notification
.id());
989 void MessageCenterView::NotificationsChanged() {
990 bool no_message_views
= message_views_
.empty();
992 no_notifications_message_view_
->SetVisible(no_message_views
);
993 button_bar_
->SetCloseAllButtonEnabled(!no_message_views
);
994 scroller_
->set_focusable(!no_message_views
);
996 scroller_
->InvalidateLayout();
997 PreferredSizeChanged();
1001 void MessageCenterView::SetNotificationViewForTest(views::View
* view
) {
1002 message_list_view_
->AddNotificationAt(view
, 0);
1005 } // namespace message_center