OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 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" |
| 17 |
| 18 namespace message_center { |
| 19 |
| 20 namespace { |
| 21 const int kAnimateClearingNextNotificationDelayMS = 40; |
| 22 } // namespace |
| 23 |
| 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); |
| 37 |
| 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 */)); |
| 50 } |
| 51 |
| 52 MessageListView::~MessageListView() { |
| 53 if (animator_.get()) |
| 54 animator_->RemoveObserver(this); |
| 55 } |
| 56 |
| 57 void MessageListView::Layout() { |
| 58 if (animator_.get()) |
| 59 return; |
| 60 |
| 61 gfx::Rect child_area = GetContentsBounds(); |
| 62 int top = child_area.y(); |
| 63 int between_items = |
| 64 kMarginBetweenItems - MessageView::GetShadowInsets().bottom(); |
| 65 |
| 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; |
| 73 } |
| 74 } |
| 75 |
| 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; |
| 87 } |
| 88 ++real_index; |
| 89 } |
| 90 |
| 91 AddChildViewAt(view, real_index); |
| 92 if (GetContentsBounds().IsEmpty()) |
| 93 return; |
| 94 |
| 95 adding_views_.insert(view); |
| 96 DoUpdateIfPossible(); |
| 97 } |
| 98 |
| 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; |
| 110 } |
| 111 DoUpdateIfPossible(); |
| 112 } |
| 113 } |
| 114 |
| 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. |
| 119 |
| 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(); |
| 128 } |
| 129 |
| 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()); |
| 136 } |
| 137 |
| 138 return gfx::Size(width + GetInsets().width(), |
| 139 GetHeightForWidth(width + GetInsets().width())); |
| 140 } |
| 141 |
| 142 int MessageListView::GetHeightForWidth(int width) const { |
| 143 if (fixed_height_ > 0) |
| 144 return fixed_height_; |
| 145 |
| 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(); |
| 155 } |
| 156 |
| 157 return height + GetInsets().height(); |
| 158 } |
| 159 |
| 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); |
| 166 } |
| 167 } |
| 168 |
| 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()); |
| 175 } |
| 176 } |
| 177 |
| 178 void MessageListView::SetRepositionTarget(const gfx::Rect& target) { |
| 179 reposition_top_ = target.y(); |
| 180 fixed_height_ = GetHeightForWidth(width()); |
| 181 } |
| 182 |
| 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(); |
| 195 } |
| 196 |
| 197 reposition_top_ = -1; |
| 198 fixed_height_ = 0; |
| 199 } |
| 200 |
| 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); |
| 210 } |
| 211 DoUpdateIfPossible(); |
| 212 } |
| 213 |
| 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)); |
| 222 } |
| 223 } |
| 224 |
| 225 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) { |
| 226 STLDeleteContainerPointers(deleted_when_done_.begin(), |
| 227 deleted_when_done_.end()); |
| 228 deleted_when_done_.clear(); |
| 229 |
| 230 if (clear_all_started_) { |
| 231 clear_all_started_ = false; |
| 232 message_center_view()->OnAllNotificationsCleared(); |
| 233 } |
| 234 |
| 235 if (has_deferred_task_) { |
| 236 has_deferred_task_ = false; |
| 237 DoUpdateIfPossible(); |
| 238 } |
| 239 |
| 240 if (GetWidget()) |
| 241 GetWidget()->SynthesizeMouseMoveEvent(); |
| 242 } |
| 243 |
| 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(); |
| 250 } |
| 251 |
| 252 void MessageListView::DoUpdateIfPossible() { |
| 253 gfx::Rect child_area = GetContentsBounds(); |
| 254 if (child_area.IsEmpty()) |
| 255 return; |
| 256 |
| 257 if (animator_.get() && animator_->IsAnimating()) { |
| 258 has_deferred_task_ = true; |
| 259 return; |
| 260 } |
| 261 |
| 262 if (!animator_.get()) { |
| 263 animator_.reset(new views::BoundsAnimator(this)); |
| 264 animator_->AddObserver(this); |
| 265 } |
| 266 |
| 267 if (!clearing_all_views_.empty()) { |
| 268 AnimateClearingOneNotification(); |
| 269 return; |
| 270 } |
| 271 |
| 272 if (top_down_ || |
| 273 base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 274 switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval)) |
| 275 AnimateNotificationsBelowTarget(); |
| 276 else |
| 277 AnimateNotificationsAboveTarget(); |
| 278 |
| 279 adding_views_.clear(); |
| 280 deleting_views_.clear(); |
| 281 } |
| 282 |
| 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; |
| 294 } |
| 295 } |
| 296 if (last_index > 0) { |
| 297 int between_items = |
| 298 kMarginBetweenItems - MessageView::GetShadowInsets().bottom(); |
| 299 int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top(); |
| 300 |
| 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; |
| 306 } |
| 307 } |
| 308 } |
| 309 |
| 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; |
| 321 } |
| 322 } |
| 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; |
| 334 } |
| 335 } |
| 336 } |
| 337 |
| 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); |
| 356 } |
| 357 return true; |
| 358 } |
| 359 |
| 360 void MessageListView::AnimateClearingOneNotification() { |
| 361 DCHECK(!clearing_all_views_.empty()); |
| 362 |
| 363 clear_all_started_ = true; |
| 364 |
| 365 views::View* child = clearing_all_views_.front(); |
| 366 clearing_all_views_.pop_front(); |
| 367 |
| 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); |
| 372 |
| 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)); |
| 380 } |
| 381 } |
| 382 |
| 383 } // namespace message_center |
OLD | NEW |