LKS_PerspectiveLayer.m 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. #ifdef SHOULD_COMPILE_LOOKIN_SERVER
  2. //
  3. // LKS_PerspectiveLayer.m
  4. // LookinServer
  5. //
  6. // Created by Li Kai on 2019/5/17.
  7. // https://lookin.work
  8. //
  9. #import "LKS_PerspectiveLayer.h"
  10. #import "LKS_PerspectiveDataSource.h"
  11. #import "LKS_PerspectiveItemLayer.h"
  12. #import "LookinAppInfo.h"
  13. #import "LookinHierarchyInfo.h"
  14. #import "LookinServerDefines.h"
  15. @interface LookinDisplayItem (LKS_PerspectiveLayer)
  16. @property(nonatomic, weak) LKS_PerspectiveItemLayer *lks_itemLayer;
  17. @end
  18. @implementation LookinDisplayItem (LKS_PerspectiveLayer)
  19. - (void)setLks_itemLayer:(LKS_PerspectiveItemLayer *)lks_itemLayer {
  20. [self lookin_bindObjectWeakly:lks_itemLayer forKey:@"lks_itemLayer"];
  21. }
  22. - (LKS_PerspectiveItemLayer *)lks_itemLayer {
  23. return [self lookin_getBindObjectForKey:@"lks_itemLayer"];
  24. }
  25. @end
  26. @interface LKS_PerspectiveLayer ()
  27. @property(nonatomic, strong) CALayer *rotateLayer;
  28. @property(nonatomic, copy) NSArray<LKS_PerspectiveItemLayer *> *itemLayers;
  29. @property(nonatomic, strong) LKS_PerspectiveDataSource *dataSource;
  30. @property(nonatomic, strong) LKS_PerspectiveItemLayer *selectedLayer;
  31. @end
  32. @implementation LKS_PerspectiveLayer
  33. - (instancetype)initWithDataSource:(LKS_PerspectiveDataSource *)dataSource {
  34. if (self = [self init]) {
  35. self.dataSource = dataSource;
  36. dataSource.perspectiveLayer = self;
  37. // [self lookin_removeImplicitAnimations];
  38. self.rotateLayer = [CALayer layer];
  39. [self addSublayer:self.rotateLayer];
  40. self.itemLayers = [NSArray array];
  41. [self _rebuildPreviewLayers];
  42. }
  43. return self;
  44. }
  45. - (void)layoutSublayers {
  46. [super layoutSublayers];
  47. LookinAppInfo *appInfo = self.dataSource.rawHierarchyInfo.appInfo;
  48. CGSize size = CGSizeMake(appInfo.screenWidth, appInfo.screenHeight);
  49. self.rotateLayer.bounds = CGRectMake(0, 0, size.width, size.height);
  50. self.rotateLayer.anchorPoint = CGPointMake(.5, .5);
  51. self.rotateLayer.position = CGPointMake(self.rotateLayer.superlayer.bounds.size.width / 2.0, self.rotateLayer.superlayer.bounds.size.height / 2.0);
  52. }
  53. - (void)setDimension:(LKS_PerspectiveDimension)dimension {
  54. _dimension = dimension;
  55. if (dimension == LKS_PerspectiveDimension2D) {
  56. self.rotateLayer.sublayerTransform = CATransform3DIdentity;
  57. [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull layer, NSUInteger idx, BOOL * _Nonnull stop) {
  58. layer.transform = CATransform3DIdentity;
  59. }];
  60. } else if (dimension == LKS_PerspectiveDimension3D) {
  61. CGFloat targetRotation = (self.rotation == 0 ? .6 : self.rotation);
  62. [self setRotation:targetRotation animated:YES completion:nil];
  63. [self _updateZIndex];
  64. } else {
  65. NSAssert(NO, @"");
  66. }
  67. }
  68. - (void)setRotation:(CGFloat)rotation {
  69. _rotation = rotation;
  70. CATransform3D transform = CATransform3DIdentity;
  71. transform.m34 = - 1 / 3000.0;
  72. transform = CATransform3DRotate(transform, rotation, 0, 1, 0);
  73. self.rotateLayer.sublayerTransform = transform;
  74. }
  75. - (void)setRotation:(CGFloat)rotation animated:(BOOL)animated completion:(void (^)(void))completionBlock {
  76. [CATransaction begin];
  77. [CATransaction setCompletionBlock:completionBlock];
  78. [CATransaction setDisableActions:!animated];
  79. [self setRotation:rotation];
  80. [CATransaction commit];
  81. }
  82. - (void)_rebuildPreviewLayers {
  83. NSArray<LookinDisplayItem *> *validItems = [self.dataSource.flatItems lookin_filter:^BOOL(LookinDisplayItem *obj) {
  84. return !obj.inNoPreviewHierarchy;
  85. }];
  86. self.itemLayers = [self.itemLayers lookin_resizeWithCount:validItems.count add:^LKS_PerspectiveItemLayer *(NSUInteger idx) {
  87. LKS_PerspectiveItemLayer *layer = [LKS_PerspectiveItemLayer new];
  88. [self.rotateLayer addSublayer:layer];
  89. return layer;
  90. } remove:^(NSUInteger idx, LKS_PerspectiveItemLayer *layer) {
  91. [layer removeFromSuperlayer];
  92. } doNext:^(NSUInteger idx, LKS_PerspectiveItemLayer *layer) {
  93. LookinDisplayItem *item = validItems[idx];
  94. layer.displayItem = item;
  95. layer.frame = item.frameToRoot;
  96. item.lks_itemLayer = layer;
  97. if (item.isSelected) {
  98. self.selectedLayer = layer;
  99. }
  100. }];
  101. [self _updateZIndex];
  102. }
  103. /**
  104. 重新计算每个 item 的 zIndex,并依 zIndex 设置对应的图层在 z 轴上的 translation。同时根据 fold 等属性来显示或隐藏图层。
  105. */
  106. - (void)_updateZIndex {
  107. [[self.dataSource.flatItems lookin_filter:^BOOL(LookinDisplayItem *obj) {
  108. return !obj.inNoPreviewHierarchy;
  109. }] enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  110. [self _updateZIndexForItem:obj];
  111. }];
  112. [self _updateZTranslationByZIndex];
  113. }
  114. - (void)_updateZIndexForItem:(LookinDisplayItem *)item {
  115. item.previewZIndex = -1;
  116. if (item.displayingInHierarchy) {
  117. LookinDisplayItem *referenceItem = [self _maxZIndexForOverlappedItemUnderItem:item];
  118. if (referenceItem) {
  119. // 如果 item 和另一个 itemA 重叠了,则 item.previewZIndex 应该比 itemA.previewZIndex 高一级
  120. item.previewZIndex = referenceItem.previewZIndex + 1;
  121. } else {
  122. item.previewZIndex = 0;
  123. }
  124. } else {
  125. if (item.superItem) {
  126. item.previewZIndex = item.superItem.previewZIndex;
  127. } else {
  128. NSAssert(NO, @"");
  129. }
  130. }
  131. if (item.previewZIndex < 0) {
  132. NSAssert(NO, @"");
  133. item.previewZIndex = 0;
  134. }
  135. }
  136. - (void)_updateZTranslationByZIndex {
  137. CGFloat interspace = 20;
  138. // key 是 zIndex,value 是该 zIndex 下有多少 item,作用是避免下文提到的 offsetToAvoidOverlapBug
  139. NSMutableDictionary<NSNumber *, NSNumber *> *zIndexAndCountDict = [NSMutableDictionary dictionary];
  140. __block NSUInteger maxZIndex = 0;
  141. [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  142. maxZIndex = MAX(maxZIndex, obj.displayItem.previewZIndex);
  143. }];
  144. [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull layer, NSUInteger idx, BOOL * _Nonnull stop) {
  145. LookinDisplayItem *item = layer.displayItem;
  146. // 将 "1, 2, 3, 4, 5 ..." 这样的 zIndex 排序调整为 “-2,-1,0,1,2 ...”,这样旋转时 Y 轴就会位于 zIndex 为中间值的那个 layer 的位置
  147. NSInteger adjustedZIndex = item.previewZIndex - round(maxZIndex / 2.0);
  148. NSUInteger countOfCurrentZIndex = [[zIndexAndCountDict objectForKey:@(adjustedZIndex)] unsignedIntegerValue];
  149. countOfCurrentZIndex++;
  150. [zIndexAndCountDict setObject:@(countOfCurrentZIndex) forKey:@(adjustedZIndex)];
  151. /// 当两个重叠的 layer 在 z 轴上具有相同的 translate 时,理论上更远离用户的那个 layer 应该被遮住,但不知道为什么某些旋转角度下会出现不合理论的情况,感觉是系统 bug,因此这里把更靠近用户的那个 layer 的 translate 增大一丁点,避免两个 layer 有完全相同的 translate 从而避免这个 bug
  152. CGFloat offsetToAvoidOverlapBug = countOfCurrentZIndex * 0.01;
  153. layer.transform = CATransform3DTranslate(CATransform3DIdentity, 0, 0, interspace * adjustedZIndex + offsetToAvoidOverlapBug);
  154. }];
  155. }
  156. /**
  157. 传入 itemA,返回另一个 itemB,itemB 满足以下条件:
  158. - itemB 在 preview 中可见
  159. - itemB 的层级比 itemA 要低(即 itemB 在 flatItems 里的 index 要比 itemA 小)
  160. - itemB 和 itemA 的 frameToRoot 有重叠,即视觉上它们是彼此遮挡的
  161. - itemB 是满足以上两个条件中的所有 items 里的 zIndex 值最高的
  162. @note 如果没有找到任何符合条件的 itemB,则返回 nil
  163. */
  164. - (LookinDisplayItem *)_maxZIndexForOverlappedItemUnderItem:(LookinDisplayItem *)item {
  165. NSArray<LookinDisplayItem *> *flatItems = [self.dataSource.flatItems lookin_filter:^BOOL(LookinDisplayItem *obj) {
  166. return !obj.inNoPreviewHierarchy;
  167. }];
  168. NSUInteger itemIndex = [flatItems indexOfObject:item];
  169. if (itemIndex == 0) {
  170. return nil;
  171. }
  172. if (itemIndex == NSNotFound) {
  173. NSAssert(NO, @"");
  174. return nil;
  175. }
  176. NSIndexSet *indexesBelow = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, itemIndex)];
  177. __block LookinDisplayItem *targetItem = nil;
  178. [flatItems enumerateObjectsAtIndexes:indexesBelow options:NSEnumerationReverse usingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  179. if (!obj.inHiddenHierarchy) {
  180. if (CGRectIntersectsRect(item.frameToRoot, obj.frameToRoot)) {
  181. if (!targetItem) {
  182. targetItem = obj;
  183. } else {
  184. if (obj.previewZIndex > targetItem.previewZIndex) {
  185. targetItem = obj;
  186. }
  187. }
  188. }
  189. }
  190. }];
  191. return targetItem;
  192. }
  193. #pragma mark - <LKS_PerspectiveDataSourceDelegate>
  194. - (void)dataSourceDidChangeSelectedItem:(LKS_PerspectiveDataSource *)dataSource {
  195. [self.selectedLayer reRender];
  196. LookinDisplayItem *item = dataSource.selectedItem;
  197. [item.lks_itemLayer reRender];
  198. self.selectedLayer = item.lks_itemLayer;
  199. }
  200. - (void)dataSourceDidChangeDisplayItems:(LKS_PerspectiveDataSource *)dataSource {
  201. [self _updateZIndex];
  202. [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  203. [obj reRender];
  204. }];
  205. }
  206. - (void)dataSourceDidChangeNoPreview:(LKS_PerspectiveDataSource *)dataSource {
  207. [self _rebuildPreviewLayers];
  208. }
  209. @end
  210. #endif /* SHOULD_COMPILE_LOOKIN_SERVER */