mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-08-25 03:48:51 -04:00
added files via upload
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// FHSSnapshotNodes.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 1/7/20.
|
||||
//
|
||||
|
||||
#import "FHSViewSnapshot.h"
|
||||
#import <SceneKit/SceneKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Container that holds references to the SceneKit nodes associated with a snapshot.
|
||||
@interface FHSSnapshotNodes : NSObject
|
||||
|
||||
+ (instancetype)snapshot:(FHSViewSnapshot *)snapshot depth:(NSInteger)depth;
|
||||
|
||||
@property (nonatomic, readonly) FHSViewSnapshot *snapshotItem;
|
||||
@property (nonatomic, readonly) NSInteger depth;
|
||||
|
||||
/// The view image itself
|
||||
@property (nonatomic, nullable) SCNNode *snapshot;
|
||||
/// Goes on top of the snapshot, has rounded top corners
|
||||
@property (nonatomic, nullable) SCNNode *header;
|
||||
/// The bounding box drawn around the snapshot
|
||||
@property (nonatomic, nullable) SCNNode *border;
|
||||
|
||||
/// Used to indicate when a view is selected
|
||||
@property (nonatomic, getter=isHighlighted) BOOL highlighted;
|
||||
/// Used to indicate when a view is de-emphasized
|
||||
@property (nonatomic, getter=isDimmed) BOOL dimmed;
|
||||
|
||||
@property (nonatomic) BOOL forceHideHeader;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// FHSSnapshotNodes.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 1/7/20.
|
||||
//
|
||||
|
||||
#import "FHSSnapshotNodes.h"
|
||||
#import "SceneKit+Snapshot.h"
|
||||
|
||||
@interface FHSSnapshotNodes ()
|
||||
@property (nonatomic, nullable) SCNNode *highlight;
|
||||
@property (nonatomic, nullable) SCNNode *dimming;
|
||||
@end
|
||||
@implementation FHSSnapshotNodes
|
||||
|
||||
+ (instancetype)snapshot:(FHSViewSnapshot *)snapshot depth:(NSInteger)depth {
|
||||
FHSSnapshotNodes *nodes = [self new];
|
||||
nodes->_snapshotItem = snapshot;
|
||||
nodes->_depth = depth;
|
||||
return nodes;
|
||||
}
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted {
|
||||
if (_highlighted != highlighted) {
|
||||
_highlighted = highlighted;
|
||||
|
||||
if (highlighted) {
|
||||
if (!self.highlight) {
|
||||
// Create highlight node
|
||||
self.highlight = [SCNNode
|
||||
highlight:self.snapshotItem
|
||||
color:[UIColor.blueColor colorWithAlphaComponent:0.5]
|
||||
];
|
||||
}
|
||||
// Add add highlight node, remove dimming node if dimmed
|
||||
[self.snapshot addChildNode:self.highlight];
|
||||
if (self.isDimmed) {
|
||||
[self.dimming removeFromParentNode];
|
||||
}
|
||||
} else {
|
||||
// Remove highlight node, add back dimming node if dimmed
|
||||
[self.highlight removeFromParentNode];
|
||||
if (self.isDimmed) {
|
||||
[self.snapshot addChildNode:self.dimming];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDimmed:(BOOL)dimmed {
|
||||
if (_dimmed != dimmed) {
|
||||
_dimmed = dimmed;
|
||||
|
||||
if (dimmed) {
|
||||
if (!self.dimming) {
|
||||
// Create dimming node
|
||||
self.dimming = [SCNNode
|
||||
highlight:self.snapshotItem
|
||||
color:[UIColor.blackColor colorWithAlphaComponent:0.5]
|
||||
];
|
||||
}
|
||||
// Add add dimming node if not highlighted
|
||||
if (!self.isHighlighted) {
|
||||
[self.snapshot addChildNode:self.dimming];
|
||||
}
|
||||
} else {
|
||||
// Remove dimming node (if not already highlighted)
|
||||
if (!self.isHighlighted) {
|
||||
[self.dimming removeFromParentNode];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setForceHideHeader:(BOOL)forceHideHeader {
|
||||
if (_forceHideHeader != forceHideHeader) {
|
||||
_forceHideHeader = forceHideHeader;
|
||||
|
||||
if (self.header.parentNode) {
|
||||
self.header.hidden = YES;
|
||||
[self.header removeFromParentNode];
|
||||
} else {
|
||||
self.header.hidden = NO;
|
||||
[self.snapshot addChildNode:self.header];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// SceneKit+Snapshot.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 1/8/20.
|
||||
//
|
||||
|
||||
#import <SceneKit/SceneKit.h>
|
||||
#import "FHSViewSnapshot.h"
|
||||
@class FHSSnapshotNodes;
|
||||
|
||||
extern CGFloat const kFHSSmallZOffset;
|
||||
|
||||
#pragma mark SCNNode
|
||||
@interface SCNNode (Snapshot)
|
||||
|
||||
/// @return the nearest ancestor snapshot node starting at this node
|
||||
@property (nonatomic, readonly) SCNNode *nearestAncestorSnapshot;
|
||||
|
||||
/// @return a node that renders a highlight overlay over a specified snapshot
|
||||
+ (instancetype)highlight:(FHSViewSnapshot *)view color:(UIColor *)color;
|
||||
/// @return a node that renders a snapshot image
|
||||
+ (instancetype)snapshot:(FHSViewSnapshot *)view;
|
||||
/// @return a node that draws a line between two vertices
|
||||
+ (instancetype)lineFrom:(SCNVector3)v1 to:(SCNVector3)v2 color:(UIColor *)lineColor;
|
||||
|
||||
/// @return a node that can be used to render a colored border around the specified node
|
||||
- (instancetype)borderWithColor:(UIColor *)color;
|
||||
/// @return a node that renders a header above a snapshot node
|
||||
/// using the title text from the view, if specified
|
||||
+ (instancetype)header:(FHSViewSnapshot *)view;
|
||||
|
||||
/// @return a SceneKit node that recursively renders a hierarchy
|
||||
/// of UI elements starting at the specified snapshot
|
||||
+ (instancetype)snapshot:(FHSViewSnapshot *)view
|
||||
parent:(FHSViewSnapshot *)parentView
|
||||
parentNode:(SCNNode *)parentNode
|
||||
root:(SCNNode *)rootNode
|
||||
depth:(NSInteger *)depthOut
|
||||
nodesMap:(NSMutableDictionary<NSString *, FHSSnapshotNodes *> *)nodesMap
|
||||
hideHeaders:(BOOL)hideHeaders;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark SCNShape
|
||||
@interface SCNShape (Snapshot)
|
||||
/// @return a shape with the given path, 0 extrusion depth, and a double-sided
|
||||
/// material with the given diffuse contents inserted at index 0
|
||||
+ (instancetype)shapeWithPath:(UIBezierPath *)path materialDiffuse:(id)contents;
|
||||
/// @return a shape that is used to render the background of the snapshot header
|
||||
+ (instancetype)nameHeader:(UIColor *)color frame:(CGRect)frame corners:(CGFloat)cornerRadius;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark SCNText
|
||||
@interface SCNText (Snapshot)
|
||||
/// @return text geometry used to render text inside the snapshot header
|
||||
+ (instancetype)labelGeometry:(NSString *)text font:(UIFont *)font;
|
||||
|
||||
@end
|
@@ -0,0 +1,278 @@
|
||||
//
|
||||
// SceneKit+Snapshot.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 1/8/20.
|
||||
//
|
||||
|
||||
#import "SceneKit+Snapshot.h"
|
||||
#import "FHSSnapshotNodes.h"
|
||||
|
||||
/// This value is chosen such that this offset can be applied to avoid
|
||||
/// z-fighting amongst nodes at the same z-position, but small enough
|
||||
/// that they appear to visually be on the same plane.
|
||||
CGFloat const kFHSSmallZOffset = 0.05;
|
||||
CGFloat const kHeaderVerticalInset = 8.0;
|
||||
|
||||
#pragma mark SCNGeometry
|
||||
@interface SCNGeometry (SnapshotPrivate)
|
||||
@end
|
||||
@implementation SCNGeometry (SnapshotPrivate)
|
||||
|
||||
- (void)addDoubleSidedMaterialWithDiffuseContents:(id)contents {
|
||||
SCNMaterial *material = [SCNMaterial new];
|
||||
material.doubleSided = YES;
|
||||
material.diffuse.contents = contents;
|
||||
[self insertMaterial:material atIndex:0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark SCNNode
|
||||
@implementation SCNNode (Snapshot)
|
||||
|
||||
- (SCNNode *)nearestAncestorSnapshot {
|
||||
SCNNode *node = self;
|
||||
|
||||
while (!node.name && node) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
+ (instancetype)shapeNodeWithSize:(CGSize)size materialDiffuse:(id)contents offsetZ:(BOOL)offsetZ {
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(
|
||||
0, 0, size.width, size.height
|
||||
)];
|
||||
SCNShape *shape = [SCNShape shapeWithPath:path materialDiffuse:contents];
|
||||
SCNNode *node = [SCNNode nodeWithGeometry:shape];
|
||||
|
||||
if (offsetZ) {
|
||||
node.position = SCNVector3Make(0, 0, kFHSSmallZOffset);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
+ (instancetype)highlight:(FHSViewSnapshot *)view color:(UIColor *)color {
|
||||
return [self shapeNodeWithSize:view.frame.size materialDiffuse:color offsetZ:YES];
|
||||
}
|
||||
|
||||
+ (instancetype)snapshot:(FHSViewSnapshot *)view {
|
||||
id image = view.snapshotImage;
|
||||
return [self shapeNodeWithSize:view.frame.size materialDiffuse:image offsetZ:NO];
|
||||
}
|
||||
|
||||
+ (instancetype)lineFrom:(SCNVector3)v1 to:(SCNVector3)v2 color:(UIColor *)lineColor {
|
||||
SCNVector3 vertices[2] = { v1, v2 };
|
||||
int32_t _indices[2] = { 0, 1 };
|
||||
NSData *indices = [NSData dataWithBytes:_indices length:sizeof(_indices)];
|
||||
|
||||
SCNGeometrySource *source = [SCNGeometrySource geometrySourceWithVertices:vertices count:2];
|
||||
SCNGeometryElement *element = [SCNGeometryElement
|
||||
geometryElementWithData:indices
|
||||
primitiveType:SCNGeometryPrimitiveTypeLine
|
||||
primitiveCount:2
|
||||
bytesPerIndex:sizeof(int32_t)
|
||||
];
|
||||
|
||||
SCNGeometry *geometry = [SCNGeometry geometryWithSources:@[source] elements:@[element]];
|
||||
[geometry addDoubleSidedMaterialWithDiffuseContents:lineColor];
|
||||
return [SCNNode nodeWithGeometry:geometry];
|
||||
}
|
||||
|
||||
- (instancetype)borderWithColor:(UIColor *)color {
|
||||
struct { SCNVector3 min, max; } bb;
|
||||
[self getBoundingBoxMin:&bb.min max:&bb.max];
|
||||
|
||||
SCNVector3 topLeft = SCNVector3Make(bb.min.x, bb.max.y, kFHSSmallZOffset);
|
||||
SCNVector3 bottomLeft = SCNVector3Make(bb.min.x, bb.min.y, kFHSSmallZOffset);
|
||||
SCNVector3 topRight = SCNVector3Make(bb.max.x, bb.max.y, kFHSSmallZOffset);
|
||||
SCNVector3 bottomRight = SCNVector3Make(bb.max.x, bb.min.y, kFHSSmallZOffset);
|
||||
|
||||
SCNNode *top = [SCNNode lineFrom:topLeft to:topRight color:color];
|
||||
SCNNode *left = [SCNNode lineFrom:bottomLeft to:topLeft color:color];
|
||||
SCNNode *bottom = [SCNNode lineFrom:bottomLeft to:bottomRight color:color];
|
||||
SCNNode *right = [SCNNode lineFrom:bottomRight to:topRight color:color];
|
||||
|
||||
SCNNode *border = [SCNNode new];
|
||||
[border addChildNode:top];
|
||||
[border addChildNode:left];
|
||||
[border addChildNode:bottom];
|
||||
[border addChildNode:right];
|
||||
|
||||
return border;
|
||||
}
|
||||
|
||||
+ (instancetype)header:(FHSViewSnapshot *)view {
|
||||
SCNText *text = [SCNText labelGeometry:view.title font:[UIFont boldSystemFontOfSize:13.0]];
|
||||
SCNNode *textNode = [SCNNode nodeWithGeometry:text];
|
||||
|
||||
struct { SCNVector3 min, max; } bb;
|
||||
[textNode getBoundingBoxMin:&bb.min max:&bb.max];
|
||||
CGFloat textWidth = bb.max.x - bb.min.x;
|
||||
CGFloat textHeight = bb.max.y - bb.min.y;
|
||||
|
||||
CGFloat snapshotWidth = view.frame.size.width;
|
||||
CGFloat headerWidth = MAX(snapshotWidth, textWidth);
|
||||
CGRect frame = CGRectMake(0, 0, headerWidth, textHeight + (kHeaderVerticalInset * 2));
|
||||
SCNNode *headerNode = [SCNNode nodeWithGeometry:[SCNShape
|
||||
nameHeader:view.headerColor frame:frame corners:8
|
||||
]];
|
||||
[headerNode addChildNode:textNode];
|
||||
|
||||
textNode.position = SCNVector3Make(
|
||||
(frame.size.width / 2.f) - (textWidth / 2.f),
|
||||
(frame.size.height / 2.f) - (textHeight / 2.f),
|
||||
kFHSSmallZOffset
|
||||
);
|
||||
headerNode.position = SCNVector3Make(
|
||||
(snapshotWidth / 2.f) - (headerWidth / 2.f),
|
||||
view.frame.size.height,
|
||||
kFHSSmallZOffset
|
||||
);
|
||||
|
||||
return headerNode;
|
||||
}
|
||||
|
||||
+ (instancetype)snapshot:(FHSViewSnapshot *)view
|
||||
parent:(FHSViewSnapshot *)parent
|
||||
parentNode:(SCNNode *)parentNode
|
||||
root:(SCNNode *)rootNode
|
||||
depth:(NSInteger *)depthOut
|
||||
nodesMap:(NSMutableDictionary<NSString *, FHSSnapshotNodes *> *)nodesMap
|
||||
hideHeaders:(BOOL)hideHeaders {
|
||||
NSInteger const depth = *depthOut;
|
||||
|
||||
// Ignore elements that are not visible.
|
||||
// These should appear in the list, but not in the 3D view.
|
||||
if (view.hidden || CGSizeEqualToSize(view.frame.size, CGSizeZero)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Create a node whose contents are the snapshot of the element
|
||||
SCNNode *node = [self snapshot:view];
|
||||
node.name = view.view.identifier;
|
||||
|
||||
// Begin building node tree
|
||||
FHSSnapshotNodes *nodes = [FHSSnapshotNodes snapshot:view depth:depth];
|
||||
nodes.snapshot = node;
|
||||
|
||||
// The node must be added to the root node
|
||||
// for the coordinate space calculations below to work
|
||||
[rootNode addChildNode:node];
|
||||
node.position = ({
|
||||
// Flip the y-coordinate since SceneKit has a
|
||||
// flipped version of the UIKit coordinate system
|
||||
CGRect pframe = parent ? parent.frame : CGRectZero;
|
||||
CGFloat y = parent ? pframe.size.height - CGRectGetMaxY(view.frame) : 0;
|
||||
|
||||
// To simplify calculating the z-axis spacing between the layers, we make
|
||||
// each snapshot node a direct child of the root rather than embedding
|
||||
// the nodes in their parent nodes in the same structure as the UI elements
|
||||
// themselves. With this flattened hierarchy, the z-position can be
|
||||
// calculated for every node simply by multiplying the spacing by the depth.
|
||||
//
|
||||
// `parentSnapshotNode` as referenced here is NOT the actual parent node
|
||||
// of `node`, it is the node corresponding to the parent of the UI element.
|
||||
// It is used to convert from frame coordinates, which are relative to
|
||||
// the bounds of the parent, to coordinates relative to the root node.
|
||||
SCNVector3 positionRelativeToParent = SCNVector3Make(view.frame.origin.x, y, 0);
|
||||
SCNVector3 positionRelativeToRoot;
|
||||
if (parent) {
|
||||
positionRelativeToRoot = [rootNode convertPosition:positionRelativeToParent fromNode:parentNode];
|
||||
} else {
|
||||
positionRelativeToRoot = positionRelativeToParent;
|
||||
}
|
||||
positionRelativeToRoot.z = 50 * depth;
|
||||
positionRelativeToRoot;
|
||||
});
|
||||
|
||||
// Make border node
|
||||
nodes.border = [node borderWithColor:view.headerColor];
|
||||
[node addChildNode:nodes.border];
|
||||
|
||||
// Make header node
|
||||
nodes.header = [SCNNode header:view];
|
||||
[node addChildNode:nodes.header];
|
||||
if (hideHeaders) {
|
||||
nodes.header.hidden = YES;
|
||||
}
|
||||
|
||||
nodesMap[view.view.identifier] = nodes;
|
||||
|
||||
NSMutableArray<FHSViewSnapshot *> *checkForIntersect = [NSMutableArray new];
|
||||
NSInteger maxChildDepth = depth;
|
||||
|
||||
// Recurse to child nodes; overlapping children have higher depths
|
||||
for (FHSViewSnapshot *child in view.children) {
|
||||
NSInteger childDepth = depth + 1;
|
||||
|
||||
// Children that intersect a sibling are rendered
|
||||
// in a separate layer above the previous siblings
|
||||
for (FHSViewSnapshot *sibling in checkForIntersect) {
|
||||
if (CGRectIntersectsRect(sibling.frame, child.frame)) {
|
||||
childDepth = maxChildDepth + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
id didMakeNode = [SCNNode
|
||||
snapshot:child
|
||||
parent:view
|
||||
parentNode:node
|
||||
root:rootNode
|
||||
depth:&childDepth
|
||||
nodesMap:nodesMap
|
||||
hideHeaders:hideHeaders
|
||||
];
|
||||
if (didMakeNode) {
|
||||
maxChildDepth = MAX(childDepth, maxChildDepth);
|
||||
[checkForIntersect addObject:child];
|
||||
}
|
||||
}
|
||||
|
||||
*depthOut = maxChildDepth;
|
||||
return node;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark SCNShape
|
||||
@implementation SCNShape (Snapshot)
|
||||
|
||||
+ (instancetype)shapeWithPath:(UIBezierPath *)path materialDiffuse:(id)contents {
|
||||
SCNShape *shape = [SCNShape shapeWithPath:path extrusionDepth:0];
|
||||
[shape addDoubleSidedMaterialWithDiffuseContents:contents];
|
||||
return shape;
|
||||
}
|
||||
|
||||
+ (instancetype)nameHeader:(UIColor *)color frame:(CGRect)frame corners:(CGFloat)radius {
|
||||
UIBezierPath *path = [UIBezierPath
|
||||
bezierPathWithRoundedRect:frame
|
||||
byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
|
||||
cornerRadii:CGSizeMake(radius, radius)
|
||||
];
|
||||
return [SCNShape shapeWithPath:path materialDiffuse:color];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark SCNText
|
||||
@implementation SCNText (Snapshot)
|
||||
|
||||
+ (instancetype)labelGeometry:(NSString *)text font:(UIFont *)font {
|
||||
NSParameterAssert(text);
|
||||
|
||||
SCNText *label = [self new];
|
||||
label.string = text;
|
||||
label.font = font;
|
||||
label.alignmentMode = kCAAlignmentCenter;
|
||||
label.truncationMode = kCATruncationEnd;
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
@end
|
Reference in New Issue
Block a user