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