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