Warp experiment

This commit is contained in:
Isaac 2024-07-25 13:59:21 +08:00
parent ddcd878d8a
commit 29917a97ac
11 changed files with 1320 additions and 112 deletions

View File

@ -93,7 +93,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case knockoutWallpaper(PresentationTheme, Bool)
case experimentalCompatibility(Bool)
case enableDebugDataDisplay(Bool)
case acceleratedStickers(Bool)
case rippleEffect(Bool)
case browserExperiment(Bool)
case localTranscription(Bool)
case enableReactionOverrides(Bool)
@ -127,7 +127,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2:
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2:
return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue
@ -216,7 +216,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 37
case .enableDebugDataDisplay:
return 38
case .acceleratedStickers:
case .rippleEffect:
return 39
case .browserExperiment:
return 40
@ -1228,12 +1228,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .acceleratedStickers(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Accelerated Stickers", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .rippleEffect(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Ripple", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.acceleratedStickers = value
settings.rippleEffect = value
return PreferencesEntry(settings)
})
}).start()
@ -1452,7 +1452,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility))
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
entries.append(.rippleEffect(experimentalSettings.rippleEffect))
#if DEBUG
entries.append(.browserExperiment(experimentalSettings.browserExperiment))
#else

View File

@ -402,7 +402,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.animationNode = animationNode
}
} else {
let animationNode = DefaultAnimatedStickerNodeImpl(useMetalCache: item.context.sharedContext.immediateExperimentalUISettings.acceleratedStickers)
let animationNode = DefaultAnimatedStickerNodeImpl(useMetalCache: false)
animationNode.started = { [weak self] in
if let strongSelf = self {
strongSelf.imageNode.alpha = 0.0

View File

@ -13,6 +13,7 @@ swift_library(
"//submodules/Display",
"//submodules/AsyncDisplayKit",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/SpaceWarpView/STCMeshView",
],
visibility = [
"//visibility:public",

View File

@ -0,0 +1,23 @@
objc_library(
name = "STCMeshView",
enable_modules = True,
module_name = "STCMeshView",
srcs = glob([
"Sources/**/*.m",
"Sources/**/*.h",
]),
hdrs = glob([
"PublicHeaders/**/*.h",
]),
includes = [
"PublicHeaders",
],
sdk_frameworks = [
"Foundation",
"UIKit",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,58 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <QuartzCore/QuartzCore.h>
/* A mesh layer allows individually transforming areas inside its subtree. */
@interface STCMeshLayer : CAReplicatorLayer
/* An array of bounds regions to use for each instance. The length
* of this array is assumed to match `instanceCount'. Required. */
@property (atomic, assign) CGRect *instanceBounds;
/* An array of positions to use for each instance. The length
* of this array is assumed to match `instanceCount'. Required. */
@property (atomic, assign) CGPoint *instancePositions;
/* An array of anchor points to use for each instance. The length
* of this array is assumed to match `instanceCount'. Required. */
@property (atomic, assign) CGPoint *instanceAnchorPoints;
/* An array of transforms to apply to each instance. The length
* of this array is assumed to match `instanceCount'. Required. */
@property (atomic, assign) CATransform3D *instanceTransforms;
/* Add content to this layer to transform it in the mesh. */
@property (atomic, strong) CALayer *contentLayer;
/* This CAReplicatorLayer property is used internally and is not
* available for use by clients. Do not set it. */
@property (atomic, assign) CFTimeInterval instanceDelay NS_UNAVAILABLE;
/* This CAReplicatorLayer property is used internally and is not
* available for use by clients. Do not set it. */
@property (atomic, assign) CATransform3D instanceTransform NS_UNAVAILABLE;
@end
@interface STCMeshLayer (UIViewSupport)
/* The wrapper replicator layer used to preserve a linear timespace. */
@property (atomic, strong) CAReplicatorLayer *wrapperLayer;
@end

View File

@ -0,0 +1,26 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <STCMeshView/STCMeshLayer.h>
// shows different part of its subviews with different transforms
@interface STCMeshView : UIView
@property (nonatomic, retain, readonly) STCMeshLayer *layer;
@property (nonatomic, retain, readwrite) UIView *contentView; // only subviews added to this are transformed
@property (nonatomic, assign, readwrite) NSInteger instanceCount; // defaults to 1
@property (nonatomic, assign, readwrite) CATransform3D *instanceTransforms; // optional
@property (nonatomic, assign, readwrite) CGRect *instanceBounds; // optional
@property (nonatomic, assign, readwrite) CGPoint *instancePositions; // optional
@property (nonatomic, assign, readwrite) CGPoint *instanceAnchorPoints; // optional
@end

View File

@ -0,0 +1,337 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <STCMeshView/STCMeshLayer.h>
static const CFTimeInterval STCMeshLayerTotalInstanceDelay = 10000000.0;
static NSString *const STCMeshLayerBoundsAnimationKey = @"STCMeshLayerBoundsAnimation";
static NSString *const STCMeshLayerTransformAnimationKey = @"STCMeshLayerTransformAnimation";
static NSString *const STCMeshLayerPositionAnimationKey = @"STCMeshLayerPositionAnimation";
static NSString *const STCMeshLayerAnchorPointAnimationKey = @"STCMeshLayerAnchorPointAnimation";
static NSString *const STCMeshLayerInstanceDelayAnimationKey = @"STCMeshLayerInstanceDelayAnimation";
@implementation STCMeshLayer {
CAReplicatorLayer *_wrapperLayer;
CALayer *_contentLayer;
CGRect *_instanceBounds;
CATransform3D *_instanceTransforms;
CGPoint *_instancePositions;
CGPoint *_instanceAnchorPoints;
}
#pragma mark - Lifecycle
- (instancetype)init
{
if ((self = [super init])) {
self.wrapperLayer = [[CAReplicatorLayer alloc] init];
self.contentLayer = [[CALayer alloc] init];
}
return self;
}
- (void)dealloc
{
free(_instanceTransforms);
_instanceTransforms = NULL;
free(_instanceBounds);
_instanceBounds = NULL;
}
- (void)layoutSublayers
{
[super layoutSublayers];
_wrapperLayer.frame = self.bounds;
_contentLayer.frame = _wrapperLayer.bounds;
[self _updateMeshAnimations];
}
#pragma mark - Properties
@dynamic instanceDelay;
@dynamic instanceTransform;
- (void)setInstanceCount:(NSInteger)instanceCount
{
if (instanceCount != self.instanceCount) {
[super setInstanceCount:instanceCount];
free(_instanceTransforms);
_instanceTransforms = NULL;
free(_instanceBounds);
_instanceBounds = NULL;
[self setNeedsLayout];
}
}
- (CATransform3D *)instanceTransforms
{
CATransform3D *instanceTransforms = _instanceTransforms;
return instanceTransforms;
}
- (void)setInstanceTransforms:(CATransform3D *)instanceTransforms
{
free(_instanceTransforms);
_instanceTransforms = NULL;
if (instanceTransforms != NULL) {
_instanceTransforms = calloc(sizeof(CATransform3D), self.instanceCount);
memcpy(_instanceTransforms, instanceTransforms, self.instanceCount * sizeof(CATransform3D));
}
[self setNeedsLayout];
}
- (CGPoint *)instancePositions
{
CGPoint *instancePositions = _instancePositions;
return instancePositions;
}
- (void)setInstancePositions:(CGPoint *)instancePositions
{
free(_instancePositions);
_instancePositions = NULL;
if (instancePositions != NULL) {
_instancePositions = calloc(sizeof(CGPoint), self.instanceCount);
memcpy(_instancePositions, instancePositions, self.instanceCount * sizeof(CGPoint));
}
[self setNeedsLayout];
}
- (CGPoint *)instanceAnchorPoints
{
CGPoint *instanceAnchorPoints = _instanceAnchorPoints;
return instanceAnchorPoints;
}
- (void)setInstanceAnchorPoints:(CGPoint *)instanceAnchorPoints
{
free(_instanceAnchorPoints);
_instanceAnchorPoints = NULL;
if (instanceAnchorPoints != NULL) {
_instanceAnchorPoints = calloc(sizeof(CGPoint), self.instanceCount);
memcpy(_instanceAnchorPoints, instanceAnchorPoints, self.instanceCount * sizeof(CGPoint));
}
[self setNeedsLayout];
}
- (CGRect *)instanceBounds
{
CGRect *instanceBounds = _instanceBounds;
return instanceBounds;
}
- (void)setInstanceBounds:(CGRect *)instanceBounds
{
free(_instanceBounds);
_instanceBounds = NULL;
if (instanceBounds != NULL) {
_instanceBounds = calloc(sizeof(CGRect), self.instanceCount);
memcpy(_instanceBounds, instanceBounds, self.instanceCount * sizeof(CGRect));
}
[self setNeedsLayout];
}
- (CALayer *)contentLayer
{
CALayer *contentLayer = _contentLayer;
return contentLayer;
}
- (void)setContentLayer:(CALayer *)contentLayer
{
if (contentLayer != _contentLayer) {
if (_contentLayer != nil) {
[_contentLayer removeFromSuperlayer];
}
_contentLayer = contentLayer;
if (_contentLayer != nil) {
[_wrapperLayer addSublayer:_contentLayer];
}
}
}
- (CAReplicatorLayer *)wrapperLayer
{
CAReplicatorLayer *wrapperLayer = _wrapperLayer;
return wrapperLayer;
}
- (void)setWrapperLayer:(CAReplicatorLayer *)wrapperLayer
{
if (wrapperLayer != _wrapperLayer) {
if (_contentLayer != nil) {
[_contentLayer removeFromSuperlayer];
}
if (_wrapperLayer != nil) {
[_wrapperLayer removeFromSuperlayer];
}
_wrapperLayer = wrapperLayer;
if (_wrapperLayer != nil) {
_wrapperLayer.masksToBounds = YES;
_wrapperLayer.instanceCount = 2;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef hiddenColor = CGColorCreate(colorSpace, (CGFloat []){ 1.0, 1.0, 1.0, 0.0 });
_wrapperLayer.instanceColor = hiddenColor;
CGColorRelease(hiddenColor);
CGColorSpaceRelease(colorSpace);
_wrapperLayer.instanceAlphaOffset = 1.0;
[self addSublayer:_wrapperLayer];
}
if (_contentLayer != nil) {
[_wrapperLayer addSublayer:_contentLayer];
}
[self setNeedsLayout];
}
}
#pragma mark - Internal Methods
- (CGRect)_boundsAtIndex:(NSUInteger)index
{
CGRect bounds = CGRectZero;
if (_instanceBounds != NULL) {
bounds = _instanceBounds[index];
}
return bounds;
}
- (CATransform3D)_transformAtIndex:(NSUInteger)index
{
CATransform3D transform = CATransform3DIdentity;
if (_instanceTransforms != NULL) {
transform = _instanceTransforms[index];
}
return transform;
}
- (CGPoint)_positionAtIndex:(NSUInteger)index
{
CGPoint position = CGPointZero;
if (_instancePositions != NULL) {
position = _instancePositions[index];
}
return position;
}
- (CGPoint)_anchorPointAtIndex:(NSUInteger)index
{
CGPoint anchorPoint = CGPointMake(0.5, 0.5);
if (_instanceAnchorPoints != NULL) {
anchorPoint = _instanceAnchorPoints[index];
}
return anchorPoint;
}
- (void)_updateMeshAnimations
{
[_wrapperLayer removeAllAnimations];
super.instanceDelay = -STCMeshLayerTotalInstanceDelay / self.instanceCount;
CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"bounds"];
boundsAnimation.calculationMode = kCAAnimationDiscrete;
boundsAnimation.duration = STCMeshLayerTotalInstanceDelay;
boundsAnimation.removedOnCompletion = NO;
NSMutableArray *boundsValues = [NSMutableArray array];
for (NSUInteger i = 0; i < self.instanceCount; i++) {
CGRect bounds = [self _boundsAtIndex:i];
NSValue *boundsValue = [NSValue valueWithBytes:&bounds objCType:@encode(CGRect)];
[boundsValues addObject:boundsValue];
}
boundsAnimation.values = boundsValues;
[_wrapperLayer addAnimation:boundsAnimation forKey:STCMeshLayerBoundsAnimationKey];
CAKeyframeAnimation *transformAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
transformAnimation.calculationMode = kCAAnimationDiscrete;
transformAnimation.duration = STCMeshLayerTotalInstanceDelay;
transformAnimation.removedOnCompletion = NO;
NSMutableArray *transformValues = [NSMutableArray array];
for (NSUInteger i = 0; i < self.instanceCount; i++) {
CATransform3D transform = [self _transformAtIndex:i];
NSValue *transformValue = [NSValue valueWithCATransform3D:transform];
[transformValues addObject:transformValue];
}
transformAnimation.values = transformValues;
[_wrapperLayer addAnimation:transformAnimation forKey:STCMeshLayerTransformAnimationKey];
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnimation.calculationMode = kCAAnimationDiscrete;
positionAnimation.duration = STCMeshLayerTotalInstanceDelay;
positionAnimation.removedOnCompletion = NO;
NSMutableArray *positionValues = [NSMutableArray array];
for (NSUInteger i = 0; i < self.instanceCount; i++) {
CGPoint position = [self _positionAtIndex:i];
NSValue *positionValue = [NSValue valueWithBytes:&position objCType:@encode(CGPoint)];
[positionValues addObject:positionValue];
}
positionAnimation.values = positionValues;
[_wrapperLayer addAnimation:positionAnimation forKey:STCMeshLayerPositionAnimationKey];
CAKeyframeAnimation *anchorPointAnimation = [CAKeyframeAnimation animationWithKeyPath:@"anchorPoint"];
anchorPointAnimation.calculationMode = kCAAnimationDiscrete;
anchorPointAnimation.duration = STCMeshLayerTotalInstanceDelay;
anchorPointAnimation.removedOnCompletion = NO;
NSMutableArray *anchorPointValues = [NSMutableArray array];
for (NSUInteger i = 0; i < self.instanceCount; i++) {
CGPoint anchorPoint = [self _anchorPointAtIndex:i];
NSValue *anchorPointValue = [NSValue valueWithBytes:&anchorPoint objCType:@encode(CGPoint)];
[anchorPointValues addObject:anchorPointValue];
}
anchorPointAnimation.values = anchorPointValues;
[_wrapperLayer addAnimation:anchorPointAnimation forKey:STCMeshLayerAnchorPointAnimationKey];
CAKeyframeAnimation *timeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"instanceDelay"];
timeAnimation.calculationMode = kCAAnimationDiscrete;
timeAnimation.duration = STCMeshLayerTotalInstanceDelay;
timeAnimation.removedOnCompletion = NO;
NSMutableArray *timeValues = [NSMutableArray array];
for (NSUInteger i = 0; i < self.instanceCount; i++) {
CFTimeInterval delay = -super.instanceDelay * i;
[timeValues addObject:@(delay)];
}
timeAnimation.values = timeValues;
[_wrapperLayer addAnimation:timeAnimation forKey:STCMeshLayerInstanceDelayAnimationKey];
}
@end

View File

@ -0,0 +1,126 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <STCMeshView/STCMeshView.h>
#import <STCMeshView/STCMeshLayer.h>
@interface _STCMeshViewReplicatorView : UIView
@property (nonatomic, readonly, retain) CAReplicatorLayer *layer;
@end
@implementation _STCMeshViewReplicatorView
- (CAReplicatorLayer *)layer
{
return (CAReplicatorLayer *)[super layer];
}
+ (Class)layerClass
{
return [CAReplicatorLayer class];
}
@end
@implementation STCMeshView {
_STCMeshViewReplicatorView *_wrapperView;
}
- (STCMeshLayer *)layer
{
return (STCMeshLayer *)[super layer];
}
+ (Class)layerClass
{
return [STCMeshLayer class];
}
- (NSInteger)instanceCount
{
return self.layer.instanceCount;
}
- (void)setInstanceCount:(NSInteger)instanceCount
{
self.layer.instanceCount = instanceCount;
}
- (CATransform3D *)instanceTransforms
{
return self.layer.instanceTransforms;
}
- (void)setInstanceTransforms:(CATransform3D *)instanceTransforms
{
self.layer.instanceTransforms = instanceTransforms;
}
- (CGRect *)instanceBounds
{
return self.layer.instanceBounds;
}
- (void)setInstanceBounds:(CGRect *)instanceBounds
{
self.layer.instanceBounds = instanceBounds;
}
- (CGPoint *)instancePositions
{
return self.layer.instancePositions;
}
- (void)setInstancePositions:(CGPoint *)instancePositions
{
self.layer.instancePositions = instancePositions;
}
- (CGPoint *)instanceAnchorPoints
{
return self.layer.instanceAnchorPoints;
}
- (void)setInstanceAnchorPoints:(CGPoint *)instanceAnchorPoints
{
self.layer.instanceAnchorPoints = instanceAnchorPoints;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
_wrapperView = [[_STCMeshViewReplicatorView alloc] init];
[self addSubview:_wrapperView];
self.layer.wrapperLayer = _wrapperView.layer;
self.contentView = [[UIView alloc] init];
}
return self;
}
- (void)setContentView:(UIView *)contentView
{
if (contentView != _contentView) {
if (_contentView != nil) {
[_contentView removeFromSuperview];
}
if (contentView != nil) {
[_wrapperView addSubview:contentView];
}
_contentView = contentView;
self.layer.contentLayer = _contentView.layer;
}
}
@end

View File

@ -3,104 +3,57 @@ import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import STCMeshView
/*open class SpaceWarpView: UIView {
private final class WarpPartView: UIView {
let cloneView: PortalView
init?(contentView: PortalSourceView) {
guard let cloneView = PortalView(matchPosition: false) else {
return nil
}
self.cloneView = cloneView
super.init(frame: CGRect())
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0)
self.clipsToBounds = true
self.addSubview(cloneView.view)
contentView.addPortal(view: cloneView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(containerSize: CGSize, rect: CGRect, transition: ComponentTransition) {
transition.setFrame(view: self.cloneView.view, frame: CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: CGSize(width: containerSize.width, height: containerSize.height)))
}
}
private final class FPSView: UIView {
private var lastTimestamp: Double?
private var counter: Int = 0
private var fpsValue: Int?
private var fpsString: NSAttributedString?
public var contentView: UIView {
return self.contentViewImpl
}
let contentViewImpl: PortalSourceView
private var warpViews: [WarpPartView] = []
override public init(frame: CGRect) {
self.contentViewImpl = PortalSourceView()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.contentView)
self.contentView.alpha = 0.1
for _ in 0 ..< 8 {
if let warpView = WarpPartView(contentView: self.contentViewImpl) {
self.warpViews.append(warpView)
self.addSubview(warpView)
}
}
self.layer.anchorPoint = CGPoint()
self.backgroundColor = .black
}
required public init?(coder: NSCoder) {
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func update(size: CGSize, warpHeight: CGFloat, transition: ComponentTransition) {
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size))
let allItemsHeight = warpHeight * 0.5
for i in 0 ..< self.warpViews.count {
let itemHeight = warpHeight / CGFloat(self.warpViews.count)
let itemFraction = CGFloat(i + 1) / CGFloat(self.warpViews.count)
let _ = itemHeight
let da = CGFloat.pi * 0.5 / CGFloat(self.warpViews.count)
let alpha = CGFloat.pi * 0.5 - itemFraction * CGFloat.pi * 0.5
let endPoint = CGPoint(x: cos(alpha), y: sin(alpha))
let prevAngle = alpha + da
let prevPt = CGPoint(x: cos(prevAngle), y: sin(prevAngle))
var angle: CGFloat
angle = -atan2(endPoint.y - prevPt.y, endPoint.x - prevPt.x)
let itemLengthVector = CGPoint(x: endPoint.x - prevPt.x, y: endPoint.y - prevPt.y)
let itemLength = sqrt(itemLengthVector.x * itemLengthVector.x + itemLengthVector.y * itemLengthVector.y) * warpHeight * 0.5
let _ = itemLength
var transform: CATransform3D
transform = CATransform3DIdentity
transform.m34 = 1.0 / 240.0
transform = CATransform3DTranslate(transform, 0.0, prevPt.x * allItemsHeight, (1.0 - prevPt.y) * allItemsHeight)
transform = CATransform3DRotate(transform, angle, 1.0, 0.0, 0.0)
let positionY = size.height - allItemsHeight + 4.0 + CGFloat(i) * itemLength
let rect = CGRect(origin: CGPoint(x: 0.0, y: positionY), size: CGSize(width: size.width, height: itemLength))
transition.setPosition(view: self.warpViews[i], position: CGPoint(x: rect.midX, y: 4.0))
transition.setBounds(view: self.warpViews[i], bounds: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: itemLength)))
transition.setTransform(view: self.warpViews[i], transform: transform)
self.warpViews[i].update(containerSize: size, rect: rect, transition: transition)
func update() {
self.counter += 1
let timestamp = CACurrentMediaTime()
let deltaTime: Double
if let lastTimestamp = self.lastTimestamp {
deltaTime = timestamp - lastTimestamp
} else {
deltaTime = 1.0 / 60.0
self.lastTimestamp = timestamp
}
if deltaTime >= 1.0 {
let fpsValue = Int(Double(self.counter) / deltaTime)
if self.fpsValue != fpsValue {
self.fpsValue = fpsValue
let fpsString = NSAttributedString(string: "\(fpsValue)", attributes: [.foregroundColor: UIColor.white])
self.bounds = fpsString.boundingRect(with: CGSize(width: 100.0, height: 100.0), context: nil).integral
self.fpsString = fpsString
self.setNeedsDisplay()
}
self.counter = 0
self.lastTimestamp = timestamp
}
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return self.contentView.hitTest(point, with: event)
override func draw(_ rect: CGRect) {
guard let fpsString = self.fpsString else {
return
}
fpsString.draw(at: CGPoint())
}
}*/
}
private extension CGPoint {
static func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
@ -148,7 +101,7 @@ private func transformCoordinate(
// The distance of the current pixel position from `origin`.
let distance = length(position - origin)
if distance < 10.0 {
if distance < 2.0 {
return position
}
@ -172,7 +125,7 @@ private func transformCoordinate(
//
// This new position moves toward or away from `origin` based on the
// sign and magnitude of `rippleAmount`.
let newPosition = position + n * rippleAmount
let newPosition = position - n * rippleAmount
return newPosition
}
@ -222,12 +175,103 @@ private func rectToQuad(
i = kEpsilon * (i > 0 ? 1.0 : -1.0)
}
//CATransform3D transform = {a/i, d/i, 0, g/i, b/i, e/i, 0, h/i, 0, 0, 1, 0, c/i, f/i, 0, 1.0}
let transform = CATransform3D(m11: a/i, m12: d/i, m13: 0, m14: g/i, m21: b/i, m22: e/i, m23: 0, m24: h/i, m31: 0, m32: 0, m33: 1, m34: 0, m41: c/i, m42: f/i, m43: 0, m44: 1.0)
return transform
}
open class SpaceWarpView: UIView {
func transformToFitQuad(frame: CGRect, topLeft tl: CGPoint, topRight tr: CGPoint, bottomLeft bl: CGPoint, bottomRight br: CGPoint) -> CATransform3D {
/*let boundingBox = UIView.boundingBox(forQuadWithTR: tr, tl: tl, bl: bl, br: br)
self.layer.transform = CATransform3DIdentity // keeps current transform from interfering
self.frame = boundingBox*/
let frameTopLeft = frame.origin
let transform = rectToQuad2(
rect: CGRect(origin: CGPoint(), size: frame.size),
quadTL: CGPoint(x: tl.x - frameTopLeft.x, y: tl.y - frameTopLeft.y),
quadTR: CGPoint(x: tr.x - frameTopLeft.x, y: tr.y - frameTopLeft.y),
quadBL: CGPoint(x: bl.x - frameTopLeft.x, y: bl.y - frameTopLeft.y),
quadBR: CGPoint(x: br.x - frameTopLeft.x, y: br.y - frameTopLeft.y)
)
// To account for anchor point, we must translate, transform, translate
let anchorPoint = frame.center
let anchorOffset = CGPoint(x: anchorPoint.x - frame.origin.x, y: anchorPoint.y - frame.origin.y)
let transPos = CATransform3DMakeTranslation(anchorOffset.x, anchorOffset.y, 0)
let transNeg = CATransform3DMakeTranslation(-anchorOffset.x, -anchorOffset.y, 0)
let fullTransform = CATransform3DConcat(CATransform3DConcat(transPos, transform), transNeg)
return fullTransform
}
private func boundingBox(forQuadWithTR tr: CGPoint, tl: CGPoint, bl: CGPoint, br: CGPoint) -> CGRect {
var boundingBox = CGRect.zero
let xmin = min(min(min(tr.x, tl.x), bl.x), br.x)
let ymin = min(min(min(tr.y, tl.y), bl.y), br.y)
let xmax = max(max(max(tr.x, tl.x), bl.x), br.x)
let ymax = max(max(max(tr.y, tl.y), bl.y), br.y)
boundingBox.origin.x = xmin
boundingBox.origin.y = ymin
boundingBox.size.width = xmax - xmin
boundingBox.size.height = ymax - ymin
return boundingBox
}
func rectToQuad2(rect: CGRect, quadTL topLeft: CGPoint, quadTR topRight: CGPoint, quadBL bottomLeft: CGPoint, quadBR bottomRight: CGPoint) -> CATransform3D {
return rectToQuad(rect: rect, quadTLX: topLeft.x, quadTLY: topLeft.y, quadTRX: topRight.x, quadTRY: topRight.y, quadBLX: bottomLeft.x, quadBLY: bottomLeft.y, quadBRX: bottomRight.x, quadBRY: bottomRight.y)
}
private func rectToQuad(rect: CGRect, quadTLX x1a: CGFloat, quadTLY y1a: CGFloat, quadTRX x2a: CGFloat, quadTRY y2a: CGFloat, quadBLX x3a: CGFloat, quadBLY y3a: CGFloat, quadBRX x4a: CGFloat, quadBRY y4a: CGFloat) -> CATransform3D {
let X = rect.origin.x
let Y = rect.origin.y
let W = rect.size.width
let H = rect.size.height
let y21 = y2a - y1a
let y32 = y3a - y2a
let y43 = y4a - y3a
let y14 = y1a - y4a
let y31 = y3a - y1a
let y42 = y4a - y2a
let a = -H * (x2a * x3a * y14 + x2a * x4a * y31 - x1a * x4a * y32 + x1a * x3a * y42)
let b = W * (x2a * x3a * y14 + x3a * x4a * y21 + x1a * x4a * y32 + x1a * x2a * y43)
let c = H * X * (x2a * x3a * y14 + x2a * x4a * y31 - x1a * x4a * y32 + x1a * x3a * y42) - H * W * x1a * (x4a * y32 - x3a * y42 + x2a * y43) - W * Y * (x2a * x3a * y14 + x3a * x4a * y21 + x1a * x4a * y32 + x1a * x2a * y43)
let d = H * (-x4a * y21 * y3a + x2a * y1a * y43 - x1a * y2a * y43 - x3a * y1a * y4a + x3a * y2a * y4a)
let e = W * (x4a * y2a * y31 - x3a * y1a * y42 - x2a * y31 * y4a + x1a * y3a * y42)
let f = -(W * (x4a * (Y * y2a * y31 + H * y1a * y32) - x3a * (H + Y) * y1a * y42 + H * x2a * y1a * y43 + x2a * Y * (y1a - y3a) * y4a + x1a * Y * y3a * (-y2a + y4a)) - H * X * (x4a * y21 * y3a - x2a * y1a * y43 + x3a * (y1a - y2a) * y4a + x1a * y2a * (-y3a + y4a)))
let g = H * (x3a * y21 - x4a * y21 + (-x1a + x2a) * y43)
let h = W * (-x2a * y31 + x4a * y31 + (x1a - x3a) * y42)
var i = W * Y * (x2a * y31 - x4a * y31 - x1a * y42 + x3a * y42) + H * (X * (-(x3a * y21) + x4a * y21 + x1a * y43 - x2a * y43) + W * (-(x3a * y2a) + x4a * y2a + x2a * y3a - x4a * y3a - x2a * y4a + x3a * y4a))
let kEpsilon = 0.0001
if abs(i) < kEpsilon {
i = kEpsilon * (i > 0 ? 1.0 : -1.0)
}
let transform = CATransform3D(
m11: a / i, m12: d / i, m13: 0, m14: g / i,
m21: b / i, m22: e / i, m23: 0, m24: h / i,
m31: 0, m32: 0, m33: 1, m34: 0,
m41: c / i, m42: f / i, m43: 0, m44: 1.0
)
return transform
}
public protocol SpaceWarpView: UIView {
var contentView: UIView { get }
func trigger(at point: CGPoint)
func update(size: CGSize, transition: ComponentTransition)
}
open class SpaceWarpView1: UIView, SpaceWarpView {
private final class GridView: UIView {
let cloneView: PortalView
let gridPosition: CGPoint
@ -410,6 +454,598 @@ open class SpaceWarpView: UIView {
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.alpha.isZero || self.isHidden || !self.isUserInteractionEnabled {
return nil
}
for view in self.contentView.subviews.reversed() {
if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
return result
}
}
let result = super.hitTest(point, with: event)
if result != self {
return result
} else {
return nil
}
}
}
open class SpaceWarpView2: UIView, SpaceWarpView {
public var contentView: UIView {
return self.contentViewImpl
}
private let contentViewImpl: UIView
private var meshView: STCMeshView?
private var link: SharedDisplayLinkDriver.Link?
private var startPoint: CGPoint?
private var timeValue: CGFloat = 0.0
private var resolution: (x: Int, y: Int)?
private var size: CGSize?
override public init(frame: CGRect) {
self.contentViewImpl = UIView()
super.init(frame: frame)
self.addSubview(self.contentView)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func trigger(at point: CGPoint) {
self.startPoint = point
self.timeValue = 0.0
if self.link == nil {
self.link = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in
guard let self else {
return
}
self.timeValue += deltaTime * (1.0 / CGFloat(UIView.animationDurationFactor()))
if let size = self.size {
self.update(size: size, transition: .immediate)
}
})
}
}
private func updateGrid(resolutionX: Int, resolutionY: Int) {
if let resolution = self.resolution, resolution.x == resolutionX, resolution.y == resolutionY {
return
}
self.resolution = (resolutionX, resolutionY)
if let meshView = self.meshView {
self.meshView = nil
meshView.removeFromSuperview()
self.contentViewImpl.removeFromSuperview()
}
let meshView = STCMeshView(frame: CGRect())
self.meshView = meshView
self.addSubview(meshView)
meshView.instanceCount = resolutionX * resolutionY
meshView.contentView.addSubview(self.contentViewImpl)
/*for gridView in self.gridViews {
gridView.removeFromSuperview()
}
var gridViews: [GridView] = []
for y in 0 ..< resolutionY {
for x in 0 ..< resolutionX {
if let gridView = GridView(contentView: self.contentViewImpl, gridPosition: CGPoint(x: CGFloat(x) / CGFloat(resolutionX), y: CGFloat(y) / CGFloat(resolutionY))) {
gridView.isUserInteractionEnabled = false
gridViews.append(gridView)
self.addSubview(gridView)
}
}
}
self.gridViews = gridViews*/
}
public func update(size: CGSize, transition: ComponentTransition) {
self.size = size
if size.width <= 0.0 || size.height <= 0.0 {
return
}
self.updateGrid(resolutionX: max(2, Int(size.width / 100.0)), resolutionY: max(2, Int(size.height / 100.0)))
guard let resolution = self.resolution, let meshView = self.meshView else {
return
}
meshView.frame = CGRect(origin: CGPoint(), size: size)
//let pixelStep = CGPoint(x: CGFloat(resolution.x) * 0.33, y: CGFloat(resolution.y) * 0.33)
let pixelStep = CGPoint()
let itemSize = CGSize(width: size.width / CGFloat(resolution.x), height: size.height / CGFloat(resolution.y))
let params = RippleParams(amplitude: 22.0, frequency: 15.0, decay: 8.0, speed: 1400.0)
var instanceBounds: [CGRect] = []
var instancePositions: [CGPoint] = []
var instanceTransforms: [CATransform3D] = []
for y in 0 ..< resolution.y {
for x in 0 ..< resolution.x {
let gridPosition = CGPoint(x: CGFloat(x) / CGFloat(resolution.x), y: CGFloat(y) / CGFloat(resolution.y))
let sourceRect = CGRect(origin: CGPoint(x: gridPosition.x * (size.width + pixelStep.x), y: gridPosition.y * (size.height + pixelStep.y)), size: itemSize)
instanceBounds.append(sourceRect)
instancePositions.append(sourceRect.center)
//gridView.bounds = CGRect(origin: CGPoint(), size: sourceRect.size)
//gridView.update(containerSize: size, rect: sourceRect, transition: transition)
let initialTopLeft = CGPoint(x: sourceRect.minX, y: sourceRect.minY)
let initialTopRight = CGPoint(x: sourceRect.maxX, y: sourceRect.minY)
let initialBottomLeft = CGPoint(x: sourceRect.minX, y: sourceRect.maxY)
let initialBottomRight = CGPoint(x: sourceRect.maxX, y: sourceRect.maxY)
var topLeft = initialTopLeft
var topRight = initialTopRight
var bottomLeft = initialBottomLeft
var bottomRight = initialBottomRight
if let startPoint = self.startPoint {
topLeft = transformCoordinate(position: topLeft, origin: startPoint, time: self.timeValue, params: params)
topRight = transformCoordinate(position: topRight, origin: startPoint, time: self.timeValue, params: params)
bottomLeft = transformCoordinate(position: bottomLeft, origin: startPoint, time: self.timeValue, params: params)
bottomRight = transformCoordinate(position: bottomRight, origin: startPoint, time: self.timeValue, params: params)
}
let distanceTopLeft = length(topLeft - initialTopLeft)
let distanceTopRight = length(topRight - initialTopRight)
let distanceBottomLeft = length(bottomLeft - initialBottomLeft)
let distanceBottomRight = length(bottomRight - initialBottomRight)
var maxDistance = max(distanceTopLeft, distanceTopRight)
maxDistance = max(maxDistance, distanceBottomLeft)
maxDistance = max(maxDistance, distanceBottomRight)
let transform = rectToQuad(rect: CGRect(origin: CGPoint(), size: itemSize), quadTL: topLeft - initialTopLeft, quadTR: topRight - initialTopLeft, quadBL: bottomLeft - initialTopLeft, quadBR: bottomRight - initialTopLeft)
instanceTransforms.append(transform)
let isActive: Bool
if maxDistance <= 0.5 {
//gridView.layer.transform = CATransform3DIdentity
isActive = false
} else {
let _ = transform
//gridView.layer.transform = transform
isActive = true
}
let _ = isActive
}
}
instanceBounds.withUnsafeMutableBufferPointer { buffer in
meshView.instanceBounds = buffer.baseAddress!
}
instancePositions.withUnsafeMutableBufferPointer { buffer in
meshView.instancePositions = buffer.baseAddress!
}
instanceTransforms.withUnsafeMutableBufferPointer { buffer in
meshView.instanceTransforms = buffer.baseAddress!
}
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.alpha.isZero || self.isHidden || !self.isUserInteractionEnabled {
return nil
}
for view in self.contentView.subviews.reversed() {
if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
return result
}
}
let result = super.hitTest(point, with: event)
if result != self {
return result
} else {
return nil
}
}
}
open class SpaceWarpView3: UIView, SpaceWarpView {
private final class GridView: UIView {
let cloneView: PortalView
let gridPosition: CGPoint
init?(contentView: PortalSourceView, gridPosition: CGPoint) {
self.gridPosition = gridPosition
guard let cloneView = PortalView(matchPosition: false) else {
return nil
}
self.cloneView = cloneView
super.init(frame: CGRect())
self.layer.anchorPoint = CGPoint(x: 0.0, y: 0.0)
self.clipsToBounds = true
self.isUserInteractionEnabled = false
self.addSubview(cloneView.view)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateIsActive(contentView: PortalSourceView, isActive: Bool) {
if isActive {
contentView.addPortal(view: self.cloneView)
} else {
contentView.removePortal(view: self.cloneView)
}
}
func update(containerSize: CGSize, rect: CGRect, transition: ComponentTransition) {
transition.setFrame(view: self.cloneView.view, frame: CGRect(origin: CGPoint(x: -rect.minX - containerSize.width * 0.5, y: -rect.minY - containerSize.height * 0.5), size: CGSize(width: containerSize.width, height: containerSize.height)))
}
}
private var gridViews: [GridView] = []
public var contentView: UIView {
return self.contentViewSource
}
private let contentViewSource: UIView
private var currentCloneView: UIView?
private let contentViewImpl: PortalSourceView
private var link: SharedDisplayLinkDriver.Link?
private var startPoint: CGPoint?
private var timeValue: CGFloat = 0.0
private var currentActiveViews: Int = 0
private var resolution: (x: Int, y: Int)?
private var size: CGSize?
override public init(frame: CGRect) {
self.contentViewSource = UIView()
self.contentViewImpl = PortalSourceView()
super.init(frame: frame)
self.addSubview(self.contentViewSource)
self.addSubview(self.contentViewImpl)
if self.link == nil {
self.link = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in
guard let self else {
return
}
self.timeValue += deltaTime * (1.0 / CGFloat(UIView.animationDurationFactor()))
if let size = self.size {
self.update(size: size, transition: .immediate)
}
})
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func trigger(at point: CGPoint) {
self.startPoint = point
self.timeValue = 0.0
}
private func updateGrid(resolutionX: Int, resolutionY: Int) {
if let resolution = self.resolution, resolution.x == resolutionX, resolution.y == resolutionY {
return
}
self.resolution = (resolutionX, resolutionY)
for gridView in self.gridViews {
gridView.removeFromSuperview()
}
var gridViews: [GridView] = []
for y in 0 ..< resolutionY {
for x in 0 ..< resolutionX {
if let gridView = GridView(contentView: self.contentViewImpl, gridPosition: CGPoint(x: CGFloat(x) / CGFloat(resolutionX), y: CGFloat(y) / CGFloat(resolutionY))) {
gridView.isUserInteractionEnabled = false
gridView.isHidden = true
gridViews.append(gridView)
self.addSubview(gridView)
}
}
}
self.gridViews = gridViews
}
public func update(size: CGSize, transition: ComponentTransition) {
if let currentCloneView = self.currentCloneView {
currentCloneView.removeFromSuperview()
self.currentCloneView = nil
}
if let cloneView = self.contentViewSource.resizableSnapshotView(from: CGRect(origin: CGPoint(), size: size), afterScreenUpdates: false, withCapInsets: UIEdgeInsets()) {
self.currentCloneView = cloneView
self.contentViewImpl.addSubview(cloneView)
}
self.size = size
if size.width <= 0.0 || size.height <= 0.0 {
return
}
self.updateGrid(resolutionX: max(2, Int(size.width / 50.0)), resolutionY: max(2, Int(size.height / 50.0)))
guard let resolution = self.resolution else {
return
}
if self.timeValue >= 3.0 {
return
}
let pixelStep = CGPoint()
let itemSize = CGSize(width: size.width / CGFloat(resolution.x), height: size.height / CGFloat(resolution.y))
let params = RippleParams(amplitude: 22.0, frequency: 15.0, decay: 8.0, speed: 1400.0)
var activeViews = 0
for gridView in self.gridViews {
let sourceRect = CGRect(origin: CGPoint(x: gridView.gridPosition.x * (size.width + pixelStep.x), y: gridView.gridPosition.y * (size.height + pixelStep.y)), size: itemSize)
gridView.bounds = CGRect(origin: CGPoint(), size: sourceRect.size)
gridView.update(containerSize: size, rect: sourceRect, transition: transition)
let initialTopLeft = CGPoint(x: sourceRect.minX, y: sourceRect.minY)
let initialTopRight = CGPoint(x: sourceRect.maxX, y: sourceRect.minY)
let initialBottomLeft = CGPoint(x: sourceRect.minX, y: sourceRect.maxY)
let initialBottomRight = CGPoint(x: sourceRect.maxX, y: sourceRect.maxY)
var topLeft = initialTopLeft
var topRight = initialTopRight
var bottomLeft = initialBottomLeft
var bottomRight = initialBottomRight
if let startPoint = self.startPoint {
topLeft = transformCoordinate(position: topLeft, origin: startPoint, time: self.timeValue, params: params)
topRight = transformCoordinate(position: topRight, origin: startPoint, time: self.timeValue, params: params)
bottomLeft = transformCoordinate(position: bottomLeft, origin: startPoint, time: self.timeValue, params: params)
bottomRight = transformCoordinate(position: bottomRight, origin: startPoint, time: self.timeValue, params: params)
}
let distanceTopLeft = length(topLeft - initialTopLeft)
let distanceTopRight = length(topRight - initialTopRight)
let distanceBottomLeft = length(bottomLeft - initialBottomLeft)
let distanceBottomRight = length(bottomRight - initialBottomRight)
var maxDistance = max(distanceTopLeft, distanceTopRight)
maxDistance = max(maxDistance, distanceBottomLeft)
maxDistance = max(maxDistance, distanceBottomRight)
let isActive: Bool
if maxDistance <= 0.5 {
gridView.layer.transform = CATransform3DIdentity
isActive = true
activeViews += 1
} else {
let transform = rectToQuad(rect: CGRect(origin: CGPoint(), size: itemSize), quadTL: topLeft, quadTR: topRight, quadBL: bottomLeft, quadBR: bottomRight)
gridView.layer.transform = transform
isActive = true
activeViews += 1
}
if gridView.isHidden != !isActive {
gridView.isHidden = !isActive
gridView.updateIsActive(contentView: self.contentViewImpl, isActive: isActive)
}
}
if self.currentActiveViews != activeViews {
self.currentActiveViews = activeViews
#if DEBUG
print("SpaceWarpView: activeViews = \(activeViews)")
#endif
}
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.alpha.isZero || self.isHidden || !self.isUserInteractionEnabled {
return nil
}
for view in self.contentView.subviews.reversed() {
if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
return result
}
}
let result = super.hitTest(point, with: event)
if result != self {
return result
} else {
return nil
}
}
}
open class SpaceWarpView4: UIView, SpaceWarpView {
public var contentView: UIView {
return self.contentViewSource
}
private let contentViewSource: UIView
private var currentCloneView: UIView?
private var meshView: STCMeshView?
private let fpsView: FPSView
private var link: SharedDisplayLinkDriver.Link?
private var startPoint: CGPoint?
private var timeValue: CGFloat = 0.0
private var resolution: (x: Int, y: Int)?
private var size: CGSize?
override public init(frame: CGRect) {
self.contentViewSource = UIView()
self.fpsView = FPSView(frame: CGRect(origin: CGPoint(x: 4.0, y: 40.0), size: CGSize()))
super.init(frame: frame)
self.addSubview(self.contentViewSource)
self.addSubview(self.fpsView)
if self.link == nil {
self.link = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in
guard let self else {
return
}
self.timeValue += deltaTime * (1.0 / CGFloat(UIView.animationDurationFactor()))
if let size = self.size {
self.update(size: size, transition: .immediate)
}
})
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func trigger(at point: CGPoint) {
self.startPoint = point
self.timeValue = 0.0
}
private func updateGrid(resolutionX: Int, resolutionY: Int) {
if let resolution = self.resolution, resolution.x == resolutionX, resolution.y == resolutionY {
return
}
self.resolution = (resolutionX, resolutionY)
if let meshView = self.meshView {
self.meshView = nil
meshView.removeFromSuperview()
}
let meshView = STCMeshView(frame: CGRect())
self.meshView = meshView
self.insertSubview(meshView, aboveSubview: self.contentViewSource)
meshView.instanceCount = resolutionX * resolutionY
}
public func update(size: CGSize, transition: ComponentTransition) {
self.size = size
if size.width <= 0.0 || size.height <= 0.0 {
return
}
self.fpsView.update()
self.updateGrid(resolutionX: max(2, Int(size.width / 40.0)), resolutionY: max(2, Int(size.height / 40.0)))
guard let resolution = self.resolution, let meshView = self.meshView else {
return
}
if let currentCloneView = self.currentCloneView {
currentCloneView.removeFromSuperview()
self.currentCloneView = nil
}
if let cloneView = self.contentViewSource.resizableSnapshotView(from: CGRect(origin: CGPoint(), size: size), afterScreenUpdates: false, withCapInsets: UIEdgeInsets()) {
self.currentCloneView = cloneView
meshView.contentView.addSubview(cloneView)
}
meshView.frame = CGRect(origin: CGPoint(), size: size)
let pixelStep = CGPoint()
let itemSize = CGSize(width: size.width / CGFloat(resolution.x), height: size.height / CGFloat(resolution.y))
let params = RippleParams(amplitude: 26.0, frequency: 15.0, decay: 8.0, speed: 1400.0)
var instanceBounds: [CGRect] = []
var instancePositions: [CGPoint] = []
var instanceTransforms: [CATransform3D] = []
for y in 0 ..< resolution.y {
for x in 0 ..< resolution.x {
let gridPosition = CGPoint(x: CGFloat(x) / CGFloat(resolution.x), y: CGFloat(y) / CGFloat(resolution.y))
let sourceRect = CGRect(origin: CGPoint(x: gridPosition.x * (size.width + pixelStep.x), y: gridPosition.y * (size.height + pixelStep.y)), size: itemSize)
instanceBounds.append(sourceRect)
instancePositions.append(sourceRect.center)
let initialTopLeft = CGPoint(x: sourceRect.minX, y: sourceRect.minY)
let initialTopRight = CGPoint(x: sourceRect.maxX, y: sourceRect.minY)
let initialBottomLeft = CGPoint(x: sourceRect.minX, y: sourceRect.maxY)
let initialBottomRight = CGPoint(x: sourceRect.maxX, y: sourceRect.maxY)
var topLeft = initialTopLeft
var topRight = initialTopRight
var bottomLeft = initialBottomLeft
var bottomRight = initialBottomRight
if let startPoint = self.startPoint {
topLeft = transformCoordinate(position: topLeft, origin: startPoint, time: self.timeValue, params: params)
topRight = transformCoordinate(position: topRight, origin: startPoint, time: self.timeValue, params: params)
bottomLeft = transformCoordinate(position: bottomLeft, origin: startPoint, time: self.timeValue, params: params)
bottomRight = transformCoordinate(position: bottomRight, origin: startPoint, time: self.timeValue, params: params)
}
let distanceTopLeft = length(topLeft - initialTopLeft)
let distanceTopRight = length(topRight - initialTopRight)
let distanceBottomLeft = length(bottomLeft - initialBottomLeft)
let distanceBottomRight = length(bottomRight - initialBottomRight)
var maxDistance = max(distanceTopLeft, distanceTopRight)
maxDistance = max(maxDistance, distanceBottomLeft)
maxDistance = max(maxDistance, distanceBottomRight)
let transform = transformToFitQuad(frame: sourceRect, topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight)
instanceTransforms.append(transform)
let isActive: Bool
if maxDistance <= 0.5 {
//gridView.layer.transform = CATransform3DIdentity
isActive = false
} else {
let _ = transform
//gridView.layer.transform = transform
isActive = true
}
let _ = isActive
}
}
instanceBounds.withUnsafeMutableBufferPointer { buffer in
meshView.instanceBounds = buffer.baseAddress!
}
instancePositions.withUnsafeMutableBufferPointer { buffer in
meshView.instancePositions = buffer.baseAddress!
}
instanceTransforms.withUnsafeMutableBufferPointer { buffer in
meshView.instanceTransforms = buffer.baseAddress!
}
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.alpha.isZero || self.isHidden || !self.isUserInteractionEnabled {
return nil

View File

@ -98,16 +98,17 @@ class ChatNodeContainer: ASDisplayNode {
}
}
override init() {
init(rippleEffect: Bool) {
self.contentNodeImpl = ASDisplayNode()
super.init()
#if DEBUG && false
self.setViewBlock({
return SpaceWarpView(frame: CGRect())
})
#endif
if rippleEffect {
self.setViewBlock({
return SpaceWarpView4(frame: CGRect())
})
self.contentNodeImpl.layer.allowsGroupOpacity = true
}
(self.view as? SpaceWarpView)?.contentView.addSubnode(self.contentNodeImpl)
}
@ -154,7 +155,7 @@ class HistoryNodeContainer: ASDisplayNode {
#if DEBUG && false
self.setViewBlock({
return SpaceWarpView(frame: CGRect())
return SpaceWarpView1(frame: CGRect())
})
#endif
@ -444,7 +445,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.backgroundNode = backgroundNode
self.contentContainerNode = ChatNodeContainer()
self.contentContainerNode = ChatNodeContainer(rippleEffect: context.sharedContext.immediateExperimentalUISettings.rippleEffect)
self.contentDimNode = ASDisplayNode()
self.contentDimNode.isUserInteractionEnabled = false
self.contentDimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.2)

View File

@ -38,7 +38,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var enableVoipTcp: Bool
public var experimentalCompatibility: Bool
public var enableDebugDataDisplay: Bool
public var acceleratedStickers: Bool
public var rippleEffect: Bool
public var inlineStickers: Bool
public var localTranscription: Bool
public var enableReactionOverrides: Bool
@ -74,7 +74,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
enableVoipTcp: false,
experimentalCompatibility: false,
enableDebugDataDisplay: false,
acceleratedStickers: false,
rippleEffect: false,
inlineStickers: false,
localTranscription: false,
enableReactionOverrides: false,
@ -111,7 +111,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
enableVoipTcp: Bool,
experimentalCompatibility: Bool,
enableDebugDataDisplay: Bool,
acceleratedStickers: Bool,
rippleEffect: Bool,
inlineStickers: Bool,
localTranscription: Bool,
enableReactionOverrides: Bool,
@ -145,7 +145,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.enableVoipTcp = enableVoipTcp
self.experimentalCompatibility = experimentalCompatibility
self.enableDebugDataDisplay = enableDebugDataDisplay
self.acceleratedStickers = acceleratedStickers
self.rippleEffect = rippleEffect
self.inlineStickers = inlineStickers
self.localTranscription = localTranscription
self.enableReactionOverrides = enableReactionOverrides
@ -183,7 +183,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.enableVoipTcp = (try container.decodeIfPresent(Int32.self, forKey: "enableVoipTcp") ?? 0) != 0
self.experimentalCompatibility = (try container.decodeIfPresent(Int32.self, forKey: "experimentalCompatibility") ?? 0) != 0
self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0
self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0
self.rippleEffect = (try container.decodeIfPresent(Int32.self, forKey: "rippleEffect") ?? 0) != 0
self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0
self.localTranscription = (try container.decodeIfPresent(Int32.self, forKey: "localTranscription") ?? 0) != 0
self.enableReactionOverrides = try container.decodeIfPresent(Bool.self, forKey: "enableReactionOverrides") ?? false
@ -221,7 +221,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encode((self.enableVoipTcp ? 1 : 0) as Int32, forKey: "enableVoipTcp")
try container.encode((self.experimentalCompatibility ? 1 : 0) as Int32, forKey: "experimentalCompatibility")
try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay")
try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers")
try container.encode((self.rippleEffect ? 1 : 0) as Int32, forKey: "rippleEffect")
try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers")
try container.encode((self.localTranscription ? 1 : 0) as Int32, forKey: "localTranscription")
try container.encode(self.enableReactionOverrides, forKey: "enableReactionOverrides")