| Index: ui/message_center/views/message_list_view.cc
|
| diff --git a/ui/message_center/views/message_list_view.cc b/ui/message_center/views/message_list_view.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..161d0bfe5d069662b55bb576c0aae08eab950585
|
| --- /dev/null
|
| +++ b/ui/message_center/views/message_list_view.cc
|
| @@ -0,0 +1,383 @@
|
| +// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "base/command_line.h"
|
| +#include "ui/gfx/animation/slide_animation.h"
|
| +#include "ui/message_center/message_center_style.h"
|
| +#include "ui/message_center/message_center_switches.h"
|
| +#include "ui/message_center/views/message_center_view.h"
|
| +#include "ui/message_center/views/message_list_view.h"
|
| +#include "ui/message_center/views/message_view.h"
|
| +#include "ui/views/animation/bounds_animator.h"
|
| +#include "ui/views/background.h"
|
| +#include "ui/views/border.h"
|
| +#include "ui/views/layout/box_layout.h"
|
| +#include "ui/views/widget/widget.h"
|
| +
|
| +namespace message_center {
|
| +
|
| +namespace {
|
| +const int kAnimateClearingNextNotificationDelayMS = 40;
|
| +} // namespace
|
| +
|
| +MessageListView::MessageListView(MessageCenterView* message_center_view,
|
| + bool top_down)
|
| + : message_center_view_(message_center_view),
|
| + reposition_top_(-1),
|
| + fixed_height_(0),
|
| + has_deferred_task_(false),
|
| + clear_all_started_(false),
|
| + top_down_(top_down),
|
| + weak_ptr_factory_(this) {
|
| + views::BoxLayout* layout =
|
| + new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
|
| + layout->SetDefaultFlex(1);
|
| + SetLayoutManager(layout);
|
| +
|
| + // Set the margin to 0 for the layout. BoxLayout assumes the same margin
|
| + // for top and bottom, but the bottom margin here should be smaller
|
| + // because of the shadow of message view. Use an empty border instead
|
| + // to provide this margin.
|
| + gfx::Insets shadow_insets = MessageView::GetShadowInsets();
|
| + set_background(
|
| + views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
|
| + SetBorder(views::Border::CreateEmptyBorder(
|
| + top_down ? 0 : kMarginBetweenItems - shadow_insets.top(), /* top */
|
| + kMarginBetweenItems - shadow_insets.left(), /* left */
|
| + top_down ? kMarginBetweenItems - shadow_insets.bottom() : 0, /* bottom */
|
| + kMarginBetweenItems - shadow_insets.right() /* right */));
|
| +}
|
| +
|
| +MessageListView::~MessageListView() {
|
| + if (animator_.get())
|
| + animator_->RemoveObserver(this);
|
| +}
|
| +
|
| +void MessageListView::Layout() {
|
| + if (animator_.get())
|
| + return;
|
| +
|
| + gfx::Rect child_area = GetContentsBounds();
|
| + int top = child_area.y();
|
| + int between_items =
|
| + kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
|
| +
|
| + for (int i = 0; i < child_count(); ++i) {
|
| + views::View* child = child_at(i);
|
| + if (!child->visible())
|
| + continue;
|
| + int height = child->GetHeightForWidth(child_area.width());
|
| + child->SetBounds(child_area.x(), top, child_area.width(), height);
|
| + top += height + between_items;
|
| + }
|
| +}
|
| +
|
| +void MessageListView::AddNotificationAt(MessageView* view, int index) {
|
| + // |index| refers to a position in a subset of valid children. |real_index|
|
| + // in a list includes the invalid children, so we compute the real index by
|
| + // walking the list until |index| number of valid children are encountered,
|
| + // or to the end of the list.
|
| + int real_index = 0;
|
| + while (real_index < child_count()) {
|
| + if (IsValidChild(child_at(real_index))) {
|
| + --index;
|
| + if (index < 0)
|
| + break;
|
| + }
|
| + ++real_index;
|
| + }
|
| +
|
| + AddChildViewAt(view, real_index);
|
| + if (GetContentsBounds().IsEmpty())
|
| + return;
|
| +
|
| + adding_views_.insert(view);
|
| + DoUpdateIfPossible();
|
| +}
|
| +
|
| +void MessageListView::RemoveNotification(MessageView* view) {
|
| + DCHECK_EQ(view->parent(), this);
|
| + if (GetContentsBounds().IsEmpty()) {
|
| + delete view;
|
| + } else {
|
| + if (view->layer()) {
|
| + deleting_views_.insert(view);
|
| + } else {
|
| + if (animator_.get())
|
| + animator_->StopAnimatingView(view);
|
| + delete view;
|
| + }
|
| + DoUpdateIfPossible();
|
| + }
|
| +}
|
| +
|
| +void MessageListView::UpdateNotification(MessageView* view,
|
| + const Notification& notification) {
|
| + int index = GetIndexOf(view);
|
| + DCHECK_LE(0, index); // GetIndexOf is negative if not a child.
|
| +
|
| + if (animator_.get())
|
| + animator_->StopAnimatingView(view);
|
| + if (deleting_views_.find(view) != deleting_views_.end())
|
| + deleting_views_.erase(view);
|
| + if (deleted_when_done_.find(view) != deleted_when_done_.end())
|
| + deleted_when_done_.erase(view);
|
| + view->UpdateWithNotification(notification);
|
| + DoUpdateIfPossible();
|
| +}
|
| +
|
| +gfx::Size MessageListView::GetPreferredSize() const {
|
| + int width = 0;
|
| + for (int i = 0; i < child_count(); i++) {
|
| + const views::View* child = child_at(i);
|
| + if (IsValidChild(child))
|
| + width = std::max(width, child->GetPreferredSize().width());
|
| + }
|
| +
|
| + return gfx::Size(width + GetInsets().width(),
|
| + GetHeightForWidth(width + GetInsets().width()));
|
| +}
|
| +
|
| +int MessageListView::GetHeightForWidth(int width) const {
|
| + if (fixed_height_ > 0)
|
| + return fixed_height_;
|
| +
|
| + width -= GetInsets().width();
|
| + int height = 0;
|
| + int padding = 0;
|
| + for (int i = 0; i < child_count(); ++i) {
|
| + const views::View* child = child_at(i);
|
| + if (!IsValidChild(child))
|
| + continue;
|
| + height += child->GetHeightForWidth(width) + padding;
|
| + padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
|
| + }
|
| +
|
| + return height + GetInsets().height();
|
| +}
|
| +
|
| +void MessageListView::PaintChildren(const ui::PaintContext& context) {
|
| + // Paint in the inversed order. Otherwise upper notification may be
|
| + // hidden by the lower one.
|
| + for (int i = child_count() - 1; i >= 0; --i) {
|
| + if (!child_at(i)->layer())
|
| + child_at(i)->Paint(context);
|
| + }
|
| +}
|
| +
|
| +void MessageListView::ReorderChildLayers(ui::Layer* parent_layer) {
|
| + // Reorder children to stack the last child layer at the top. Otherwise
|
| + // upper notification may be hidden by the lower one.
|
| + for (int i = 0; i < child_count(); ++i) {
|
| + if (child_at(i)->layer())
|
| + parent_layer->StackAtBottom(child_at(i)->layer());
|
| + }
|
| +}
|
| +
|
| +void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
|
| + reposition_top_ = target.y();
|
| + fixed_height_ = GetHeightForWidth(width());
|
| +}
|
| +
|
| +void MessageListView::ResetRepositionSession() {
|
| + // Don't call DoUpdateIfPossible(), but let Layout() do the task without
|
| + // animation. Reset will cause the change of the bubble size itself, and
|
| + // animation from the old location will look weird.
|
| + if (reposition_top_ >= 0 && animator_.get()) {
|
| + has_deferred_task_ = false;
|
| + // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
|
| + animator_->Cancel();
|
| + STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
|
| + deleting_views_.clear();
|
| + adding_views_.clear();
|
| + animator_.reset();
|
| + }
|
| +
|
| + reposition_top_ = -1;
|
| + fixed_height_ = 0;
|
| +}
|
| +
|
| +void MessageListView::ClearAllNotifications(
|
| + const gfx::Rect& visible_scroll_rect) {
|
| + for (int i = 0; i < child_count(); ++i) {
|
| + views::View* child = child_at(i);
|
| + if (!child->visible())
|
| + continue;
|
| + if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
|
| + continue;
|
| + clearing_all_views_.push_back(child);
|
| + }
|
| + DoUpdateIfPossible();
|
| +}
|
| +
|
| +void MessageListView::OnBoundsAnimatorProgressed(
|
| + views::BoundsAnimator* animator) {
|
| + DCHECK_EQ(animator_.get(), animator);
|
| + for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
|
| + iter != deleted_when_done_.end(); ++iter) {
|
| + const gfx::SlideAnimation* animation = animator->GetAnimationForView(*iter);
|
| + if (animation)
|
| + (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
|
| + }
|
| +}
|
| +
|
| +void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
|
| + STLDeleteContainerPointers(deleted_when_done_.begin(),
|
| + deleted_when_done_.end());
|
| + deleted_when_done_.clear();
|
| +
|
| + if (clear_all_started_) {
|
| + clear_all_started_ = false;
|
| + message_center_view()->OnAllNotificationsCleared();
|
| + }
|
| +
|
| + if (has_deferred_task_) {
|
| + has_deferred_task_ = false;
|
| + DoUpdateIfPossible();
|
| + }
|
| +
|
| + if (GetWidget())
|
| + GetWidget()->SynthesizeMouseMoveEvent();
|
| +}
|
| +
|
| +bool MessageListView::IsValidChild(const views::View* child) const {
|
| + return child->visible() &&
|
| + deleting_views_.find(const_cast<views::View*>(child)) ==
|
| + deleting_views_.end() &&
|
| + deleted_when_done_.find(const_cast<views::View*>(child)) ==
|
| + deleted_when_done_.end();
|
| +}
|
| +
|
| +void MessageListView::DoUpdateIfPossible() {
|
| + gfx::Rect child_area = GetContentsBounds();
|
| + if (child_area.IsEmpty())
|
| + return;
|
| +
|
| + if (animator_.get() && animator_->IsAnimating()) {
|
| + has_deferred_task_ = true;
|
| + return;
|
| + }
|
| +
|
| + if (!animator_.get()) {
|
| + animator_.reset(new views::BoundsAnimator(this));
|
| + animator_->AddObserver(this);
|
| + }
|
| +
|
| + if (!clearing_all_views_.empty()) {
|
| + AnimateClearingOneNotification();
|
| + return;
|
| + }
|
| +
|
| + if (top_down_ ||
|
| + base::CommandLine::ForCurrentProcess()->HasSwitch(
|
| + switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval))
|
| + AnimateNotificationsBelowTarget();
|
| + else
|
| + AnimateNotificationsAboveTarget();
|
| +
|
| + adding_views_.clear();
|
| + deleting_views_.clear();
|
| +}
|
| +
|
| +void MessageListView::AnimateNotificationsBelowTarget() {
|
| + int last_index = -1;
|
| + for (int i = 0; i < child_count(); ++i) {
|
| + views::View* child = child_at(i);
|
| + if (!IsValidChild(child)) {
|
| + AnimateChild(child, child->y(), child->height());
|
| + } else if (reposition_top_ < 0 || child->y() > reposition_top_) {
|
| + // Find first notification below target (or all notifications if no
|
| + // target).
|
| + last_index = i;
|
| + break;
|
| + }
|
| + }
|
| + if (last_index > 0) {
|
| + int between_items =
|
| + kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
|
| + int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top();
|
| +
|
| + for (int i = last_index; i < child_count(); ++i) {
|
| + // Animate notifications below target upwards.
|
| + views::View* child = child_at(i);
|
| + if (AnimateChild(child, top, child->height()))
|
| + top += child->height() + between_items;
|
| + }
|
| + }
|
| +}
|
| +
|
| +void MessageListView::AnimateNotificationsAboveTarget() {
|
| + int last_index = -1;
|
| + for (int i = child_count() - 1; i >= 0; --i) {
|
| + views::View* child = child_at(i);
|
| + if (!IsValidChild(child)) {
|
| + AnimateChild(child, child->y(), child->height());
|
| + } else if (reposition_top_ < 0 || child->y() < reposition_top_) {
|
| + // Find first notification above target (or all notifications if no
|
| + // target).
|
| + last_index = i;
|
| + break;
|
| + }
|
| + }
|
| + if (last_index >= 0) {
|
| + int between_items =
|
| + kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
|
| + int bottom = (reposition_top_ > 0)
|
| + ? reposition_top_ + child_at(last_index)->height()
|
| + : GetHeightForWidth(width()) - GetInsets().bottom();
|
| + for (int i = last_index; i >= 0; --i) {
|
| + // Animate notifications above target downwards.
|
| + views::View* child = child_at(i);
|
| + if (AnimateChild(child, bottom - child->height(), child->height()))
|
| + bottom -= child->height() + between_items;
|
| + }
|
| + }
|
| +}
|
| +
|
| +bool MessageListView::AnimateChild(views::View* child, int top, int height) {
|
| + gfx::Rect child_area = GetContentsBounds();
|
| + if (adding_views_.find(child) != adding_views_.end()) {
|
| + child->SetBounds(child_area.right(), top, child_area.width(), height);
|
| + animator_->AnimateViewTo(
|
| + child, gfx::Rect(child_area.x(), top, child_area.width(), height));
|
| + } else if (deleting_views_.find(child) != deleting_views_.end()) {
|
| + DCHECK(child->layer());
|
| + // No moves, but animate to fade-out.
|
| + animator_->AnimateViewTo(child, child->bounds());
|
| + deleted_when_done_.insert(child);
|
| + return false;
|
| + } else {
|
| + gfx::Rect target(child_area.x(), top, child_area.width(), height);
|
| + if (child->bounds().origin() != target.origin())
|
| + animator_->AnimateViewTo(child, target);
|
| + else
|
| + child->SetBoundsRect(target);
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +void MessageListView::AnimateClearingOneNotification() {
|
| + DCHECK(!clearing_all_views_.empty());
|
| +
|
| + clear_all_started_ = true;
|
| +
|
| + views::View* child = clearing_all_views_.front();
|
| + clearing_all_views_.pop_front();
|
| +
|
| + // Slide from left to right.
|
| + gfx::Rect new_bounds = child->bounds();
|
| + new_bounds.set_x(new_bounds.right() + kMarginBetweenItems);
|
| + animator_->AnimateViewTo(child, new_bounds);
|
| +
|
| + // Schedule to start sliding out next notification after a short delay.
|
| + if (!clearing_all_views_.empty()) {
|
| + base::MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE, base::Bind(&MessageListView::AnimateClearingOneNotification,
|
| + weak_ptr_factory_.GetWeakPtr()),
|
| + base::TimeDelta::FromMilliseconds(
|
| + kAnimateClearingNextNotificationDelayMS));
|
| + }
|
| +}
|
| +
|
| +} // namespace message_center
|
|
|