blob: aca500c6792c16ceae74c4454f1a6a10f063d94c [file] [log] [blame]
// Copyright 2018 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/controls/animated_image_view.h"
#include <utility>
#include "base/logging.h"
#include "cc/paint/skottie_wrapper.h"
#include "ui/gfx/canvas.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
bool AreAnimatedImagesEqual(const gfx::SkiaVectorAnimation& animation_1,
const gfx::SkiaVectorAnimation& animation_2) {
// In rare cases this may return false, even if the animated images are backed
// by the same resource file.
return animation_1.skottie() == animation_2.skottie();
}
} // namespace
AnimatedImageView::AnimatedImageView() = default;
AnimatedImageView::~AnimatedImageView() = default;
void AnimatedImageView::SetAnimatedImage(
std::unique_ptr<gfx::SkiaVectorAnimation> animated_image) {
if (animated_image_ &&
AreAnimatedImagesEqual(*animated_image, *animated_image_)) {
Stop();
return;
}
gfx::Size preferred_size(GetPreferredSize());
animated_image_ = std::move(animated_image);
// Stop the animation to reset it.
Stop();
if (preferred_size != GetPreferredSize())
PreferredSizeChanged();
SchedulePaint();
}
void AnimatedImageView::Play() {
DCHECK(animated_image_);
DCHECK_EQ(state_, State::kStopped);
state_ = State::kPlaying;
SetCompositorFromWidget();
animated_image_->Start();
}
void AnimatedImageView::Stop() {
if (state_ == State::kStopped)
return;
DCHECK(animated_image_);
ClearCurrentCompositor();
animated_image_->Stop();
state_ = State::kStopped;
}
gfx::Size AnimatedImageView::GetImageSize() const {
return image_size_.value_or(
animated_image_ ? animated_image_->GetOriginalSize() : gfx::Size());
}
void AnimatedImageView::OnPaint(gfx::Canvas* canvas) {
View::OnPaint(canvas);
if (!animated_image_)
return;
canvas->Save();
canvas->Translate(GetImageBounds().origin().OffsetFromOrigin());
// OnPaint may be called before clock tick was received; in that case just
// paint the first frame.
if (!previous_timestamp_.is_null() && state_ != State::kStopped)
animated_image_->Paint(canvas, previous_timestamp_, GetImageSize());
else
animated_image_->PaintFrame(canvas, 0, GetImageSize());
canvas->Restore();
}
void AnimatedImageView::NativeViewHierarchyChanged() {
ui::Compositor* compositor = GetWidget()->GetCompositor();
DCHECK(compositor);
if (compositor_ != compositor) {
ClearCurrentCompositor();
// Restore the Play() state with the new compositor.
if (state_ == State::kPlaying)
SetCompositorFromWidget();
}
}
void AnimatedImageView::RemovedFromWidget() {
if (compositor_) {
Stop();
ClearCurrentCompositor();
}
}
void AnimatedImageView::OnAnimationStep(base::TimeTicks timestamp) {
previous_timestamp_ = timestamp;
SchedulePaint();
}
void AnimatedImageView::OnCompositingShuttingDown(ui::Compositor* compositor) {
if (compositor_ == compositor) {
Stop();
ClearCurrentCompositor();
}
}
void AnimatedImageView::SetCompositorFromWidget() {
DCHECK(!compositor_);
auto* widget = GetWidget();
DCHECK(widget);
compositor_ = widget->GetCompositor();
DCHECK(!compositor_->HasAnimationObserver(this));
compositor_->AddAnimationObserver(this);
}
void AnimatedImageView::ClearCurrentCompositor() {
if (compositor_) {
DCHECK(compositor_->HasAnimationObserver(this));
compositor_->RemoveAnimationObserver(this);
compositor_ = nullptr;
}
}
BEGIN_METADATA(AnimatedImageView)
METADATA_PARENT_CLASS(ImageViewBase)
END_METADATA()
} // namespace views