chromium/ash/system/holding_space/holding_space_util.cc

// Copyright 2020 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_util.h"

#include <memory>
#include <optional>

#include "ui/color/color_provider.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/rrect_f.h"
#include "ui/views/background.h"
#include "ui/views/painter.h"
#include "ui/views/view.h"

namespace ash::holding_space_util {

namespace {

// CallbackPathGenerator -------------------------------------------------------

class CallbackPathGenerator : public views::HighlightPathGenerator {
 public:
  using Callback = base::RepeatingCallback<gfx::RRectF()>;

  explicit CallbackPathGenerator(Callback callback)
      : callback_(std::move(callback)) {}
  CallbackPathGenerator(const CallbackPathGenerator&) = delete;
  CallbackPathGenerator& operator=(const CallbackPathGenerator&) = delete;
  ~CallbackPathGenerator() override = default;

 private:
  // views::HighlightPathGenerator:
  std::optional<gfx::RRectF> GetRoundRect(const gfx::RectF& rect) override {
    return callback_.Run();
  }

  Callback callback_;
};

// CircleBackground ------------------------------------------------------------

class CircleBackground : public views::Background {
 public:
  CircleBackground(ui::ColorId color_id, size_t fixed_size)
      : color_id_(color_id), fixed_size_(fixed_size) {}

  CircleBackground(ui::ColorId color_id, const gfx::InsetsF& insets)
      : color_id_(color_id), insets_(insets) {}

  CircleBackground(const CircleBackground&) = delete;
  CircleBackground& operator=(const CircleBackground&) = delete;
  ~CircleBackground() override = default;

  // views::Background:
  void Paint(gfx::Canvas* canvas, views::View* view) const override {
    gfx::RectF bounds(view->GetLocalBounds());

    if (insets_.has_value())
      bounds.Inset(insets_.value());

    const float radius =
        fixed_size_.has_value()
            ? fixed_size_.value() / 2.f
            : std::min(bounds.size().width(), bounds.size().height()) / 2.f;

    cc::PaintFlags flags;
    flags.setAntiAlias(true);
    flags.setColor(get_color());

    canvas->DrawCircle(bounds.CenterPoint(), radius, flags);
  }

  void OnViewThemeChanged(views::View* view) override {
    SetNativeControlColor(view->GetColorProvider()->GetColor(color_id_));
    view->SchedulePaint();
  }

 private:
  const ui::ColorId color_id_;
  const std::optional<size_t> fixed_size_;
  const std::optional<gfx::InsetsF> insets_;
};

// Helpers ---------------------------------------------------------------------

// Creates a `ui::LayerAnimationSequence` for the specified `element` with
// optional `delay`, observed by the specified `observer`.
std::unique_ptr<ui::LayerAnimationSequence> CreateObservedSequence(
    std::unique_ptr<ui::LayerAnimationElement> element,
    base::TimeDelta delay,
    ui::LayerAnimationObserver* observer) {
  auto sequence = std::make_unique<ui::LayerAnimationSequence>();
  if (!delay.is_zero()) {
    sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement(
        element->properties(), delay));
  }
  sequence->AddElement(std::move(element));
  sequence->AddObserver(observer);
  return sequence;
}

// Animates the specified `view` to a target `opacity` with the specified
// `duration` and optional `delay`, associating `observer` with the created
// animation sequences.
void AnimateTo(views::View* view,
               float opacity,
               base::TimeDelta duration,
               base::TimeDelta delay,
               ui::LayerAnimationObserver* observer) {
  // Opacity animation.
  auto opacity_element =
      ui::LayerAnimationElement::CreateOpacityElement(opacity, duration);
  opacity_element->set_tween_type(gfx::Tween::Type::LINEAR);

  // Note that the `ui::LayerAnimator` takes ownership of any animation
  // sequences so they need to be released.
  view->layer()->GetAnimator()->StartAnimation(
      CreateObservedSequence(std::move(opacity_element), delay, observer)
          .release());
}

}  // namespace

// Animates in the specified `view` with the specified `duration` and optional
// `delay`, associating `observer` with the created animation sequences.
void AnimateIn(views::View* view,
               base::TimeDelta duration,
               base::TimeDelta delay,
               ui::LayerAnimationObserver* observer) {
  view->layer()->SetOpacity(0.f);
  AnimateTo(view, /*opacity=*/1.f, duration, delay, observer);
}

// Animates out the specified `view` with the specified `duration, associating
// `observer` with the created animation sequences.
void AnimateOut(views::View* view,
                base::TimeDelta duration,
                ui::LayerAnimationObserver* observer) {
  AnimateTo(view, /*opacity=*/0.f, duration, /*delay=*/base::TimeDelta(),
            observer);
}

std::unique_ptr<views::Background> CreateCircleBackground(ui::ColorId color_id,
                                                          size_t fixed_size) {
  return std::make_unique<CircleBackground>(color_id, fixed_size);
}

std::unique_ptr<views::Background> CreateCircleBackground(
    ui::ColorId color_id,
    const gfx::InsetsF& insets) {
  return std::make_unique<CircleBackground>(color_id, insets);
}

std::unique_ptr<views::HighlightPathGenerator> CreateHighlightPathGenerator(
    base::RepeatingCallback<gfx::RRectF()> callback) {
  return std::make_unique<CallbackPathGenerator>(std::move(callback));
}

}  // namespace ash::holding_space_util