blob: c90117e60891391aa32777cb87aeb7ff1b182422 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/examples/animation_example.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/strings/utf_string_conversions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/layer_delegate.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/animation/bounds_animator.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/examples/grit/views_examples_resources.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_manager_base.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/typography.h"
#include "ui/views/style/typography_provider.h"
#include "ui/views/view.h"
namespace views {
namespace examples {
AnimationExample::AnimationExample() : ExampleBase("Animation") {}
AnimationExample::~AnimationExample() = default;
class AnimatingSquare : public View {
public:
explicit AnimatingSquare(size_t index);
AnimatingSquare(const AnimatingSquare&) = delete;
AnimatingSquare& operator=(const AnimatingSquare&) = delete;
~AnimatingSquare() override = default;
protected:
// views::View override
void OnPaint(gfx::Canvas* canvas) override;
private:
int index_;
int paint_counter_ = 0;
gfx::FontList font_list_ =
LayoutProvider::Get()->GetTypographyProvider().GetFont(
style::CONTEXT_DIALOG_TITLE,
style::STYLE_PRIMARY);
};
AnimatingSquare::AnimatingSquare(size_t index) : index_(index) {
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
layer()->SetFillsBoundsCompletely(false);
}
void AnimatingSquare::OnPaint(gfx::Canvas* canvas) {
View::OnPaint(canvas);
const SkColor color = SkColorSetRGB((5 - index_) * 51, 0, index_ * 51);
const SkColor colors[2] = {color,
color_utils::HSLShift(color, {-1.0, -1.0, 0.75})};
cc::PaintFlags flags;
gfx::Rect local_bounds = gfx::Rect(layer()->size());
const float dsf = canvas->UndoDeviceScaleFactor();
gfx::RectF local_bounds_f = gfx::RectF(local_bounds);
local_bounds_f.Scale(dsf);
SkRect bounds = gfx::RectToSkRect(gfx::ToEnclosingRect(local_bounds_f));
flags.setAntiAlias(true);
flags.setShader(cc::PaintShader::MakeRadialGradient(
SkPoint::Make(bounds.centerX(), bounds.centerY()), bounds.width() / 2,
colors, nullptr, 2, SkTileMode::kClamp));
canvas->DrawRect(gfx::ToEnclosingRect(local_bounds_f), flags);
int width = 0;
int height = 0;
std::u16string counter = base::NumberToString16(++paint_counter_);
canvas->SizeStringInt(counter, font_list_, &width, &height, 0,
gfx::Canvas::TEXT_ALIGN_CENTER);
local_bounds.ClampToCenteredSize(gfx::Size(width, height));
canvas->DrawStringRectWithFlags(counter, font_list_, SK_ColorBLACK,
local_bounds, gfx::Canvas::TEXT_ALIGN_CENTER);
}
class SquaresLayoutManager : public LayoutManagerBase {
public:
SquaresLayoutManager() = default;
~SquaresLayoutManager() override = default;
protected:
// LayoutManagerBase:
ProposedLayout CalculateProposedLayout(
const SizeBounds& size_bounds) const override;
void LayoutImpl() override;
void OnInstalled(View* host) override;
private:
static constexpr int kPadding = 25;
static constexpr gfx::Size kSize = gfx::Size(100, 100);
std::unique_ptr<BoundsAnimator> bounds_animator_;
};
// static
constexpr gfx::Size SquaresLayoutManager::kSize;
ProposedLayout SquaresLayoutManager::CalculateProposedLayout(
const SizeBounds& size_bounds) const {
ProposedLayout layout;
const auto& children = host_view()->children();
const int item_width = kSize.width() + kPadding;
const int item_height = kSize.height() + kPadding;
const int max_width = kPadding + (children.size() * item_width);
const int bounds_width =
std::max(kPadding + item_width, size_bounds.width().min_of(max_width));
const int views_per_row = (bounds_width - kPadding) / item_width;
for (size_t i = 0; i < children.size(); ++i) {
const size_t row = i / views_per_row;
const size_t column = i % views_per_row;
const gfx::Point origin(kPadding + column * item_width,
kPadding + row * item_height);
layout.child_layouts.push_back(
{children[i], true, gfx::Rect(origin, kSize), SizeBounds(kSize)});
}
const size_t num_rows = (children.size() + views_per_row - 1) / views_per_row;
const int max_height = kPadding + (num_rows * item_height);
const int bounds_height =
std::max(kPadding + item_height, size_bounds.height().min_of(max_height));
layout.host_size = {bounds_width, bounds_height};
return layout;
}
void SquaresLayoutManager::LayoutImpl() {
ProposedLayout proposed_layout = GetProposedLayout(host_view()->size());
for (auto child_layout : proposed_layout.child_layouts) {
bounds_animator_->AnimateViewTo(child_layout.child_view,
child_layout.bounds);
}
}
void SquaresLayoutManager::OnInstalled(View* host) {
bounds_animator_ = std::make_unique<BoundsAnimator>(host, true);
bounds_animator_->SetAnimationDuration(base::Seconds(1));
}
void AnimationExample::CreateExampleView(View* container) {
container->SetLayoutManager(std::make_unique<BoxLayout>(
BoxLayout::Orientation::kVertical, gfx::Insets(), 10));
View* squares_container = container->AddChildView(std::make_unique<View>());
squares_container->SetBackground(CreateSolidBackground(SK_ColorWHITE));
squares_container->SetPaintToLayer();
squares_container->layer()->SetMasksToBounds(true);
squares_container->layer()->SetFillsBoundsOpaquely(true);
squares_container->SetLayoutManager(std::make_unique<SquaresLayoutManager>());
for (size_t i = 0; i < 5; ++i)
squares_container->AddChildView(std::make_unique<AnimatingSquare>(i));
{
gfx::RoundedCornersF rounded_corners(12.0f, 12.0f, 12.0f, 12.0f);
AnimationBuilder b;
abort_handle_ = b.GetAbortHandle();
for (auto* view : squares_container->children()) {
// Property setting calls on the builder would be replaced with
// view->SetOpacity(..) after animation integration with view::View class
b.Once()
.SetDuration(base::Seconds(10))
.SetRoundedCorners(view, rounded_corners);
b.Repeatedly()
.SetDuration(base::Seconds(2))
.SetOpacity(view, 0.4f, gfx::Tween::LINEAR_OUT_SLOW_IN)
.Then()
.SetDuration(base::Seconds(2))
.SetOpacity(view, 0.9f, gfx::Tween::EASE_OUT_3);
}
}
container->AddChildView(std::make_unique<MdTextButton>(
base::BindRepeating(
[](std::unique_ptr<AnimationAbortHandle>* abort_handle) {
abort_handle->reset();
},
&abort_handle_),
l10n_util::GetStringUTF16(IDS_ABORT_ANIMATION_BUTTON)));
}
} // namespace examples
} // namespace views