experimental/skottie_ios: Skottie iOS/Metal App

To use, see instructions in experimental/skottie_ios/README.md .

No-Try: true
Change-Id: I4fb71576c5e38c7776d14561930b8c2598cfb48f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/240284
Commit-Queue: Hal Canary <halcanary@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/experimental/skottie_ios/main.mm b/experimental/skottie_ios/main.mm
new file mode 100644
index 0000000..73ea26d
--- /dev/null
+++ b/experimental/skottie_ios/main.mm
@@ -0,0 +1,141 @@
+// Copyright 2019 Google LLC.
+// Use of this source cofcee 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,
+                                       GrContext* grContext) {
+    UIStackView* stack = [[UIStackView alloc] init];
+    [stack setAxis:UILayoutConstraintAxisVertical];
+    [stack setDistribution:UIStackViewDistributionEqualSpacing];
+
+    NSBundle* mainBundle = [NSBundle mainBundle];
+    NSArray<NSString*>* paths = [mainBundle pathsForResourcesOfType:@"json"
+                                            inDirectory:nil];
+    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 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) 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;
+        }
+        GrContextOptions grContextOptions;  // set different options here.
+        fGrContext = SkMetalDeviceToGrContext([self metalDevice], grContextOptions);
+    }
+
+    [self setStackView:make_skottie_stack([[UIScreen mainScreen] bounds].size.width,
+                                          [self metalDevice], 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]));
+    }
+}