mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-08-24 11:28:52 -04:00
305 lines
8.2 KiB
Objective-C
305 lines
8.2 KiB
Objective-C
//
|
|
// 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 {
|
|
_spacingSlider = [UISlider new];
|
|
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:(UISlider *)slider {
|
|
// 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
|