FBTVNavigationTracker.m 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /**
  2. * Copyright (c) 2018-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. #import "FBTVNavigationTracker.h"
  10. #import "FBTVNavigationTracker-Private.h"
  11. #import "FBMathUtils.h"
  12. #import "XCUIElement+FBCaching.h"
  13. #import "XCUIElement+FBUtilities.h"
  14. #import "XCUIElement+FBWebDriverAttributes.h"
  15. #import "XCUIApplication+FBHelpers.h"
  16. #if TARGET_OS_TV
  17. @implementation FBTVNavigationItem
  18. + (instancetype)itemWithUid:(NSString *) uid
  19. {
  20. return [[FBTVNavigationItem alloc] initWithUid:uid];
  21. }
  22. - (instancetype)initWithUid:(NSString *) uid
  23. {
  24. self = [super init];
  25. if(self) {
  26. _uid = uid;
  27. _directions = [NSMutableSet set];
  28. }
  29. return self;
  30. }
  31. @end
  32. @interface FBTVNavigationTracker ()
  33. @property (nonatomic, strong) XCUIElement *targetElement;
  34. @property (nonatomic, assign) CGPoint targetCenter;
  35. @property (nonatomic, strong) NSMutableDictionary<NSString *, FBTVNavigationItem* >* navigationItems;
  36. @end
  37. @implementation FBTVNavigationTracker
  38. + (instancetype)trackerWithTargetElement:(XCUIElement *)targetElement
  39. {
  40. FBTVNavigationTracker *tracker = [[FBTVNavigationTracker alloc] initWithTargetElement:targetElement];
  41. tracker.targetElement = targetElement;
  42. return tracker;
  43. }
  44. - (instancetype)initWithTargetElement:(XCUIElement *)targetElement
  45. {
  46. self = [super init];
  47. if (self) {
  48. _targetElement = targetElement;
  49. CGRect frame = targetElement.wdFrame;
  50. _targetCenter = FBRectGetCenter(frame);
  51. _navigationItems = [NSMutableDictionary dictionary];
  52. }
  53. return self;
  54. }
  55. - (FBTVDirection)directionToFocusedElement
  56. {
  57. XCUIElement *focused = XCUIApplication.fb_activeApplication.fb_focusedElement;
  58. CGPoint focusedCenter = FBRectGetCenter(focused.wdFrame);
  59. FBTVNavigationItem *item = [self navigationItemWithElement:focused];
  60. CGFloat yDelta = self.targetCenter.y - focusedCenter.y;
  61. CGFloat xDelta = self.targetCenter.x - focusedCenter.x;
  62. FBTVDirection direction;
  63. if (fabs(yDelta) > fabs(xDelta)) {
  64. direction = [self verticalDirectionWithItem:item andDelta:yDelta];
  65. if (direction == FBTVDirectionNone) {
  66. direction = [self horizontalDirectionWithItem:item andDelta:xDelta];
  67. }
  68. } else {
  69. direction = [self horizontalDirectionWithItem:item andDelta:xDelta];
  70. if (direction == FBTVDirectionNone) {
  71. direction = [self verticalDirectionWithItem:item andDelta:yDelta];
  72. }
  73. }
  74. return direction;
  75. }
  76. #pragma mark - Utilities
  77. - (FBTVNavigationItem*)navigationItemWithElement:(id<FBElement>)element
  78. {
  79. NSString *uid = element.wdUID;
  80. if (nil == uid) {
  81. return nil;
  82. }
  83. FBTVNavigationItem* item = [self.navigationItems objectForKey:uid];
  84. if (nil != item) {
  85. return item;
  86. }
  87. item = [FBTVNavigationItem itemWithUid:uid];
  88. [self.navigationItems setObject:item forKey:uid];
  89. return item;
  90. }
  91. - (FBTVDirection)horizontalDirectionWithItem:(FBTVNavigationItem *)item andDelta:(CGFloat)delta
  92. {
  93. // GCFloat is double in 64bit. tvOS is only for arm64
  94. if (delta > DBL_EPSILON &&
  95. ![item.directions containsObject: [NSNumber numberWithInteger: FBTVDirectionRight]]) {
  96. [item.directions addObject: [NSNumber numberWithInteger: FBTVDirectionRight]];
  97. return FBTVDirectionRight;
  98. }
  99. if (delta < -DBL_EPSILON &&
  100. ![item.directions containsObject: [NSNumber numberWithInteger: FBTVDirectionLeft]]) {
  101. [item.directions addObject: [NSNumber numberWithInteger: FBTVDirectionLeft]];
  102. return FBTVDirectionLeft;
  103. }
  104. return FBTVDirectionNone;
  105. }
  106. - (FBTVDirection)verticalDirectionWithItem:(FBTVNavigationItem *)item andDelta:(CGFloat)delta
  107. {
  108. // GCFloat is double in 64bit. tvOS is only for arm64
  109. if (delta > DBL_EPSILON &&
  110. ![item.directions containsObject: [NSNumber numberWithInteger: FBTVDirectionDown]]) {
  111. [item.directions addObject: [NSNumber numberWithInteger: FBTVDirectionDown]];
  112. return FBTVDirectionDown;
  113. }
  114. if (delta < -DBL_EPSILON &&
  115. ![item.directions containsObject: [NSNumber numberWithInteger: FBTVDirectionUp]]) {
  116. [item.directions addObject: [NSNumber numberWithInteger: FBTVDirectionUp]];
  117. return FBTVDirectionUp;
  118. }
  119. return FBTVDirectionNone;
  120. }
  121. @end
  122. #endif