mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Merge pull request #1273 from aaronschubert0/tvOS
[tvOS] Add default focus states to ASControlNode & ASImageNode.
This commit is contained in:
@@ -59,6 +59,7 @@
|
||||
*/
|
||||
- (void)setAttributedTitle:(nullable NSAttributedString *)title forState:(ASControlState)state;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
/**
|
||||
* Sets the title to use for the specified state. This will reset styled title previously set with -setAttributedTitle:forState.
|
||||
*
|
||||
@@ -68,7 +69,7 @@
|
||||
* @param state The state that uses the specified title. The possible values are described in ASControlState.
|
||||
*/
|
||||
- (void)setTitle:(nonnull NSString *)title withFont:(nullable UIFont *)font withColor:(nullable UIColor *)color forState:(ASControlState)state;
|
||||
|
||||
#endif
|
||||
/**
|
||||
* Returns the image used for a button state.
|
||||
*
|
||||
|
||||
@@ -241,40 +241,42 @@
|
||||
|
||||
- (ASVerticalAlignment)contentVerticalAlignment
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _contentVerticalAlignment;
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _contentVerticalAlignment;
|
||||
}
|
||||
|
||||
- (void)setContentVerticalAlignment:(ASVerticalAlignment)contentVerticalAlignment
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_contentVerticalAlignment = contentVerticalAlignment;
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_contentVerticalAlignment = contentVerticalAlignment;
|
||||
}
|
||||
|
||||
- (ASHorizontalAlignment)contentHorizontalAlignment
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _contentHorizontalAlignment;
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _contentHorizontalAlignment;
|
||||
}
|
||||
|
||||
- (void)setContentHorizontalAlignment:(ASHorizontalAlignment)contentHorizontalAlignment
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_contentHorizontalAlignment = contentHorizontalAlignment;
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_contentHorizontalAlignment = contentHorizontalAlignment;
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)contentEdgeInsets
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _contentEdgeInsets;
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _contentEdgeInsets;
|
||||
}
|
||||
|
||||
- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_contentEdgeInsets = contentEdgeInsets;
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_contentEdgeInsets = contentEdgeInsets;
|
||||
}
|
||||
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
- (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(ASControlState)state
|
||||
{
|
||||
NSDictionary *attributes = @{
|
||||
@@ -286,6 +288,7 @@
|
||||
attributes:attributes];
|
||||
[self setAttributedTitle:string forState:state];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (NSAttributedString *)attributedTitleForState:(ASControlState)state
|
||||
{
|
||||
|
||||
@@ -30,6 +30,9 @@ typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent)
|
||||
ASControlNodeEventTouchUpOutside = 1 << 5,
|
||||
/** A system event canceling the current touches for the control node. */
|
||||
ASControlNodeEventTouchCancel = 1 << 6,
|
||||
/** A system event when the Play/Pause button on the Apple TV remote is pressed. */
|
||||
ASControlNodeEventPrimaryActionTriggered = 1 << 13,
|
||||
|
||||
/** All events, including system events. */
|
||||
ASControlNodeEventAllEvents = 0xFFFFFFFF
|
||||
};
|
||||
@@ -119,7 +122,12 @@ typedef NS_OPTIONS(NSUInteger, ASControlState) {
|
||||
@param event The event which triggered these control actions. May be nil.
|
||||
*/
|
||||
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(nullable UIEvent *)event;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
/**
|
||||
@abstract How the node looks when it isn't focused. Exposed here so that subclasses can override.
|
||||
*/
|
||||
- (void)setDefaultState;
|
||||
#endif
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -89,9 +89,21 @@ static BOOL _enableHitTestDebug = NO;
|
||||
|
||||
// As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on.
|
||||
self.userInteractionEnabled = NO;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)didLoad
|
||||
{
|
||||
//On tvOS all control views, such as buttons, interact with the focus system even if they don't have a target set on them. Here we add our own internal tap gesture to handle this behaviour.
|
||||
self.userInteractionEnabled = YES;
|
||||
UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pressDown)];
|
||||
tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)];
|
||||
[self.view addGestureRecognizer:tapGestureRec];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled
|
||||
{
|
||||
[super setUserInteractionEnabled:userInteractionEnabled];
|
||||
@@ -456,6 +468,82 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
|
||||
{
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
#pragma mark - tvOS
|
||||
- (void)pressDown
|
||||
{
|
||||
[UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{
|
||||
[self setPressedState];
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
[UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{
|
||||
[self setFocusedState];
|
||||
} completion:nil];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFocused
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)shouldUpdateFocusInContext:(nonnull UIFocusUpdateContext *)context
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
|
||||
{
|
||||
//FIXME: This is never valid inside an ASCellNode
|
||||
if (context.nextFocusedView && context.nextFocusedView == self.view) {
|
||||
//Focused
|
||||
[coordinator addCoordinatedAnimations:^{
|
||||
[self setFocusedState];
|
||||
} completion:nil];
|
||||
} else{
|
||||
//Not focused
|
||||
[coordinator addCoordinatedAnimations:^{
|
||||
[self setDefaultState];
|
||||
} completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFocusedState
|
||||
{
|
||||
CALayer *layer = self.layer;
|
||||
layer.shadowOffset = CGSizeMake(2, 10);
|
||||
[self applyDefaultShadowProperties: layer];
|
||||
self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1);
|
||||
}
|
||||
|
||||
- (void)setPressedState
|
||||
{
|
||||
CALayer *layer = self.layer;
|
||||
layer.shadowOffset = CGSizeMake(2, 2);
|
||||
[self applyDefaultShadowProperties: layer];
|
||||
self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1);
|
||||
}
|
||||
|
||||
- (void)applyDefaultShadowProperties:(CALayer *)layer
|
||||
{
|
||||
layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
layer.shadowRadius = 12.0;
|
||||
layer.shadowOpacity = 0.45;
|
||||
layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath;
|
||||
}
|
||||
|
||||
- (void)setDefaultState
|
||||
{
|
||||
CALayer *layer = self.layer;
|
||||
layer.shadowOffset = CGSizeZero;
|
||||
layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
layer.shadowRadius = 0;
|
||||
layer.shadowOpacity = 0;
|
||||
layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath;
|
||||
self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1);
|
||||
}
|
||||
#endif //TARGET_OS_TV
|
||||
#pragma mark - Debug
|
||||
// Layout method required when _enableHitTestDebug is enabled.
|
||||
- (void)layout
|
||||
|
||||
@@ -2688,7 +2688,7 @@ ASEnvironmentLayoutExtensibilityForwarding
|
||||
|
||||
- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
|
||||
{
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
|
||||
|
||||
@@ -73,6 +73,11 @@
|
||||
void (^_displayCompletionBlock)(BOOL canceled);
|
||||
ASDN::RecursiveMutex _imageLock;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
//tvOS
|
||||
BOOL isDefaultState;
|
||||
#endif
|
||||
|
||||
// Cropping.
|
||||
BOOL _cropEnabled; // Defaults to YES.
|
||||
BOOL _forceUpscaling; //Defaults to NO.
|
||||
@@ -453,6 +458,174 @@
|
||||
_imageModificationBlock = imageModificationBlock;
|
||||
}
|
||||
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
isDefaultState = NO;
|
||||
UIView *view = [self getView];
|
||||
CALayer *layer = view.layer;
|
||||
|
||||
CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8);
|
||||
[layer removeAllAnimations];
|
||||
[CATransaction begin];
|
||||
[CATransaction setCompletionBlock:^{
|
||||
layer.shadowOffset = targetShadowOffset;
|
||||
}];
|
||||
|
||||
CABasicAnimation *shadowOffsetAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOffset"];
|
||||
shadowOffsetAnimation.toValue = [NSValue valueWithCGSize:targetShadowOffset];
|
||||
shadowOffsetAnimation.duration = 0.4;
|
||||
shadowOffsetAnimation.removedOnCompletion = NO;
|
||||
shadowOffsetAnimation.fillMode = kCAFillModeForwards;
|
||||
shadowOffsetAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"];
|
||||
[layer addAnimation:shadowOffsetAnimation forKey:@"shadowOffset"];
|
||||
[CATransaction commit];
|
||||
|
||||
CABasicAnimation *shadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
||||
shadowOpacityAnimation.toValue = [NSNumber numberWithFloat:0.45];
|
||||
shadowOpacityAnimation.duration = 0.4;
|
||||
shadowOpacityAnimation.removedOnCompletion = false;
|
||||
shadowOpacityAnimation.fillMode = kCAFillModeForwards;
|
||||
shadowOpacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"];
|
||||
[layer addAnimation:shadowOpacityAnimation forKey:@"shadowOpacityAnimation"];
|
||||
|
||||
view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25);
|
||||
|
||||
[CATransaction commit];
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
[super touchesMoved:touches withEvent:event];
|
||||
|
||||
if (!isDefaultState) {
|
||||
UIView *view = [self getView];
|
||||
|
||||
UITouch *touch = [touches anyObject];
|
||||
// Get the specific point that was touched
|
||||
// This is quite messy in it's current state so is not ready for production. The reason it is here is for others to contribute and to make it clear what is occuring.
|
||||
// TODO: Clean up, and improve visuals.
|
||||
CGPoint point = [touch locationInView:self.view];
|
||||
float pitch = 0;
|
||||
float yaw = 0;
|
||||
BOOL topHalf = NO;
|
||||
if (point.y > CGRectGetHeight(self.view.frame)) {
|
||||
pitch = 15;
|
||||
} else if (point.y < -CGRectGetHeight(self.view.frame)) {
|
||||
pitch = -15;
|
||||
} else {
|
||||
pitch = (point.y/CGRectGetHeight(self.view.frame))*15;
|
||||
}
|
||||
if (pitch < 0) {
|
||||
topHalf = YES;
|
||||
}
|
||||
|
||||
if (point.x > CGRectGetWidth(self.view.frame)) {
|
||||
yaw = 10;
|
||||
} else if (point.x < -CGRectGetWidth(self.view.frame)) {
|
||||
yaw = -10;
|
||||
} else {
|
||||
yaw = (point.x/CGRectGetWidth(self.view.frame))*10;
|
||||
}
|
||||
if (!topHalf) {
|
||||
if (yaw > 0) {
|
||||
yaw = -yaw;
|
||||
} else {
|
||||
yaw = fabsf(yaw);
|
||||
}
|
||||
}
|
||||
|
||||
CATransform3D pitchTransform = CATransform3DMakeRotation([self degressToRadians:pitch],1.0,0.0,0.0);
|
||||
CATransform3D yawTransform = CATransform3DMakeRotation([self degressToRadians:yaw],0.0,1.0,0.0);
|
||||
CATransform3D transform = CATransform3DConcat(pitchTransform, yawTransform);
|
||||
CATransform3D scaleAndTransform = CATransform3DConcat(transform, CATransform3DMakeAffineTransform(CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25)));
|
||||
|
||||
[UIView animateWithDuration:0.5 animations:^{
|
||||
view.layer.transform = scaleAndTransform;
|
||||
}];
|
||||
} else {
|
||||
[self setDefaultState];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesEnded:touches withEvent:event];
|
||||
[self finishTouches];
|
||||
}
|
||||
|
||||
- (void)finishTouches
|
||||
{
|
||||
if (!isDefaultState) {
|
||||
UIView *view = [self getView];
|
||||
CALayer *layer = view.layer;
|
||||
|
||||
CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8);
|
||||
CATransform3D targetScaleTransform = CATransform3DMakeScale(1.2, 1.2, 1.2);
|
||||
[CATransaction begin];
|
||||
[CATransaction setCompletionBlock:^{
|
||||
layer.shadowOffset = targetShadowOffset;
|
||||
}];
|
||||
[CATransaction commit];
|
||||
|
||||
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
|
||||
view.layer.transform = targetScaleTransform;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
[layer removeAnimationForKey:@"shadowOffset"];
|
||||
[layer removeAnimationForKey:@"shadowOpacity"];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[self setDefaultState];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFocusedState
|
||||
{
|
||||
UIView *view = [self getView];
|
||||
CALayer *layer = view.layer;
|
||||
layer.shadowOffset = CGSizeMake(2, 10);
|
||||
layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
layer.shadowRadius = 12.0;
|
||||
layer.shadowOpacity = 0.45;
|
||||
layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath;
|
||||
view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25);
|
||||
}
|
||||
|
||||
- (void)setDefaultState
|
||||
{
|
||||
UIView *view = [self getView];
|
||||
CALayer *layer = view.layer;
|
||||
view.transform = CGAffineTransformIdentity;
|
||||
layer.shadowOpacity = 0;
|
||||
layer.shadowOffset = CGSizeZero;
|
||||
layer.shadowRadius = 0;
|
||||
layer.shadowPath = nil;
|
||||
[layer removeAnimationForKey:@"shadowOffset"];
|
||||
[layer removeAnimationForKey:@"shadowOpacity"];
|
||||
isDefaultState = YES;
|
||||
}
|
||||
|
||||
- (UIView *)getView
|
||||
{
|
||||
UIView *view = self.view;
|
||||
//If we are inside a ASCellNode, then we need to apply our focus effects to the ASCellNode view/layer rather than the ASImageNode view/layer.
|
||||
if (CGSizeEqualToSize(self.view.superview.frame.size, self.view.frame.size) && self.view.superview.superview) {
|
||||
view = self.view.superview.superview;
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
- (float)degressToRadians:(float)value
|
||||
{
|
||||
return value * M_PI / 180;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#pragma mark - Debug
|
||||
- (void)layout
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <AsyncDisplayKit/ASButtonNode.h>
|
||||
|
||||
@class AVAsset, AVPlayer, AVPlayerItem;
|
||||
@@ -63,5 +64,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)videoNodeWasTapped:(ASVideoNode *)videoNode;
|
||||
@end
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import "ASVideoNode.h"
|
||||
#import "ASDefaultPlayButton.h"
|
||||
|
||||
@@ -572,3 +572,4 @@ static NSString * const kStatus = @"status";
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@@ -99,7 +99,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
|
||||
// Focus Engine
|
||||
- (BOOL)canBecomeFocused
|
||||
{
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setNeedsFocusUpdate
|
||||
@@ -116,7 +116,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
|
||||
|
||||
- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
|
||||
{
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
|
||||
|
||||
Reference in New Issue
Block a user