mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
357 lines
13 KiB
Objective-C
357 lines
13 KiB
Objective-C
#import "TGPhotoEditorRadialBlurView.h"
|
|
|
|
#import "LegacyComponentsInternal.h"
|
|
|
|
#import <LegacyComponents/TGPhotoEditorUtils.h>
|
|
|
|
const CGFloat TGRadialBlurInsetProximity = 20;
|
|
const CGFloat TGRadialBlurMinimumFalloff = 0.1f;
|
|
const CGFloat TGRadialBlurMinimumDifference = 0.02f;
|
|
const CGFloat TGRadialBlurViewCenterInset = 30.0f;
|
|
const CGFloat TGRadialBlurViewRadiusInset = 30.0f;
|
|
|
|
typedef enum {
|
|
TGRadialBlurViewActiveControlNone,
|
|
TGRadialBlurViewActiveControlCenter,
|
|
TGRadialBlurViewActiveControlInnerRadius,
|
|
TGRadialBlurViewActiveControlOuterRadius,
|
|
TGRadialBlurViewActiveControlWholeArea
|
|
} TGRadialBlurViewActiveControl;
|
|
|
|
@interface TGPhotoEditorRadialBlurView () <UIGestureRecognizerDelegate>
|
|
{
|
|
UILongPressGestureRecognizer *_pressGestureRecognizer;
|
|
UIPanGestureRecognizer *_panGestureRecognizer;
|
|
UIPinchGestureRecognizer *_pinchGestureRecognizer;
|
|
|
|
TGRadialBlurViewActiveControl _activeControl;
|
|
CGPoint _startCenterPoint;
|
|
CGFloat _startDistance;
|
|
CGFloat _startRadius;
|
|
}
|
|
@end
|
|
|
|
@implementation TGPhotoEditorRadialBlurView
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame
|
|
{
|
|
self = [super initWithFrame:frame];
|
|
if (self != nil)
|
|
{
|
|
self.backgroundColor = [UIColor clearColor];
|
|
self.contentMode = UIViewContentModeRedraw;
|
|
|
|
self.centerPoint = CGPointMake(0.5f, 0.5f);
|
|
self.falloff = 0.15f;
|
|
self.size = 0.35f;
|
|
|
|
_pressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePress:)];
|
|
_pressGestureRecognizer.delegate = self;
|
|
_pressGestureRecognizer.minimumPressDuration = 0.1f;
|
|
[self addGestureRecognizer:_pressGestureRecognizer];
|
|
|
|
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
|
|
_panGestureRecognizer.delegate = self;
|
|
[self addGestureRecognizer:_panGestureRecognizer];
|
|
|
|
_pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
|
|
_pinchGestureRecognizer.delegate = self;
|
|
[self addGestureRecognizer:_pinchGestureRecognizer];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)handlePress:(UILongPressGestureRecognizer *)gestureRecognizer
|
|
{
|
|
switch (gestureRecognizer.state)
|
|
{
|
|
case UIGestureRecognizerStateBegan:
|
|
[self setSelected:true animated:true];
|
|
break;
|
|
|
|
case UIGestureRecognizerStateEnded:
|
|
case UIGestureRecognizerStateCancelled:
|
|
case UIGestureRecognizerStateFailed:
|
|
[self setSelected:false animated:true];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer
|
|
{
|
|
CGPoint location = [gestureRecognizer locationInView:self];
|
|
CGPoint centerPoint = [self _actualCenterPoint];
|
|
CGPoint delta = CGPointMake(location.x - centerPoint.x, location.y - centerPoint.y);
|
|
CGFloat distance = CGSqrt(delta.x * delta.x + delta.y * delta.y);
|
|
|
|
CGFloat shorterSide = (self.actualAreaSize.width > self.actualAreaSize.height) ? self.actualAreaSize.height : self.actualAreaSize.width;
|
|
|
|
CGFloat innerRadius = shorterSide * self.falloff;
|
|
CGFloat outerRadius = shorterSide * self.size;
|
|
|
|
switch (gestureRecognizer.state)
|
|
{
|
|
case UIGestureRecognizerStateBegan:
|
|
{
|
|
bool close = ABS(outerRadius - innerRadius) < TGRadialBlurInsetProximity;
|
|
CGFloat innerRadiusOuterInset = close ? 0 : TGRadialBlurViewRadiusInset;
|
|
CGFloat outerRadiusInnerInset = close ? 0 : TGRadialBlurViewRadiusInset;
|
|
|
|
if (distance < TGRadialBlurViewCenterInset)
|
|
{
|
|
_activeControl = TGRadialBlurViewActiveControlCenter;
|
|
_startCenterPoint = centerPoint;
|
|
}
|
|
else if (distance > innerRadius - TGRadialBlurViewRadiusInset && distance < innerRadius + innerRadiusOuterInset)
|
|
{
|
|
_activeControl = TGRadialBlurViewActiveControlInnerRadius;
|
|
_startDistance = distance;
|
|
_startRadius = innerRadius;
|
|
}
|
|
else if (distance > outerRadius - outerRadiusInnerInset && distance < outerRadius + TGRadialBlurViewRadiusInset)
|
|
{
|
|
_activeControl = TGRadialBlurViewActiveControlOuterRadius;
|
|
_startDistance = distance;
|
|
_startRadius = outerRadius;
|
|
}
|
|
|
|
[self setSelected:true animated:true];
|
|
}
|
|
break;
|
|
|
|
case UIGestureRecognizerStateChanged:
|
|
{
|
|
switch (_activeControl)
|
|
{
|
|
case TGRadialBlurViewActiveControlCenter:
|
|
{
|
|
CGPoint translation = [gestureRecognizer translationInView:self];
|
|
|
|
CGRect actualArea = CGRectMake((self.frame.size.width - self.actualAreaSize.width) / 2, (self.frame.size.height - self.actualAreaSize.height) / 2, self.actualAreaSize.width, self.actualAreaSize.height);
|
|
|
|
CGPoint newPoint = CGPointMake(MAX(CGRectGetMinX(actualArea), MIN(CGRectGetMaxX(actualArea), _startCenterPoint.x + translation.x)),
|
|
MAX(CGRectGetMinY(actualArea), MIN(CGRectGetMaxY(actualArea), _startCenterPoint.y + translation.y)));
|
|
|
|
CGPoint offset = CGPointMake(0, (self.actualAreaSize.width - self.actualAreaSize.height) / 2);
|
|
CGPoint actualPoint = CGPointMake(newPoint.x - actualArea.origin.x, newPoint.y - actualArea.origin.y);
|
|
self.centerPoint = CGPointMake((actualPoint.x + offset.x) / self.actualAreaSize.width, (actualPoint.y + offset.y) / self.actualAreaSize.width);
|
|
}
|
|
break;
|
|
|
|
case TGRadialBlurViewActiveControlInnerRadius:
|
|
{
|
|
CGFloat delta = distance - _startDistance;
|
|
self.falloff = MIN(MAX(TGRadialBlurMinimumFalloff, (_startRadius + delta) / shorterSide), self.size - TGRadialBlurMinimumDifference);
|
|
}
|
|
break;
|
|
|
|
case TGRadialBlurViewActiveControlOuterRadius:
|
|
{
|
|
CGFloat delta = distance - _startDistance;
|
|
self.size = MAX(self.falloff + TGRadialBlurMinimumDifference, (_startRadius + delta) / shorterSide);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
[self setNeedsDisplay];
|
|
|
|
if (self.valueChanged != nil)
|
|
self.valueChanged(self.centerPoint, self.falloff, self.size);
|
|
}
|
|
break;
|
|
|
|
case UIGestureRecognizerStateEnded:
|
|
case UIGestureRecognizerStateCancelled:
|
|
case UIGestureRecognizerStateFailed:
|
|
{
|
|
_activeControl = TGRadialBlurViewActiveControlNone;
|
|
|
|
[self setSelected:false animated:true];
|
|
|
|
if (self.interactionEnded != nil)
|
|
self.interactionEnded();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer
|
|
{
|
|
switch (gestureRecognizer.state)
|
|
{
|
|
case UIGestureRecognizerStateBegan:
|
|
{
|
|
_activeControl = TGRadialBlurViewActiveControlWholeArea;
|
|
|
|
[self setSelected:true animated:true];
|
|
}
|
|
case UIGestureRecognizerStateChanged:
|
|
{
|
|
CGFloat scale = gestureRecognizer.scale;
|
|
|
|
self.falloff = MAX(TGRadialBlurMinimumFalloff, self.falloff * scale);
|
|
self.size = MAX(self.falloff + TGRadialBlurMinimumDifference, self.size * scale);
|
|
|
|
gestureRecognizer.scale = 1.0f;
|
|
|
|
[self setNeedsDisplay];
|
|
|
|
if (self.valueChanged != nil)
|
|
self.valueChanged(self.centerPoint, self.falloff, self.size);
|
|
}
|
|
break;
|
|
|
|
case UIGestureRecognizerStateEnded:
|
|
{
|
|
_activeControl = TGRadialBlurViewActiveControlNone;
|
|
|
|
[self setSelected:false animated:true];
|
|
}
|
|
break;
|
|
|
|
case UIGestureRecognizerStateCancelled:
|
|
case UIGestureRecognizerStateFailed:
|
|
{
|
|
_activeControl = TGRadialBlurViewActiveControlNone;
|
|
|
|
[self setSelected:false animated:true];
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
|
{
|
|
if (gestureRecognizer == _pressGestureRecognizer || gestureRecognizer == _panGestureRecognizer)
|
|
{
|
|
CGPoint location = [gestureRecognizer locationInView:self];
|
|
CGPoint centerPoint = [self _actualCenterPoint];
|
|
CGPoint delta = CGPointMake(location.x - centerPoint.x, location.y - centerPoint.y);
|
|
|
|
CGFloat distance = CGSqrt(delta.x * delta.x + delta.y * delta.y);
|
|
|
|
CGFloat innerRadius = [self _actualInnerRadius];
|
|
CGFloat outerRadius = [self _actualOuterRadius];
|
|
|
|
bool close = ABS(outerRadius - innerRadius) < TGRadialBlurInsetProximity;
|
|
CGFloat innerRadiusOuterInset = close ? 0 : TGRadialBlurViewRadiusInset;
|
|
CGFloat outerRadiusInnerInset = close ? 0 : TGRadialBlurViewRadiusInset;
|
|
|
|
if (distance < TGRadialBlurViewCenterInset && gestureRecognizer == _panGestureRecognizer)
|
|
return true;
|
|
else if (distance > innerRadius - TGRadialBlurViewRadiusInset && distance < innerRadius + innerRadiusOuterInset)
|
|
return true;
|
|
else if (distance > outerRadius - outerRadiusInnerInset && distance < outerRadius + TGRadialBlurViewRadiusInset)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
|
{
|
|
if (gestureRecognizer == _pressGestureRecognizer || otherGestureRecognizer == _pressGestureRecognizer)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
- (void)setSelected:(bool)selected animated:(bool)animated
|
|
{
|
|
if (animated)
|
|
{
|
|
[UIView animateWithDuration:0.16f delay:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^
|
|
{
|
|
self.alpha = selected ? 0.6f : 1.0f;
|
|
} completion:nil];
|
|
}
|
|
else
|
|
{
|
|
self.alpha = selected ? 0.6f : 1.0f;
|
|
}
|
|
}
|
|
|
|
- (void)drawRect:(CGRect)__unused rect
|
|
{
|
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
|
|
CGPoint centerPoint = [self _actualCenterPoint];
|
|
CGFloat innerRadius = [self _actualInnerRadius];
|
|
CGFloat outerRadius = [self _actualOuterRadius];
|
|
|
|
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
|
|
CGContextSetShadowWithColor(context, CGSizeZero, 2.5f, [UIColor colorWithWhite:0.0f alpha:0.3f].CGColor);
|
|
|
|
CGFloat radSpace = TGDegreesToRadians(6.15f);
|
|
CGFloat radLen = TGDegreesToRadians(10.2f);
|
|
for (NSInteger i = 0; i < 22; i++)
|
|
{
|
|
CGMutablePathRef path = CGPathCreateMutable();
|
|
CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, innerRadius,
|
|
i * (radSpace + radLen), i * (radSpace + radLen) + radLen, false);
|
|
|
|
CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(path, NULL, 1.5f, kCGLineCapButt, kCGLineJoinMiter, 10);
|
|
|
|
CGContextAddPath(context, strokedArc);
|
|
|
|
CGPathRelease(strokedArc);
|
|
CGPathRelease(path);
|
|
}
|
|
|
|
radSpace = TGDegreesToRadians(2.02f);
|
|
radLen = TGDegreesToRadians(3.6f);
|
|
for (NSInteger i = 0; i < 64; i++)
|
|
{
|
|
CGMutablePathRef path = CGPathCreateMutable();
|
|
|
|
CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, outerRadius,
|
|
i * (radSpace + radLen), i * (radSpace + radLen) + radLen, false);
|
|
|
|
CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(path, NULL, 1.5f, kCGLineCapButt, kCGLineJoinMiter, 10);
|
|
|
|
CGContextAddPath(context, strokedArc);
|
|
|
|
CGPathRelease(strokedArc);
|
|
CGPathRelease(path);
|
|
}
|
|
|
|
CGContextFillPath(context);
|
|
|
|
CGContextFillEllipseInRect(context, CGRectMake(centerPoint.x - 16 / 2, centerPoint.y - 16 / 2, 16, 16));
|
|
}
|
|
|
|
- (CGPoint)_actualCenterPoint
|
|
{
|
|
CGRect actualArea = CGRectMake((self.frame.size.width - self.actualAreaSize.width) / 2, (self.frame.size.height - self.actualAreaSize.height) / 2, self.actualAreaSize.width, self.actualAreaSize.height);
|
|
CGPoint offset = CGPointMake(0, (self.actualAreaSize.width - self.actualAreaSize.height) / 2);
|
|
return CGPointMake(actualArea.origin.x - offset.x + self.centerPoint.x * self.actualAreaSize.width, actualArea.origin.y - offset.y + self.centerPoint.y * self.actualAreaSize.width);
|
|
}
|
|
|
|
- (CGFloat)_actualInnerRadius
|
|
{
|
|
CGFloat shorterSide = (self.actualAreaSize.width > self.actualAreaSize.height) ? self.actualAreaSize.height : self.actualAreaSize.width;
|
|
return shorterSide * self.falloff;;
|
|
}
|
|
|
|
- (CGFloat)_actualOuterRadius
|
|
{
|
|
CGFloat shorterSide = (self.actualAreaSize.width > self.actualAreaSize.height) ? self.actualAreaSize.height : self.actualAreaSize.width;
|
|
return shorterSide * self.size;
|
|
}
|
|
|
|
@end
|