| // 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])); |
| } |
| } |