chromium/ash/system/phonehub/phone_hub_app_loading_icon.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/phonehub/phone_hub_app_loading_icon.h"

#include "ash/style/ash_color_provider.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/views/animation/animation_builder.h"

namespace ash {

namespace {

// Constants for loading animation
constexpr float kAnimationOpacityHigh = 1.0f;
constexpr float kAnimationOpacityLow = 0.5f;
constexpr int kAnimationFadeDownDurationInMs = 500;
constexpr int kAnimationFadeUpDurationInMs = 500;

class LoadingCircle : public gfx::CanvasImageSource {
 public:
  explicit LoadingCircle(int size)
      : CanvasImageSource(AppIcon::GetRecommendedImageSize(size)) {}

  LoadingCircle(const LoadingCircle&) = delete;
  LoadingCircle& operator=(const LoadingCircle&) = delete;

  void Draw(gfx::Canvas* canvas) override {
    float radius = size().width() / 2.0f;
    cc::PaintFlags flags;
    flags.setStyle(cc::PaintFlags::kFill_Style);
    flags.setAntiAlias(true);
    flags.setColor(AshColorProvider::Get()->GetControlsLayerColor(
        AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive));
    canvas->DrawCircle(gfx::PointF(radius, radius), radius, flags);
  }
};

}  // namespace

AppLoadingIcon::AppLoadingIcon(int size)
    : AppIcon(gfx::Image(
                  gfx::CanvasImageSource::MakeImageSkia<LoadingCircle>(size)),
              size) {
  SetPaintToLayer();
  layer()->SetFillsBoundsOpaquely(false);
  layer()->SetFillsBoundsCompletely(false);
}

AppLoadingIcon::~AppLoadingIcon() = default;

void AppLoadingIcon::StartLoadingAnimation(
    std::optional<base::TimeDelta> initial_delay) {
  if (initial_delay) {
    animation_initial_delay_timer_.Start(
        FROM_HERE, *initial_delay,
        base::BindOnce(&AppLoadingIcon::StartLoadingAnimation,
                       base::Unretained(this),
                       /*initial_delay=*/std::nullopt));
    return;
  }

  views::AnimationBuilder builder;
  animation_abort_handle_ = builder.GetAbortHandle();
  builder.Repeatedly()
      .SetDuration(base::Milliseconds(kAnimationFadeDownDurationInMs))
      .SetOpacity(this, kAnimationOpacityLow, gfx::Tween::ACCEL_30_DECEL_20_85)
      .Then()
      .SetDuration(base::Milliseconds(kAnimationFadeUpDurationInMs))
      .SetOpacity(this, kAnimationOpacityHigh, gfx::Tween::LINEAR);
}

void AppLoadingIcon::StopLoadingAnimation() {
  animation_abort_handle_.reset();
  animation_initial_delay_timer_.Stop();
}

BEGIN_METADATA(AppLoadingIcon)
END_METADATA

}  // namespace ash