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..849eb1c3328f3ca4ec5b2da1b8e4717de99912b7 100644 |
--- a/ash/common/system/web_notification/web_notification_tray.cc |
+++ b/ash/common/system/web_notification/web_notification_tray.cc |
@@ -63,8 +63,22 @@ 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(3, 3); |
+ |
+constexpr int kTrayItemAnimationDurationMS = 200; |
+ |
+constexpr size_t kMaximumNotificationNumber = 99; |
+ |
+// Flag to disable animation. Only for testing. |
+static bool disable_animations_for_test = false; |
oshima
2016/08/10 06:58:44
nit: you don't need static. anonymous namespace is
yoshiki
2016/08/10 10:18:53
Done.
|
} |
namespace { |
@@ -115,92 +129,169 @@ 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); |
- } |
- |
- no_unread_icon_.SetImage(image); |
- no_unread_icon_.set_owned_by_client(); |
+ WebNotificationItem(gfx::AnimationContainer* container, |
+ WebNotificationTray* tray) |
+ : tray_(tray) { |
+ SetPaintToLayer(true); |
+ layer()->SetFillsBoundsOpaquely(false); |
+ views::View::SetVisible(false); |
+ 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) so that |
+ // the icons on the left are shifted with the animation. |
+ // Note that TrayItemView does the same thing. |
+ 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.)); |
} 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.), |
+ 0); |
+ } |
+ 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); |
} |
- SchedulePaint(); |
} |
+ void AnimationCanceled(const gfx::Animation* animation) override { |
+ AnimationEnded(animation); |
+ } |
+ |
+ std::unique_ptr<gfx::SlideAnimation> animation_; |
+ bool delete_after_animation_ = false; |
+ WebNotificationTray* tray_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationItem); |
+}; |
- bool is_bubble_visible_; |
- int unread_count_; |
+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_); |
+ } |
- views::ImageView no_unread_icon_; |
- views::Label unread_label_; |
+ private: |
+ views::ImageView* view_; |
- DISALLOW_COPY_AND_ASSIGN(WebNotificationButton); |
+ 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) { |
+ notification_count = std::min(notification_count, |
+ kMaximumNotificationNumber); // cap with 99 |
+ |
+ // TODO(yoshiki): Use a string for "99" and "+99". |
+ |
+ base::string16 str = base::FormatNumber(notification_count); |
+ if (small_icons_exist) { |
+ if (!base::i18n::IsRTL()) |
+ str = base::ASCIIToUTF16("+") + str; |
+ else |
+ str = str + base::ASCIIToUTF16("+"); |
+ } |
xiyuan
2016/08/09 16:50:05
We should add a string template to resource
+<ph
yoshiki
2016/08/10 10:18:53
Got it. I'll do it in a separated patch.
|
+ |
+ view_->SetText(str); |
+ view_->SetEnabledColor(kWebNotificationColorWithUnread); |
+ AddChildView(view_); |
+ SchedulePaint(); |
+ } |
+ |
+ private: |
+ views::Label* view_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationLabel); |
}; |
WebNotificationTray::WebNotificationTray(WmShelf* shelf, |
@@ -209,18 +300,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 +337,8 @@ WebNotificationTray::WebNotificationTray(WmShelf* shelf, |
popup_alignment_delegate_->StartObserving(display::Screen::GetScreen(), |
display); |
OnMessageCenterTrayChanged(); |
+ |
+ tray_container()->SetMargin(kTrayItemInsets); |
} |
WebNotificationTray::~WebNotificationTray() { |
@@ -243,6 +348,11 @@ WebNotificationTray::~WebNotificationTray() { |
popup_collection_.reset(); |
} |
+// static |
+void WebNotificationTray::DisableAnimationsForTest(bool disable) { |
+ disable_animations_for_test = disable; |
+} |
+ |
// Public methods. |
bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) { |
@@ -255,7 +365,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 +383,6 @@ bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) { |
system_tray_->SetHideNotifications(true); |
shelf()->UpdateAutoHideState(); |
- button_->SetBubbleVisible(true); |
SetDrawBackgroundAsActive(true); |
return true; |
} |
@@ -291,7 +400,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 +568,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 +583,61 @@ void WebNotificationTray::UpdateTrayContent() { |
return; |
should_update_tray_content_ = false; |
+ std::unordered_set<std::string> notification_ids; |
+ for (auto i : visible_small_icons_) |
oshima
2016/08/10 06:58:44
nit: auto pair
yoshiki
2016/08/10 10:18:53
Done.
|
+ notification_ids.insert(i.first); |
+ |
+ // 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()) |