// // UIAppView.m // Moonlight // // Created by Diego Waxemberg on 10/22/14. // Copyright (c) 2014 Moonlight Stream. All rights reserved. // #import "UIAppView.h" #import "AppAssetManager.h" static const float REFRESH_CYCLE = 2.0f; @implementation UIAppView { TemporaryApp* _app; UILabel* _appLabel; UIImageView* _appOverlay; UIImageView* _appImage; NSCache* _artCache; id _callback; } static UIImage* noImage; - (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id)callback { self = [super init]; _app = app; _callback = callback; _artCache = cache; // Cache the NoAppImage ourselves to avoid // having to load it each time if (noImage == nil) { noImage = [UIImage imageNamed:@"NoAppImage"]; } #if TARGET_OS_TV self.frame = CGRectMake(0, 0, 200, 265); #else self.frame = CGRectMake(0, 0, 150, 200); #endif _appImage = [[UIImageView alloc] initWithFrame:self.frame]; [_appImage setImage:noImage]; [self addSubview:_appImage]; if (@available(iOS 9.0, tvOS 9.0, *)) { [self addTarget:self action:@selector(appClicked) forControlEvents:UIControlEventPrimaryActionTriggered]; } else { [self addTarget:self action:@selector(appClicked) forControlEvents:UIControlEventTouchUpInside]; } [self addTarget:self action:@selector(buttonSelected:) forControlEvents:UIControlEventTouchDown]; [self addTarget:self action:@selector(buttonDeselected:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchDragExit]; #if TARGET_OS_TV _appImage.adjustsImageWhenAncestorFocused = YES; #else // Rasterizing the cell layer increases rendering performance by quite a bit // but we want it unrasterized for tvOS where it must be scaled. self.layer.shouldRasterize = YES; self.layer.rasterizationScale = [UIScreen mainScreen].scale; if (@available(iOS 13.4.1, *)) { // Allow the button style to change when moused over self.pointerInteractionEnabled = YES; } #endif [self updateAppImage]; [self startUpdateLoop]; return self; } - (void) appClicked { [_callback appClicked:_app]; } - (void) updateAppImage { if (_appOverlay != nil) { [_appOverlay removeFromSuperview]; _appOverlay = nil; } if (_appLabel != nil) { [_appLabel removeFromSuperview]; _appLabel = nil; } BOOL noAppImage = false; // First check the memory cache UIImage* appImage = [_artCache objectForKey:_app]; if (appImage == nil) { // Next try to load from the on disk cache appImage = [UIImage imageWithContentsOfFile:[AppAssetManager boxArtPathForApp:_app]]; if (appImage != nil) { [_artCache setObject:appImage forKey:_app]; } } if (appImage != nil) { // This size of image might be blank image received from GameStream. // TODO: Improve no-app image detection if (!(appImage.size.width == 130.f && appImage.size.height == 180.f) && // GFE 2.0 !(appImage.size.width == 628.f && appImage.size.height == 888.f)) { // GFE 3.0 [_appImage setImage:appImage]; } else { noAppImage = true; } } else { noAppImage = true; } if ([_app.id isEqualToString:_app.host.currentGame]) { // Only create the app overlay if needed _appOverlay = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Play"]]; _appOverlay.layer.shadowColor = [UIColor blackColor].CGColor; _appOverlay.layer.shadowOffset = CGSizeMake(0, 0); _appOverlay.layer.shadowOpacity = 1; _appOverlay.layer.shadowRadius = 4.0; _appOverlay.contentMode = UIViewContentModeScaleAspectFit; } if (noAppImage) { _appLabel = [[UILabel alloc] init]; [_appLabel setTextColor:[UIColor whiteColor]]; [_appLabel setText:_app.name]; [_appLabel setFont:[UIFont systemFontOfSize:24]]; [_appLabel setBaselineAdjustment:UIBaselineAdjustmentAlignCenters]; [_appLabel setTextAlignment:NSTextAlignmentCenter]; [_appLabel setLineBreakMode:NSLineBreakByWordWrapping]; [_appLabel setNumberOfLines:0]; } [self positionSubviews]; #if TARGET_OS_TV [_appImage.overlayContentView addSubview:_appLabel]; [_appImage.overlayContentView addSubview:_appOverlay]; #else [self addSubview:_appLabel]; [self addSubview:_appOverlay]; #endif } - (void) buttonSelected:(id)sender { _appImage.layer.opacity = 0.5f; } - (void) buttonDeselected:(id)sender { _appImage.layer.opacity = 1.0f; } - (void) positionSubviews { CGFloat padding = 5.f; CGSize frameSize = _appImage.frame.size; CGPoint center = _appImage.center; if (_appLabel != nil) { if (_appOverlay != nil) { _appOverlay.frame = CGRectMake(0, 0, frameSize.width / 3, frameSize.width / 3); _appOverlay.center = CGPointMake(frameSize.width / 2, padding + _appOverlay.frame.size.height / 2); [_appLabel setFrame:CGRectMake(padding, _appOverlay.frame.size.height + padding, frameSize.width - 2 * padding, frameSize.height - _appOverlay.frame.size.height - 2 * padding)]; } else { [_appLabel setFrame:CGRectMake(padding, padding, frameSize.width - 2 * padding, frameSize.height - 2 * padding)]; } } else if (_appOverlay != nil) { _appOverlay.frame = CGRectMake(0, 0, frameSize.width / 2, frameSize.width / 2); _appOverlay.center = center; } } - (void) startUpdateLoop { [self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE]; } - (void) updateLoop { // Update the app image if neccessary if ((_appOverlay != nil && ![_app.id isEqualToString:_app.host.currentGame]) || (_appOverlay == nil && [_app.id isEqualToString:_app.host.currentGame])) { [self updateAppImage]; } // Stop updating when we detach from our parent view if (self.superview != nil) { [self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE]; } } @end