Merge pull request #341 from ascagnel/tv-fixes

tvOS app UI/UX fixes
This commit is contained in:
Cameron Gutman
2018-11-19 20:16:50 -08:00
committed by GitHub
30 changed files with 294 additions and 15 deletions

View File

@@ -15,18 +15,57 @@
self.parent = settings;
#if TARGET_OS_TV
NSInteger _bitrate = [[NSUserDefaults standardUserDefaults] integerForKey:@"bitrate"];
NSInteger _framerate = [[NSUserDefaults standardUserDefaults] integerForKey:@"framerate"];
if (_bitrate) {
self.bitrate = [NSNumber numberWithInteger:_bitrate];
} else {
self.bitrate = [NSNumber numberWithInteger:20000];
}
if (_framerate) {
self.framerate = [NSNumber numberWithInteger:_framerate];
} else {
self.framerate = [NSNumber numberWithInteger:60];
}
self.useHevc = [[NSUserDefaults standardUserDefaults] boolForKey:@"useHevc"] || NO;
self.playAudioOnPC = [[NSUserDefaults standardUserDefaults] boolForKey:@"audioOnPC"] || NO;
self.enableHdr = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableHdr"] || NO;
self.optimizeGames = [[NSUserDefaults standardUserDefaults] boolForKey:@"optimizeGames"] || YES;
self.multiController = YES;
NSInteger _screenSize = [[NSUserDefaults standardUserDefaults] integerForKey:@"streamResolution"];
switch (_screenSize) {
case 0:
self.height = [NSNumber numberWithInteger:720];
self.width = [NSNumber numberWithInteger:1280];
break;
case 2:
self.height = [NSNumber numberWithInteger:2160];
self.width = [NSNumber numberWithInteger:3840];
break;
case 1:
default:
self.height = [NSNumber numberWithInteger:1080];
self.width = [NSNumber numberWithInteger:1920];
break;
}
#else
self.bitrate = settings.bitrate;
self.framerate = settings.framerate;
self.height = settings.height;
self.width = settings.width;
self.onscreenControls = settings.onscreenControls;
self.uniqueId = settings.uniqueId;
self.streamingRemotely = settings.streamingRemotely;
self.useHevc = settings.useHevc;
self.multiController = settings.multiController;
self.playAudioOnPC = settings.playAudioOnPC;
self.enableHdr = settings.enableHdr;
self.optimizeGames = settings.optimizeGames;
self.multiController = settings.multiController;
#endif
self.onscreenControls = settings.onscreenControls;
self.uniqueId = settings.uniqueId;
self.streamingRemotely = settings.streamingRemotely;
return self;
}

View File

@@ -32,7 +32,11 @@ static UIImage* noImage;
noImage = [UIImage imageNamed:@"NoAppImage"];
}
#if TARGET_OS_TV
_appButton = [UIButton buttonWithType:UIButtonTypeSystem];
#else
_appButton = [UIButton buttonWithType:UIButtonTypeCustom];
#endif
[_appButton setBackgroundImage:noImage forState:UIControlStateNormal];
[_appButton setContentEdgeInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
[_appButton sizeToFit];
@@ -64,10 +68,12 @@ static UIImage* noImage;
if ([_app.id isEqualToString:_app.host.currentGame]) {
// Only create the app overlay if needed
_appOverlay = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Play"]];
#if !TARGET_OS_TV
_appOverlay.layer.shadowColor = [UIColor blackColor].CGColor;
_appOverlay.layer.shadowOffset = CGSizeMake(0, 0);
_appOverlay.layer.shadowOpacity = 1;
_appOverlay.layer.shadowRadius = 2.0;
#endif
[self addSubview:_appOverlay];
@@ -91,8 +97,20 @@ static UIImage* noImage;
// 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
#if TARGET_OS_TV
//custom image to do TvOS hover popup effect
UIImageView *imageView = [[UIImageView alloc] initWithImage:appImage];
imageView.userInteractionEnabled = YES;
imageView.adjustsImageWhenAncestorFocused = YES;
imageView.frame = CGRectMake(0, 0, 200, 265);
[_appButton addSubview:imageView];
_appButton.frame = CGRectMake(0, 0, 200, 265);
self.frame = CGRectMake(0, 0, 200, 265);
#else
_appButton.frame = CGRectMake(0, 0, appImage.size.width / 2, appImage.size.height / 2);
self.frame = CGRectMake(0, 0, appImage.size.width / 2, appImage.size.height / 2);
#endif
_appOverlay.frame = CGRectMake(0, 0, self.frame.size.width / 2.f, self.frame.size.height / 4.f);
_appOverlay.layer.shadowRadius = 4.0;
[_appOverlay setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/6)];
@@ -107,18 +125,35 @@ static UIImage* noImage;
if (noAppImage) {
_appLabel = [[UILabel alloc] init];
CGFloat padding = 4.f;
[_appLabel setFrame: CGRectMake(padding, padding, _appButton.frame.size.width - 2 * padding, _appButton.frame.size.height - 2 * padding)];
[_appLabel setTextColor:[UIColor whiteColor]];
[_appLabel setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
[_appLabel setTextAlignment:NSTextAlignmentCenter];
[_appLabel setLineBreakMode:NSLineBreakByWordWrapping];
[_appLabel setNumberOfLines:0];
[_appLabel setText:_app.name];
#if TARGET_OS_TV
[_appLabel setFont:[UIFont systemFontOfSize:16]];
#endif
[_appLabel setText:_app.name];
[_appLabel setAdjustsFontSizeToFitWidth:YES];
[_appLabel setFrame: CGRectMake(0, 0, 200, 265)];
//custom image to do TvOS hover popup effect
UIImageView *imageView = [[UIImageView alloc] initWithImage:appImage];
imageView.userInteractionEnabled = YES;
imageView.adjustsImageWhenAncestorFocused = YES;
imageView.frame = CGRectMake(0, 0, 200, 265);
UIGraphicsBeginImageContextWithOptions(_appLabel.frame.size, false, 0);
[imageView.layer renderInContext:(UIGraphicsGetCurrentContext())];
[_appLabel.layer renderInContext:(UIGraphicsGetCurrentContext())];
UIImage *imageWithText = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[imageView setImage:imageWithText];
[_appButton addSubview:imageView];
_appButton.frame = CGRectMake(0, 0, 200, 265);
self.frame = CGRectMake(0, 0, 200, 265);
#else
CGFloat padding = 4.f;
[_appLabel setFrame: CGRectMake(padding, padding, _appButton.frame.size.width - 2 * padding, _appButton.frame.size.height - 2 * padding)];
[_appButton addSubview:_appLabel];
#endif
}
}

View File

@@ -54,7 +54,9 @@ static const int LABEL_DY = 20;
[_hostLabel setText:@"Add Host"];
[_hostLabel sizeToFit];
#if !TARGET_OS_TV
_hostLabel.textColor = [UIColor whiteColor];
#endif
_hostLabel.center = CGPointMake(_hostButton.frame.origin.x + (_hostButton.frame.size.width / 2), _hostButton.frame.origin.y + _hostButton.frame.size.height + LABEL_DY);
UIImageView* addIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"AddComputerIcon"]];
@@ -128,7 +130,9 @@ static const int LABEL_DY = 20;
- (void) updateContentsForHost:(TemporaryHost*)host {
_hostLabel.text = _host.name;
#if !TARGET_OS_TV
_hostLabel.textColor = [UIColor whiteColor];
#endif
[_hostLabel sizeToFit];
switch (host.pairState) {
@@ -142,7 +146,9 @@ static const int LABEL_DY = 20;
_hostPairState.text = @"Paired";
break;
}
#if !TARGET_OS_TV
_hostPairState.textColor = [UIColor whiteColor];
#endif
[_hostPairState sizeToFit];
if (host.online) {

View File

@@ -1001,9 +1001,8 @@ static NSMutableSet* hostList;
cell.layer.shadowOpacity = 0.5f;
cell.layer.shadowPath = shadowPath.CGPath;
cell.layer.borderWidth = 1;
#if !TARGET_OS_TV
cell.layer.borderWidth = 1;
cell.layer.borderColor = [[UIColor colorWithRed:0 green:0 blue:0 alpha:0.3f] CGColor];
cell.exclusiveTouch = YES;
#endif
@@ -1055,12 +1054,20 @@ static NSMutableSet* hostList;
self.navigationController.navigationBar.topItem.rightBarButtonItem.enabled = YES;
}
#if TARGET_OS_TV
- (BOOL)canBecomeFocused {
return YES;
}
#endif
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator {
#if !TARGET_OS_TV
if (context.nextFocusedView != nil) {
[context.nextFocusedView setAlpha:0.8];
}
[context.previouslyFocusedView setAlpha:1.0];
#endif
}
@end

View File

@@ -12,7 +12,13 @@
#import <UIKit/UIKit.h>
#if TARGET_OS_TV
@import GameController;
@interface StreamFrameViewController : GCEventViewController <ConnectionCallbacks, EdgeDetectionDelegate>
#else
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, EdgeDetectionDelegate>
#endif
@property (strong, nonatomic) IBOutlet UILabel *stageLabel;
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *spinner;
@property (nonatomic) StreamConfiguration* streamConfig;

View File

@@ -20,6 +20,8 @@
ControllerSupport *_controllerSupport;
StreamManager *_streamMan;
NSTimer *_inactivityTimer;
UITapGestureRecognizer *_menuGestureRecognizer;
UITapGestureRecognizer *_menuDoubleTapGestureRecognizer;
}
- (void)viewDidAppear:(BOOL)animated
@@ -31,6 +33,15 @@
#endif
}
#ifdef TARGET_OS_TV
- (void)controllerPauseButtonPressed:(id)sender { }
- (void)controllerPauseButtonDoublePressed:(id)sender {
Log(LOG_I, @"Menu double-pressed -- backing out of stream");
[self returnToMainFrame];
}
#endif
- (void)viewDidLoad
{
[super viewDidLoad];
@@ -46,6 +57,21 @@
_controllerSupport = [[ControllerSupport alloc] initWithConfig:self.streamConfig];
_inactivityTimer = nil;
#if TARGET_OS_TV
if (!_menuGestureRecognizer || !_menuDoubleTapGestureRecognizer) {
_menuGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(controllerPauseButtonPressed:)];
_menuGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
_menuDoubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(controllerPauseButtonDoublePressed:)];
_menuDoubleTapGestureRecognizer.numberOfTapsRequired = 2;
[_menuGestureRecognizer requireGestureRecognizerToFail:_menuDoubleTapGestureRecognizer];
_menuDoubleTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
}
[self.view addGestureRecognizer:_menuGestureRecognizer];
[self.view addGestureRecognizer:_menuDoubleTapGestureRecognizer];
#endif
_streamMan = [[StreamManager alloc] initWithConfig:self.streamConfig
renderView:self.view
connectionCallbacks:self];

View File

@@ -1,7 +1,8 @@
{
"images" : [
{
"idiom" : "tv"
"idiom" : "tv",
"filename" : "bg-app.jpg"
}
],
"info" : {

View File

@@ -1,7 +1,8 @@
{
"images" : [
{
"idiom" : "tv"
"idiom" : "tv",
"filename" : "wedges-app.png"
}
],
"info" : {

View File

@@ -2,10 +2,12 @@
"images" : [
{
"idiom" : "tv",
"filename" : "bg.jpg",
"scale" : "1x"
},
{
"idiom" : "tv",
"filename" : "bg@2.jpg",
"scale" : "2x"
}
],

View File

@@ -2,10 +2,12 @@
"images" : [
{
"idiom" : "tv",
"filename" : "Wedges.png",
"scale" : "1x"
},
{
"idiom" : "tv",
"filename" : "Wedges@2.png",
"scale" : "2x"
}
],

View File

@@ -2,18 +2,22 @@
"images" : [
{
"idiom" : "tv",
"filename" : "topshelf-wide@1.png",
"scale" : "1x"
},
{
"idiom" : "tv",
"filename" : "topshelf-wide@2.png",
"scale" : "2x"
},
{
"idiom" : "tv-marketing",
"filename" : "topshelf-wide@1-1.png",
"scale" : "1x"
},
{
"idiom" : "tv-marketing",
"filename" : "topshelf-wide@2-1.png",
"scale" : "2x"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -2,18 +2,22 @@
"images" : [
{
"idiom" : "tv",
"filename" : "topshelf@1.png",
"scale" : "1x"
},
{
"idiom" : "tv",
"filename" : "topshelf@2.png",
"scale" : "2x"
},
{
"idiom" : "tv-marketing",
"filename" : "topshelf@1-1.png",
"scale" : "1x"
},
{
"idiom" : "tv-marketing",
"filename" : "topshelf@2-1.png",
"scale" : "2x"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="14113" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="pui-7y-JNH">
<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="14313.18" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="pui-7y-JNH">
<device id="appleTV" orientation="landscape">
<adaptation id="light"/>
</device>
<dependencies>
<deployment identifier="tvOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -39,7 +39,6 @@
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" prefetchingEnabled="YES" id="8P0-wP-bDe">
<rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.33333334329999997" green="0.33333334329999997" blue="0.33333334329999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="50" minimumInteritemSpacing="50" id="m6l-TM-5FZ">
<size key="itemSize" width="300" height="400"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>

View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>StringsTable</key>
<string>Root</string>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string>Host Configuration</string>
</dict>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>DefaultValue</key>
<false/>
<key>Title</key>
<string>Play Audio on Host PC</string>
<key>Key</key>
<string>audioOnPC</string>
</dict>
<dict>
<key>TrueValue</key>
<string>Optimize Games</string>
<key>FalseValue</key>
<string>Off</string>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Optimize Games</string>
<key>Key</key>
<string>optimizeGames</string>
<key>DefaultValue</key>
<false/>
</dict>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>HEVC Video</string>
<key>Key</key>
<string>useHevc</string>
<key>DefaultValue</key>
<false/>
</dict>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Enable HDR</string>
<key>Key</key>
<string>enableHdr</string>
<key>DefaultValue</key>
<false/>
</dict>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string>Stream Configuration</string>
</dict>
<dict>
<key>Type</key>
<string>PSMultiValueSpecifier</string>
<key>Title</key>
<string>Stream Resolution</string>
<key>Key</key>
<string>streamResolution</string>
<key>DefaultValue</key>
<string>1</string>
<key>Values</key>
<array>
<string>0</string>
<string>1</string>
<string>2</string>
</array>
<key>Titles</key>
<array>
<string>720p</string>
<string>1080p</string>
<string>4K</string>
</array>
</dict>
<dict>
<key>Type</key>
<string>PSMultiValueSpecifier</string>
<key>Title</key>
<string>Frame Rate</string>
<key>Key</key>
<string>framerate</string>
<key>DefaultValue</key>
<string>60</string>
<key>Values</key>
<array>
<string>30</string>
<string>60</string>
</array>
<key>Titles</key>
<array>
<string>30 FPS</string>
<string>60 FPS</string>
</array>
</dict>
<dict>
<key>Type</key>
<string>PSMultiValueSpecifier</string>
<key>Title</key>
<string>Bitrate</string>
<key>Key</key>
<string>bitrate</string>
<key>DefaultValue</key>
<string>10000</string>
<key>Titles</key>
<array>
<string>0.5 Mbps</string>
<string>1 Mbps</string>
<string>2 Mbps</string>
<string>5 Mbps</string>
<string>10 Mbps</string>
<string>20 Mbps</string>
<string>25 Mbps</string>
<string>50 Mbps</string>
<string>100 Mbps</string>
</array>
<key>Values</key>
<array>
<string>500</string>
<string>1000</string>
<string>2000</string>
<string>5000</string>
<string>10000</string>
<string>20000</string>
<string>25000</string>
<string>50000</string>
<string>100000</string>
</array>
</dict>
</array>
</dict>
</plist>

Binary file not shown.

View File

@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
693B3A9B218638CD00982F7B /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 693B3A9A218638CD00982F7B /* Settings.bundle */; };
9832D1361BBCD5C50036EF48 /* TemporaryApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 9832D1351BBCD5C50036EF48 /* TemporaryApp.m */; };
9865DC30213260B40005B9B9 /* libmoonlight-common-tv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB1A68152132509400507771 /* libmoonlight-common-tv.a */; };
9865DC31213260F10005B9B9 /* mkcert.c in Sources */ = {isa = PBXBuildFile; fileRef = FB89460719F646E200339C8A /* mkcert.c */; };
@@ -160,6 +161,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
98132E8C20BC9A62007A053F /* Moonlight v1.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.1.xcdatamodel"; sourceTree = "<group>"; };
9832D1341BBCD5C50036EF48 /* TemporaryApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporaryApp.h; path = Database/TemporaryApp.h; sourceTree = "<group>"; };
9832D1351BBCD5C50036EF48 /* TemporaryApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TemporaryApp.m; path = Database/TemporaryApp.m; sourceTree = "<group>"; };
@@ -440,6 +442,7 @@
FB1A67612132419A00507771 /* Assets.xcassets */,
FB1A67632132419A00507771 /* Info.plist */,
FB1A67642132419A00507771 /* main.m */,
693B3A9A218638CD00982F7B /* Settings.bundle */,
);
path = "Moonlight TV";
sourceTree = "<group>";
@@ -968,6 +971,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
693B3A9B218638CD00982F7B /* Settings.bundle in Resources */,
FB1A681E21328A1B00507771 /* Images.xcassets in Resources */,
FB1A67622132419A00507771 /* Assets.xcassets in Resources */,
FB1A67602132419700507771 /* Main.storyboard in Resources */,