Add ICU message format support
[chromium-blink-merge.git] / ui / message_center / views / message_list_view.cc
blob8c1ef3fc056bc80e24470ec91e29a0620736ba13
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/animation/bounds_animator.h"
13 #include "ui/views/background.h"
14 #include "ui/views/border.h"
15 #include "ui/views/layout/box_layout.h"
16 #include "ui/views/widget/widget.h"
18 namespace message_center {
20 namespace {
21 const int kAnimateClearingNextNotificationDelayMS = 40;
22 } // namespace
24 MessageListView::MessageListView(MessageCenterView* message_center_view,
25 bool top_down)
26 : message_center_view_(message_center_view),
27 reposition_top_(-1),
28 fixed_height_(0),
29 has_deferred_task_(false),
30 clear_all_started_(false),
31 top_down_(top_down),
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();
43 set_background(
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 */));
52 MessageListView::~MessageListView() {
53 if (animator_.get())
54 animator_->RemoveObserver(this);
57 void MessageListView::Layout() {
58 if (animator_.get() && animator_->IsAnimating())
59 return;
61 gfx::Rect child_area = GetContentsBounds();
62 int top = child_area.y();
63 int between_items =
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())
69 continue;
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.
81 int real_index = 0;
82 while (real_index < child_count()) {
83 if (IsValidChild(child_at(real_index))) {
84 --index;
85 if (index < 0)
86 break;
88 ++real_index;
91 AddChildViewAt(view, real_index);
92 if (GetContentsBounds().IsEmpty())
93 return;
95 adding_views_.insert(view);
96 DoUpdateIfPossible();
99 void MessageListView::RemoveNotification(MessageView* view) {
100 DCHECK_EQ(view->parent(), this);
101 if (GetContentsBounds().IsEmpty()) {
102 delete view;
103 } else {
104 if (view->layer()) {
105 deleting_views_.insert(view);
106 } else {
107 if (animator_.get())
108 animator_->StopAnimatingView(view);
109 delete view;
111 DoUpdateIfPossible();
115 void MessageListView::UpdateNotification(MessageView* view,
116 const Notification& notification) {
117 int index = GetIndexOf(view);
118 DCHECK_LE(0, index); // GetIndexOf is negative if not a child.
120 if (animator_.get())
121 animator_->StopAnimatingView(view);
122 if (deleting_views_.find(view) != deleting_views_.end())
123 deleting_views_.erase(view);
124 if (deleted_when_done_.find(view) != deleted_when_done_.end())
125 deleted_when_done_.erase(view);
126 view->UpdateWithNotification(notification);
127 DoUpdateIfPossible();
130 gfx::Size MessageListView::GetPreferredSize() const {
131 int width = 0;
132 for (int i = 0; i < child_count(); i++) {
133 const views::View* child = child_at(i);
134 if (IsValidChild(child))
135 width = std::max(width, child->GetPreferredSize().width());
138 return gfx::Size(width + GetInsets().width(),
139 GetHeightForWidth(width + GetInsets().width()));
142 int MessageListView::GetHeightForWidth(int width) const {
143 if (fixed_height_ > 0)
144 return fixed_height_;
146 width -= GetInsets().width();
147 int height = 0;
148 int padding = 0;
149 for (int i = 0; i < child_count(); ++i) {
150 const views::View* child = child_at(i);
151 if (!IsValidChild(child))
152 continue;
153 height += child->GetHeightForWidth(width) + padding;
154 padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
157 return height + GetInsets().height();
160 void MessageListView::PaintChildren(const ui::PaintContext& context) {
161 // Paint in the inversed order. Otherwise upper notification may be
162 // hidden by the lower one.
163 for (int i = child_count() - 1; i >= 0; --i) {
164 if (!child_at(i)->layer())
165 child_at(i)->Paint(context);
169 void MessageListView::ReorderChildLayers(ui::Layer* parent_layer) {
170 // Reorder children to stack the last child layer at the top. Otherwise
171 // upper notification may be hidden by the lower one.
172 for (int i = 0; i < child_count(); ++i) {
173 if (child_at(i)->layer())
174 parent_layer->StackAtBottom(child_at(i)->layer());
178 void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
179 reposition_top_ = target.y();
180 fixed_height_ = GetHeightForWidth(width());
183 void MessageListView::ResetRepositionSession() {
184 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
185 // animation. Reset will cause the change of the bubble size itself, and
186 // animation from the old location will look weird.
187 if (reposition_top_ >= 0 && animator_.get()) {
188 has_deferred_task_ = false;
189 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
190 animator_->Cancel();
191 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
192 deleting_views_.clear();
193 adding_views_.clear();
194 animator_.reset();
197 reposition_top_ = -1;
198 fixed_height_ = 0;
201 void MessageListView::ClearAllNotifications(
202 const gfx::Rect& visible_scroll_rect) {
203 for (int i = 0; i < child_count(); ++i) {
204 views::View* child = child_at(i);
205 if (!child->visible())
206 continue;
207 if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
208 continue;
209 clearing_all_views_.push_back(child);
211 DoUpdateIfPossible();
214 void MessageListView::OnBoundsAnimatorProgressed(
215 views::BoundsAnimator* animator) {
216 DCHECK_EQ(animator_.get(), animator);
217 for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
218 iter != deleted_when_done_.end(); ++iter) {
219 const gfx::SlideAnimation* animation = animator->GetAnimationForView(*iter);
220 if (animation)
221 (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
225 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
226 STLDeleteContainerPointers(deleted_when_done_.begin(),
227 deleted_when_done_.end());
228 deleted_when_done_.clear();
230 if (clear_all_started_) {
231 clear_all_started_ = false;
232 message_center_view()->OnAllNotificationsCleared();
235 if (has_deferred_task_) {
236 has_deferred_task_ = false;
237 DoUpdateIfPossible();
240 if (GetWidget())
241 GetWidget()->SynthesizeMouseMoveEvent();
244 bool MessageListView::IsValidChild(const views::View* child) const {
245 return child->visible() &&
246 deleting_views_.find(const_cast<views::View*>(child)) ==
247 deleting_views_.end() &&
248 deleted_when_done_.find(const_cast<views::View*>(child)) ==
249 deleted_when_done_.end();
252 void MessageListView::DoUpdateIfPossible() {
253 gfx::Rect child_area = GetContentsBounds();
254 if (child_area.IsEmpty())
255 return;
257 if (animator_.get() && animator_->IsAnimating()) {
258 has_deferred_task_ = true;
259 return;
262 if (!animator_.get()) {
263 animator_.reset(new views::BoundsAnimator(this));
264 animator_->AddObserver(this);
267 if (!clearing_all_views_.empty()) {
268 AnimateClearingOneNotification();
269 return;
272 if (top_down_ ||
273 base::CommandLine::ForCurrentProcess()->HasSwitch(
274 switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval))
275 AnimateNotificationsBelowTarget();
276 else
277 AnimateNotificationsAboveTarget();
279 adding_views_.clear();
280 deleting_views_.clear();
283 void MessageListView::AnimateNotificationsBelowTarget() {
284 int last_index = -1;
285 for (int i = 0; i < child_count(); ++i) {
286 views::View* child = child_at(i);
287 if (!IsValidChild(child)) {
288 AnimateChild(child, child->y(), child->height());
289 } else if (reposition_top_ < 0 || child->y() > reposition_top_) {
290 // Find first notification below target (or all notifications if no
291 // target).
292 last_index = i;
293 break;
296 if (last_index > 0) {
297 int between_items =
298 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
299 int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top();
301 for (int i = last_index; i < child_count(); ++i) {
302 // Animate notifications below target upwards.
303 views::View* child = child_at(i);
304 if (AnimateChild(child, top, child->height()))
305 top += child->height() + between_items;
310 void MessageListView::AnimateNotificationsAboveTarget() {
311 int last_index = -1;
312 for (int i = child_count() - 1; i >= 0; --i) {
313 views::View* child = child_at(i);
314 if (!IsValidChild(child)) {
315 AnimateChild(child, child->y(), child->height());
316 } else if (reposition_top_ < 0 || child->y() < reposition_top_) {
317 // Find first notification above target (or all notifications if no
318 // target).
319 last_index = i;
320 break;
323 if (last_index >= 0) {
324 int between_items =
325 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
326 int bottom = (reposition_top_ > 0)
327 ? reposition_top_ + child_at(last_index)->height()
328 : GetHeightForWidth(width()) - GetInsets().bottom();
329 for (int i = last_index; i >= 0; --i) {
330 // Animate notifications above target downwards.
331 views::View* child = child_at(i);
332 if (AnimateChild(child, bottom - child->height(), child->height()))
333 bottom -= child->height() + between_items;
338 bool MessageListView::AnimateChild(views::View* child, int top, int height) {
339 gfx::Rect child_area = GetContentsBounds();
340 if (adding_views_.find(child) != adding_views_.end()) {
341 child->SetBounds(child_area.right(), top, child_area.width(), height);
342 animator_->AnimateViewTo(
343 child, gfx::Rect(child_area.x(), top, child_area.width(), height));
344 } else if (deleting_views_.find(child) != deleting_views_.end()) {
345 DCHECK(child->layer());
346 // No moves, but animate to fade-out.
347 animator_->AnimateViewTo(child, child->bounds());
348 deleted_when_done_.insert(child);
349 return false;
350 } else {
351 gfx::Rect target(child_area.x(), top, child_area.width(), height);
352 if (child->bounds().origin() != target.origin())
353 animator_->AnimateViewTo(child, target);
354 else
355 child->SetBoundsRect(target);
357 return true;
360 void MessageListView::AnimateClearingOneNotification() {
361 DCHECK(!clearing_all_views_.empty());
363 clear_all_started_ = true;
365 views::View* child = clearing_all_views_.front();
366 clearing_all_views_.pop_front();
368 // Slide from left to right.
369 gfx::Rect new_bounds = child->bounds();
370 new_bounds.set_x(new_bounds.right() + kMarginBetweenItems);
371 animator_->AnimateViewTo(child, new_bounds);
373 // Schedule to start sliding out next notification after a short delay.
374 if (!clearing_all_views_.empty()) {
375 base::MessageLoop::current()->PostDelayedTask(
376 FROM_HERE, base::Bind(&MessageListView::AnimateClearingOneNotification,
377 weak_ptr_factory_.GetWeakPtr()),
378 base::TimeDelta::FromMilliseconds(
379 kAnimateClearingNextNotificationDelayMS));
383 } // namespace message_center