chromium/ash/system/status_area_animation_controller.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/status_area_animation_controller.h"

#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/tray/tray_container.h"
#include "base/memory/weak_ptr.h"
#include "ui/compositor/layer.h"
#include "ui/views/animation/animation_builder.h"

namespace ash {

StatusAreaAnimationController::StatusAreaAnimationController(
    NotificationCenterTray* notification_center_tray)
    : notification_center_tray_(notification_center_tray) {
  if (!notification_center_tray_) {
    return;
  }

  notification_center_tray_->AddTrayBackgroundViewObserver(this);
  notification_center_tray_->AddNotificationCenterTrayObserver(this);
  notification_center_tray_default_animation_enabler_ =
      std::make_unique<base::ScopedClosureRunner>(
          notification_center_tray->SetUseCustomVisibilityAnimations());
  notification_center_tray_item_animation_enablers_ =
      std::list<base::ScopedClosureRunner>();
}

StatusAreaAnimationController::~StatusAreaAnimationController() {
  if (notification_center_tray_) {
    notification_center_tray_->RemoveNotificationCenterTrayObserver(this);
    notification_center_tray_->RemoveTrayBackgroundViewObserver(this);
  }
}

void StatusAreaAnimationController::OnVisiblePreferredChanged(
    bool visible_preferred) {
  PerformAnimation(visible_preferred);
}

void StatusAreaAnimationController::OnAllTrayItemsAdded() {
  // `NotificationCenterTray`'s `TrayItemView`s need to have their animations
  // disabled ahead of the first time the `NotificationCenterTray` becomes
  // visible. This is the right time to disable those animations because it is
  // after all the `TrayItemView`s have been added to the tray but before the
  // tray has had a chance to update its visibility from the default non-visible
  // state.
  DisableNotificationCenterTrayItemAnimations();
}

void StatusAreaAnimationController::
    DisableNotificationCenterTrayItemAnimations() {
  for (views::View* tray_item :
       notification_center_tray_->tray_container()->children()) {
    notification_center_tray_item_animation_enablers_.push_back(
        static_cast<TrayItemView*>(tray_item)->DisableAnimation());
  }
}

void StatusAreaAnimationController::
    EnableNotificationCenterTrayItemAnimations() {
  notification_center_tray_item_animation_enablers_.clear();
}

void StatusAreaAnimationController::PerformAnimation(bool visible) {
  if (visible) {
    notification_center_tray_->layer()->SetVisible(true);
    views::AnimationBuilder()
        .SetPreemptionStrategy(ui::LayerAnimator::PreemptionStrategy::
                                   IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
        .OnAborted(base::BindOnce(
            [](base::WeakPtr<StatusAreaAnimationController> ptr) {
              if (!ptr || !ptr->notification_center_tray_) {
                return;
              }
              ptr->notification_center_tray_->OnAnimationAborted();

              // Don't enable notification center tray item animations if this
              // show animation was interrupted by a hide animation.
              if (!ptr->notification_center_tray_->visible_preferred()) {
                return;
              }
              ptr->EnableNotificationCenterTrayItemAnimations();
            },
            weak_factory_.GetWeakPtr()))
        .OnEnded(base::BindOnce(
            [](base::WeakPtr<StatusAreaAnimationController> ptr) {
              if (!ptr || !ptr->notification_center_tray_) {
                return;
              }
              ptr->notification_center_tray_->OnAnimationEnded();
              ptr->EnableNotificationCenterTrayItemAnimations();
            },
            weak_factory_.GetWeakPtr()))
        .Once()
        .Offset(base::Milliseconds(50))
        .SetDuration(base::Milliseconds(150))
        .SetOpacity(notification_center_tray_, 1, gfx::Tween::LINEAR);
  } else {
    DisableNotificationCenterTrayItemAnimations();
    views::AnimationBuilder()
        .SetPreemptionStrategy(ui::LayerAnimator::PreemptionStrategy::
                                   IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
        .OnScheduled(base::BindOnce(
            [](base::WeakPtr<StatusAreaAnimationController> ptr) {
              if (!ptr) {
                return;
              }
              ptr->is_hide_animation_scheduled_ = true;
            },
            weak_factory_.GetWeakPtr()))
        .OnStarted(base::BindOnce(
            [](base::WeakPtr<StatusAreaAnimationController> ptr) {
              if (!ptr || !ptr->notification_center_tray_) {
                return;
              }
              ptr->notification_center_tray_->OnHideAnimationStarted();
            },
            weak_factory_.GetWeakPtr()))
        .OnAborted(base::BindOnce(
            [](base::WeakPtr<StatusAreaAnimationController> ptr) {
              if (!ptr || !ptr->notification_center_tray_) {
                return;
              }
              ptr->is_hide_animation_scheduled_ = false;
              ptr->notification_center_tray_->OnAnimationAborted();
              ptr->ImmediatelyUpdateTrayItemVisibilities();
            },
            weak_factory_.GetWeakPtr()))
        .OnEnded(base::BindOnce(
            [](base::WeakPtr<StatusAreaAnimationController> ptr) {
              if (!ptr || !ptr->notification_center_tray_) {
                return;
              }
              ptr->is_hide_animation_scheduled_ = false;
              ptr->notification_center_tray_->OnAnimationEnded();
              ptr->ImmediatelyUpdateTrayItemVisibilities();
            },
            weak_factory_.GetWeakPtr()))
        .Once()
        .SetDuration(base::Milliseconds(150))
        .SetOpacity(notification_center_tray_, 0, gfx::Tween::LINEAR)
        .SetVisibility(notification_center_tray_, false);
  }
}

void StatusAreaAnimationController::ImmediatelyUpdateTrayItemVisibilities() {
  for (views::View* tray_item :
       notification_center_tray_->tray_container()->children()) {
    static_cast<TrayItemView*>(tray_item)->ImmediatelyUpdateVisibility();
  }
}

}  // namespace ash