Chromium Code Reviews| Index: ash/common/system/web_notification/web_notification_tray.cc |
| diff --git a/ash/common/system/web_notification/web_notification_tray.cc b/ash/common/system/web_notification/web_notification_tray.cc |
| index feba9fffdf06e91fba9d42e6d2c0e151a1782607..98fd7d2c83089937c4cfdd76d267c850dc6a7bc8 100644 |
| --- a/ash/common/system/web_notification/web_notification_tray.cc |
| +++ b/ash/common/system/web_notification/web_notification_tray.cc |
| @@ -63,8 +63,20 @@ namespace ash { |
| namespace { |
| // Menu commands |
| -const int kToggleQuietMode = 0; |
| -const int kEnableQuietModeDay = 2; |
| +constexpr int kToggleQuietMode = 0; |
| +constexpr int kEnableQuietModeDay = 2; |
| + |
| +constexpr int kMaximumSmallIconCount = 3; |
| + |
| +constexpr gfx::Size kTrayItemInnerIconSize(16, 16); |
| +constexpr gfx::Size kTrayItemInnerBellIconSize(18, 18); |
| +constexpr gfx::Size kTrayItemOuterSize(26, 26); |
| +constexpr gfx::Insets kTrayItemInsets(6, 6); |
| + |
| +constexpr int kTrayItemAnimationDurationMS = 200; |
| + |
| +// Flag to disable animation. Only for testing. |
| +static bool disable_animations_for_test = false; |
| } |
| namespace { |
| @@ -115,92 +127,162 @@ class WebNotificationBubbleWrapper { |
| DISALLOW_COPY_AND_ASSIGN(WebNotificationBubbleWrapper); |
| }; |
| -class WebNotificationButton : public views::CustomButton { |
| +class WebNotificationItem : public views::View, public gfx::AnimationDelegate { |
| public: |
| - WebNotificationButton(views::ButtonListener* listener) |
| - : views::CustomButton(listener), |
| - is_bubble_visible_(false), |
| - unread_count_(0) { |
| - SetLayoutManager(new views::FillLayout); |
| - |
| - gfx::ImageSkia image; |
| - if (MaterialDesignController::IsShelfMaterial()) { |
| - image = CreateVectorIcon(gfx::VectorIconId::SHELF_NOTIFICATIONS, |
| - kShelfIconColor); |
| - } else { |
| - image = |
| - CreateVectorIcon(gfx::VectorIconId::NOTIFICATIONS, kNoUnreadIconSize, |
| - kWebNotificationColorNoUnread); |
| - } |
| + WebNotificationItem(gfx::AnimationContainer* container, |
| + WebNotificationTray* tray) |
| + : tray_(tray) { |
| + SetPaintToLayer(true); |
| + layer()->SetFillsBoundsOpaquely(false); |
| + views::View::SetVisible(false); |
| + set_owned_by_client(); |
| - no_unread_icon_.SetImage(image); |
| - no_unread_icon_.set_owned_by_client(); |
| - |
| - unread_label_.set_owned_by_client(); |
| - SetupLabelForTray(&unread_label_); |
| + SetLayoutManager(new views::FillLayout); |
| - AddChildView(&no_unread_icon_); |
| + animation_.reset(new gfx::SlideAnimation(this)); |
| + animation_->SetContainer(container); |
| + animation_->SetSlideDuration(kTrayItemAnimationDurationMS); |
| + animation_->SetTweenType(gfx::Tween::LINEAR); |
| } |
| - void SetBubbleVisible(bool visible) { |
| - if (visible == is_bubble_visible_) |
| + void SetVisible(bool set_visible) override { |
| + if (!GetWidget() || disable_animations_for_test) { |
| + views::View::SetVisible(set_visible); |
| return; |
| + } |
| - is_bubble_visible_ = visible; |
| - UpdateIconVisibility(); |
| + if (!set_visible) { |
| + animation_->Hide(); |
| + AnimationProgressed(animation_.get()); |
| + } else { |
| + animation_->Show(); |
| + AnimationProgressed(animation_.get()); |
| + views::View::SetVisible(true); |
| + } |
| } |
| - void SetUnreadCount(int unread_count) { |
| - // base::FormatNumber doesn't convert to arabic numeric characters. |
| - // TODO(mukai): use ICU to support conversion for such locales. |
| - unread_count_ = unread_count; |
| - UpdateIconVisibility(); |
| + void HideAndDelete() { |
| + SetVisible(false); |
| + |
| + if (!visible() && !animation_->is_animating()) { |
| + if (parent()) |
| + parent()->RemoveChildView(this); |
| + base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this); |
| + } else { |
| + delete_after_animation_ = true; |
| + } |
| } |
| protected: |
| // Overridden from views::ImageButton: |
| gfx::Size GetPreferredSize() const override { |
| - const int size = GetTrayConstant(TRAY_ITEM_HEIGHT_LEGACY); |
| - return gfx::Size(size, size); |
| + if (!animation_.get() || !animation_->is_animating()) |
| + return kTrayItemOuterSize; |
| + |
| + // Animate the width (or height) when this item shows (or hides). |
| + // Note that other tray items do the same effect. |
|
oshima
2016/08/05 15:08:08
This does not explain why you want to change the s
yoshiki
2016/08/05 16:15:38
Yes, I'd like to shift the left buttons along with
|
| + gfx::Size size = kTrayItemOuterSize; |
| + if (IsHorizontalLayout()) { |
| + size.set_width(std::max( |
| + 1, gfx::ToRoundedInt(size.width() * animation_->GetCurrentValue()))); |
| + } else { |
| + size.set_height(std::max( |
| + 1, gfx::ToRoundedInt(size.height() * animation_->GetCurrentValue()))); |
| + } |
| + return size; |
| } |
| int GetHeightForWidth(int width) const override { |
| return GetPreferredSize().height(); |
| } |
| + bool IsHorizontalLayout() const { |
| + return IsHorizontalAlignment(tray_->shelf_alignment()); |
| + } |
| + |
| private: |
| - void UpdateIconVisibility() { |
| - if (unread_count_ == 0) { |
| - if (!Contains(&no_unread_icon_)) { |
| - RemoveAllChildViews(false /* delete_children */); |
| - AddChildView(&no_unread_icon_); |
| - } |
| + // gfx::AnimationDelegate: |
| + void AnimationProgressed(const gfx::Animation* animation) override { |
| + gfx::Transform transform; |
| + if (IsHorizontalLayout()) { |
| + transform.Translate(0, animation->CurrentValueBetween( |
| + static_cast<double>(height()) / 2, 0.)); |
|
oshima
2016/08/05 15:08:08
height() / 2.f
yoshiki
2016/08/05 16:15:38
Let me use "2." for double since it should be doub
|
| } else { |
| - if (!Contains(&unread_label_)) { |
| - RemoveAllChildViews(false /* delete_children */); |
| - AddChildView(&unread_label_); |
| - } |
| - |
| - // TODO(mukai): move NINE_PLUS message to ui_strings, it doesn't need to |
| - // be in ash_strings. |
| - unread_label_.SetText( |
| - (unread_count_ > 9) ? l10n_util::GetStringUTF16( |
| - IDS_ASH_NOTIFICATION_UNREAD_COUNT_NINE_PLUS) |
| - : base::FormatNumber(unread_count_)); |
| - unread_label_.SetEnabledColor((unread_count_ > 0) |
| - ? kWebNotificationColorWithUnread |
| - : kWebNotificationColorNoUnread); |
| + transform.Translate( |
| + animation->CurrentValueBetween(static_cast<double>(width() / 2), 0.), |
|
oshima
2016/08/05 15:08:08
ditto
yoshiki
2016/08/05 16:15:38
Done.
|
| + 0); |
| } |
| - SchedulePaint(); |
| + transform.Scale(animation->GetCurrentValue(), animation->GetCurrentValue()); |
| + layer()->SetTransform(transform); |
| + PreferredSizeChanged(); |
| + } |
| + void AnimationEnded(const gfx::Animation* animation) override { |
| + if (animation->GetCurrentValue() < 0.1) |
| + views::View::SetVisible(false); |
| + |
| + if (delete_after_animation_) { |
| + if (parent()) |
| + parent()->RemoveChildView(this); |
| + base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this); |
| + } |
| + } |
| + void AnimationCanceled(const gfx::Animation* animation) override { |
| + AnimationEnded(animation); |
| } |
| - bool is_bubble_visible_; |
| - int unread_count_; |
| + std::unique_ptr<gfx::SlideAnimation> animation_; |
| + bool delete_after_animation_ = false; |
| + WebNotificationTray* tray_; |
| - views::ImageView no_unread_icon_; |
| - views::Label unread_label_; |
| + DISALLOW_COPY_AND_ASSIGN(WebNotificationItem); |
| +}; |
| + |
| +class WebNotificationImage : public WebNotificationItem { |
| + public: |
| + WebNotificationImage(const gfx::ImageSkia& image, |
| + gfx::Size size, |
| + gfx::AnimationContainer* container, |
| + WebNotificationTray* tray) |
| + : WebNotificationItem(container, tray) { |
| + view_ = new views::ImageView(); |
| + view_->SetImage(image); |
| + view_->SetImageSize(size); |
| + AddChildView(view_); |
| + } |
| - DISALLOW_COPY_AND_ASSIGN(WebNotificationButton); |
| + private: |
| + views::ImageView* view_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(WebNotificationImage); |
| +}; |
| + |
| +class WebNotificationLabel : public WebNotificationItem { |
| + public: |
| + WebNotificationLabel(gfx::AnimationContainer* container, |
| + WebNotificationTray* tray) |
| + : WebNotificationItem(container, tray) { |
| + view_ = new views::Label(); |
| + SetupLabelForTray(view_); |
| + } |
| + |
| + void SetNotificationCount(bool small_icons_exist, size_t notification_count) { |
| + if (notification_count > 99) |
| + notification_count = 99; // cap with 99. |
|
oshima
2016/08/05 15:08:08
std::min ?
yoshiki
2016/08/05 16:15:38
Done.
|
| + |
| + base::string16 str = base::FormatNumber(notification_count); |
| + if (small_icons_exist) |
| + str = base::ASCIIToUTF16("+") + str; |
|
oshima
2016/08/05 15:08:08
Does this work in RTL?
yoshiki
2016/08/05 16:15:38
Done.
|
| + |
| + view_->SetText(str); |
| + view_->SetEnabledColor(kWebNotificationColorWithUnread); |
| + AddChildView(view_); |
| + SchedulePaint(); |
| + } |
| + |
| + private: |
| + views::Label* view_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(WebNotificationLabel); |
| }; |
| WebNotificationTray::WebNotificationTray(WmShelf* shelf, |
| @@ -209,18 +291,30 @@ WebNotificationTray::WebNotificationTray(WmShelf* shelf, |
| : TrayBackgroundView(shelf), |
| status_area_window_(status_area_window), |
| system_tray_(system_tray), |
| - button_(nullptr), |
| show_message_center_on_unlock_(false), |
| should_update_tray_content_(false), |
| should_block_shelf_auto_hide_(false) { |
| DCHECK(shelf); |
| DCHECK(status_area_window_); |
| DCHECK(system_tray_); |
| - button_ = new WebNotificationButton(this); |
| - button_->set_triggerable_event_flags(ui::EF_LEFT_MOUSE_BUTTON | |
| - ui::EF_RIGHT_MOUSE_BUTTON); |
| - tray_container()->AddChildView(button_); |
| - button_->SetFocusBehavior(FocusBehavior::NEVER); |
| + |
| + gfx::ImageSkia bell_image; |
| + if (MaterialDesignController::IsShelfMaterial()) { |
| + bell_image = CreateVectorIcon(gfx::VectorIconId::SHELF_NOTIFICATIONS, |
| + kShelfIconColor); |
| + } else { |
| + bell_image = |
| + CreateVectorIcon(gfx::VectorIconId::NOTIFICATIONS, kNoUnreadIconSize, |
| + kWebNotificationColorNoUnread); |
| + } |
| + bell_icon_.reset(new WebNotificationImage(bell_image, |
| + kTrayItemInnerBellIconSize, |
| + animation_container_.get(), this)); |
| + tray_container()->AddChildView(bell_icon_.get()); |
| + |
| + counter_.reset(new WebNotificationLabel(animation_container_.get(), this)); |
| + tray_container()->AddChildView(counter_.get()); |
| + |
| SetContentsBackground(); |
| tray_container()->SetBorder(views::Border::NullBorder()); |
| message_center_tray_.reset(new message_center::MessageCenterTray( |
| @@ -234,6 +328,8 @@ WebNotificationTray::WebNotificationTray(WmShelf* shelf, |
| popup_alignment_delegate_->StartObserving(display::Screen::GetScreen(), |
| display); |
| OnMessageCenterTrayChanged(); |
| + |
| + tray_container()->set_insets(kTrayItemInsets); |
| } |
| WebNotificationTray::~WebNotificationTray() { |
| @@ -243,6 +339,11 @@ WebNotificationTray::~WebNotificationTray() { |
| popup_collection_.reset(); |
| } |
| +// static |
| +void WebNotificationTray::DisableAnimationsForTest() { |
| + disable_animations_for_test = true; |
|
oshima
2016/08/05 15:08:08
Once one test set this, this will be used for all
yoshiki
2016/08/05 16:15:38
Fixed. TrayItemView does same thing so I thought i
|
| +} |
| + |
| // Public methods. |
| bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) { |
| @@ -255,7 +356,7 @@ bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) { |
| message_center_tray_.get(), true); |
| int max_height; |
| - if (IsHorizontalAlignment(shelf()->GetAlignment())) { |
| + if (IsHorizontalAlignment(shelf_alignment())) { |
| max_height = shelf()->GetIdealBounds().y(); |
| } else { |
| // Assume the status area and bubble bottoms are aligned when vertical. |
| @@ -273,7 +374,6 @@ bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) { |
| system_tray_->SetHideNotifications(true); |
| shelf()->UpdateAutoHideState(); |
| - button_->SetBubbleVisible(true); |
| SetDrawBackgroundAsActive(true); |
| return true; |
| } |
| @@ -291,7 +391,6 @@ void WebNotificationTray::HideMessageCenter() { |
| show_message_center_on_unlock_ = false; |
| system_tray_->SetHideNotifications(false); |
| shelf()->UpdateAutoHideState(); |
| - button_->SetBubbleVisible(false); |
| } |
| void WebNotificationTray::SetTrayBubbleHeight(int height) { |
| @@ -460,12 +559,6 @@ void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) { |
| message_center()->EnterQuietModeWithExpire(expires_in); |
| } |
| -void WebNotificationTray::ButtonPressed(views::Button* sender, |
| - const ui::Event& event) { |
| - DCHECK_EQ(button_, sender); |
| - PerformAction(event); |
| -} |
| - |
| void WebNotificationTray::OnMessageCenterTrayChanged() { |
| // Do not update the tray contents directly. Multiple change events can happen |
| // consecutively, and calling Update in the middle of those events will show |
| @@ -481,15 +574,62 @@ void WebNotificationTray::UpdateTrayContent() { |
| return; |
| should_update_tray_content_ = false; |
| + std::unordered_set<std::string> notification_ids; |
| + for (auto i : visible_small_icons_) { |
| + notification_ids.insert(i.first); |
| + } |
|
oshima
2016/08/05 15:08:08
nit: nuke {}
yoshiki
2016/08/05 16:15:37
Done.
|
| + |
| + // Add small icons (up to kMaximumSmallIconCount = 3). |
| message_center::MessageCenter* message_center = |
| message_center_tray_->message_center(); |
| - button_->SetUnreadCount(message_center->UnreadNotificationCount()); |
| - if (IsMessageCenterBubbleVisible()) |
| - button_->SetState(views::CustomButton::STATE_PRESSED); |
| - else |
| - button_->SetState(views::CustomButton::STATE_NORMAL); |
| + size_t visible_small_icon_count = 0; |
| + for (const auto* notification : message_center->GetVisibleNotifications()) { |
| + gfx::Image image = notification->small_image(); |
| + if (image.IsEmpty()) |
| + continue; |
| + |
| + if (visible_small_icon_count >= kMaximumSmallIconCount) |
| + break; |
| + visible_small_icon_count++; |
| + |
| + notification_ids.erase(notification->id()); |
| + if (visible_small_icons_.count(notification->id()) != 0) |
| + continue; |
| + |
| + auto* item = |
| + new WebNotificationImage(image.AsImageSkia(), kTrayItemInnerIconSize, |
| + animation_container_.get(), this); |
| + visible_small_icons_.insert(std::make_pair(notification->id(), item)); |
| + |
| + tray_container()->AddChildViewAt(item, 0); |
| + item->SetVisible(true); |
| + } |
| + |
| + // Remove unnecessary icons. |
| + for (const std::string& id : notification_ids) { |
| + WebNotificationImage* item = visible_small_icons_[id]; |
| + visible_small_icons_.erase(id); |
| + item->HideAndDelete(); |
| + } |
| + |
| + // Show or hide the bell icon. |
| + size_t visible_notification_count = message_center->NotificationCount(); |
| + bell_icon_->SetVisible(visible_notification_count == 0); |
| + |
| + // Show or hide the counter. |
| + size_t hidden_icon_count = |
| + visible_notification_count - visible_small_icon_count; |
| + if (hidden_icon_count != 0) { |
| + counter_->SetVisible(true); |
| + counter_->SetNotificationCount( |
| + (visible_small_icon_count != 0), // small_icons_exist |
| + hidden_icon_count); |
| + } else { |
| + counter_->SetVisible(false); |
| + } |
| SetVisible(IsLoggedIn()); |
| + PreferredSizeChanged(); |
| Layout(); |
| SchedulePaint(); |
| if (IsLoggedIn()) |