Commit 16eb3ab1 authored by Tim Song's avatar Tim Song Committed by Commit Bot

Ash Tray: Animate hiding notification bar after dimissing notification.

Currently, the notification bar abruptly disappears after the second to last
notification is dismissed, which is quite jarring.

This same animation will also be used when the clear all animation is updated to
spec.

Video:
https://screencast.googleplex.com/cast/NDg3MjczMzQ3MzExMjA2NHw3ZmNmNGY5OC01Yw

TEST=manually verified, updated unit test
BUG=958276

Change-Id: I55bfeb4b23c1c2bda47ccd21dbef5a5955a62675
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1592701
Commit-Queue: Tim Song <tengs@chromium.org>
Reviewed-by: default avatarTetsui Ohkubo <tetsui@chromium.org>
Cr-Commit-Position: refs/heads/master@{#658683}
parent 31b550f3
......@@ -21,6 +21,7 @@
#include "ash/system/unified/unified_system_tray_view.h"
#include "base/metrics/user_metrics.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
......@@ -40,6 +41,9 @@ namespace ash {
namespace {
constexpr base::TimeDelta kHideStackingBarAnimationDuration =
base::TimeDelta::FromMilliseconds(330);
enum ClearAllButtonTag {
kStackingBarClearAllButtonTag,
kBottomClearAllButtonTag,
......@@ -205,7 +209,7 @@ bool StackingNotificationCounterView::SetCount(int total_notification_count,
stacked_notification_count_ = stacked_notification_count;
if (features::IsNotificationStackingBarRedesignEnabled()) {
SetVisible(total_notification_count_ > 1);
UpdateVisibility();
auto tooltip = l10n_util::GetStringFUTF16Int(
IDS_ASH_MESSAGE_CENTER_STACKING_BAR_CLEAR_ALL_BUTTON_TOOLTIP,
......@@ -229,6 +233,12 @@ bool StackingNotificationCounterView::SetCount(int total_notification_count,
return true;
}
void StackingNotificationCounterView::SetAnimationState(
UnifiedMessageCenterAnimationState animation_state) {
animation_state_ = animation_state;
UpdateVisibility();
}
void StackingNotificationCounterView::OnPaint(gfx::Canvas* canvas) {
cc::PaintFlags flags;
flags.setColor(message_center::kNotificationBackgroundColor);
......@@ -269,6 +279,20 @@ void StackingNotificationCounterView::OnPaint(gfx::Canvas* canvas) {
views::View::OnPaint(canvas);
}
void StackingNotificationCounterView::UpdateVisibility() {
switch (animation_state_) {
case UnifiedMessageCenterAnimationState::IDLE:
SetVisible(total_notification_count_ > 1);
break;
case UnifiedMessageCenterAnimationState::HIDE_STACKING_BAR:
SetVisible(true);
break;
case UnifiedMessageCenterAnimationState::COLLAPSE:
SetVisible(false);
break;
}
}
UnifiedMessageCenterView::UnifiedMessageCenterView(
UnifiedSystemTrayView* parent,
UnifiedSystemTrayModel* model)
......@@ -278,7 +302,8 @@ UnifiedMessageCenterView::UnifiedMessageCenterView(
scroll_bar_(new MessageCenterScrollBar(this)),
scroller_(new views::ScrollView()),
message_list_view_(new UnifiedMessageListView(this, model)),
last_scroll_position_from_bottom_(kClearAllButtonRowHeight) {
last_scroll_position_from_bottom_(kClearAllButtonRowHeight),
animation_(std::make_unique<gfx::LinearAnimation>(this)) {
message_list_view_->Init();
AddChildView(stacking_counter_);
......@@ -309,6 +334,13 @@ void UnifiedMessageCenterView::SetAvailableHeight(int available_height) {
UpdateVisibility();
}
void UnifiedMessageCenterView::OnNotificationSlidOut() {
if (stacking_counter_->visible() &&
message_list_view_->GetTotalNotificationCount() <= 1) {
StartHideStackingBarAnimation();
}
}
void UnifiedMessageCenterView::ListPreferredSizeChanged() {
UpdateVisibility();
PreferredSizeChanged();
......@@ -341,12 +373,20 @@ void UnifiedMessageCenterView::Layout() {
GetStackedNotificationCount());
if (stacking_counter_->visible()) {
gfx::Rect counter_bounds(GetContentsBounds());
counter_bounds.set_height(GetStackingNotificationCounterHeight());
int stacking_counter_height = GetStackingNotificationCounterHeight();
int stacking_counter_offset = 0;
if (animation_state_ ==
UnifiedMessageCenterAnimationState::HIDE_STACKING_BAR)
stacking_counter_offset = GetAnimationValue() * stacking_counter_height;
counter_bounds.set_height(stacking_counter_height);
counter_bounds.set_y(counter_bounds.y() - stacking_counter_offset);
stacking_counter_->SetBoundsRect(counter_bounds);
gfx::Rect scroller_bounds(GetContentsBounds());
scroller_bounds.Inset(
gfx::Insets(GetStackingNotificationCounterHeight(), 0, 0, 0));
scroller_bounds.Inset(gfx::Insets(
stacking_counter_height - stacking_counter_offset, 0, 0, 0));
scroller_->SetBoundsRect(scroller_bounds);
} else {
scroller_->SetBoundsRect(GetContentsBounds());
......@@ -360,8 +400,11 @@ gfx::Size UnifiedMessageCenterView::CalculatePreferredSize() const {
gfx::Size preferred_size = scroller_->GetPreferredSize();
if (stacking_counter_->visible()) {
preferred_size.set_height(preferred_size.height() +
GetStackingNotificationCounterHeight());
int bar_height = GetStackingNotificationCounterHeight();
if (animation_state_ ==
UnifiedMessageCenterAnimationState::HIDE_STACKING_BAR)
bar_height -= GetAnimationValue() * bar_height;
preferred_size.set_height(preferred_size.height() + bar_height);
}
// Hide Clear All button at the buttom from initial viewport.
......@@ -420,11 +463,53 @@ void UnifiedMessageCenterView::OnDidChangeFocus(views::View* before,
OnMessageCenterScrolled();
}
void UnifiedMessageCenterView::AnimationEnded(const gfx::Animation* animation) {
// This is also called from AnimationCanceled().
animation_->SetCurrentValue(1.0);
PreferredSizeChanged();
switch (animation_state_) {
case UnifiedMessageCenterAnimationState::IDLE:
break;
case UnifiedMessageCenterAnimationState::HIDE_STACKING_BAR:
break;
case UnifiedMessageCenterAnimationState::COLLAPSE:
NOTIMPLEMENTED();
break;
}
animation_state_ = UnifiedMessageCenterAnimationState::IDLE;
stacking_counter_->SetAnimationState(animation_state_);
}
void UnifiedMessageCenterView::AnimationProgressed(
const gfx::Animation* animation) {
PreferredSizeChanged();
}
void UnifiedMessageCenterView::AnimationCanceled(
const gfx::Animation* animation) {
AnimationEnded(animation);
}
void UnifiedMessageCenterView::SetNotificationRectBelowScroll(
const gfx::Rect& rect_below_scroll) {
parent_->SetNotificationRectBelowScroll(rect_below_scroll);
}
void UnifiedMessageCenterView::StartHideStackingBarAnimation() {
animation_->End();
animation_state_ = UnifiedMessageCenterAnimationState::HIDE_STACKING_BAR;
stacking_counter_->SetAnimationState(animation_state_);
animation_->SetDuration(kHideStackingBarAnimationDuration);
animation_->Start();
}
double UnifiedMessageCenterView::GetAnimationValue() const {
return gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN,
animation_->GetCurrentValue());
}
void UnifiedMessageCenterView::UpdateVisibility() {
SessionControllerImpl* session_controller =
Shell::Get()->session_controller();
......
......@@ -8,12 +8,17 @@
#include "ash/ash_export.h"
#include "ash/system/message_center/message_center_scroll_bar.h"
#include "ash/system/message_center/unified_message_list_view.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/label.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/view.h"
namespace gfx {
class LinearAnimation;
} // namespace gfx
namespace views {
class ScrollView;
} // namespace views
......@@ -24,6 +29,25 @@ class MessageCenterScrollBar;
class UnifiedSystemTrayModel;
class UnifiedSystemTrayView;
// Note: This enum represents the current animation state for
// UnifiedMessageCenterView. There is an equivalent animation state emum in
// the child UnifiedMessageListView. The animations for these two views can
// occur simultaneously or independently, so states for both views are tracked
// separately.
enum class UnifiedMessageCenterAnimationState {
// No animation is running.
IDLE,
// Animating hiding the stacking bar. Runs when the user dismisses the
// second to last notification and during the clear all animation.
HIDE_STACKING_BAR,
// Animating collapsing the entire message center. Runs after the user
// dismisses the last notification and during the clear all animation.
// TODO(tengs): This animation is not yet implemented.
COLLAPSE,
};
// The header shown above the notification list displaying the number of hidden
// notifications. There are currently two UI implementations toggled by the
// NotificationStackingBarRedesign feature flag.
......@@ -36,14 +60,21 @@ class StackingNotificationCounterView : public views::View {
// true if the count was updated from the previous SetCount() call.
bool SetCount(int total_notification_count, int stacked_notification_count);
// Sets the current animation state.
void SetAnimationState(UnifiedMessageCenterAnimationState animation_state);
// views::View:
void OnPaint(gfx::Canvas* canvas) override;
private:
friend class UnifiedMessageCenterViewTest;
void UpdateVisibility();
int total_notification_count_ = 0;
int stacked_notification_count_ = 0;
UnifiedMessageCenterAnimationState animation_state_ =
UnifiedMessageCenterAnimationState::IDLE;
// These UI elements are only created and shown when the
// NotificationStackingBarRedesign feature is enabled.
......@@ -58,7 +89,8 @@ class ASH_EXPORT UnifiedMessageCenterView
: public views::View,
public MessageCenterScrollBar::Observer,
public views::ButtonListener,
public views::FocusChangeListener {
public views::FocusChangeListener,
public gfx::AnimationDelegate {
public:
UnifiedMessageCenterView(UnifiedSystemTrayView* parent,
UnifiedSystemTrayModel* model);
......@@ -77,6 +109,10 @@ class ASH_EXPORT UnifiedMessageCenterView
// Called from UnifiedMessageListView when the preferred size is changed.
void ListPreferredSizeChanged();
// Called from the UnifiedMessageListView after a notification is dismissed by
// the user and the slide animation is finished.
void OnNotificationSlidOut();
// Configures MessageView to forward scroll events. Called from
// UnifiedMessageListView.
void ConfigureMessageView(message_center::MessageView* message_view);
......@@ -100,6 +136,11 @@ class ASH_EXPORT UnifiedMessageCenterView
void OnWillChangeFocus(views::View* before, views::View* now) override;
void OnDidChangeFocus(views::View* before, views::View* now) override;
// gfx::AnimationDelegate:
void AnimationEnded(const gfx::Animation* animation) override;
void AnimationProgressed(const gfx::Animation* animation) override;
void AnimationCanceled(const gfx::Animation* animation) override;
protected:
// Virtual for testing.
virtual void SetNotificationRectBelowScroll(
......@@ -108,6 +149,14 @@ class ASH_EXPORT UnifiedMessageCenterView
private:
friend class UnifiedMessageCenterViewTest;
// Starts the animation to hide the notification stacking bar.
void StartHideStackingBarAnimation();
// Returns the current animation value after tweening.
double GetAnimationValue() const;
// Decides whether the message center should be shown or not based on
// current state.
void UpdateVisibility();
// Scroll the notification list to the target position.
......@@ -132,6 +181,11 @@ class ASH_EXPORT UnifiedMessageCenterView
// or collapsed).
int available_height_ = 0;
// Tracks the current animation state.
UnifiedMessageCenterAnimationState animation_state_ =
UnifiedMessageCenterAnimationState::IDLE;
const std::unique_ptr<gfx::LinearAnimation> animation_;
views::FocusManager* focus_manager_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(UnifiedMessageCenterView);
......
......@@ -118,17 +118,17 @@ class UnifiedMessageCenterViewTest : public AshTestBase,
size_changed_count_ = 0;
}
void AnimateToValue(float value) {
void AnimateMessageListToValue(float value) {
GetMessageListView()->animation_->SetCurrentValue(value);
GetMessageListView()->AnimationProgressed(
GetMessageListView()->animation_.get());
}
void AnimateToMiddle() { AnimateToValue(0.5); }
void AnimateMessageListToMiddle() { AnimateMessageListToValue(0.5); }
void AnimateToEnd() { GetMessageListView()->animation_->End(); }
void AnimateMessageListToEnd() { GetMessageListView()->animation_->End(); }
void AnimateUntilIdle() {
void AnimateMessageListUntilIdle() {
while (GetMessageListView()->animation_->is_animating())
GetMessageListView()->animation_->End();
}
......@@ -144,6 +144,10 @@ class UnifiedMessageCenterViewTest : public AshTestBase,
return message_center_view()->message_list_view_;
}
gfx::LinearAnimation* GetMessageCenterAnimation() {
return message_center_view()->animation_.get();
}
views::ScrollView* GetScroller() { return message_center_view()->scroller_; }
MessageCenterScrollBar* GetScrollBar() {
......@@ -224,10 +228,10 @@ TEST_F(UnifiedMessageCenterViewTest, AddAndRemoveNotification) {
GetScroller()->GetVisibleRect().bottom());
MessageCenter::Get()->RemoveNotification(id0, true /* by_user */);
AnimateToEnd();
AnimateToMiddle();
AnimateMessageListToEnd();
AnimateMessageListToMiddle();
EXPECT_TRUE(message_center_view()->visible());
AnimateToEnd();
AnimateMessageListToEnd();
EXPECT_FALSE(message_center_view()->visible());
}
......@@ -251,8 +255,8 @@ TEST_F(UnifiedMessageCenterViewTest, RemoveNotificationAtTail) {
// The first animation slides the notification out of the list, and the second
// animation collapses the list.
AnimateToEnd();
AnimateToValue(0);
AnimateMessageListToEnd();
AnimateMessageListToValue(0);
// The scroll position should not change after sliding the notification out
// and instead should wait until the animation finishes.
......@@ -260,7 +264,7 @@ TEST_F(UnifiedMessageCenterViewTest, RemoveNotificationAtTail) {
// The scroll position should be reduced by the height of the removed
// notification after collapsing.
AnimateToEnd();
AnimateMessageListToEnd();
EXPECT_EQ(scroll_position - GetMessageViewVisibleBounds(0).height(),
GetScroller()->GetVisibleRect().y());
......@@ -283,7 +287,7 @@ TEST_F(UnifiedMessageCenterViewTest, ContentsRelayout) {
const int previous_list_height = GetMessageListView()->height();
MessageCenter::Get()->RemoveNotification(ids.back(), true /* by_user */);
AnimateUntilIdle();
AnimateMessageListUntilIdle();
EXPECT_TRUE(message_center_view()->visible());
EXPECT_GT(previous_contents_height, GetScrollerContents()->height());
EXPECT_GT(previous_list_height, GetMessageListView()->height());
......@@ -368,7 +372,7 @@ TEST_F(UnifiedMessageCenterViewTest, ClearAllPressed) {
// When Clear All button is pressed, all notifications are removed and the
// view becomes invisible.
message_center_view()->ButtonPressed(nullptr, DummyEvent());
AnimateUntilIdle();
AnimateMessageListUntilIdle();
EXPECT_FALSE(message_center_view()->visible());
}
......@@ -521,7 +525,7 @@ TEST_F(UnifiedMessageCenterViewTest, StackingCounterRemovedWithNotifications) {
EXPECT_TRUE(GetStackingCounter()->visible());
for (size_t i = 0; i < 5; ++i) {
MessageCenter::Get()->RemoveNotification(ids[i], true /* by_user */);
AnimateUntilIdle();
AnimateMessageListUntilIdle();
}
EXPECT_FALSE(GetStackingCounter()->visible());
}
......@@ -615,7 +619,7 @@ TEST_F(UnifiedMessageCenterViewTest,
EXPECT_TRUE(GetStackingCounter()->visible());
for (size_t i = 0; i < 4; ++i) {
MessageCenter::Get()->RemoveNotification(ids[i], true /* by_user */);
AnimateUntilIdle();
AnimateMessageListUntilIdle();
}
EXPECT_TRUE(GetStackingCounter()->visible());
EXPECT_FALSE(GetStackingCounterLabel()->visible());
......@@ -629,9 +633,25 @@ TEST_F(UnifiedMessageCenterViewTest,
message_center_view()->height());
// Dismiss until there is only 1 notification left. The bar should be
// invisible.
// hidden after an animation.
MessageCenter::Get()->RemoveNotification(ids[4], true /* by_user */);
AnimateUntilIdle();
EXPECT_TRUE(GetStackingCounter()->visible());
// The HIDE_STACKING_BAR animation starts after the notification is slid out.
AnimateMessageListToEnd();
auto* hide_animation = GetMessageCenterAnimation();
EXPECT_TRUE(hide_animation->is_animating());
EXPECT_TRUE(GetStackingCounter()->visible());
// Animate to middle. The bar should still be visible.
AnimateMessageListToMiddle();
hide_animation->SetCurrentValue(0.5);
message_center_view()->AnimationProgressed(hide_animation);
EXPECT_TRUE(GetStackingCounter()->visible());
// Animate to end. The bar should now be hidden.
AnimateMessageListToEnd();
hide_animation->End();
EXPECT_FALSE(GetStackingCounter()->visible());
}
......@@ -778,7 +798,7 @@ TEST_F(UnifiedMessageCenterViewTest, FocusClearedAfterNotificationRemoval) {
// Remove the notification and observe that the focus is cleared.
MessageCenter::Get()->RemoveNotification(id1, true /* by_user */);
AnimateUntilIdle();
AnimateMessageListUntilIdle();
EXPECT_FALSE(message_center_view()->GetFocusManager()->GetFocusedView());
widget->GetRootView()->RemoveChildView(message_center_view());
......
......@@ -395,6 +395,8 @@ void UnifiedMessageListView::AnimationEnded(const gfx::Animation* animation) {
UpdateClearAllAnimation();
}
UpdateBorders();
if (state_ != State::IDLE)
StartAnimation();
}
......@@ -457,9 +459,11 @@ void UnifiedMessageListView::CollapseAllNotifications() {
}
void UnifiedMessageListView::UpdateBorders() {
// When the stacking bar is shown, there should never be a top notification.
bool is_top = !features::IsNotificationStackingBarRedesignEnabled() ||
children().size() == 1;
// The top notification is drawn with rounded corners when the stacking bar is
// not shown.
bool is_top = (!features::IsNotificationStackingBarRedesignEnabled() ||
children().size() == 1) &&
state_ != State::MOVE_DOWN;
for (auto* child : children()) {
AsMVC(child)->UpdateBorder(is_top, child == children().back());
is_top = false;
......@@ -532,8 +536,13 @@ void UnifiedMessageListView::StartAnimation() {
case State::IDLE:
break;
case State::SLIDE_OUT:
FALLTHROUGH;
animation_->SetDuration(kClosingAnimationDuration);
animation_->Start();
break;
case State::MOVE_DOWN:
// |message_center_view_| can be null in tests.
if (message_center_view_)
message_center_view_->OnNotificationSlidOut();
animation_->SetDuration(kClosingAnimationDuration);
animation_->Start();
break;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment