1 /*
2  * Copyright 2017 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "SkottieSlide.h"
9 
10 #include "SkAnimTimer.h"
11 #include "SkCanvas.h"
12 #include "SkMakeUnique.h"
13 #include "Skottie.h"
14 #include "SkOSFile.h"
15 #include "SkOSPath.h"
16 #include "SkSGColor.h"
17 #include "SkSGDraw.h"
18 #include "SkSGGroup.h"
19 #include "SkSGRenderNode.h"
20 #include "SkSGScene.h"
21 #include "SkSGText.h"
22 #include "SkSGTransform.h"
23 #include "SkStream.h"
24 #include "SkTypeface.h"
25 
26 #include <cmath>
27 
28 static constexpr int CELL_WIDTH  = 240;
29 static constexpr int CELL_HEIGHT = 160;
30 static constexpr int COL_COUNT   = 4;
31 static constexpr int SPACER_X    = 12;
32 static constexpr int SPACER_Y    = 24;
33 static constexpr int MARGIN      = 8;
34 
35 class SkottieSlide2::AnimationWrapper final : public sksg::RenderNode {
36 public:
37     explicit AnimationWrapper(std::unique_ptr<skottie::Animation> anim)
38         : fAnimation(std::move(anim)) {
39         SkASSERT(fAnimation);
40     }
41 
42     void tick(SkMSec t) {
43         fAnimation->animationTick(t);
44         this->invalidate();
45     }
46 
47     void setShowInval(bool show) { fAnimation->setShowInval(show); }
48 
49     // Trivial sksg::Animator -> skottie::Animation tick adapter
50     class ForwardingAnimator final : public sksg::Animator {
51     public:
52         ForwardingAnimator(sk_sp<AnimationWrapper> wrapper) : fWrapper(std::move(wrapper)) {}
53 
54     protected:
55         void onTick(float t) override {
56             fWrapper->tick(SkScalarRoundToInt(t));
57         }
58 
59     private:
60         sk_sp<AnimationWrapper> fWrapper;
61 
62         using INHERITED = sksg::Animator;
63     };
64 
65 protected:
66     SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
67         return SkRect::MakeSize(fAnimation->size());
68     }
69 
70     void onRender(SkCanvas* canvas) const override {
71         fAnimation->render(canvas);
72     }
73 
74 private:
75     const std::unique_ptr<skottie::Animation> fAnimation;
76 
77     using INHERITED = sksg::RenderNode;
78 };
79 
80 SkottieSlide2::Rec::Rec(sk_sp<AnimationWrapper> wrapper)
81     : fWrapper(std::move(wrapper)) {}
82 
83 SkottieSlide2::Rec::Rec(Rec&& o) = default;
84 
85 SkottieSlide2::SkottieSlide2(const SkString& path)
86     : fPath(path)
87 {
88     fName.set("skottie-dir");
89 }
90 
91 // Build a global scene using tranformed animation fragments:
92 //
93 // [Group(root)]
94 //     [Transform]
95 //         [Group]
96 //             [AnimationWrapper]
97 //             [Draw]
98 //                 [Text]
99 //                 [Color]
100 //     [Transform]
101 //         [Group]
102 //             [AnimationWrapper]
103 //             [Draw]
104 //                 [Text]
105 //                 [Color]
106 //     ...
107 //
108 // Note: for now animation wrappers are also tracked externally in fAnims, for tick dispatching.
109 
110 static sk_sp<sksg::RenderNode> MakeLabel(const SkString& txt,
111                                          const SkRect& src,
112                                          const SkMatrix& dstXform) {
113     auto text = sksg::Text::Make(nullptr, txt);
114     text->setFlags(SkPaint::kAntiAlias_Flag);
115     text->setSize(12 / std::sqrt(dstXform.getScaleX() * dstXform.getScaleY()));
116     text->setAlign(SkPaint::kCenter_Align);
117     text->setPosition(SkPoint::Make(src.width() / 2, src.height() + text->getSize()));
118 
119     return sksg::Draw::Make(std::move(text), sksg::Color::Make(SK_ColorBLACK));
120 }
121 
122 void SkottieSlide2::load(SkScalar, SkScalar) {
123     SkString name;
124     SkOSFile::Iter iter(fPath.c_str(), "json");
125 
126     int x = 0, y = 0;
127 
128     auto scene_root = sksg::Group::Make();
129     sksg::AnimatorList scene_animators;
130 
131     while (iter.next(&name)) {
132         SkString path = SkOSPath::Join(fPath.c_str(), name.c_str());
133         if (auto anim  = skottie::Animation::MakeFromFile(path.c_str())) {
134             const auto src = SkRect::MakeSize(anim->size()),
135                        dst = SkRect::MakeXYWH(MARGIN + x * (CELL_WIDTH + SPACER_X),
136                                               MARGIN + y * (CELL_HEIGHT + SPACER_Y),
137                                               CELL_WIDTH, CELL_HEIGHT);
138             const auto m   = SkMatrix::MakeRectToRect(src, dst, SkMatrix::kCenter_ScaleToFit);
139             auto wrapper   = sk_make_sp<AnimationWrapper>(std::move(anim));
140             auto group     = sksg::Group::Make();
141 
142             group->addChild(wrapper);
143             group->addChild(MakeLabel(name, src, m));
144 
145             auto xform     = sksg::Transform::Make(std::move(group), m);
146 
147             scene_animators.push_back(
148                 skstd::make_unique<AnimationWrapper::ForwardingAnimator>(wrapper));
149             scene_root->addChild(xform);
150             fAnims.emplace_back(std::move(wrapper));
151 
152             if (++x == COL_COUNT) {
153                 x = 0;
154                 y += 1;
155             }
156         }
157     }
158 
159     fScene = sksg::Scene::Make(std::move(scene_root), std::move(scene_animators));
160 }
161 
162 void SkottieSlide2::unload() {
163     fAnims.reset();
164     fScene.reset();
165     fTimeBase = 0;
166 }
167 
168 SkISize SkottieSlide2::getDimensions() const {
169     const int rows = (fAnims.count() + COL_COUNT - 1) / COL_COUNT;
170     return {
171         MARGIN + (COL_COUNT - 1) * SPACER_X + COL_COUNT * CELL_WIDTH + MARGIN,
172         MARGIN + (rows - 1) * SPACER_Y + rows * CELL_HEIGHT + MARGIN,
173     };
174 }
175 
176 void SkottieSlide2::draw(SkCanvas* canvas) {
177     fScene->render(canvas);
178 }
179 
180 bool SkottieSlide2::animate(const SkAnimTimer& timer) {
181     if (fTimeBase == 0) {
182         // Reset the animation time.
183         fTimeBase = timer.msec();
184     }
185     fScene->animate(timer.msec() - fTimeBase);
186 
187     return true;
188 }
189 
190 bool SkottieSlide2::onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
191                            uint32_t modifiers) {
192     if (fTrackingCell < 0 && state == sk_app::Window::kDown_InputState) {
193         fTrackingCell = this->findCell(x, y);
194     }
195     if (fTrackingCell >= 0 && state == sk_app::Window::kUp_InputState) {
196         int index = this->findCell(x, y);
197         if (fTrackingCell == index) {
198             fAnims[index].fShowAnimationInval = !fAnims[index].fShowAnimationInval;
199             fAnims[index].fWrapper->setShowInval(fAnims[index].fShowAnimationInval);
200         }
201         fTrackingCell = -1;
202     }
203     return fTrackingCell >= 0;
204 }
205 
206 int SkottieSlide2::findCell(float x, float y) const {
207     x -= MARGIN;
208     y -= MARGIN;
209     int index = -1;
210     if (x >= 0 && y >= 0) {
211         int ix = (int)x;
212         int iy = (int)y;
213         int col = ix / (CELL_WIDTH + SPACER_X);
214         int row = iy / (CELL_HEIGHT + SPACER_Y);
215         index = row * COL_COUNT + col;
216         if (index >= fAnims.count()) {
217             index = -1;
218         }
219     }
220     return index;
221 }
222