| // 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/SkMetalViewBridge.h" | 
 | #include "experimental/skottie_ios/SkottieMtkView.h" | 
 |  | 
 | #include "include/gpu/GrContext.h" | 
 | #include "include/gpu/GrContextOptions.h" | 
 |  | 
 | #import <Metal/Metal.h> | 
 | #import <MetalKit/MetalKit.h> | 
 | #import <UIKit/UIKit.h> | 
 |  | 
 | static UIStackView* make_skottie_stack(CGFloat width, | 
 |                                        id<MTLDevice> metalDevice, | 
 |                                        id<MTLCommandQueue> metalQueue, | 
 |                                        GrContext* grContext) { | 
 |     UIStackView* stack = [[UIStackView alloc] init]; | 
 |     [stack setAxis:UILayoutConstraintAxisVertical]; | 
 |     [stack setDistribution:UIStackViewDistributionEqualSpacing]; | 
 |  | 
 |     NSBundle* mainBundle = [NSBundle mainBundle]; | 
 |     NSArray<NSString*>* paths = [mainBundle pathsForResourcesOfType:@"json" | 
 |                                             inDirectory:@"data"]; | 
 |     constexpr CGFloat kSpacing = 2; | 
 |     CGFloat totalHeight = kSpacing; | 
 |     for (NSUInteger i = 0; i < [paths count]; ++i) { | 
 |         NSString* path = [paths objectAtIndex:i]; | 
 |         NSData* content = [NSData dataWithContentsOfFile:path]; | 
 |         if (!content) { | 
 |             NSLog(@"'%@' not found", path); | 
 |             continue; | 
 |         } | 
 |         SkottieMtkView* skottieView = [[SkottieMtkView alloc] init]; | 
 |         if (![skottieView loadAnimation:content]) { | 
 |             continue; | 
 |         } | 
 |         [skottieView setDevice:metalDevice]; | 
 |         [skottieView setQueue:metalQueue]; | 
 |         [skottieView setGrContext:grContext]; | 
 |         SkMtkViewConfigForSkia(skottieView); | 
 |         CGSize animSize = [skottieView size]; | 
 |         CGFloat height = animSize.width ? (width * animSize.height / animSize.width) : 0; | 
 |         [skottieView setFrame:{{0, 0}, {width, height}}]; | 
 |         [skottieView setPreferredFramesPerSecond:30]; | 
 |         [[[skottieView heightAnchor] constraintEqualToConstant:height] setActive:true]; | 
 |         [[[skottieView widthAnchor] constraintEqualToConstant:width] setActive:true]; | 
 |         [stack addArrangedSubview:skottieView]; | 
 |         totalHeight += height + kSpacing; | 
 |     } | 
 |     [stack setFrame:{{0, 0}, {width, totalHeight}}]; | 
 |     return stack; | 
 | } | 
 |  | 
 | @interface AppViewController : UIViewController | 
 |     @property (strong) id<MTLDevice> metalDevice; | 
 |     @property (strong) id<MTLCommandQueue> metalQueue; | 
 |     @property (strong) UIStackView* stackView; | 
 | @end | 
 |  | 
 | @implementation AppViewController { | 
 |     sk_sp<GrContext> fGrContext; | 
 | } | 
 |  | 
 | - (void)dealloc { | 
 |     fGrContext = nullptr; | 
 |     [super dealloc]; | 
 | } | 
 |  | 
 | - (void)loadView { | 
 |     [self setView:[[UIView alloc] init]]; | 
 | } | 
 |  | 
 | - (void)viewDidLoad { | 
 |     [super viewDidLoad]; | 
 |     if (!fGrContext) { | 
 |         [self setMetalDevice:MTLCreateSystemDefaultDevice()]; | 
 |         if(![self metalDevice]) { | 
 |             NSLog(@"Metal is not supported on this device"); | 
 |             return; | 
 |         } | 
 |         [self setMetalQueue:[[self metalDevice] newCommandQueue]]; | 
 |         GrContextOptions grContextOptions;  // set different options here. | 
 |         fGrContext = SkMetalDeviceToGrContext([self metalDevice], [self metalQueue], | 
 |                                               grContextOptions); | 
 |     } | 
 |  | 
 |     [self setStackView:make_skottie_stack([[UIScreen mainScreen] bounds].size.width, | 
 |                                           [self metalDevice], [self metalQueue], fGrContext.get())]; | 
 |  | 
 |     CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height; | 
 |     CGSize mainScreenSize = [[UIScreen mainScreen] bounds].size; | 
 |     CGRect scrollViewBounds = {{0, statusBarHeight}, | 
 |                                {mainScreenSize.width, mainScreenSize.height - statusBarHeight}}; | 
 |     UIScrollView* scrollView = [[UIScrollView alloc] initWithFrame:scrollViewBounds]; | 
 |     [scrollView setContentSize:[[self stackView] frame].size]; | 
 |     [scrollView addSubview:[self stackView]]; | 
 |     [scrollView setBackgroundColor:[UIColor blackColor]]; | 
 |  | 
 |     UIView* mainView = [self view]; | 
 |     [mainView setBounds:{{0, 0}, mainScreenSize}]; | 
 |     [mainView setBackgroundColor:[UIColor whiteColor]]; | 
 |     [mainView addSubview:scrollView]; | 
 |  | 
 |     UITapGestureRecognizer* tapGestureRecognizer = [[UITapGestureRecognizer alloc] init]; | 
 |     [tapGestureRecognizer addTarget:self action:@selector(handleTap:)]; | 
 |     [mainView addGestureRecognizer:tapGestureRecognizer]; | 
 | } | 
 |  | 
 | - (void)handleTap:(UIGestureRecognizer*)sender { | 
 |     if (![sender state] == UIGestureRecognizerStateEnded) { | 
 |         return; | 
 |     } | 
 |     NSArray<UIView*>* subviews = [[self stackView] subviews]; | 
 |     for (NSUInteger i = 0; i < [subviews count]; ++i) { | 
 |         UIView* subview = [subviews objectAtIndex:i]; | 
 |         if (![subview isKindOfClass:[SkottieMtkView class]]) { | 
 |             continue; | 
 |         } | 
 |         SkottieMtkView* skottieView = (SkottieMtkView*)subview; | 
 |         BOOL paused = [skottieView togglePaused]; | 
 |         [skottieView setEnableSetNeedsDisplay:paused]; | 
 |         [skottieView setPaused:paused]; | 
 |     } | 
 | } | 
 | @end | 
 |  | 
 | @interface AppDelegate : UIResponder <UIApplicationDelegate> | 
 | @property (strong, nonatomic) UIWindow* window; | 
 | @end | 
 |  | 
 | @implementation AppDelegate | 
 |  | 
 | - (BOOL)application:(UIApplication*)app didFinishLaunchingWithOptions:(NSDictionary*)ops { | 
 |     [self setWindow:[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]]; | 
 |     [[self window] setRootViewController:[[AppViewController alloc] init]]; | 
 |     [[self window] makeKeyAndVisible]; | 
 |     return YES; | 
 | } | 
 | @end | 
 |  | 
 | int main(int argc, char* argv[]) { | 
 |     @autoreleasepool { | 
 |         return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); | 
 |     } | 
 | } |