123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- //
- // FHSSnapshotView.m
- // FLEX
- //
- // Created by Tanner Bennett on 1/7/20.
- // Copyright © 2020 FLEX Team. All rights reserved.
- //
- #import "FHSSnapshotView.h"
- #import "FHSSnapshotNodes.h"
- #import "SceneKit+Snapshot.h"
- #import "FLEXColor.h"
- @interface FHSSnapshotView ()
- @property (nonatomic, readonly) SCNView *sceneView;
- @property (nonatomic) NSString *currentSummary;
- /// Maps nodes by snapshot IDs
- @property (nonatomic) NSDictionary<NSString *, FHSSnapshotNodes *> *nodesMap;
- @property (nonatomic) NSInteger maxDepth;
- @property (nonatomic) FHSSnapshotNodes *highlightedNodes;
- @property (nonatomic, getter=wantsHideHeaders) BOOL hideHeaders;
- @property (nonatomic, getter=wantsHideBorders) BOOL hideBorders;
- @property (nonatomic) BOOL suppressSelectionEvents;
- @property (nonatomic, readonly) BOOL mustHideHeaders;
- @end
- @implementation FHSSnapshotView
- #pragma mark - Initialization
- + (instancetype)delegate:(id<FHSSnapshotViewDelegate>)delegate {
- FHSSnapshotView *view = [self new];
- view.delegate = delegate;
- return view;
- }
- - (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:CGRectZero];
- if (self) {
- [self initSpacingSlider];
- [self initDepthSlider];
- [self initSceneView]; // Must be last; calls setMaxDepth
- // self.hideHeaders = YES;
-
- // Self
- self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- // Scene
- self.sceneView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- [self addGestureRecognizer:[[UITapGestureRecognizer alloc]
- initWithTarget:self action:@selector(handleTap:)
- ]];
- }
- return self;
- }
- - (void)initSceneView {
- _sceneView = [SCNView new];
- self.sceneView.allowsCameraControl = YES;
- [self addSubview:self.sceneView];
- }
- - (void)initSpacingSlider {
- #if !TARGET_OS_TV
- _spacingSlider = [UISlider new];
- #else
- _spacingSlider = [KBSlider new];
- #endif
- self.spacingSlider.minimumValue = 0;
- self.spacingSlider.maximumValue = 100;
- self.spacingSlider.continuous = YES;
- [self.spacingSlider
- addTarget:self
- action:@selector(spacingSliderDidChange:)
- forControlEvents:UIControlEventValueChanged
- ];
- self.spacingSlider.value = 50;
- }
- - (void)initDepthSlider {
- _depthSlider = [FHSRangeSlider new];
- [self.depthSlider
- addTarget:self
- action:@selector(depthSliderDidChange:)
- forControlEvents:UIControlEventValueChanged
- ];
- }
- #pragma mark - Public
- - (void)setSelectedView:(FHSViewSnapshot *)view {
- // Ivar set in selectSnapshot:
- [self selectSnapshot:view ? self.nodesMap[view.view.identifier] : nil];
- }
- - (void)setSnapshots:(NSArray<FHSViewSnapshot *> *)snapshots {
- _snapshots = snapshots;
- // Create new scene (possibly discarding old scene)
- SCNScene *scene = [SCNScene new];
- scene.background.contents = FLEXColor.primaryBackgroundColor;
- self.sceneView.scene = scene;
- NSInteger depth = 0;
- NSMutableDictionary *nodesMap = [NSMutableDictionary new];
- // Add every root snapshot to the root scene node with increasing depths
- SCNNode *root = scene.rootNode;
- for (FHSViewSnapshot *snapshot in self.snapshots) {
- [SCNNode
- snapshot:snapshot
- parent:nil
- parentNode:nil
- root:root
- depth:&depth
- nodesMap:nodesMap
- hideHeaders:_hideHeaders
- ];
- }
- self.maxDepth = depth;
- self.nodesMap = nodesMap;
- }
- - (void)setHeaderExclusions:(NSArray<Class> *)headerExclusions {
- _headerExclusions = headerExclusions;
- if (headerExclusions.count) {
- for (FHSSnapshotNodes *nodes in self.nodesMap.allValues) {
- if ([headerExclusions containsObject:nodes.snapshotItem.view.view.class]) {
- nodes.forceHideHeader = YES;
- } else {
- nodes.forceHideHeader = NO;
- }
- }
- }
- }
- - (void)emphasizeViews:(NSArray<UIView *> *)emphasizedViews {
- if (emphasizedViews.count) {
- [self emphasizeViews:emphasizedViews inSnapshots:self.snapshots];
- [self setNeedsLayout];
- }
- }
- - (void)emphasizeViews:(NSArray<UIView *> *)emphasizedViews inSnapshots:(NSArray<FHSViewSnapshot *> *)snapshots {
- for (FHSViewSnapshot *snapshot in snapshots) {
- FHSSnapshotNodes *nodes = self.nodesMap[snapshot.view.identifier];
- nodes.dimmed = ![emphasizedViews containsObject:snapshot.view.view];
- [self emphasizeViews:emphasizedViews inSnapshots:snapshot.children];
- }
- }
- - (void)toggleShowHeaders {
- self.hideHeaders = !self.hideHeaders;
- }
- - (void)toggleShowBorders {
- self.hideBorders = !self.hideBorders;
- }
- - (void)hideView:(FHSViewSnapshot *)view {
- NSParameterAssert(view);
- FHSSnapshotNodes *nodes = self.nodesMap[view.view.identifier];
- [nodes.snapshot removeFromParentNode];
- }
- #pragma mark - Helper
- - (BOOL)mustHideHeaders {
- return self.spacingSlider.value <= kFHSSmallZOffset;
- }
- - (void)setMaxDepth:(NSInteger)maxDepth {
- _maxDepth = maxDepth;
- self.depthSlider.allowedMinValue = 0;
- self.depthSlider.allowedMaxValue = maxDepth;
- self.depthSlider.maxValue = maxDepth;
- self.depthSlider.minValue = 0;
- }
- - (void)setHideHeaders:(BOOL)hideHeaders {
- if (_hideHeaders != hideHeaders) {
- _hideHeaders = hideHeaders;
- if (!self.mustHideHeaders) {
- if (hideHeaders) {
- [self hideHeaders];
- } else {
- [self unhideHeaders];
- }
- }
- }
- }
- - (void)setHideBorders:(BOOL)hideBorders {
- if (_hideBorders != hideBorders) {
- _hideBorders = hideBorders;
- for (FHSSnapshotNodes *nodes in self.nodesMap.allValues) {
- nodes.border.hidden = hideBorders;
- }
- }
- }
- - (FHSSnapshotNodes *)nodesAtPoint:(CGPoint)point {
- NSArray<SCNHitTestResult *> *results = [self.sceneView hitTest:point options:nil];
- for (SCNHitTestResult *result in results) {
- SCNNode *nearestSnapshot = result.node.nearestAncestorSnapshot;
- if (nearestSnapshot) {
- return self.nodesMap[nearestSnapshot.name];
- }
- }
- return nil;
- }
- - (void)selectSnapshot:(FHSSnapshotNodes *)selected {
- // Notify delegate of de-select
- if (!selected && self.selectedView) {
- [self.delegate didDeselectView:self.selectedView];
- }
- _selectedView = selected.snapshotItem;
- // Case: selected the currently selected node
- if (selected == self.highlightedNodes) {
- return;
- }
- // No-op if nothng is selected (yay objc!)
- self.highlightedNodes.highlighted = NO;
- self.highlightedNodes = nil;
- // No node means we tapped the background
- if (selected) {
- selected.highlighted = YES;
- // TODO: update description text here
- self.highlightedNodes = selected;
- }
- // Notify delegate
- [self.delegate didSelectView:selected.snapshotItem];
- [self setNeedsLayout];
- }
- - (void)hideHeaders {
- for (FHSSnapshotNodes *nodes in self.nodesMap.allValues) {
- nodes.header.hidden = YES;
- }
- }
- - (void)unhideHeaders {
- for (FHSSnapshotNodes *nodes in self.nodesMap.allValues) {
- if (!nodes.forceHideHeader) {
- nodes.header.hidden = NO;
- }
- }
- }
- #pragma mark - Event Handlers
- - (void)handleTap:(UITapGestureRecognizer *)gesture {
- if (gesture.state == UIGestureRecognizerStateRecognized) {
- CGPoint tap = [gesture locationInView:self.sceneView];
- [self selectSnapshot:[self nodesAtPoint:tap]];
- }
- }
- - (void)spacingSliderDidChange:(KBSlider *)slider { //easier to make a KBSlider since they are API compatible - one less #if macro!
- // TODO: hiding the header when flat logic
- for (FHSSnapshotNodes *nodes in self.nodesMap.allValues) {
- nodes.snapshot.position = ({
- SCNVector3 pos = nodes.snapshot.position;
- pos.z = MAX(slider.value, kFHSSmallZOffset) * nodes.depth;
- pos;
- });
- if (!self.wantsHideHeaders) {
- if (self.mustHideHeaders) {
- [self hideHeaders];
- } else {
- [self unhideHeaders];
- }
- }
- }
- }
- - (void)depthSliderDidChange:(FHSRangeSlider *)slider {
- CGFloat min = slider.minValue, max = slider.maxValue;
- for (FHSSnapshotNodes *nodes in self.nodesMap.allValues) {
- CGFloat depth = nodes.depth;
- nodes.snapshot.hidden = depth < min || max < depth;
- }
- }
- @end
|