xref: /aosp_15_r20/external/skia/tools/viewer/SkottieSlide.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2017 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "tools/viewer/SkottieSlide.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #if defined(SK_ENABLE_SKOTTIE)
11*c8dee2aaSAndroid Build Coastguard Worker 
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFont.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFontMgr.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkNoncopyable.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTPin.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "modules/audioplayer/SkAudioPlayer.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/include/Skottie.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/include/SkottieProperty.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/include/SlotManager.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/utils/SkottieUtils.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/utils/TextEditor.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skresources/include/SkResources.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkTime.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkOSFile.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "src/utils/SkOSPath.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "tools/Resources.h"
28*c8dee2aaSAndroid Build Coastguard Worker #include "tools/fonts/FontToolUtils.h"
29*c8dee2aaSAndroid Build Coastguard Worker #include "tools/timer/TimeUtils.h"
30*c8dee2aaSAndroid Build Coastguard Worker 
31*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
32*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
33*c8dee2aaSAndroid Build Coastguard Worker 
34*c8dee2aaSAndroid Build Coastguard Worker #include "imgui.h"
35*c8dee2aaSAndroid Build Coastguard Worker 
36*c8dee2aaSAndroid Build Coastguard Worker namespace {
37*c8dee2aaSAndroid Build Coastguard Worker 
38*c8dee2aaSAndroid Build Coastguard Worker class Track final : public skresources::ExternalTrackAsset {
39*c8dee2aaSAndroid Build Coastguard Worker public:
Track(std::unique_ptr<SkAudioPlayer> player)40*c8dee2aaSAndroid Build Coastguard Worker     explicit Track(std::unique_ptr<SkAudioPlayer> player) : fPlayer(std::move(player)) {}
41*c8dee2aaSAndroid Build Coastguard Worker 
42*c8dee2aaSAndroid Build Coastguard Worker private:
seek(float t)43*c8dee2aaSAndroid Build Coastguard Worker     void seek(float t) override {
44*c8dee2aaSAndroid Build Coastguard Worker         if (fPlayer->isStopped() && t >=0) {
45*c8dee2aaSAndroid Build Coastguard Worker             fPlayer->play();
46*c8dee2aaSAndroid Build Coastguard Worker         }
47*c8dee2aaSAndroid Build Coastguard Worker 
48*c8dee2aaSAndroid Build Coastguard Worker         if (fPlayer->isPlaying()) {
49*c8dee2aaSAndroid Build Coastguard Worker             if (t < 0) {
50*c8dee2aaSAndroid Build Coastguard Worker                 fPlayer->stop();
51*c8dee2aaSAndroid Build Coastguard Worker             } else {
52*c8dee2aaSAndroid Build Coastguard Worker                 static constexpr float kTolerance = 0.075f;
53*c8dee2aaSAndroid Build Coastguard Worker                 const auto player_pos = fPlayer->time();
54*c8dee2aaSAndroid Build Coastguard Worker 
55*c8dee2aaSAndroid Build Coastguard Worker                 if (std::abs(player_pos - t) > kTolerance) {
56*c8dee2aaSAndroid Build Coastguard Worker                     fPlayer->setTime(t);
57*c8dee2aaSAndroid Build Coastguard Worker                 }
58*c8dee2aaSAndroid Build Coastguard Worker             }
59*c8dee2aaSAndroid Build Coastguard Worker         }
60*c8dee2aaSAndroid Build Coastguard Worker     }
61*c8dee2aaSAndroid Build Coastguard Worker 
62*c8dee2aaSAndroid Build Coastguard Worker     const std::unique_ptr<SkAudioPlayer> fPlayer;
63*c8dee2aaSAndroid Build Coastguard Worker };
64*c8dee2aaSAndroid Build Coastguard Worker 
65*c8dee2aaSAndroid Build Coastguard Worker class AudioProviderProxy final : public skresources::ResourceProviderProxyBase {
66*c8dee2aaSAndroid Build Coastguard Worker public:
AudioProviderProxy(sk_sp<skresources::ResourceProvider> rp)67*c8dee2aaSAndroid Build Coastguard Worker     explicit AudioProviderProxy(sk_sp<skresources::ResourceProvider> rp)
68*c8dee2aaSAndroid Build Coastguard Worker         : skresources::ResourceProviderProxyBase(std::move(rp)) {}
69*c8dee2aaSAndroid Build Coastguard Worker 
70*c8dee2aaSAndroid Build Coastguard Worker private:
loadAudioAsset(const char path[],const char name[],const char[])71*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<skresources::ExternalTrackAsset> loadAudioAsset(const char path[],
72*c8dee2aaSAndroid Build Coastguard Worker                                                           const char name[],
73*c8dee2aaSAndroid Build Coastguard Worker                                                           const char[] /*id*/) override {
74*c8dee2aaSAndroid Build Coastguard Worker         if (auto data = this->load(path, name)) {
75*c8dee2aaSAndroid Build Coastguard Worker             if (auto player = SkAudioPlayer::Make(std::move(data))) {
76*c8dee2aaSAndroid Build Coastguard Worker                 return sk_make_sp<Track>(std::move(player));
77*c8dee2aaSAndroid Build Coastguard Worker             }
78*c8dee2aaSAndroid Build Coastguard Worker         }
79*c8dee2aaSAndroid Build Coastguard Worker 
80*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
81*c8dee2aaSAndroid Build Coastguard Worker     }
82*c8dee2aaSAndroid Build Coastguard Worker };
83*c8dee2aaSAndroid Build Coastguard Worker 
84*c8dee2aaSAndroid Build Coastguard Worker class Decorator : public SkNoncopyable {
85*c8dee2aaSAndroid Build Coastguard Worker public:
86*c8dee2aaSAndroid Build Coastguard Worker     virtual ~Decorator() = default;
87*c8dee2aaSAndroid Build Coastguard Worker 
88*c8dee2aaSAndroid Build Coastguard Worker     // We pass in the Matrix and have the Decorator handle using it independently
89*c8dee2aaSAndroid Build Coastguard Worker     // This is so decorators can keep position on screen after moving.
90*c8dee2aaSAndroid Build Coastguard Worker     virtual void render(SkCanvas*, double, const SkMatrix) = 0;
91*c8dee2aaSAndroid Build Coastguard Worker };
92*c8dee2aaSAndroid Build Coastguard Worker 
93*c8dee2aaSAndroid Build Coastguard Worker class SimpleMarker final : public Decorator {
94*c8dee2aaSAndroid Build Coastguard Worker public:
95*c8dee2aaSAndroid Build Coastguard Worker     ~SimpleMarker() override = default;
96*c8dee2aaSAndroid Build Coastguard Worker 
Make()97*c8dee2aaSAndroid Build Coastguard Worker     static std::unique_ptr<Decorator> Make() { return std::make_unique<SimpleMarker>(); }
98*c8dee2aaSAndroid Build Coastguard Worker 
render(SkCanvas * canvas,double t,const SkMatrix transform)99*c8dee2aaSAndroid Build Coastguard Worker     void render(SkCanvas* canvas, double t, const SkMatrix transform) override {
100*c8dee2aaSAndroid Build Coastguard Worker         canvas->concat(transform);
101*c8dee2aaSAndroid Build Coastguard Worker         SkPaint p;
102*c8dee2aaSAndroid Build Coastguard Worker         p.setAntiAlias(true);
103*c8dee2aaSAndroid Build Coastguard Worker 
104*c8dee2aaSAndroid Build Coastguard Worker         p.setColor(SK_ColorGREEN);
105*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawCircle(0, 0, 5, p);
106*c8dee2aaSAndroid Build Coastguard Worker 
107*c8dee2aaSAndroid Build Coastguard Worker         p.setColor(SK_ColorRED);
108*c8dee2aaSAndroid Build Coastguard Worker         p.setStrokeWidth(1.5f);
109*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawLine(-10, 0, 10, 0, p);
110*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawLine(0, -10, 0, 10, p);
111*c8dee2aaSAndroid Build Coastguard Worker     }
112*c8dee2aaSAndroid Build Coastguard Worker };
113*c8dee2aaSAndroid Build Coastguard Worker 
114*c8dee2aaSAndroid Build Coastguard Worker class TestingResourceProvider : public skresources::ResourceProvider {
115*c8dee2aaSAndroid Build Coastguard Worker public:
TestingResourceProvider()116*c8dee2aaSAndroid Build Coastguard Worker     TestingResourceProvider() {}
117*c8dee2aaSAndroid Build Coastguard Worker 
load(const char resource_path[],const char resource_name[]) const118*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkData> load(const char resource_path[], const char resource_name[]) const override {
119*c8dee2aaSAndroid Build Coastguard Worker         auto it = fResources.find(resource_name);
120*c8dee2aaSAndroid Build Coastguard Worker         if (it != fResources.end()) {
121*c8dee2aaSAndroid Build Coastguard Worker             return it->second;
122*c8dee2aaSAndroid Build Coastguard Worker         } else {
123*c8dee2aaSAndroid Build Coastguard Worker             return GetResourceAsData(SkOSPath::Join(resource_path, resource_name).c_str());
124*c8dee2aaSAndroid Build Coastguard Worker         }
125*c8dee2aaSAndroid Build Coastguard Worker     }
126*c8dee2aaSAndroid Build Coastguard Worker 
loadImageAsset(const char resource_path[],const char resource_name[],const char[]) const127*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<skresources::ImageAsset> loadImageAsset(const char resource_path[],
128*c8dee2aaSAndroid Build Coastguard Worker                                                   const char resource_name[],
129*c8dee2aaSAndroid Build Coastguard Worker                                                   const char /*resource_id*/[]) const override {
130*c8dee2aaSAndroid Build Coastguard Worker         auto data = this->load(resource_path, resource_name);
131*c8dee2aaSAndroid Build Coastguard Worker         // Viewer should have already registered the codecs necessary for MultiFrameImageAsset
132*c8dee2aaSAndroid Build Coastguard Worker         return skresources::MultiFrameImageAsset::Make(data);
133*c8dee2aaSAndroid Build Coastguard Worker     }
134*c8dee2aaSAndroid Build Coastguard Worker 
addPath(const char resource_name[],const SkPath & path)135*c8dee2aaSAndroid Build Coastguard Worker     void addPath(const char resource_name[], const SkPath& path) {
136*c8dee2aaSAndroid Build Coastguard Worker         fResources[resource_name] = path.serialize();
137*c8dee2aaSAndroid Build Coastguard Worker     }
138*c8dee2aaSAndroid Build Coastguard Worker 
139*c8dee2aaSAndroid Build Coastguard Worker private:
140*c8dee2aaSAndroid Build Coastguard Worker     std::unordered_map<std::string, sk_sp<SkData>> fResources;
141*c8dee2aaSAndroid Build Coastguard Worker };
142*c8dee2aaSAndroid Build Coastguard Worker 
143*c8dee2aaSAndroid Build Coastguard Worker static const struct DecoratorRec {
144*c8dee2aaSAndroid Build Coastguard Worker     const char* fName;
145*c8dee2aaSAndroid Build Coastguard Worker     std::unique_ptr<Decorator>(*fFactory)();
146*c8dee2aaSAndroid Build Coastguard Worker } kDecorators[] = {
147*c8dee2aaSAndroid Build Coastguard Worker     { "Simple marker",       SimpleMarker::Make },
148*c8dee2aaSAndroid Build Coastguard Worker };
149*c8dee2aaSAndroid Build Coastguard Worker 
150*c8dee2aaSAndroid Build Coastguard Worker class TextTracker final : public skottie::PropertyObserver {
151*c8dee2aaSAndroid Build Coastguard Worker public:
TextTracker(sk_sp<PropertyObserver> delegate)152*c8dee2aaSAndroid Build Coastguard Worker     explicit TextTracker(sk_sp<PropertyObserver> delegate) : fDelegate(std::move(delegate)) {}
153*c8dee2aaSAndroid Build Coastguard Worker 
props()154*c8dee2aaSAndroid Build Coastguard Worker     std::vector<std::unique_ptr<skottie::TextPropertyHandle>>& props() {
155*c8dee2aaSAndroid Build Coastguard Worker         return fTextProps;
156*c8dee2aaSAndroid Build Coastguard Worker     }
157*c8dee2aaSAndroid Build Coastguard Worker 
158*c8dee2aaSAndroid Build Coastguard Worker private:
onTextProperty(const char node_name[],const LazyHandle<skottie::TextPropertyHandle> & lh)159*c8dee2aaSAndroid Build Coastguard Worker     void onTextProperty(const char node_name[],
160*c8dee2aaSAndroid Build Coastguard Worker                         const LazyHandle<skottie::TextPropertyHandle>& lh) override {
161*c8dee2aaSAndroid Build Coastguard Worker         fTextProps.push_back(lh());
162*c8dee2aaSAndroid Build Coastguard Worker 
163*c8dee2aaSAndroid Build Coastguard Worker         if (fDelegate) {
164*c8dee2aaSAndroid Build Coastguard Worker             fDelegate->onTextProperty(node_name, lh);
165*c8dee2aaSAndroid Build Coastguard Worker         }
166*c8dee2aaSAndroid Build Coastguard Worker     }
167*c8dee2aaSAndroid Build Coastguard Worker 
168*c8dee2aaSAndroid Build Coastguard Worker     const sk_sp<PropertyObserver>                             fDelegate;
169*c8dee2aaSAndroid Build Coastguard Worker     std::vector<std::unique_ptr<skottie::TextPropertyHandle>> fTextProps;
170*c8dee2aaSAndroid Build Coastguard Worker };
171*c8dee2aaSAndroid Build Coastguard Worker 
172*c8dee2aaSAndroid Build Coastguard Worker } // namespace
173*c8dee2aaSAndroid Build Coastguard Worker 
174*c8dee2aaSAndroid Build Coastguard Worker class SkottieSlide::TransformTracker : public skottie::PropertyObserver {
175*c8dee2aaSAndroid Build Coastguard Worker public:
renderUI()176*c8dee2aaSAndroid Build Coastguard Worker     void renderUI() {
177*c8dee2aaSAndroid Build Coastguard Worker         if (ImGui::Begin("Transform Tracker", nullptr)) {
178*c8dee2aaSAndroid Build Coastguard Worker             if (ImGui::BeginCombo("Transform", fTransformSelect
179*c8dee2aaSAndroid Build Coastguard Worker                                        ? std::get<0>(*fTransformSelect).c_str()
180*c8dee2aaSAndroid Build Coastguard Worker                                        : nullptr)) {
181*c8dee2aaSAndroid Build Coastguard Worker                 if (ImGui::Selectable("(none)", true)) {
182*c8dee2aaSAndroid Build Coastguard Worker                     fTransformSelect = nullptr;
183*c8dee2aaSAndroid Build Coastguard Worker                 }
184*c8dee2aaSAndroid Build Coastguard Worker                 for (const auto& entry : fTransforms) {
185*c8dee2aaSAndroid Build Coastguard Worker                     const auto* transform_name = std::get<0>(entry).c_str();
186*c8dee2aaSAndroid Build Coastguard Worker                     if (ImGui::Selectable(transform_name, false)) {
187*c8dee2aaSAndroid Build Coastguard Worker                         if (!fTransformSelect ||
188*c8dee2aaSAndroid Build Coastguard Worker                             transform_name != std::get<0>(*fTransformSelect).c_str()) {
189*c8dee2aaSAndroid Build Coastguard Worker                             fTransformSelect = &entry;
190*c8dee2aaSAndroid Build Coastguard Worker                             // Reset the decorator on transform change.
191*c8dee2aaSAndroid Build Coastguard Worker                             fDecorator = fDecoratorSelect->fFactory();
192*c8dee2aaSAndroid Build Coastguard Worker                         }
193*c8dee2aaSAndroid Build Coastguard Worker                     }
194*c8dee2aaSAndroid Build Coastguard Worker                 }
195*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::EndCombo();
196*c8dee2aaSAndroid Build Coastguard Worker             }
197*c8dee2aaSAndroid Build Coastguard Worker 
198*c8dee2aaSAndroid Build Coastguard Worker             if (ImGui::BeginCombo("Decoration", fDecoratorSelect->fName)) {
199*c8dee2aaSAndroid Build Coastguard Worker                 for (const auto& dec : kDecorators) {
200*c8dee2aaSAndroid Build Coastguard Worker                     if (ImGui::Selectable(dec.fName, false)) {
201*c8dee2aaSAndroid Build Coastguard Worker                         if (dec.fName != fDecoratorSelect->fName) {
202*c8dee2aaSAndroid Build Coastguard Worker                             fDecoratorSelect = &dec;
203*c8dee2aaSAndroid Build Coastguard Worker                             fDecorator = fDecoratorSelect->fFactory();
204*c8dee2aaSAndroid Build Coastguard Worker                         }
205*c8dee2aaSAndroid Build Coastguard Worker                     }
206*c8dee2aaSAndroid Build Coastguard Worker                 }
207*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::EndCombo();
208*c8dee2aaSAndroid Build Coastguard Worker             }
209*c8dee2aaSAndroid Build Coastguard Worker         }
210*c8dee2aaSAndroid Build Coastguard Worker         ImGui::End();
211*c8dee2aaSAndroid Build Coastguard Worker     }
212*c8dee2aaSAndroid Build Coastguard Worker 
renderTracker(SkCanvas * canvas,double time,const SkSize & win_size,const SkSize & anim_size) const213*c8dee2aaSAndroid Build Coastguard Worker     void renderTracker(SkCanvas* canvas, double time, const SkSize& win_size, const SkSize& anim_size) const {
214*c8dee2aaSAndroid Build Coastguard Worker         if (!fTransformSelect) {
215*c8dee2aaSAndroid Build Coastguard Worker             return;
216*c8dee2aaSAndroid Build Coastguard Worker         }
217*c8dee2aaSAndroid Build Coastguard Worker 
218*c8dee2aaSAndroid Build Coastguard Worker         const auto tprop = std::get<1>(*fTransformSelect)->get();
219*c8dee2aaSAndroid Build Coastguard Worker 
220*c8dee2aaSAndroid Build Coastguard Worker         const auto m = SkMatrix::Translate(tprop.fPosition.fX, tprop.fPosition.fY)
221*c8dee2aaSAndroid Build Coastguard Worker                      * SkMatrix::RotateDeg(tprop.fRotation)
222*c8dee2aaSAndroid Build Coastguard Worker                      * SkMatrix::Scale    (tprop.fScale.fX*0.01f, tprop.fScale.fY*0.01f)
223*c8dee2aaSAndroid Build Coastguard Worker                      * SkMatrix::Translate(tprop.fAnchorPoint.fX, tprop.fAnchorPoint.fY);
224*c8dee2aaSAndroid Build Coastguard Worker 
225*c8dee2aaSAndroid Build Coastguard Worker         const auto viewer_matrix = SkMatrix::RectToRect(SkRect::MakeSize(anim_size),
226*c8dee2aaSAndroid Build Coastguard Worker                                                         SkRect::MakeSize(win_size),
227*c8dee2aaSAndroid Build Coastguard Worker                                                         SkMatrix::kCenter_ScaleToFit);
228*c8dee2aaSAndroid Build Coastguard Worker 
229*c8dee2aaSAndroid Build Coastguard Worker         SkAutoCanvasRestore acr(canvas, true);
230*c8dee2aaSAndroid Build Coastguard Worker         canvas->concat(viewer_matrix);
231*c8dee2aaSAndroid Build Coastguard Worker 
232*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(fDecorator);
233*c8dee2aaSAndroid Build Coastguard Worker         fDecorator->render(canvas, time, m);
234*c8dee2aaSAndroid Build Coastguard Worker     }
235*c8dee2aaSAndroid Build Coastguard Worker 
236*c8dee2aaSAndroid Build Coastguard Worker private:
onTransformProperty(const char name[],const LazyHandle<skottie::TransformPropertyHandle> & lh)237*c8dee2aaSAndroid Build Coastguard Worker     void onTransformProperty(const char name[],
238*c8dee2aaSAndroid Build Coastguard Worker                              const LazyHandle<skottie::TransformPropertyHandle>& lh) override {
239*c8dee2aaSAndroid Build Coastguard Worker 
240*c8dee2aaSAndroid Build Coastguard Worker         fTransforms.push_back(std::make_tuple(SkString(name), lh()));
241*c8dee2aaSAndroid Build Coastguard Worker     }
242*c8dee2aaSAndroid Build Coastguard Worker 
243*c8dee2aaSAndroid Build Coastguard Worker     using TransformT = std::tuple<SkString, std::unique_ptr<skottie::TransformPropertyHandle>>;
244*c8dee2aaSAndroid Build Coastguard Worker 
245*c8dee2aaSAndroid Build Coastguard Worker     std::vector<TransformT>    fTransforms;
246*c8dee2aaSAndroid Build Coastguard Worker     std::unique_ptr<Decorator> fDecorator;
247*c8dee2aaSAndroid Build Coastguard Worker     const TransformT*          fTransformSelect = nullptr;
248*c8dee2aaSAndroid Build Coastguard Worker     const DecoratorRec*        fDecoratorSelect = &kDecorators[0];
249*c8dee2aaSAndroid Build Coastguard Worker };
250*c8dee2aaSAndroid Build Coastguard Worker 
251*c8dee2aaSAndroid Build Coastguard Worker // Holds a pointer to a slot manager and the list of slots for the UI widget to track
252*c8dee2aaSAndroid Build Coastguard Worker class SkottieSlide::SlotManagerInterface {
253*c8dee2aaSAndroid Build Coastguard Worker public:
SlotManagerInterface(sk_sp<skottie::SlotManager> slotManager,sk_sp<skresources::ResourceProvider> rp)254*c8dee2aaSAndroid Build Coastguard Worker     SlotManagerInterface(sk_sp<skottie::SlotManager> slotManager, sk_sp<skresources::ResourceProvider> rp)
255*c8dee2aaSAndroid Build Coastguard Worker         : fSlotManager(std::move(slotManager))
256*c8dee2aaSAndroid Build Coastguard Worker         , fResourceProvider(std::move(rp))
257*c8dee2aaSAndroid Build Coastguard Worker     {}
258*c8dee2aaSAndroid Build Coastguard Worker 
259*c8dee2aaSAndroid Build Coastguard Worker 
renderUI()260*c8dee2aaSAndroid Build Coastguard Worker     void renderUI() {
261*c8dee2aaSAndroid Build Coastguard Worker         if (ImGui::Begin("Slot Manager", nullptr)) {
262*c8dee2aaSAndroid Build Coastguard Worker             ImGui::Text("Color Slots");
263*c8dee2aaSAndroid Build Coastguard Worker             for (size_t i = 0; i < fColorSlots.size(); i++) {
264*c8dee2aaSAndroid Build Coastguard Worker                 auto& cSlot = fColorSlots.at(i);
265*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PushID(i);
266*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::Text("%s", cSlot.first.c_str());
267*c8dee2aaSAndroid Build Coastguard Worker                 if (ImGui::ColorEdit4("Color", cSlot.second.data())) {
268*c8dee2aaSAndroid Build Coastguard Worker                     this->pushSlots();
269*c8dee2aaSAndroid Build Coastguard Worker                 }
270*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PopID();
271*c8dee2aaSAndroid Build Coastguard Worker             }
272*c8dee2aaSAndroid Build Coastguard Worker             ImGui::Text("Scalar Slots");
273*c8dee2aaSAndroid Build Coastguard Worker             for (size_t i = 0; i < fScalarSlots.size(); i++) {
274*c8dee2aaSAndroid Build Coastguard Worker                 auto& oSlot = fScalarSlots.at(i);
275*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PushID(i);
276*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::Text("%s", oSlot.first.c_str());
277*c8dee2aaSAndroid Build Coastguard Worker                 if (ImGui::InputFloat("Scalar", &(oSlot.second))) {
278*c8dee2aaSAndroid Build Coastguard Worker                     this->pushSlots();
279*c8dee2aaSAndroid Build Coastguard Worker                 }
280*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PopID();
281*c8dee2aaSAndroid Build Coastguard Worker             }
282*c8dee2aaSAndroid Build Coastguard Worker             ImGui::Text("Vec2 Slots");
283*c8dee2aaSAndroid Build Coastguard Worker             for (size_t i = 0; i < fVec2Slots.size(); i++) {
284*c8dee2aaSAndroid Build Coastguard Worker                 auto& vSlot = fVec2Slots.at(i);
285*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PushID(i);
286*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::Text("%s", vSlot.first.c_str());
287*c8dee2aaSAndroid Build Coastguard Worker                 if (ImGui::InputFloat2("x, y", &(vSlot.second.x))) {
288*c8dee2aaSAndroid Build Coastguard Worker                     this->pushSlots();
289*c8dee2aaSAndroid Build Coastguard Worker                 }
290*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PopID();
291*c8dee2aaSAndroid Build Coastguard Worker             }
292*c8dee2aaSAndroid Build Coastguard Worker             ImGui::Text("Text Slots");
293*c8dee2aaSAndroid Build Coastguard Worker             for (size_t i = 0; i < fTextStringSlots.size(); i++) {
294*c8dee2aaSAndroid Build Coastguard Worker                 auto& tSlot = fTextStringSlots.at(i);
295*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PushID(i);
296*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::Text("%s", tSlot.first.c_str());
297*c8dee2aaSAndroid Build Coastguard Worker                 if (ImGui::InputText("Text", tSlot.second.source.data(),
298*c8dee2aaSAndroid Build Coastguard Worker                                              tSlot.second.source.size())) {
299*c8dee2aaSAndroid Build Coastguard Worker                     this->pushSlots();
300*c8dee2aaSAndroid Build Coastguard Worker                 }
301*c8dee2aaSAndroid Build Coastguard Worker                 if (ImGui::BeginCombo("Font", tSlot.second.font.data())) {
302*c8dee2aaSAndroid Build Coastguard Worker                     for (const auto& typeface : fTypefaceList) {
303*c8dee2aaSAndroid Build Coastguard Worker                         if (ImGui::Selectable(typeface, false)) {
304*c8dee2aaSAndroid Build Coastguard Worker                             tSlot.second.font = typeface;
305*c8dee2aaSAndroid Build Coastguard Worker                             this->pushSlots();
306*c8dee2aaSAndroid Build Coastguard Worker                         }
307*c8dee2aaSAndroid Build Coastguard Worker                     }
308*c8dee2aaSAndroid Build Coastguard Worker                     ImGui::EndCombo();
309*c8dee2aaSAndroid Build Coastguard Worker                 }
310*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PopID();
311*c8dee2aaSAndroid Build Coastguard Worker             }
312*c8dee2aaSAndroid Build Coastguard Worker 
313*c8dee2aaSAndroid Build Coastguard Worker             ImGui::Text("Image Slots");
314*c8dee2aaSAndroid Build Coastguard Worker             for (size_t i = 0; i < fImageSlots.size(); i++) {
315*c8dee2aaSAndroid Build Coastguard Worker                 auto& iSlot = fImageSlots.at(i);
316*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PushID(i);
317*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::Text("%s", iSlot.first.c_str());
318*c8dee2aaSAndroid Build Coastguard Worker                 if (ImGui::BeginCombo("Resource", iSlot.second.data())) {
319*c8dee2aaSAndroid Build Coastguard Worker                     for (const auto& res : fResList) {
320*c8dee2aaSAndroid Build Coastguard Worker                         if (ImGui::Selectable(res.c_str(), false)) {
321*c8dee2aaSAndroid Build Coastguard Worker                             iSlot.second = res.c_str();
322*c8dee2aaSAndroid Build Coastguard Worker                             this->pushSlots();
323*c8dee2aaSAndroid Build Coastguard Worker                         }
324*c8dee2aaSAndroid Build Coastguard Worker                     }
325*c8dee2aaSAndroid Build Coastguard Worker                     ImGui::EndCombo();
326*c8dee2aaSAndroid Build Coastguard Worker                 }
327*c8dee2aaSAndroid Build Coastguard Worker                 ImGui::PopID();
328*c8dee2aaSAndroid Build Coastguard Worker             }
329*c8dee2aaSAndroid Build Coastguard Worker         }
330*c8dee2aaSAndroid Build Coastguard Worker         ImGui::End();
331*c8dee2aaSAndroid Build Coastguard Worker     }
332*c8dee2aaSAndroid Build Coastguard Worker 
pushSlots()333*c8dee2aaSAndroid Build Coastguard Worker     void pushSlots() {
334*c8dee2aaSAndroid Build Coastguard Worker         for(const auto& s : fColorSlots) {
335*c8dee2aaSAndroid Build Coastguard Worker             fSlotManager->setColorSlot(s.first, SkColor4f{s.second[0], s.second[1],
336*c8dee2aaSAndroid Build Coastguard Worker                                                           s.second[2], s.second[3]}.toSkColor());
337*c8dee2aaSAndroid Build Coastguard Worker         }
338*c8dee2aaSAndroid Build Coastguard Worker         for(const auto& s : fScalarSlots) {
339*c8dee2aaSAndroid Build Coastguard Worker             fSlotManager->setScalarSlot(s.first, s.second);
340*c8dee2aaSAndroid Build Coastguard Worker         }
341*c8dee2aaSAndroid Build Coastguard Worker         for(const auto& s : fVec2Slots) {
342*c8dee2aaSAndroid Build Coastguard Worker             fSlotManager->setVec2Slot(s.first, {s.second.x, s.second.y});
343*c8dee2aaSAndroid Build Coastguard Worker         }
344*c8dee2aaSAndroid Build Coastguard Worker         for(const auto& s : fTextStringSlots) {
345*c8dee2aaSAndroid Build Coastguard Worker             auto t = fSlotManager->getTextSlot(s.first);
346*c8dee2aaSAndroid Build Coastguard Worker             t->fText = SkString(s.second.source.data());
347*c8dee2aaSAndroid Build Coastguard Worker             t->fTypeface = ToolUtils::TestFontMgr()->matchFamilyStyle(s.second.font.c_str(),
348*c8dee2aaSAndroid Build Coastguard Worker                                                                       SkFontStyle());
349*c8dee2aaSAndroid Build Coastguard Worker             fSlotManager->setTextSlot(s.first, *t);
350*c8dee2aaSAndroid Build Coastguard Worker         }
351*c8dee2aaSAndroid Build Coastguard Worker         for(const auto& s : fImageSlots) {
352*c8dee2aaSAndroid Build Coastguard Worker             auto image = fResourceProvider->loadImageAsset("images/", s.second.c_str(), nullptr);
353*c8dee2aaSAndroid Build Coastguard Worker             fSlotManager->setImageSlot(s.first, image);
354*c8dee2aaSAndroid Build Coastguard Worker         }
355*c8dee2aaSAndroid Build Coastguard Worker     }
356*c8dee2aaSAndroid Build Coastguard Worker 
initializeSlotManagerUI()357*c8dee2aaSAndroid Build Coastguard Worker     void initializeSlotManagerUI() {
358*c8dee2aaSAndroid Build Coastguard Worker         prepareImageAssetList(GetResourcePath("skottie/images").c_str());
359*c8dee2aaSAndroid Build Coastguard Worker         // only initialize if slots are unpopulated
360*c8dee2aaSAndroid Build Coastguard Worker         if (fColorSlots.empty() && fScalarSlots.empty() && fTextStringSlots.empty()) {
361*c8dee2aaSAndroid Build Coastguard Worker             auto slotInfos = fSlotManager->getSlotInfo();
362*c8dee2aaSAndroid Build Coastguard Worker             for (const auto &sid : slotInfos.fColorSlotIDs) {
363*c8dee2aaSAndroid Build Coastguard Worker                 addColorSlot(sid);
364*c8dee2aaSAndroid Build Coastguard Worker             }
365*c8dee2aaSAndroid Build Coastguard Worker             for (const auto &sid : slotInfos.fScalarSlotIDs) {
366*c8dee2aaSAndroid Build Coastguard Worker                 addScalarSlot(sid);
367*c8dee2aaSAndroid Build Coastguard Worker             }
368*c8dee2aaSAndroid Build Coastguard Worker             for (const auto &sid : slotInfos.fVec2SlotIDs) {
369*c8dee2aaSAndroid Build Coastguard Worker                 addVec2Slot(sid);
370*c8dee2aaSAndroid Build Coastguard Worker             }
371*c8dee2aaSAndroid Build Coastguard Worker             for (const auto &sid : slotInfos.fImageSlotIDs) {
372*c8dee2aaSAndroid Build Coastguard Worker                 addImageSlot(sid);
373*c8dee2aaSAndroid Build Coastguard Worker             }
374*c8dee2aaSAndroid Build Coastguard Worker             for (const auto &sid : slotInfos.fTextSlotIDs) {
375*c8dee2aaSAndroid Build Coastguard Worker                 addTextSlot(sid);
376*c8dee2aaSAndroid Build Coastguard Worker             }
377*c8dee2aaSAndroid Build Coastguard Worker         }
378*c8dee2aaSAndroid Build Coastguard Worker     }
379*c8dee2aaSAndroid Build Coastguard Worker 
380*c8dee2aaSAndroid Build Coastguard Worker private:
381*c8dee2aaSAndroid Build Coastguard Worker     static constexpr int kBufferLen = 256;
382*c8dee2aaSAndroid Build Coastguard Worker 
383*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<skottie::SlotManager> fSlotManager;
384*c8dee2aaSAndroid Build Coastguard Worker     const sk_sp<skresources::ResourceProvider> fResourceProvider;
385*c8dee2aaSAndroid Build Coastguard Worker     std::vector<SkString> fResList;
386*c8dee2aaSAndroid Build Coastguard Worker     static constexpr std::array<const char*, 4> fTypefaceList = {"Arial",
387*c8dee2aaSAndroid Build Coastguard Worker                                                                  "Courier New",
388*c8dee2aaSAndroid Build Coastguard Worker                                                                  "Roboto-Regular",
389*c8dee2aaSAndroid Build Coastguard Worker                                                                  "Georgia"};
390*c8dee2aaSAndroid Build Coastguard Worker 
391*c8dee2aaSAndroid Build Coastguard Worker     using GuiTextBuffer = std::array<char, kBufferLen>;
392*c8dee2aaSAndroid Build Coastguard Worker 
addColorSlot(SkString slotID)393*c8dee2aaSAndroid Build Coastguard Worker     void addColorSlot(SkString slotID) {
394*c8dee2aaSAndroid Build Coastguard Worker         auto c = fSlotManager->getColorSlot(slotID);
395*c8dee2aaSAndroid Build Coastguard Worker         SkColor4f color4f = SkColor4f::FromColor(*c);
396*c8dee2aaSAndroid Build Coastguard Worker         fColorSlots.push_back(std::make_pair(slotID, color4f.array()));
397*c8dee2aaSAndroid Build Coastguard Worker     }
398*c8dee2aaSAndroid Build Coastguard Worker 
addScalarSlot(SkString slotID)399*c8dee2aaSAndroid Build Coastguard Worker     void addScalarSlot(SkString slotID) {
400*c8dee2aaSAndroid Build Coastguard Worker         fScalarSlots.push_back(std::make_pair(slotID, *fSlotManager->getScalarSlot(slotID)));
401*c8dee2aaSAndroid Build Coastguard Worker     }
402*c8dee2aaSAndroid Build Coastguard Worker 
addVec2Slot(SkString slotID)403*c8dee2aaSAndroid Build Coastguard Worker     void addVec2Slot(SkString slotID) {
404*c8dee2aaSAndroid Build Coastguard Worker         fVec2Slots.push_back(std::make_pair(slotID, *fSlotManager->getVec2Slot(slotID)));
405*c8dee2aaSAndroid Build Coastguard Worker     }
406*c8dee2aaSAndroid Build Coastguard Worker 
addTextSlot(SkString slotID)407*c8dee2aaSAndroid Build Coastguard Worker     void addTextSlot(SkString slotID) {
408*c8dee2aaSAndroid Build Coastguard Worker         std::array<char, kBufferLen> textSource = {'\0'};
409*c8dee2aaSAndroid Build Coastguard Worker         SkString s = fSlotManager->getTextSlot(slotID)->fText;
410*c8dee2aaSAndroid Build Coastguard Worker         std::copy(s.data(), s.data() + s.size(), textSource.data());
411*c8dee2aaSAndroid Build Coastguard Worker         TextSlotData data = {textSource, fTypefaceList[0]};
412*c8dee2aaSAndroid Build Coastguard Worker         fTextStringSlots.push_back(std::make_pair(slotID, data));
413*c8dee2aaSAndroid Build Coastguard Worker     }
414*c8dee2aaSAndroid Build Coastguard Worker 
addImageSlot(SkString slotID)415*c8dee2aaSAndroid Build Coastguard Worker     void addImageSlot(SkString slotID) {
416*c8dee2aaSAndroid Build Coastguard Worker         fImageSlots.push_back(std::make_pair(slotID, fResList[0].data()));
417*c8dee2aaSAndroid Build Coastguard Worker     }
418*c8dee2aaSAndroid Build Coastguard Worker 
prepareImageAssetList(const char * dirname)419*c8dee2aaSAndroid Build Coastguard Worker     void prepareImageAssetList(const char* dirname) {
420*c8dee2aaSAndroid Build Coastguard Worker         fResList.clear();
421*c8dee2aaSAndroid Build Coastguard Worker         SkOSFile::Iter iter(dirname, ".png");
422*c8dee2aaSAndroid Build Coastguard Worker         for (SkString file; iter.next(&file); ) {
423*c8dee2aaSAndroid Build Coastguard Worker             fResList.push_back(file);
424*c8dee2aaSAndroid Build Coastguard Worker         }
425*c8dee2aaSAndroid Build Coastguard Worker     }
426*c8dee2aaSAndroid Build Coastguard Worker 
427*c8dee2aaSAndroid Build Coastguard Worker     struct TextSlotData {
428*c8dee2aaSAndroid Build Coastguard Worker         GuiTextBuffer source;
429*c8dee2aaSAndroid Build Coastguard Worker         std::string   font;
430*c8dee2aaSAndroid Build Coastguard Worker     };
431*c8dee2aaSAndroid Build Coastguard Worker 
432*c8dee2aaSAndroid Build Coastguard Worker     std::vector<std::pair<SkString, std::array<float, 4>>> fColorSlots;
433*c8dee2aaSAndroid Build Coastguard Worker     std::vector<std::pair<SkString, float>>                fScalarSlots;
434*c8dee2aaSAndroid Build Coastguard Worker     std::vector<std::pair<SkString, SkV2>>                 fVec2Slots;
435*c8dee2aaSAndroid Build Coastguard Worker     std::vector<std::pair<SkString, TextSlotData>>         fTextStringSlots;
436*c8dee2aaSAndroid Build Coastguard Worker     std::vector<std::pair<SkString, std::string>>          fImageSlots;
437*c8dee2aaSAndroid Build Coastguard Worker 
438*c8dee2aaSAndroid Build Coastguard Worker };
439*c8dee2aaSAndroid Build Coastguard Worker 
draw_stats_box(SkCanvas * canvas,const skottie::Animation::Builder::Stats & stats)440*c8dee2aaSAndroid Build Coastguard Worker static void draw_stats_box(SkCanvas* canvas, const skottie::Animation::Builder::Stats& stats) {
441*c8dee2aaSAndroid Build Coastguard Worker     static constexpr SkRect kR = { 10, 10, 280, 120 };
442*c8dee2aaSAndroid Build Coastguard Worker     static constexpr SkScalar kTextSize = 20;
443*c8dee2aaSAndroid Build Coastguard Worker 
444*c8dee2aaSAndroid Build Coastguard Worker     SkPaint paint;
445*c8dee2aaSAndroid Build Coastguard Worker     paint.setAntiAlias(true);
446*c8dee2aaSAndroid Build Coastguard Worker     paint.setColor(0xffeeeeee);
447*c8dee2aaSAndroid Build Coastguard Worker 
448*c8dee2aaSAndroid Build Coastguard Worker     SkFont font(ToolUtils::DefaultTypeface(), kTextSize);
449*c8dee2aaSAndroid Build Coastguard Worker 
450*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawRect(kR, paint);
451*c8dee2aaSAndroid Build Coastguard Worker 
452*c8dee2aaSAndroid Build Coastguard Worker     paint.setColor(SK_ColorBLACK);
453*c8dee2aaSAndroid Build Coastguard Worker 
454*c8dee2aaSAndroid Build Coastguard Worker     const auto json_size = SkStringPrintf("Json size: %zu bytes",
455*c8dee2aaSAndroid Build Coastguard Worker                                           stats.fJsonSize);
456*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawString(json_size, kR.x() + 10, kR.y() + kTextSize * 1, font, paint);
457*c8dee2aaSAndroid Build Coastguard Worker     const auto animator_count = SkStringPrintf("Animator count: %zu",
458*c8dee2aaSAndroid Build Coastguard Worker                                                stats.fAnimatorCount);
459*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawString(animator_count, kR.x() + 10, kR.y() + kTextSize * 2, font, paint);
460*c8dee2aaSAndroid Build Coastguard Worker     const auto json_parse_time = SkStringPrintf("Json parse time: %.3f ms",
461*c8dee2aaSAndroid Build Coastguard Worker                                                 stats.fJsonParseTimeMS);
462*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawString(json_parse_time, kR.x() + 10, kR.y() + kTextSize * 3, font, paint);
463*c8dee2aaSAndroid Build Coastguard Worker     const auto scene_parse_time = SkStringPrintf("Scene build time: %.3f ms",
464*c8dee2aaSAndroid Build Coastguard Worker                                                  stats.fSceneParseTimeMS);
465*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawString(scene_parse_time, kR.x() + 10, kR.y() + kTextSize * 4, font, paint);
466*c8dee2aaSAndroid Build Coastguard Worker     const auto total_load_time = SkStringPrintf("Total load time: %.3f ms",
467*c8dee2aaSAndroid Build Coastguard Worker                                                 stats.fTotalLoadTimeMS);
468*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawString(total_load_time, kR.x() + 10, kR.y() + kTextSize * 5, font, paint);
469*c8dee2aaSAndroid Build Coastguard Worker 
470*c8dee2aaSAndroid Build Coastguard Worker     paint.setStyle(SkPaint::kStroke_Style);
471*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawRect(kR, paint);
472*c8dee2aaSAndroid Build Coastguard Worker }
473*c8dee2aaSAndroid Build Coastguard Worker 
SkottieSlide(const SkString & name,const SkString & path)474*c8dee2aaSAndroid Build Coastguard Worker SkottieSlide::SkottieSlide(const SkString& name, const SkString& path)
475*c8dee2aaSAndroid Build Coastguard Worker     : fPath(path) {
476*c8dee2aaSAndroid Build Coastguard Worker     fName = name;
477*c8dee2aaSAndroid Build Coastguard Worker }
478*c8dee2aaSAndroid Build Coastguard Worker 
init()479*c8dee2aaSAndroid Build Coastguard Worker void SkottieSlide::init() {
480*c8dee2aaSAndroid Build Coastguard Worker     class Logger final : public skottie::Logger {
481*c8dee2aaSAndroid Build Coastguard Worker     public:
482*c8dee2aaSAndroid Build Coastguard Worker         struct LogEntry {
483*c8dee2aaSAndroid Build Coastguard Worker             SkString fMessage,
484*c8dee2aaSAndroid Build Coastguard Worker                      fJSON;
485*c8dee2aaSAndroid Build Coastguard Worker         };
486*c8dee2aaSAndroid Build Coastguard Worker 
487*c8dee2aaSAndroid Build Coastguard Worker         void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
488*c8dee2aaSAndroid Build Coastguard Worker             auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
489*c8dee2aaSAndroid Build Coastguard Worker             log.push_back({ SkString(message), json ? SkString(json) : SkString() });
490*c8dee2aaSAndroid Build Coastguard Worker         }
491*c8dee2aaSAndroid Build Coastguard Worker 
492*c8dee2aaSAndroid Build Coastguard Worker         void report() const {
493*c8dee2aaSAndroid Build Coastguard Worker             SkDebugf("Animation loaded with %zu error%s, %zu warning%s.\n",
494*c8dee2aaSAndroid Build Coastguard Worker                      fErrors.size(), fErrors.size() == 1 ? "" : "s",
495*c8dee2aaSAndroid Build Coastguard Worker                      fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
496*c8dee2aaSAndroid Build Coastguard Worker 
497*c8dee2aaSAndroid Build Coastguard Worker             const auto& show = [](const LogEntry& log, const char prefix[]) {
498*c8dee2aaSAndroid Build Coastguard Worker                 SkDebugf("%s%s", prefix, log.fMessage.c_str());
499*c8dee2aaSAndroid Build Coastguard Worker                 if (!log.fJSON.isEmpty())
500*c8dee2aaSAndroid Build Coastguard Worker                     SkDebugf(" : %s", log.fJSON.c_str());
501*c8dee2aaSAndroid Build Coastguard Worker                 SkDebugf("\n");
502*c8dee2aaSAndroid Build Coastguard Worker             };
503*c8dee2aaSAndroid Build Coastguard Worker 
504*c8dee2aaSAndroid Build Coastguard Worker             for (const auto& err : fErrors)   show(err, "  !! ");
505*c8dee2aaSAndroid Build Coastguard Worker             for (const auto& wrn : fWarnings) show(wrn, "  ?? ");
506*c8dee2aaSAndroid Build Coastguard Worker         }
507*c8dee2aaSAndroid Build Coastguard Worker 
508*c8dee2aaSAndroid Build Coastguard Worker     private:
509*c8dee2aaSAndroid Build Coastguard Worker         std::vector<LogEntry> fErrors,
510*c8dee2aaSAndroid Build Coastguard Worker                               fWarnings;
511*c8dee2aaSAndroid Build Coastguard Worker     };
512*c8dee2aaSAndroid Build Coastguard Worker 
513*c8dee2aaSAndroid Build Coastguard Worker     auto logger = sk_make_sp<Logger>();
514*c8dee2aaSAndroid Build Coastguard Worker 
515*c8dee2aaSAndroid Build Coastguard Worker     uint32_t flags = 0;
516*c8dee2aaSAndroid Build Coastguard Worker     if (fPreferGlyphPaths) {
517*c8dee2aaSAndroid Build Coastguard Worker         flags |= skottie::Animation::Builder::kPreferEmbeddedFonts;
518*c8dee2aaSAndroid Build Coastguard Worker     }
519*c8dee2aaSAndroid Build Coastguard Worker     skottie::Animation::Builder builder(flags);
520*c8dee2aaSAndroid Build Coastguard Worker 
521*c8dee2aaSAndroid Build Coastguard Worker     // Viewer should have already registered the codecs necessary for DataURIResourceProviderProxy
522*c8dee2aaSAndroid Build Coastguard Worker     auto predecode = skresources::ImageDecodeStrategy::kPreDecode;
523*c8dee2aaSAndroid Build Coastguard Worker     auto resource_provider =
524*c8dee2aaSAndroid Build Coastguard Worker             sk_make_sp<AudioProviderProxy>(skresources::DataURIResourceProviderProxy::Make(
525*c8dee2aaSAndroid Build Coastguard Worker                     skresources::FileResourceProvider::Make(SkOSPath::Dirname(fPath.c_str()),
526*c8dee2aaSAndroid Build Coastguard Worker                                                             predecode),
527*c8dee2aaSAndroid Build Coastguard Worker                     predecode,
528*c8dee2aaSAndroid Build Coastguard Worker                     ToolUtils::TestFontMgr()));
529*c8dee2aaSAndroid Build Coastguard Worker 
530*c8dee2aaSAndroid Build Coastguard Worker     static constexpr char kInterceptPrefix[] = "__";
531*c8dee2aaSAndroid Build Coastguard Worker     auto precomp_interceptor =
532*c8dee2aaSAndroid Build Coastguard Worker             sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(resource_provider,
533*c8dee2aaSAndroid Build Coastguard Worker                                                                            kInterceptPrefix);
534*c8dee2aaSAndroid Build Coastguard Worker 
535*c8dee2aaSAndroid Build Coastguard Worker     fTransformTracker = sk_make_sp<TransformTracker>();
536*c8dee2aaSAndroid Build Coastguard Worker     auto text_tracker = sk_make_sp<TextTracker>(fTransformTracker);
537*c8dee2aaSAndroid Build Coastguard Worker 
538*c8dee2aaSAndroid Build Coastguard Worker     builder.setLogger(logger)
539*c8dee2aaSAndroid Build Coastguard Worker            .setFontManager(ToolUtils::TestFontMgr())
540*c8dee2aaSAndroid Build Coastguard Worker            .setPrecompInterceptor(std::move(precomp_interceptor))
541*c8dee2aaSAndroid Build Coastguard Worker            .setResourceProvider(resource_provider)
542*c8dee2aaSAndroid Build Coastguard Worker            .setPropertyObserver(text_tracker);
543*c8dee2aaSAndroid Build Coastguard Worker 
544*c8dee2aaSAndroid Build Coastguard Worker     fAnimation = builder.makeFromFile(fPath.c_str());
545*c8dee2aaSAndroid Build Coastguard Worker     fAnimationStats = builder.getStats();
546*c8dee2aaSAndroid Build Coastguard Worker     fTimeBase       = 0; // force a time reset
547*c8dee2aaSAndroid Build Coastguard Worker 
548*c8dee2aaSAndroid Build Coastguard Worker     if (fAnimation) {
549*c8dee2aaSAndroid Build Coastguard Worker         if (!fSlotManagerInterface) {
550*c8dee2aaSAndroid Build Coastguard Worker             fSlotManagerInterface =
551*c8dee2aaSAndroid Build Coastguard Worker                 std::make_unique<SlotManagerInterface>(builder.getSlotManager(), resource_provider);
552*c8dee2aaSAndroid Build Coastguard Worker         }
553*c8dee2aaSAndroid Build Coastguard Worker 
554*c8dee2aaSAndroid Build Coastguard Worker         fSlotManagerInterface->initializeSlotManagerUI();
555*c8dee2aaSAndroid Build Coastguard Worker 
556*c8dee2aaSAndroid Build Coastguard Worker         fAnimation->seek(0);
557*c8dee2aaSAndroid Build Coastguard Worker         fFrameTimes.resize(SkScalarCeilToInt(fAnimation->duration() * fAnimation->fps()));
558*c8dee2aaSAndroid Build Coastguard Worker         SkDebugf("Loaded Bodymovin animation v: %s, size: [%f %f]\n",
559*c8dee2aaSAndroid Build Coastguard Worker                  fAnimation->version().c_str(),
560*c8dee2aaSAndroid Build Coastguard Worker                  fAnimation->size().width(),
561*c8dee2aaSAndroid Build Coastguard Worker                  fAnimation->size().height());
562*c8dee2aaSAndroid Build Coastguard Worker         logger->report();
563*c8dee2aaSAndroid Build Coastguard Worker 
564*c8dee2aaSAndroid Build Coastguard Worker         if (auto text_props = std::move(text_tracker->props()); !text_props.empty()) {
565*c8dee2aaSAndroid Build Coastguard Worker             // Attach the editor to the first text layer, and track the rest as dependents.
566*c8dee2aaSAndroid Build Coastguard Worker             auto editor_target = std::move(text_props[0]);
567*c8dee2aaSAndroid Build Coastguard Worker             text_props.erase(text_props.cbegin());
568*c8dee2aaSAndroid Build Coastguard Worker             fTextEditor = sk_make_sp<skottie_utils::TextEditor>(std::move(editor_target),
569*c8dee2aaSAndroid Build Coastguard Worker                                                                 std::move(text_props));
570*c8dee2aaSAndroid Build Coastguard Worker             fTextEditor->setCursorWeight(1.2f);
571*c8dee2aaSAndroid Build Coastguard Worker         }
572*c8dee2aaSAndroid Build Coastguard Worker     } else {
573*c8dee2aaSAndroid Build Coastguard Worker         SkDebugf("failed to load Bodymovin animation: %s\n", fPath.c_str());
574*c8dee2aaSAndroid Build Coastguard Worker     }
575*c8dee2aaSAndroid Build Coastguard Worker }
576*c8dee2aaSAndroid Build Coastguard Worker 
load(SkScalar w,SkScalar h)577*c8dee2aaSAndroid Build Coastguard Worker void SkottieSlide::load(SkScalar w, SkScalar h) {
578*c8dee2aaSAndroid Build Coastguard Worker     fWinSize = SkSize::Make(w, h);
579*c8dee2aaSAndroid Build Coastguard Worker     this->init();
580*c8dee2aaSAndroid Build Coastguard Worker }
581*c8dee2aaSAndroid Build Coastguard Worker 
unload()582*c8dee2aaSAndroid Build Coastguard Worker void SkottieSlide::unload() {
583*c8dee2aaSAndroid Build Coastguard Worker     fAnimation.reset();
584*c8dee2aaSAndroid Build Coastguard Worker }
585*c8dee2aaSAndroid Build Coastguard Worker 
resize(SkScalar w,SkScalar h)586*c8dee2aaSAndroid Build Coastguard Worker void SkottieSlide::resize(SkScalar w, SkScalar h) {
587*c8dee2aaSAndroid Build Coastguard Worker     fWinSize = { w, h };
588*c8dee2aaSAndroid Build Coastguard Worker }
589*c8dee2aaSAndroid Build Coastguard Worker 
draw(SkCanvas * canvas)590*c8dee2aaSAndroid Build Coastguard Worker void SkottieSlide::draw(SkCanvas* canvas) {
591*c8dee2aaSAndroid Build Coastguard Worker     if (fAnimation) {
592*c8dee2aaSAndroid Build Coastguard Worker         SkAutoCanvasRestore acr(canvas, true);
593*c8dee2aaSAndroid Build Coastguard Worker         const auto dstR = SkRect::MakeSize(fWinSize);
594*c8dee2aaSAndroid Build Coastguard Worker 
595*c8dee2aaSAndroid Build Coastguard Worker         {
596*c8dee2aaSAndroid Build Coastguard Worker             const auto t0 = SkTime::GetNSecs();
597*c8dee2aaSAndroid Build Coastguard Worker             fAnimation->render(canvas, &dstR);
598*c8dee2aaSAndroid Build Coastguard Worker 
599*c8dee2aaSAndroid Build Coastguard Worker             // TODO: this does not capture GPU flush time!
600*c8dee2aaSAndroid Build Coastguard Worker             const auto  frame_index  = static_cast<size_t>(fCurrentFrame);
601*c8dee2aaSAndroid Build Coastguard Worker             fFrameTimes[frame_index] = static_cast<float>((SkTime::GetNSecs() - t0) * 1e-6);
602*c8dee2aaSAndroid Build Coastguard Worker         }
603*c8dee2aaSAndroid Build Coastguard Worker 
604*c8dee2aaSAndroid Build Coastguard Worker         double fr = 60;
605*c8dee2aaSAndroid Build Coastguard Worker         if (fFrameRate != 0) {
606*c8dee2aaSAndroid Build Coastguard Worker             fr = fFrameRate;
607*c8dee2aaSAndroid Build Coastguard Worker         }
608*c8dee2aaSAndroid Build Coastguard Worker         fTransformTracker->renderTracker(canvas, fCurrentFrame/fr, fWinSize, fAnimation->size());
609*c8dee2aaSAndroid Build Coastguard Worker 
610*c8dee2aaSAndroid Build Coastguard Worker         if (fShowAnimationStats) {
611*c8dee2aaSAndroid Build Coastguard Worker             draw_stats_box(canvas, fAnimationStats);
612*c8dee2aaSAndroid Build Coastguard Worker         }
613*c8dee2aaSAndroid Build Coastguard Worker         if (fShowAnimationInval) {
614*c8dee2aaSAndroid Build Coastguard Worker             const auto t = SkMatrix::RectToRect(SkRect::MakeSize(fAnimation->size()), dstR,
615*c8dee2aaSAndroid Build Coastguard Worker                                                 SkMatrix::kCenter_ScaleToFit);
616*c8dee2aaSAndroid Build Coastguard Worker             SkPaint fill, stroke;
617*c8dee2aaSAndroid Build Coastguard Worker             fill.setAntiAlias(true);
618*c8dee2aaSAndroid Build Coastguard Worker             fill.setColor(0x40ff0000);
619*c8dee2aaSAndroid Build Coastguard Worker             stroke.setAntiAlias(true);
620*c8dee2aaSAndroid Build Coastguard Worker             stroke.setColor(0xffff0000);
621*c8dee2aaSAndroid Build Coastguard Worker             stroke.setStyle(SkPaint::kStroke_Style);
622*c8dee2aaSAndroid Build Coastguard Worker 
623*c8dee2aaSAndroid Build Coastguard Worker             for (const auto& r : fInvalController) {
624*c8dee2aaSAndroid Build Coastguard Worker                 SkRect bounds;
625*c8dee2aaSAndroid Build Coastguard Worker                 t.mapRect(&bounds, r);
626*c8dee2aaSAndroid Build Coastguard Worker                 canvas->drawRect(bounds, fill);
627*c8dee2aaSAndroid Build Coastguard Worker                 canvas->drawRect(bounds, stroke);
628*c8dee2aaSAndroid Build Coastguard Worker             }
629*c8dee2aaSAndroid Build Coastguard Worker         }
630*c8dee2aaSAndroid Build Coastguard Worker         if (fShowUI) {
631*c8dee2aaSAndroid Build Coastguard Worker             this->renderUI();
632*c8dee2aaSAndroid Build Coastguard Worker         }
633*c8dee2aaSAndroid Build Coastguard Worker         if (fShowSlotManager) {
634*c8dee2aaSAndroid Build Coastguard Worker             // not able to track layers with a PropertyObserver while using SM's PropertyObserver
635*c8dee2aaSAndroid Build Coastguard Worker             fShowTrackerUI = false;
636*c8dee2aaSAndroid Build Coastguard Worker             fSlotManagerInterface->renderUI();
637*c8dee2aaSAndroid Build Coastguard Worker         }
638*c8dee2aaSAndroid Build Coastguard Worker         if (fShowTrackerUI) {
639*c8dee2aaSAndroid Build Coastguard Worker             fTransformTracker->renderUI();
640*c8dee2aaSAndroid Build Coastguard Worker         }
641*c8dee2aaSAndroid Build Coastguard Worker     }
642*c8dee2aaSAndroid Build Coastguard Worker }
643*c8dee2aaSAndroid Build Coastguard Worker 
animate(double nanos)644*c8dee2aaSAndroid Build Coastguard Worker bool SkottieSlide::animate(double nanos) {
645*c8dee2aaSAndroid Build Coastguard Worker     if (!fTimeBase) {
646*c8dee2aaSAndroid Build Coastguard Worker         // Reset the animation time.
647*c8dee2aaSAndroid Build Coastguard Worker         fTimeBase = nanos;
648*c8dee2aaSAndroid Build Coastguard Worker     }
649*c8dee2aaSAndroid Build Coastguard Worker 
650*c8dee2aaSAndroid Build Coastguard Worker     if (fAnimation) {
651*c8dee2aaSAndroid Build Coastguard Worker         fInvalController.reset();
652*c8dee2aaSAndroid Build Coastguard Worker 
653*c8dee2aaSAndroid Build Coastguard Worker         const auto frame_count = fAnimation->duration() * fAnimation->fps();
654*c8dee2aaSAndroid Build Coastguard Worker 
655*c8dee2aaSAndroid Build Coastguard Worker         if (!fDraggingProgress) {
656*c8dee2aaSAndroid Build Coastguard Worker             // Clock-driven progress: update current frame.
657*c8dee2aaSAndroid Build Coastguard Worker             const double t_sec = (nanos - fTimeBase) * 1e-9;
658*c8dee2aaSAndroid Build Coastguard Worker             fCurrentFrame = std::fmod(t_sec * fAnimation->fps(), frame_count);
659*c8dee2aaSAndroid Build Coastguard Worker         } else {
660*c8dee2aaSAndroid Build Coastguard Worker             // Slider-driven progress: update the time origin.
661*c8dee2aaSAndroid Build Coastguard Worker             fTimeBase = nanos - fCurrentFrame / fAnimation->fps() * 1e9;
662*c8dee2aaSAndroid Build Coastguard Worker         }
663*c8dee2aaSAndroid Build Coastguard Worker 
664*c8dee2aaSAndroid Build Coastguard Worker         // Sanitize and rate-lock the current frame.
665*c8dee2aaSAndroid Build Coastguard Worker         fCurrentFrame = SkTPin<float>(fCurrentFrame, 0.0f, frame_count - 1);
666*c8dee2aaSAndroid Build Coastguard Worker         if (fFrameRate > 0) {
667*c8dee2aaSAndroid Build Coastguard Worker             const auto fps_scale = fFrameRate / fAnimation->fps();
668*c8dee2aaSAndroid Build Coastguard Worker             fCurrentFrame = std::trunc(fCurrentFrame * fps_scale) / fps_scale;
669*c8dee2aaSAndroid Build Coastguard Worker         }
670*c8dee2aaSAndroid Build Coastguard Worker 
671*c8dee2aaSAndroid Build Coastguard Worker         fAnimation->seekFrame(fCurrentFrame, fShowAnimationInval ? &fInvalController
672*c8dee2aaSAndroid Build Coastguard Worker                                                                  : nullptr);
673*c8dee2aaSAndroid Build Coastguard Worker     }
674*c8dee2aaSAndroid Build Coastguard Worker     return true;
675*c8dee2aaSAndroid Build Coastguard Worker }
676*c8dee2aaSAndroid Build Coastguard Worker 
onChar(SkUnichar c)677*c8dee2aaSAndroid Build Coastguard Worker bool SkottieSlide::onChar(SkUnichar c) {
678*c8dee2aaSAndroid Build Coastguard Worker     if (fTextEditor && fTextEditor->onCharInput(c)) {
679*c8dee2aaSAndroid Build Coastguard Worker         return true;
680*c8dee2aaSAndroid Build Coastguard Worker     }
681*c8dee2aaSAndroid Build Coastguard Worker 
682*c8dee2aaSAndroid Build Coastguard Worker     switch (c) {
683*c8dee2aaSAndroid Build Coastguard Worker     case 'I':
684*c8dee2aaSAndroid Build Coastguard Worker         fShowAnimationStats = !fShowAnimationStats;
685*c8dee2aaSAndroid Build Coastguard Worker         return true;
686*c8dee2aaSAndroid Build Coastguard Worker     case 'G':
687*c8dee2aaSAndroid Build Coastguard Worker         fPreferGlyphPaths = !fPreferGlyphPaths;
688*c8dee2aaSAndroid Build Coastguard Worker         this->load(fWinSize.width(), fWinSize.height());
689*c8dee2aaSAndroid Build Coastguard Worker         return true;
690*c8dee2aaSAndroid Build Coastguard Worker     case 'T':
691*c8dee2aaSAndroid Build Coastguard Worker         fShowTrackerUI = !fShowTrackerUI;
692*c8dee2aaSAndroid Build Coastguard Worker         return true;
693*c8dee2aaSAndroid Build Coastguard Worker     case 'M':
694*c8dee2aaSAndroid Build Coastguard Worker         fShowSlotManager = !fShowSlotManager;
695*c8dee2aaSAndroid Build Coastguard Worker         return true;
696*c8dee2aaSAndroid Build Coastguard Worker     case 'E':
697*c8dee2aaSAndroid Build Coastguard Worker         if (fTextEditor) {
698*c8dee2aaSAndroid Build Coastguard Worker             fTextEditor->toggleEnabled();
699*c8dee2aaSAndroid Build Coastguard Worker         }
700*c8dee2aaSAndroid Build Coastguard Worker         return true;
701*c8dee2aaSAndroid Build Coastguard Worker     }
702*c8dee2aaSAndroid Build Coastguard Worker 
703*c8dee2aaSAndroid Build Coastguard Worker     return Slide::onChar(c);
704*c8dee2aaSAndroid Build Coastguard Worker }
705*c8dee2aaSAndroid Build Coastguard Worker 
onMouse(SkScalar x,SkScalar y,skui::InputState state,skui::ModifierKey mod)706*c8dee2aaSAndroid Build Coastguard Worker bool SkottieSlide::onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey mod) {
707*c8dee2aaSAndroid Build Coastguard Worker     if (fTextEditor && fTextEditor->onMouseInput(x, y, state, mod)) {
708*c8dee2aaSAndroid Build Coastguard Worker         return true;
709*c8dee2aaSAndroid Build Coastguard Worker     }
710*c8dee2aaSAndroid Build Coastguard Worker 
711*c8dee2aaSAndroid Build Coastguard Worker     switch (state) {
712*c8dee2aaSAndroid Build Coastguard Worker     case skui::InputState::kUp:
713*c8dee2aaSAndroid Build Coastguard Worker         fShowAnimationInval = !fShowAnimationInval;
714*c8dee2aaSAndroid Build Coastguard Worker         fShowAnimationStats = !fShowAnimationStats;
715*c8dee2aaSAndroid Build Coastguard Worker         break;
716*c8dee2aaSAndroid Build Coastguard Worker     default:
717*c8dee2aaSAndroid Build Coastguard Worker         break;
718*c8dee2aaSAndroid Build Coastguard Worker     }
719*c8dee2aaSAndroid Build Coastguard Worker 
720*c8dee2aaSAndroid Build Coastguard Worker     fShowUI = this->UIArea().contains(x, y);
721*c8dee2aaSAndroid Build Coastguard Worker 
722*c8dee2aaSAndroid Build Coastguard Worker     return false;
723*c8dee2aaSAndroid Build Coastguard Worker }
724*c8dee2aaSAndroid Build Coastguard Worker 
UIArea() const725*c8dee2aaSAndroid Build Coastguard Worker SkRect SkottieSlide::UIArea() const {
726*c8dee2aaSAndroid Build Coastguard Worker     static constexpr float kUIHeight = 120.0f;
727*c8dee2aaSAndroid Build Coastguard Worker 
728*c8dee2aaSAndroid Build Coastguard Worker     return SkRect::MakeXYWH(0, fWinSize.height() - kUIHeight, fWinSize.width(), kUIHeight);
729*c8dee2aaSAndroid Build Coastguard Worker }
730*c8dee2aaSAndroid Build Coastguard Worker 
renderUI()731*c8dee2aaSAndroid Build Coastguard Worker void SkottieSlide::renderUI() {
732*c8dee2aaSAndroid Build Coastguard Worker     static constexpr auto kUI_opacity     = 0.35f,
733*c8dee2aaSAndroid Build Coastguard Worker                           kUI_hist_height = 50.0f,
734*c8dee2aaSAndroid Build Coastguard Worker                           kUI_fps_width   = 100.0f;
735*c8dee2aaSAndroid Build Coastguard Worker 
736*c8dee2aaSAndroid Build Coastguard Worker     auto add_frame_rate_option = [this](const char* label, double rate) {
737*c8dee2aaSAndroid Build Coastguard Worker         const auto is_selected = (fFrameRate == rate);
738*c8dee2aaSAndroid Build Coastguard Worker         if (ImGui::Selectable(label, is_selected)) {
739*c8dee2aaSAndroid Build Coastguard Worker             fFrameRate      = rate;
740*c8dee2aaSAndroid Build Coastguard Worker             fFrameRateLabel = label;
741*c8dee2aaSAndroid Build Coastguard Worker         }
742*c8dee2aaSAndroid Build Coastguard Worker         if (is_selected) {
743*c8dee2aaSAndroid Build Coastguard Worker             ImGui::SetItemDefaultFocus();
744*c8dee2aaSAndroid Build Coastguard Worker         }
745*c8dee2aaSAndroid Build Coastguard Worker     };
746*c8dee2aaSAndroid Build Coastguard Worker 
747*c8dee2aaSAndroid Build Coastguard Worker     ImGui::SetNextWindowBgAlpha(kUI_opacity);
748*c8dee2aaSAndroid Build Coastguard Worker     if (ImGui::Begin("Skottie Controls", nullptr, ImGuiWindowFlags_NoDecoration |
749*c8dee2aaSAndroid Build Coastguard Worker                                                   ImGuiWindowFlags_NoResize |
750*c8dee2aaSAndroid Build Coastguard Worker                                                   ImGuiWindowFlags_NoMove |
751*c8dee2aaSAndroid Build Coastguard Worker                                                   ImGuiWindowFlags_NoSavedSettings |
752*c8dee2aaSAndroid Build Coastguard Worker                                                   ImGuiWindowFlags_NoFocusOnAppearing |
753*c8dee2aaSAndroid Build Coastguard Worker                                                   ImGuiWindowFlags_NoNav)) {
754*c8dee2aaSAndroid Build Coastguard Worker         const auto ui_area = this->UIArea();
755*c8dee2aaSAndroid Build Coastguard Worker         ImGui::SetWindowPos(ImVec2(ui_area.x(), ui_area.y()));
756*c8dee2aaSAndroid Build Coastguard Worker         ImGui::SetWindowSize(ImVec2(ui_area.width(), ui_area.height()));
757*c8dee2aaSAndroid Build Coastguard Worker 
758*c8dee2aaSAndroid Build Coastguard Worker         ImGui::PushItemWidth(-1);
759*c8dee2aaSAndroid Build Coastguard Worker         ImGui::PlotHistogram("", fFrameTimes.data(), fFrameTimes.size(),
760*c8dee2aaSAndroid Build Coastguard Worker                                  0, nullptr, FLT_MAX, FLT_MAX, ImVec2(0, kUI_hist_height));
761*c8dee2aaSAndroid Build Coastguard Worker         ImGui::SliderFloat("", &fCurrentFrame, 0, fAnimation->duration() * fAnimation->fps() - 1);
762*c8dee2aaSAndroid Build Coastguard Worker         fDraggingProgress = ImGui::IsItemActive();
763*c8dee2aaSAndroid Build Coastguard Worker         ImGui::PopItemWidth();
764*c8dee2aaSAndroid Build Coastguard Worker 
765*c8dee2aaSAndroid Build Coastguard Worker         ImGui::PushItemWidth(kUI_fps_width);
766*c8dee2aaSAndroid Build Coastguard Worker         if (ImGui::BeginCombo("FPS", fFrameRateLabel)) {
767*c8dee2aaSAndroid Build Coastguard Worker             add_frame_rate_option("", 0.0);
768*c8dee2aaSAndroid Build Coastguard Worker             add_frame_rate_option("Native", fAnimation->fps());
769*c8dee2aaSAndroid Build Coastguard Worker             add_frame_rate_option( "1",  1.0);
770*c8dee2aaSAndroid Build Coastguard Worker             add_frame_rate_option("15", 15.0);
771*c8dee2aaSAndroid Build Coastguard Worker             add_frame_rate_option("24", 24.0);
772*c8dee2aaSAndroid Build Coastguard Worker             add_frame_rate_option("30", 30.0);
773*c8dee2aaSAndroid Build Coastguard Worker             add_frame_rate_option("60", 60.0);
774*c8dee2aaSAndroid Build Coastguard Worker             ImGui::EndCombo();
775*c8dee2aaSAndroid Build Coastguard Worker         }
776*c8dee2aaSAndroid Build Coastguard Worker         ImGui::PopItemWidth();
777*c8dee2aaSAndroid Build Coastguard Worker     }
778*c8dee2aaSAndroid Build Coastguard Worker     ImGui::End();
779*c8dee2aaSAndroid Build Coastguard Worker }
780*c8dee2aaSAndroid Build Coastguard Worker 
781*c8dee2aaSAndroid Build Coastguard Worker #endif // SK_ENABLE_SKOTTIE
782