1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2017 Google Inc.
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#include "tools/viewer/SkottieSlide.h"
9cb93a386Sopenharmony_ci
10cb93a386Sopenharmony_ci#if defined(SK_ENABLE_SKOTTIE)
11cb93a386Sopenharmony_ci
12cb93a386Sopenharmony_ci#include "include/core/SkCanvas.h"
13cb93a386Sopenharmony_ci#include "include/core/SkFont.h"
14cb93a386Sopenharmony_ci#include "include/core/SkTime.h"
15cb93a386Sopenharmony_ci#include "include/private/SkTPin.h"
16cb93a386Sopenharmony_ci#include "modules/audioplayer/SkAudioPlayer.h"
17cb93a386Sopenharmony_ci#include "modules/skottie/include/Skottie.h"
18cb93a386Sopenharmony_ci#include "modules/skottie/utils/SkottieUtils.h"
19cb93a386Sopenharmony_ci#include "modules/skresources/include/SkResources.h"
20cb93a386Sopenharmony_ci#include "src/utils/SkOSPath.h"
21cb93a386Sopenharmony_ci#include "tools/timer/TimeUtils.h"
22cb93a386Sopenharmony_ci
23cb93a386Sopenharmony_ci#include <cmath>
24cb93a386Sopenharmony_ci
25cb93a386Sopenharmony_ci#include "imgui.h"
26cb93a386Sopenharmony_ci
27cb93a386Sopenharmony_cinamespace {
28cb93a386Sopenharmony_ci
29cb93a386Sopenharmony_ciclass Track final : public skresources::ExternalTrackAsset {
30cb93a386Sopenharmony_cipublic:
31cb93a386Sopenharmony_ci    explicit Track(std::unique_ptr<SkAudioPlayer> player) : fPlayer(std::move(player)) {}
32cb93a386Sopenharmony_ci
33cb93a386Sopenharmony_ciprivate:
34cb93a386Sopenharmony_ci    void seek(float t) override {
35cb93a386Sopenharmony_ci        if (fPlayer->isStopped() && t >=0) {
36cb93a386Sopenharmony_ci            fPlayer->play();
37cb93a386Sopenharmony_ci        }
38cb93a386Sopenharmony_ci
39cb93a386Sopenharmony_ci        if (fPlayer->isPlaying()) {
40cb93a386Sopenharmony_ci            if (t < 0) {
41cb93a386Sopenharmony_ci                fPlayer->stop();
42cb93a386Sopenharmony_ci            } else {
43cb93a386Sopenharmony_ci                static constexpr float kTolerance = 0.075f;
44cb93a386Sopenharmony_ci                const auto player_pos = fPlayer->time();
45cb93a386Sopenharmony_ci
46cb93a386Sopenharmony_ci                if (std::abs(player_pos - t) > kTolerance) {
47cb93a386Sopenharmony_ci                    fPlayer->setTime(t);
48cb93a386Sopenharmony_ci                }
49cb93a386Sopenharmony_ci            }
50cb93a386Sopenharmony_ci        }
51cb93a386Sopenharmony_ci    }
52cb93a386Sopenharmony_ci
53cb93a386Sopenharmony_ci    const std::unique_ptr<SkAudioPlayer> fPlayer;
54cb93a386Sopenharmony_ci};
55cb93a386Sopenharmony_ci
56cb93a386Sopenharmony_ciclass AudioProviderProxy final : public skresources::ResourceProviderProxyBase {
57cb93a386Sopenharmony_cipublic:
58cb93a386Sopenharmony_ci    explicit AudioProviderProxy(sk_sp<skresources::ResourceProvider> rp)
59cb93a386Sopenharmony_ci        : INHERITED(std::move(rp)) {}
60cb93a386Sopenharmony_ci
61cb93a386Sopenharmony_ciprivate:
62cb93a386Sopenharmony_ci    sk_sp<skresources::ExternalTrackAsset> loadAudioAsset(const char path[],
63cb93a386Sopenharmony_ci                                                          const char name[],
64cb93a386Sopenharmony_ci                                                          const char[] /*id*/) override {
65cb93a386Sopenharmony_ci        if (auto data = this->load(path, name)) {
66cb93a386Sopenharmony_ci            if (auto player = SkAudioPlayer::Make(std::move(data))) {
67cb93a386Sopenharmony_ci                return sk_make_sp<Track>(std::move(player));
68cb93a386Sopenharmony_ci            }
69cb93a386Sopenharmony_ci        }
70cb93a386Sopenharmony_ci
71cb93a386Sopenharmony_ci        return nullptr;
72cb93a386Sopenharmony_ci    }
73cb93a386Sopenharmony_ci
74cb93a386Sopenharmony_ci    using INHERITED = skresources::ResourceProviderProxyBase;
75cb93a386Sopenharmony_ci};
76cb93a386Sopenharmony_ci
77cb93a386Sopenharmony_ci} // namespace
78cb93a386Sopenharmony_ci
79cb93a386Sopenharmony_cistatic void draw_stats_box(SkCanvas* canvas, const skottie::Animation::Builder::Stats& stats) {
80cb93a386Sopenharmony_ci    static constexpr SkRect kR = { 10, 10, 280, 120 };
81cb93a386Sopenharmony_ci    static constexpr SkScalar kTextSize = 20;
82cb93a386Sopenharmony_ci
83cb93a386Sopenharmony_ci    SkPaint paint;
84cb93a386Sopenharmony_ci    paint.setAntiAlias(true);
85cb93a386Sopenharmony_ci    paint.setColor(0xffeeeeee);
86cb93a386Sopenharmony_ci
87cb93a386Sopenharmony_ci    SkFont font(nullptr, kTextSize);
88cb93a386Sopenharmony_ci
89cb93a386Sopenharmony_ci    canvas->drawRect(kR, paint);
90cb93a386Sopenharmony_ci
91cb93a386Sopenharmony_ci    paint.setColor(SK_ColorBLACK);
92cb93a386Sopenharmony_ci
93cb93a386Sopenharmony_ci    const auto json_size = SkStringPrintf("Json size: %zu bytes",
94cb93a386Sopenharmony_ci                                          stats.fJsonSize);
95cb93a386Sopenharmony_ci    canvas->drawString(json_size, kR.x() + 10, kR.y() + kTextSize * 1, font, paint);
96cb93a386Sopenharmony_ci    const auto animator_count = SkStringPrintf("Animator count: %zu",
97cb93a386Sopenharmony_ci                                               stats.fAnimatorCount);
98cb93a386Sopenharmony_ci    canvas->drawString(animator_count, kR.x() + 10, kR.y() + kTextSize * 2, font, paint);
99cb93a386Sopenharmony_ci    const auto json_parse_time = SkStringPrintf("Json parse time: %.3f ms",
100cb93a386Sopenharmony_ci                                                stats.fJsonParseTimeMS);
101cb93a386Sopenharmony_ci    canvas->drawString(json_parse_time, kR.x() + 10, kR.y() + kTextSize * 3, font, paint);
102cb93a386Sopenharmony_ci    const auto scene_parse_time = SkStringPrintf("Scene build time: %.3f ms",
103cb93a386Sopenharmony_ci                                                 stats.fSceneParseTimeMS);
104cb93a386Sopenharmony_ci    canvas->drawString(scene_parse_time, kR.x() + 10, kR.y() + kTextSize * 4, font, paint);
105cb93a386Sopenharmony_ci    const auto total_load_time = SkStringPrintf("Total load time: %.3f ms",
106cb93a386Sopenharmony_ci                                                stats.fTotalLoadTimeMS);
107cb93a386Sopenharmony_ci    canvas->drawString(total_load_time, kR.x() + 10, kR.y() + kTextSize * 5, font, paint);
108cb93a386Sopenharmony_ci
109cb93a386Sopenharmony_ci    paint.setStyle(SkPaint::kStroke_Style);
110cb93a386Sopenharmony_ci    canvas->drawRect(kR, paint);
111cb93a386Sopenharmony_ci}
112cb93a386Sopenharmony_ci
113cb93a386Sopenharmony_ciSkottieSlide::SkottieSlide(const SkString& name, const SkString& path)
114cb93a386Sopenharmony_ci    : fPath(path) {
115cb93a386Sopenharmony_ci    fName = name;
116cb93a386Sopenharmony_ci}
117cb93a386Sopenharmony_ci
118cb93a386Sopenharmony_civoid SkottieSlide::load(SkScalar w, SkScalar h) {
119cb93a386Sopenharmony_ci    class Logger final : public skottie::Logger {
120cb93a386Sopenharmony_ci    public:
121cb93a386Sopenharmony_ci        struct LogEntry {
122cb93a386Sopenharmony_ci            SkString fMessage,
123cb93a386Sopenharmony_ci                     fJSON;
124cb93a386Sopenharmony_ci        };
125cb93a386Sopenharmony_ci
126cb93a386Sopenharmony_ci        void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
127cb93a386Sopenharmony_ci            auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
128cb93a386Sopenharmony_ci            log.push_back({ SkString(message), json ? SkString(json) : SkString() });
129cb93a386Sopenharmony_ci        }
130cb93a386Sopenharmony_ci
131cb93a386Sopenharmony_ci        void report() const {
132cb93a386Sopenharmony_ci            SkDebugf("Animation loaded with %zu error%s, %zu warning%s.\n",
133cb93a386Sopenharmony_ci                     fErrors.size(), fErrors.size() == 1 ? "" : "s",
134cb93a386Sopenharmony_ci                     fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
135cb93a386Sopenharmony_ci
136cb93a386Sopenharmony_ci            const auto& show = [](const LogEntry& log, const char prefix[]) {
137cb93a386Sopenharmony_ci                SkDebugf("%s%s", prefix, log.fMessage.c_str());
138cb93a386Sopenharmony_ci                if (!log.fJSON.isEmpty())
139cb93a386Sopenharmony_ci                    SkDebugf(" : %s", log.fJSON.c_str());
140cb93a386Sopenharmony_ci                SkDebugf("\n");
141cb93a386Sopenharmony_ci            };
142cb93a386Sopenharmony_ci
143cb93a386Sopenharmony_ci            for (const auto& err : fErrors)   show(err, "  !! ");
144cb93a386Sopenharmony_ci            for (const auto& wrn : fWarnings) show(wrn, "  ?? ");
145cb93a386Sopenharmony_ci        }
146cb93a386Sopenharmony_ci
147cb93a386Sopenharmony_ci    private:
148cb93a386Sopenharmony_ci        std::vector<LogEntry> fErrors,
149cb93a386Sopenharmony_ci                              fWarnings;
150cb93a386Sopenharmony_ci    };
151cb93a386Sopenharmony_ci
152cb93a386Sopenharmony_ci    auto logger = sk_make_sp<Logger>();
153cb93a386Sopenharmony_ci
154cb93a386Sopenharmony_ci    uint32_t flags = 0;
155cb93a386Sopenharmony_ci    if (fPreferGlyphPaths) {
156cb93a386Sopenharmony_ci        flags |= skottie::Animation::Builder::kPreferEmbeddedFonts;
157cb93a386Sopenharmony_ci    }
158cb93a386Sopenharmony_ci    skottie::Animation::Builder builder(flags);
159cb93a386Sopenharmony_ci
160cb93a386Sopenharmony_ci    auto resource_provider =
161cb93a386Sopenharmony_ci        sk_make_sp<AudioProviderProxy>(
162cb93a386Sopenharmony_ci            skresources::DataURIResourceProviderProxy::Make(
163cb93a386Sopenharmony_ci                skresources::FileResourceProvider::Make(SkOSPath::Dirname(fPath.c_str()),
164cb93a386Sopenharmony_ci                                                        /*predecode=*/true),
165cb93a386Sopenharmony_ci                /*predecode=*/true));
166cb93a386Sopenharmony_ci
167cb93a386Sopenharmony_ci    static constexpr char kInterceptPrefix[] = "__";
168cb93a386Sopenharmony_ci    auto precomp_interceptor =
169cb93a386Sopenharmony_ci            sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(resource_provider,
170cb93a386Sopenharmony_ci                                                                           kInterceptPrefix);
171cb93a386Sopenharmony_ci    fAnimation      = builder
172cb93a386Sopenharmony_ci            .setLogger(logger)
173cb93a386Sopenharmony_ci            .setResourceProvider(std::move(resource_provider))
174cb93a386Sopenharmony_ci            .setPrecompInterceptor(std::move(precomp_interceptor))
175cb93a386Sopenharmony_ci            .makeFromFile(fPath.c_str());
176cb93a386Sopenharmony_ci    fAnimationStats = builder.getStats();
177cb93a386Sopenharmony_ci    fWinSize        = SkSize::Make(w, h);
178cb93a386Sopenharmony_ci    fTimeBase       = 0; // force a time reset
179cb93a386Sopenharmony_ci
180cb93a386Sopenharmony_ci    if (fAnimation) {
181cb93a386Sopenharmony_ci        fAnimation->seek(0);
182cb93a386Sopenharmony_ci        fFrameTimes.resize(SkScalarCeilToInt(fAnimation->duration() * fAnimation->fps()));
183cb93a386Sopenharmony_ci        SkDebugf("Loaded Bodymovin animation v: %s, size: [%f %f]\n",
184cb93a386Sopenharmony_ci                 fAnimation->version().c_str(),
185cb93a386Sopenharmony_ci                 fAnimation->size().width(),
186cb93a386Sopenharmony_ci                 fAnimation->size().height());
187cb93a386Sopenharmony_ci        logger->report();
188cb93a386Sopenharmony_ci    } else {
189cb93a386Sopenharmony_ci        SkDebugf("failed to load Bodymovin animation: %s\n", fPath.c_str());
190cb93a386Sopenharmony_ci    }
191cb93a386Sopenharmony_ci}
192cb93a386Sopenharmony_ci
193cb93a386Sopenharmony_civoid SkottieSlide::unload() {
194cb93a386Sopenharmony_ci    fAnimation.reset();
195cb93a386Sopenharmony_ci}
196cb93a386Sopenharmony_ci
197cb93a386Sopenharmony_civoid SkottieSlide::resize(SkScalar w, SkScalar h) {
198cb93a386Sopenharmony_ci    fWinSize = { w, h };
199cb93a386Sopenharmony_ci}
200cb93a386Sopenharmony_ci
201cb93a386Sopenharmony_ciSkISize SkottieSlide::getDimensions() const {
202cb93a386Sopenharmony_ci    // We always scale to fill the window.
203cb93a386Sopenharmony_ci    return fWinSize.toCeil();
204cb93a386Sopenharmony_ci}
205cb93a386Sopenharmony_ci
206cb93a386Sopenharmony_civoid SkottieSlide::draw(SkCanvas* canvas) {
207cb93a386Sopenharmony_ci    if (fAnimation) {
208cb93a386Sopenharmony_ci        SkAutoCanvasRestore acr(canvas, true);
209cb93a386Sopenharmony_ci        const auto dstR = SkRect::MakeSize(fWinSize);
210cb93a386Sopenharmony_ci
211cb93a386Sopenharmony_ci        {
212cb93a386Sopenharmony_ci            const auto t0 = SkTime::GetNSecs();
213cb93a386Sopenharmony_ci            fAnimation->render(canvas, &dstR);
214cb93a386Sopenharmony_ci
215cb93a386Sopenharmony_ci            // TODO: this does not capture GPU flush time!
216cb93a386Sopenharmony_ci            const auto  frame_index  = static_cast<size_t>(fCurrentFrame);
217cb93a386Sopenharmony_ci            fFrameTimes[frame_index] = static_cast<float>((SkTime::GetNSecs() - t0) * 1e-6);
218cb93a386Sopenharmony_ci        }
219cb93a386Sopenharmony_ci
220cb93a386Sopenharmony_ci        if (fShowAnimationStats) {
221cb93a386Sopenharmony_ci            draw_stats_box(canvas, fAnimationStats);
222cb93a386Sopenharmony_ci        }
223cb93a386Sopenharmony_ci        if (fShowAnimationInval) {
224cb93a386Sopenharmony_ci            const auto t = SkMatrix::RectToRect(SkRect::MakeSize(fAnimation->size()), dstR,
225cb93a386Sopenharmony_ci                                                SkMatrix::kCenter_ScaleToFit);
226cb93a386Sopenharmony_ci            SkPaint fill, stroke;
227cb93a386Sopenharmony_ci            fill.setAntiAlias(true);
228cb93a386Sopenharmony_ci            fill.setColor(0x40ff0000);
229cb93a386Sopenharmony_ci            stroke.setAntiAlias(true);
230cb93a386Sopenharmony_ci            stroke.setColor(0xffff0000);
231cb93a386Sopenharmony_ci            stroke.setStyle(SkPaint::kStroke_Style);
232cb93a386Sopenharmony_ci
233cb93a386Sopenharmony_ci            for (const auto& r : fInvalController) {
234cb93a386Sopenharmony_ci                SkRect bounds;
235cb93a386Sopenharmony_ci                t.mapRect(&bounds, r);
236cb93a386Sopenharmony_ci                canvas->drawRect(bounds, fill);
237cb93a386Sopenharmony_ci                canvas->drawRect(bounds, stroke);
238cb93a386Sopenharmony_ci            }
239cb93a386Sopenharmony_ci        }
240cb93a386Sopenharmony_ci        if (fShowUI) {
241cb93a386Sopenharmony_ci            this->renderUI();
242cb93a386Sopenharmony_ci        }
243cb93a386Sopenharmony_ci
244cb93a386Sopenharmony_ci    }
245cb93a386Sopenharmony_ci}
246cb93a386Sopenharmony_ci
247cb93a386Sopenharmony_cibool SkottieSlide::animate(double nanos) {
248cb93a386Sopenharmony_ci    if (!fTimeBase) {
249cb93a386Sopenharmony_ci        // Reset the animation time.
250cb93a386Sopenharmony_ci        fTimeBase = nanos;
251cb93a386Sopenharmony_ci    }
252cb93a386Sopenharmony_ci
253cb93a386Sopenharmony_ci    if (fAnimation) {
254cb93a386Sopenharmony_ci        fInvalController.reset();
255cb93a386Sopenharmony_ci
256cb93a386Sopenharmony_ci        const auto frame_count = fAnimation->duration() * fAnimation->fps();
257cb93a386Sopenharmony_ci
258cb93a386Sopenharmony_ci        if (!fDraggingProgress) {
259cb93a386Sopenharmony_ci            // Clock-driven progress: update current frame.
260cb93a386Sopenharmony_ci            const double t_sec = (nanos - fTimeBase) * 1e-9;
261cb93a386Sopenharmony_ci            fCurrentFrame = std::fmod(t_sec * fAnimation->fps(), frame_count);
262cb93a386Sopenharmony_ci        } else {
263cb93a386Sopenharmony_ci            // Slider-driven progress: update the time origin.
264cb93a386Sopenharmony_ci            fTimeBase = nanos - fCurrentFrame / fAnimation->fps() * 1e9;
265cb93a386Sopenharmony_ci        }
266cb93a386Sopenharmony_ci
267cb93a386Sopenharmony_ci        // Sanitize and rate-lock the current frame.
268cb93a386Sopenharmony_ci        fCurrentFrame = SkTPin<float>(fCurrentFrame, 0.0f, frame_count - 1);
269cb93a386Sopenharmony_ci        if (fFrameRate > 0) {
270cb93a386Sopenharmony_ci            const auto fps_scale = fFrameRate / fAnimation->fps();
271cb93a386Sopenharmony_ci            fCurrentFrame = std::trunc(fCurrentFrame * fps_scale) / fps_scale;
272cb93a386Sopenharmony_ci        }
273cb93a386Sopenharmony_ci
274cb93a386Sopenharmony_ci        fAnimation->seekFrame(fCurrentFrame, fShowAnimationInval ? &fInvalController
275cb93a386Sopenharmony_ci                                                                 : nullptr);
276cb93a386Sopenharmony_ci    }
277cb93a386Sopenharmony_ci    return true;
278cb93a386Sopenharmony_ci}
279cb93a386Sopenharmony_ci
280cb93a386Sopenharmony_cibool SkottieSlide::onChar(SkUnichar c) {
281cb93a386Sopenharmony_ci    switch (c) {
282cb93a386Sopenharmony_ci    case 'I':
283cb93a386Sopenharmony_ci        fShowAnimationStats = !fShowAnimationStats;
284cb93a386Sopenharmony_ci        return true;
285cb93a386Sopenharmony_ci    case 'G':
286cb93a386Sopenharmony_ci        fPreferGlyphPaths = !fPreferGlyphPaths;
287cb93a386Sopenharmony_ci        this->load(fWinSize.width(), fWinSize.height());
288cb93a386Sopenharmony_ci        return true;
289cb93a386Sopenharmony_ci    }
290cb93a386Sopenharmony_ci
291cb93a386Sopenharmony_ci    return INHERITED::onChar(c);
292cb93a386Sopenharmony_ci}
293cb93a386Sopenharmony_ci
294cb93a386Sopenharmony_cibool SkottieSlide::onMouse(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey) {
295cb93a386Sopenharmony_ci    switch (state) {
296cb93a386Sopenharmony_ci    case skui::InputState::kUp:
297cb93a386Sopenharmony_ci        fShowAnimationInval = !fShowAnimationInval;
298cb93a386Sopenharmony_ci        fShowAnimationStats = !fShowAnimationStats;
299cb93a386Sopenharmony_ci        break;
300cb93a386Sopenharmony_ci    default:
301cb93a386Sopenharmony_ci        break;
302cb93a386Sopenharmony_ci    }
303cb93a386Sopenharmony_ci
304cb93a386Sopenharmony_ci    fShowUI = this->UIArea().contains(x, y);
305cb93a386Sopenharmony_ci
306cb93a386Sopenharmony_ci    return false;
307cb93a386Sopenharmony_ci}
308cb93a386Sopenharmony_ci
309cb93a386Sopenharmony_ciSkRect SkottieSlide::UIArea() const {
310cb93a386Sopenharmony_ci    static constexpr float kUIHeight = 120.0f;
311cb93a386Sopenharmony_ci
312cb93a386Sopenharmony_ci    return SkRect::MakeXYWH(0, fWinSize.height() - kUIHeight, fWinSize.width(), kUIHeight);
313cb93a386Sopenharmony_ci}
314cb93a386Sopenharmony_ci
315cb93a386Sopenharmony_civoid SkottieSlide::renderUI() {
316cb93a386Sopenharmony_ci    static constexpr auto kUI_opacity     = 0.35f,
317cb93a386Sopenharmony_ci                          kUI_hist_height = 50.0f,
318cb93a386Sopenharmony_ci                          kUI_fps_width   = 100.0f;
319cb93a386Sopenharmony_ci
320cb93a386Sopenharmony_ci    auto add_frame_rate_option = [this](const char* label, double rate) {
321cb93a386Sopenharmony_ci        const auto is_selected = (fFrameRate == rate);
322cb93a386Sopenharmony_ci        if (ImGui::Selectable(label, is_selected)) {
323cb93a386Sopenharmony_ci            fFrameRate      = rate;
324cb93a386Sopenharmony_ci            fFrameRateLabel = label;
325cb93a386Sopenharmony_ci        }
326cb93a386Sopenharmony_ci        if (is_selected) {
327cb93a386Sopenharmony_ci            ImGui::SetItemDefaultFocus();
328cb93a386Sopenharmony_ci        }
329cb93a386Sopenharmony_ci    };
330cb93a386Sopenharmony_ci
331cb93a386Sopenharmony_ci    ImGui::SetNextWindowBgAlpha(kUI_opacity);
332cb93a386Sopenharmony_ci    if (ImGui::Begin("Skottie Controls", nullptr, ImGuiWindowFlags_NoDecoration |
333cb93a386Sopenharmony_ci                                                  ImGuiWindowFlags_NoResize |
334cb93a386Sopenharmony_ci                                                  ImGuiWindowFlags_NoMove |
335cb93a386Sopenharmony_ci                                                  ImGuiWindowFlags_NoSavedSettings |
336cb93a386Sopenharmony_ci                                                  ImGuiWindowFlags_NoFocusOnAppearing |
337cb93a386Sopenharmony_ci                                                  ImGuiWindowFlags_NoNav)) {
338cb93a386Sopenharmony_ci        const auto ui_area = this->UIArea();
339cb93a386Sopenharmony_ci        ImGui::SetWindowPos(ImVec2(ui_area.x(), ui_area.y()));
340cb93a386Sopenharmony_ci        ImGui::SetWindowSize(ImVec2(ui_area.width(), ui_area.height()));
341cb93a386Sopenharmony_ci
342cb93a386Sopenharmony_ci        ImGui::PushItemWidth(-1);
343cb93a386Sopenharmony_ci        ImGui::PlotHistogram("", fFrameTimes.data(), fFrameTimes.size(),
344cb93a386Sopenharmony_ci                                 0, nullptr, FLT_MAX, FLT_MAX, ImVec2(0, kUI_hist_height));
345cb93a386Sopenharmony_ci        ImGui::SliderFloat("", &fCurrentFrame, 0, fAnimation->duration() * fAnimation->fps() - 1);
346cb93a386Sopenharmony_ci        fDraggingProgress = ImGui::IsItemActive();
347cb93a386Sopenharmony_ci        ImGui::PopItemWidth();
348cb93a386Sopenharmony_ci
349cb93a386Sopenharmony_ci        ImGui::PushItemWidth(kUI_fps_width);
350cb93a386Sopenharmony_ci        if (ImGui::BeginCombo("FPS", fFrameRateLabel)) {
351cb93a386Sopenharmony_ci            add_frame_rate_option("", 0.0);
352cb93a386Sopenharmony_ci            add_frame_rate_option("Native", fAnimation->fps());
353cb93a386Sopenharmony_ci            add_frame_rate_option( "1",  1.0);
354cb93a386Sopenharmony_ci            add_frame_rate_option("15", 15.0);
355cb93a386Sopenharmony_ci            add_frame_rate_option("24", 24.0);
356cb93a386Sopenharmony_ci            add_frame_rate_option("30", 30.0);
357cb93a386Sopenharmony_ci            add_frame_rate_option("60", 60.0);
358cb93a386Sopenharmony_ci            ImGui::EndCombo();
359cb93a386Sopenharmony_ci        }
360cb93a386Sopenharmony_ci        ImGui::PopItemWidth();
361cb93a386Sopenharmony_ci    }
362cb93a386Sopenharmony_ci    ImGui::End();
363cb93a386Sopenharmony_ci}
364cb93a386Sopenharmony_ci
365cb93a386Sopenharmony_ci#endif // SK_ENABLE_SKOTTIE
366