1 // Copyright (c) 2015 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 "base/command_line.h"
6 #include "ui/gfx/animation/slide_animation.h"
7 #include "ui/message_center/message_center_style.h"
8 #include "ui/message_center/message_center_switches.h"
9 #include "ui/message_center/views/message_center_view.h"
10 #include "ui/message_center/views/message_list_view.h"
11 #include "ui/message_center/views/message_view.h"
12 #include "ui/views/background.h"
13 #include "ui/views/border.h"
14 #include "ui/views/layout/box_layout.h"
15 #include "ui/views/widget/widget.h"
17 namespace message_center
{
20 const int kAnimateClearingNextNotificationDelayMS
= 40;
23 MessageListView::MessageListView(MessageCenterView
* message_center_view
,
25 : message_center_view_(message_center_view
),
28 has_deferred_task_(false),
29 clear_all_started_(false),
32 weak_ptr_factory_(this) {
33 views::BoxLayout
* layout
=
34 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 1);
35 layout
->SetDefaultFlex(1);
36 SetLayoutManager(layout
);
38 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
39 // for top and bottom, but the bottom margin here should be smaller
40 // because of the shadow of message view. Use an empty border instead
41 // to provide this margin.
42 gfx::Insets shadow_insets
= MessageView::GetShadowInsets();
44 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor
));
45 SetBorder(views::Border::CreateEmptyBorder(
46 top_down
? 0 : kMarginBetweenItems
- shadow_insets
.top(), /* top */
47 kMarginBetweenItems
- shadow_insets
.left(), /* left */
48 top_down
? kMarginBetweenItems
- shadow_insets
.bottom() : 0, /* bottom */
49 kMarginBetweenItems
- shadow_insets
.right() /* right */));
50 animator_
.AddObserver(this);
53 MessageListView::~MessageListView() {
54 animator_
.RemoveObserver(this);
57 void MessageListView::Layout() {
58 if (animator_
.IsAnimating())
61 gfx::Rect child_area
= GetContentsBounds();
62 int top
= child_area
.y();
64 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
66 for (int i
= 0; i
< child_count(); ++i
) {
67 views::View
* child
= child_at(i
);
68 if (!child
->visible())
70 int height
= child
->GetHeightForWidth(child_area
.width());
71 child
->SetBounds(child_area
.x(), top
, child_area
.width(), height
);
72 top
+= height
+ between_items
;
76 void MessageListView::AddNotificationAt(MessageView
* view
, int index
) {
77 // |index| refers to a position in a subset of valid children. |real_index|
78 // in a list includes the invalid children, so we compute the real index by
79 // walking the list until |index| number of valid children are encountered,
80 // or to the end of the list.
82 while (real_index
< child_count()) {
83 if (IsValidChild(child_at(real_index
))) {
91 AddChildViewAt(view
, real_index
);
92 if (GetContentsBounds().IsEmpty())
95 adding_views_
.insert(view
);
99 void MessageListView::RemoveNotification(MessageView
* view
) {
100 DCHECK_EQ(view
->parent(), this);
101 if (GetContentsBounds().IsEmpty()) {
105 deleting_views_
.insert(view
);
107 animator_
.StopAnimatingView(view
);
110 DoUpdateIfPossible();
114 void MessageListView::UpdateNotification(MessageView
* view
,
115 const Notification
& notification
) {
116 int index
= GetIndexOf(view
);
117 DCHECK_LE(0, index
); // GetIndexOf is negative if not a child.
119 animator_
.StopAnimatingView(view
);
120 if (deleting_views_
.find(view
) != deleting_views_
.end())
121 deleting_views_
.erase(view
);
122 if (deleted_when_done_
.find(view
) != deleted_when_done_
.end())
123 deleted_when_done_
.erase(view
);
124 view
->UpdateWithNotification(notification
);
125 DoUpdateIfPossible();
128 gfx::Size
MessageListView::GetPreferredSize() const {
130 for (int i
= 0; i
< child_count(); i
++) {
131 const views::View
* child
= child_at(i
);
132 if (IsValidChild(child
))
133 width
= std::max(width
, child
->GetPreferredSize().width());
136 return gfx::Size(width
+ GetInsets().width(),
137 GetHeightForWidth(width
+ GetInsets().width()));
140 int MessageListView::GetHeightForWidth(int width
) const {
141 if (fixed_height_
> 0)
142 return fixed_height_
;
144 width
-= GetInsets().width();
147 for (int i
= 0; i
< child_count(); ++i
) {
148 const views::View
* child
= child_at(i
);
149 if (!IsValidChild(child
))
151 height
+= child
->GetHeightForWidth(width
) + padding
;
152 padding
= kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
155 return height
+ GetInsets().height();
158 void MessageListView::PaintChildren(const ui::PaintContext
& context
) {
159 // Paint in the inversed order. Otherwise upper notification may be
160 // hidden by the lower one.
161 for (int i
= child_count() - 1; i
>= 0; --i
) {
162 if (!child_at(i
)->layer())
163 child_at(i
)->Paint(context
);
167 void MessageListView::ReorderChildLayers(ui::Layer
* parent_layer
) {
168 // Reorder children to stack the last child layer at the top. Otherwise
169 // upper notification may be hidden by the lower one.
170 for (int i
= 0; i
< child_count(); ++i
) {
171 if (child_at(i
)->layer())
172 parent_layer
->StackAtBottom(child_at(i
)->layer());
176 void MessageListView::SetRepositionTarget(const gfx::Rect
& target
) {
177 reposition_top_
= target
.y();
178 fixed_height_
= GetHeightForWidth(width());
181 void MessageListView::ResetRepositionSession() {
182 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
183 // animation. Reset will cause the change of the bubble size itself, and
184 // animation from the old location will look weird.
185 if (reposition_top_
>= 0) {
186 has_deferred_task_
= false;
187 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
189 STLDeleteContainerPointers(deleting_views_
.begin(), deleting_views_
.end());
190 deleting_views_
.clear();
191 adding_views_
.clear();
194 reposition_top_
= -1;
198 void MessageListView::ClearAllNotifications(
199 const gfx::Rect
& visible_scroll_rect
) {
200 for (int i
= 0; i
< child_count(); ++i
) {
201 views::View
* child
= child_at(i
);
202 if (!child
->visible())
204 if (gfx::IntersectRects(child
->bounds(), visible_scroll_rect
).IsEmpty())
206 clearing_all_views_
.push_back(child
);
208 DoUpdateIfPossible();
211 void MessageListView::OnBoundsAnimatorProgressed(
212 views::BoundsAnimator
* animator
) {
213 DCHECK_EQ(&animator_
, animator
);
214 for (std::set
<views::View
*>::iterator iter
= deleted_when_done_
.begin();
215 iter
!= deleted_when_done_
.end(); ++iter
) {
216 const gfx::SlideAnimation
* animation
= animator
->GetAnimationForView(*iter
);
218 (*iter
)->layer()->SetOpacity(animation
->CurrentValueBetween(1.0, 0.0));
222 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator
* animator
) {
223 STLDeleteContainerPointers(deleted_when_done_
.begin(),
224 deleted_when_done_
.end());
225 deleted_when_done_
.clear();
227 if (clear_all_started_
) {
228 clear_all_started_
= false;
229 message_center_view()->OnAllNotificationsCleared();
232 if (has_deferred_task_
) {
233 has_deferred_task_
= false;
234 DoUpdateIfPossible();
238 GetWidget()->SynthesizeMouseMoveEvent();
241 bool MessageListView::IsValidChild(const views::View
* child
) const {
242 return child
->visible() &&
243 deleting_views_
.find(const_cast<views::View
*>(child
)) ==
244 deleting_views_
.end() &&
245 deleted_when_done_
.find(const_cast<views::View
*>(child
)) ==
246 deleted_when_done_
.end();
249 void MessageListView::DoUpdateIfPossible() {
250 gfx::Rect child_area
= GetContentsBounds();
251 if (child_area
.IsEmpty())
254 if (animator_
.IsAnimating()) {
255 has_deferred_task_
= true;
259 if (!clearing_all_views_
.empty()) {
260 AnimateClearingOneNotification();
265 base::CommandLine::ForCurrentProcess()->HasSwitch(
266 switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval
))
267 AnimateNotificationsBelowTarget();
269 AnimateNotificationsAboveTarget();
271 adding_views_
.clear();
272 deleting_views_
.clear();
275 void MessageListView::AnimateNotificationsBelowTarget() {
277 for (int i
= 0; i
< child_count(); ++i
) {
278 views::View
* child
= child_at(i
);
279 if (!IsValidChild(child
)) {
280 AnimateChild(child
, child
->y(), child
->height());
281 } else if (reposition_top_
< 0 || child
->y() > reposition_top_
) {
282 // Find first notification below target (or all notifications if no
288 if (last_index
> 0) {
290 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
291 int top
= (reposition_top_
> 0) ? reposition_top_
: GetInsets().top();
293 for (int i
= last_index
; i
< child_count(); ++i
) {
294 // Animate notifications below target upwards.
295 views::View
* child
= child_at(i
);
296 if (AnimateChild(child
, top
, child
->height()))
297 top
+= child
->height() + between_items
;
302 void MessageListView::AnimateNotificationsAboveTarget() {
304 for (int i
= child_count() - 1; i
>= 0; --i
) {
305 views::View
* child
= child_at(i
);
306 if (!IsValidChild(child
)) {
307 AnimateChild(child
, child
->y(), child
->height());
308 } else if (reposition_top_
< 0 || child
->y() < reposition_top_
) {
309 // Find first notification above target (or all notifications if no
315 if (last_index
>= 0) {
317 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
318 int bottom
= (reposition_top_
> 0)
319 ? reposition_top_
+ child_at(last_index
)->height()
320 : GetHeightForWidth(width()) - GetInsets().bottom();
321 for (int i
= last_index
; i
>= 0; --i
) {
322 // Animate notifications above target downwards.
323 views::View
* child
= child_at(i
);
324 if (AnimateChild(child
, bottom
- child
->height(), child
->height()))
325 bottom
-= child
->height() + between_items
;
330 bool MessageListView::AnimateChild(views::View
* child
, int top
, int height
) {
331 gfx::Rect child_area
= GetContentsBounds();
332 if (adding_views_
.find(child
) != adding_views_
.end()) {
333 child
->SetBounds(child_area
.right(), top
, child_area
.width(), height
);
334 animator_
.AnimateViewTo(
335 child
, gfx::Rect(child_area
.x(), top
, child_area
.width(), height
));
336 } else if (deleting_views_
.find(child
) != deleting_views_
.end()) {
337 DCHECK(child
->layer());
338 // No moves, but animate to fade-out.
339 animator_
.AnimateViewTo(child
, child
->bounds());
340 deleted_when_done_
.insert(child
);
343 gfx::Rect
target(child_area
.x(), top
, child_area
.width(), height
);
344 if (child
->bounds().origin() != target
.origin())
345 animator_
.AnimateViewTo(child
, target
);
347 child
->SetBoundsRect(target
);
352 void MessageListView::AnimateClearingOneNotification() {
353 DCHECK(!clearing_all_views_
.empty());
355 clear_all_started_
= true;
357 views::View
* child
= clearing_all_views_
.front();
358 clearing_all_views_
.pop_front();
360 // Slide from left to right.
361 gfx::Rect new_bounds
= child
->bounds();
362 new_bounds
.set_x(new_bounds
.right() + kMarginBetweenItems
);
363 animator_
.AnimateViewTo(child
, new_bounds
);
365 // Schedule to start sliding out next notification after a short delay.
366 if (!clearing_all_views_
.empty()) {
367 base::MessageLoop::current()->PostDelayedTask(
368 FROM_HERE
, base::Bind(&MessageListView::AnimateClearingOneNotification
,
369 weak_ptr_factory_
.GetWeakPtr()),
370 base::TimeDelta::FromMilliseconds(
371 kAnimateClearingNextNotificationDelayMS
));
375 } // namespace message_center