//Copyright 2019 Google LLC. //Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. #include "experimental/skottie_ios/SkottieMtkView.h" #include "include/core/SkCanvas.h" #include "include/core/SkPaint.h" #include "include/core/SkSurface.h" #include "include/core/SkTime.h" #include "modules/skottie/include/Skottie.h" #include "experimental/skottie_ios/SkMetalViewBridge.h" @implementation SkottieMtkView { sk_sp fAnimation; // owner CGSize fSize; double fStartTime; // used when running float fAnimationMoment; // when paused. SkMatrix fMatrix; SkSize fAnimationSize; bool fPaused; } - (void)drawRect:(CGRect)rect { [super drawRect:rect]; // TODO(halcanary): Use the rect and the InvalidationController to speed up rendering. if (!fAnimation || ![[self currentDrawable] texture] || ![self grContext]) { return; } CGSize size = [self drawableSize]; if (size.width != fSize.width || size.height != fSize.height) { // Cache the current matrix; change only if size changes. if (fAnimationSize.width() > 0 && fAnimationSize.height() > 0) { float scale = std::min(size.width / fAnimationSize.width(), size.height / fAnimationSize.height()); fMatrix.setScaleTranslate( scale, scale, ((float)size.width - fAnimationSize.width() * scale) * 0.5f, ((float)size.height - fAnimationSize.height() * scale) * 0.5f); } else { fMatrix = SkMatrix(); } fSize = size; } SkPaint whitePaint(SkColors::kWhite); if (!fPaused) { fAnimation->seekFrameTime([self currentTime], nullptr); } sk_sp surface = SkMtkViewToSurface(self, [self grContext]); if (!surface) { NSLog(@"error: no sksurface"); return; } SkCanvas* canvas = surface->getCanvas(); canvas->concat(fMatrix); canvas->drawRect(SkRect{0, 0, fAnimationSize.width(), fAnimationSize.height()}, whitePaint); fAnimation->render(canvas); surface->flush(); surface = nullptr; id commandBuffer = [[self queue] commandBuffer]; [commandBuffer presentDrawable:[self currentDrawable]]; [commandBuffer commit]; } - (BOOL)loadAnimation:(NSData*) data { skottie::Animation::Builder builder; fAnimation = builder.make((const char*)[data bytes], (size_t)[data length]); fStartTime = SkTime::GetNSecs(); fAnimationMoment = 0; fSize = {0, 0}; fAnimationSize = fAnimation ? fAnimation->size() : SkSize{0, 0}; return fAnimation != nullptr; } - (float)animationDurationSeconds { return fAnimation ? fAnimation->duration() : 0; } - (float)currentTime { if (!fAnimation) { return 0; } if (fPaused) { return fAnimationMoment; } double time = 1e-9 * (SkTime::GetNSecs() - fStartTime); double duration = fAnimation->duration(); if ([self stopAtEnd] && time >= duration) { fPaused = true; fAnimationMoment = duration; return fAnimationMoment; } return std::fmod(time, duration); } - (void)seek:(float)seconds { if (fAnimation) { if (fPaused) { fAnimationMoment = std::fmod(seconds, fAnimation->duration()); fAnimation->seekFrameTime(fAnimationMoment); } else { fStartTime = SkTime::GetNSecs() - 1e9 * seconds; } } } - (CGSize)size { return {(CGFloat)fAnimationSize.width(), (CGFloat)fAnimationSize.height()}; } - (BOOL)togglePaused { if (fPaused) { double offset = fAnimationMoment >= fAnimation->duration() ? 0 : -1e9 * fAnimationMoment; fStartTime = SkTime::GetNSecs() + offset; fPaused = false; } else { fAnimationMoment = [self currentTime]; fPaused = true; } return fPaused; } - (BOOL)isPaused { return fPaused; } @end