FBTVNavigationTracker.m 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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.fb_isResolvedFromCache.boolValue
  50. ? [FBXCElementSnapshotWrapper ensureWrapped:targetElement.lastSnapshot].wdFrame
  51. : targetElement.wdFrame;
  52. _targetCenter = FBRectGetCenter(frame);
  53. _navigationItems = [NSMutableDictionary dictionary];
  54. }
  55. return self;
  56. }
  57. - (FBTVDirection)directionToFocusedElement
  58. {
  59. XCUIElement *focused = XCUIApplication.fb_activeApplication.fb_focusedElement;
  60. CGPoint focusedCenter = FBRectGetCenter(focused.wdFrame);
  61. FBTVNavigationItem *item = [self navigationItemWithElement:focused];
  62. CGFloat yDelta = self.targetCenter.y - focusedCenter.y;
  63. CGFloat xDelta = self.targetCenter.x - focusedCenter.x;
  64. FBTVDirection direction;
  65. if (fabs(yDelta) > fabs(xDelta)) {
  66. direction = [self verticalDirectionWithItem:item andDelta:yDelta];
  67. if (direction == FBTVDirectionNone) {
  68. direction = [self horizontalDirectionWithItem:item andDelta:xDelta];
  69. }
  70. } else {
  71. direction = [self horizontalDirectionWithItem:item andDelta:xDelta];
  72. if (direction == FBTVDirectionNone) {
  73. direction = [self verticalDirectionWithItem:item andDelta:yDelta];
  74. }
  75. }
  76. return direction;
  77. }
  78. #pragma mark - Utilities
  79. - (FBTVNavigationItem*)navigationItemWithElement:(id<FBElement>)element
  80. {
  81. NSString *uid = element.wdUID;
  82. if (nil == uid) {
  83. return nil;
  84. }
  85. FBTVNavigationItem* item = [self.navigationItems objectForKey:uid];
  86. if (nil != item) {
  87. return item;
  88. }
  89. item = [FBTVNavigationItem itemWithUid:uid];
  90. [self.navigationItems setObject:item forKey:uid];
  91. return item;
  92. }
  93. - (FBTVDirection)horizontalDirectionWithItem:(FBTVNavigationItem *)item andDelta:(CGFloat)delta
  94. {
  95. // GCFloat is double in 64bit. tvOS is only for arm64
  96. if (delta > DBL_EPSILON &&
  97. ![item.directions containsObject: [NSNumber numberWithInteger: FBTVDirectionRight]]) {
  98. [item.directions addObject: [NSNumber numberWithInteger: FBTVDirectionRight]];
  99. return FBTVDirectionRight;
  100. }
  101. if (delta < -DBL_EPSILON &&
  102. ![item.directions containsObject: [NSNumber numberWithInteger: FBTVDirectionLeft]]) {
  103. [item.directions addObject: [NSNumber numberWithInteger: FBTVDirectionLeft]];
  104. return FBTVDirectionLeft;
  105. }
  106. return FBTVDirectionNone;
  107. }
  108. - (FBTVDirection)verticalDirectionWithItem:(FBTVNavigationItem *)item andDelta:(CGFloat)delta
  109. {
  110. // GCFloat is double in 64bit. tvOS is only for arm64
  111. if (delta > DBL_EPSILON &&
  112. ![item.directions containsObject: [NSNumber numberWithInteger: FBTVDirectionDown]]) {
  113. [item.directions addObject: [NSNumber numberWithInteger: FBTVDirectionDown]];
  114. return FBTVDirectionDown;
  115. }
  116. if (delta < -DBL_EPSILON &&
  117. ![item.directions containsObject: [NSNumber numberWithInteger: FBTVDirectionUp]]) {
  118. [item.directions addObject: [NSNumber numberWithInteger: FBTVDirectionUp]];
  119. return FBTVDirectionUp;
  120. }
  121. return FBTVDirectionNone;
  122. }
  123. @end
  124. #endif