chromium/ash/system/holding_space/holding_space_progress_indicator_util.cc

// Copyright 2022 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/holding_space/holding_space_progress_indicator_util.h"

#include "ash/public/cpp/holding_space/holding_space_controller.h"
#include "ash/public/cpp/holding_space/holding_space_controller_observer.h"
#include "ash/public/cpp/holding_space/holding_space_item.h"
#include "ash/public/cpp/holding_space/holding_space_item_updated_fields.h"
#include "ash/public/cpp/holding_space/holding_space_model.h"
#include "ash/public/cpp/holding_space/holding_space_model_observer.h"
#include "ash/public/cpp/holding_space/holding_space_progress.h"
#include "ash/system/holding_space/holding_space_animation_registry.h"
#include "ash/system/progress_indicator/progress_indicator.h"
#include "ash/system/progress_indicator/progress_indicator_animation_registry.h"
#include "base/memory/raw_ptr.h"

namespace ash {
namespace holding_space_util {
namespace {

// HoldingSpaceControllerProgressIndicator -------------------------------------

// A class owning a `ui::Layer` which paints indication of progress for all
// items in the model attached to its associated holding space `controller_`.
// NOTE: The owned `layer()` is not painted if there are no items in progress.
class HoldingSpaceControllerProgressIndicator
    : public ProgressIndicator,
      public HoldingSpaceControllerObserver,
      public HoldingSpaceModelObserver {
 public:
  explicit HoldingSpaceControllerProgressIndicator(
      HoldingSpaceController* controller)
      : ProgressIndicator(
            HoldingSpaceAnimationRegistry::GetInstance(),
            ProgressIndicatorAnimationRegistry::AsAnimationKey(controller)),
        controller_(controller) {
    controller_observation_.Observe(controller_.get());
    if (controller_->model())
      OnHoldingSpaceModelAttached(controller_->model());
  }

 private:
  // ProgressIndicator:
  std::optional<float> CalculateProgress() const override {
    // If there is no `model` attached, then there are no in-progress holding
    // space items. Do not paint the progress indication.
    const HoldingSpaceModel* model = controller_->model();
    if (!model)
      return kProgressComplete;

    HoldingSpaceProgress cumulative_progress;

    // Iterate over all holding space items.
    for (const auto& item : model->items()) {
      // Ignore any holding space items that are not yet initialized, since
      // they are not visible to the user, or items that are not visibly
      // in-progress, since they do not contribute to `cumulative_progress`.
      if (item->IsInitialized() && !item->progress().IsHidden() &&
          !item->progress().IsComplete()) {
        cumulative_progress += item->progress();
      }
    }

    return cumulative_progress.GetValue();
  }

  // HoldingSpaceControllerObserver:
  void OnHoldingSpaceModelAttached(HoldingSpaceModel* model) override {
    model_observation_.Observe(model);
    InvalidateLayer();
  }

  void OnHoldingSpaceModelDetached(HoldingSpaceModel* model) override {
    model_observation_.Reset();
    InvalidateLayer();
  }

  // HoldingSpaceModelObserver:
  void OnHoldingSpaceItemsAdded(
      const std::vector<const HoldingSpaceItem*>& items) override {
    for (const HoldingSpaceItem* item : items) {
      if (item->IsInitialized() && !item->progress().IsComplete()) {
        InvalidateLayer();
        return;
      }
    }
  }

  void OnHoldingSpaceItemsRemoved(
      const std::vector<const HoldingSpaceItem*>& items) override {
    for (const HoldingSpaceItem* item : items) {
      if (item->IsInitialized() && !item->progress().IsComplete()) {
        InvalidateLayer();
        return;
      }
    }
  }

  void OnHoldingSpaceItemUpdated(
      const HoldingSpaceItem* item,
      const HoldingSpaceItemUpdatedFields& updated_fields) override {
    if (item->IsInitialized() && updated_fields.previous_progress) {
      InvalidateLayer();
    }
  }

  void OnHoldingSpaceItemInitialized(const HoldingSpaceItem* item) override {
    if (!item->progress().IsComplete())
      InvalidateLayer();
  }

  // The associated holding space `controller_` for which to indicate progress
  // of all holding space items in its attached model.
  const raw_ptr<HoldingSpaceController> controller_;

  base::ScopedObservation<HoldingSpaceController,
                          HoldingSpaceControllerObserver>
      controller_observation_{this};

  base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
      model_observation_{this};
};

// HoldingSpaceItemProgressIndicator -------------------------------------------

// A class owning a `ui::Layer` which paints indication of progress for its
// associated holding space `item_`. NOTE: The owned `layer()` is not painted if
// the associated `item_` is not in progress.
class HoldingSpaceItemProgressIndicator : public ProgressIndicator,
                                          public HoldingSpaceModelObserver {
 public:
  explicit HoldingSpaceItemProgressIndicator(const HoldingSpaceItem* item)
      : ProgressIndicator(
            HoldingSpaceAnimationRegistry::GetInstance(),
            ProgressIndicatorAnimationRegistry::AsAnimationKey(item)),
        item_(item) {
    model_observation_.Observe(HoldingSpaceController::Get()->model());
  }

 private:
  // ProgressIndicator:
  std::optional<float> CalculateProgress() const override {
    // If `item_` is `nullptr` it is being destroyed. Ensure the progress
    // indication is not painted in this case. Similarly, ensure the progress
    // indication is not painted when progress is hidden.
    return item_ && !item_->progress().IsHidden() ? item_->progress().GetValue()
                                                  : kProgressComplete;
  }

  // HoldingSpaceModelObserver:
  void OnHoldingSpaceItemUpdated(
      const HoldingSpaceItem* item,
      const HoldingSpaceItemUpdatedFields& updated_fields) override {
    if (item_ == item && updated_fields.previous_progress) {
      InvalidateLayer();
    }
  }

  void OnHoldingSpaceItemsRemoved(
      const std::vector<const HoldingSpaceItem*>& items) override {
    for (const HoldingSpaceItem* item : items) {
      if (item_ == item) {
        item_ = nullptr;
        return;
      }
    }
  }

  // The associated holding space `item` for which to indicate progress.
  // NOTE: May temporarily be `nullptr` during the `item`s destruction sequence.
  raw_ptr<const HoldingSpaceItem> item_ = nullptr;

  base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
      model_observation_{this};
};

}  // namespace

// Utilities -------------------------------------------------------------------

std::unique_ptr<ProgressIndicator> CreateProgressIndicatorForController(
    HoldingSpaceController* controller) {
  return std::make_unique<HoldingSpaceControllerProgressIndicator>(controller);
}

std::unique_ptr<ProgressIndicator> CreateProgressIndicatorForItem(
    const HoldingSpaceItem* item) {
  return std::make_unique<HoldingSpaceItemProgressIndicator>(item);
}

}  // namespace holding_space_util
}  // namespace ash