#import "TGPhotoEditorLinearBlurView.h" #import "LegacyComponentsInternal.h" #import const CGFloat TGLinearBlurInsetProximity = 20; const CGFloat TGLinearBlurMinimumFalloff = 0.1f; const CGFloat TGLinearBlurMinimumDifference = 0.02f; const CGFloat TGLinearBlurViewCenterInset = 30.0f; const CGFloat TGLinearBlurViewRadiusInset = 30.0f; typedef enum { TGLinearBlurViewActiveControlNone, TGLinearBlurViewActiveControlCenter, TGLinearBlurViewActiveControlInnerRadius, TGLinearBlurViewActiveControlOuterRadius, TGLinearBlurViewActiveControlWholeArea, TGLinearBlurViewActiveControlRotation } TGLinearBlurViewActiveControl; @interface TGPhotoEditorLinearBlurView () { UILongPressGestureRecognizer *_pressGestureRecognizer; UIPanGestureRecognizer *_panGestureRecognizer; UIPinchGestureRecognizer *_pinchGestureRecognizer; TGLinearBlurViewActiveControl _activeControl; CGPoint _startCenterPoint; CGFloat _startDistance; CGFloat _startRadius; } @end @implementation TGPhotoEditorLinearBlurView - (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 radialDistance = CGSqrt(delta.x * delta.x + delta.y * delta.y); CGFloat distance = ABS(delta.x * CGCos(self.angle + (CGFloat)M_PI_2) + delta.y * CGSin(self.angle + (CGFloat)M_PI_2)); 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) < TGLinearBlurInsetProximity; CGFloat innerRadiusOuterInset = close ? 0 : TGLinearBlurViewRadiusInset; CGFloat outerRadiusInnerInset = close ? 0 : TGLinearBlurViewRadiusInset; if (radialDistance < TGLinearBlurViewCenterInset) { _activeControl = TGLinearBlurViewActiveControlCenter; _startCenterPoint = centerPoint; } else if (distance > innerRadius - TGLinearBlurViewRadiusInset && distance < innerRadius + innerRadiusOuterInset) { _activeControl = TGLinearBlurViewActiveControlInnerRadius; _startDistance = distance; _startRadius = innerRadius; } else if (distance > outerRadius - outerRadiusInnerInset && distance < outerRadius + TGLinearBlurViewRadiusInset) { _activeControl = TGLinearBlurViewActiveControlOuterRadius; _startDistance = distance; _startRadius = outerRadius; } else if (distance <= innerRadius - TGLinearBlurViewRadiusInset || distance >= outerRadius + TGLinearBlurViewRadiusInset) { _activeControl = TGLinearBlurViewActiveControlRotation; } [self setSelected:true animated:true]; } break; case UIGestureRecognizerStateChanged: { switch (_activeControl) { case TGLinearBlurViewActiveControlCenter: { 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 TGLinearBlurViewActiveControlInnerRadius: { CGFloat delta = distance - _startDistance; self.falloff = MIN(MAX(TGLinearBlurMinimumFalloff, (_startRadius + delta) / shorterSide), self.size - TGLinearBlurMinimumDifference); } break; case TGLinearBlurViewActiveControlOuterRadius: { CGFloat delta = distance - _startDistance; self.size = MAX(self.falloff + TGLinearBlurMinimumDifference, (_startRadius + delta) / shorterSide); } break; case TGLinearBlurViewActiveControlRotation: { CGPoint translation = [gestureRecognizer translationInView:self]; bool clockwise = false; bool right = location.x > centerPoint.x; bool bottom = location.y > centerPoint.y; if (!right && !bottom) { if (ABS(translation.y) > ABS(translation.x)) { if (translation.y < 0) clockwise = true; } else { if (translation.x > 0) clockwise = true; } } else if (right && !bottom) { if (ABS(translation.y) > ABS(translation.x)) { if (translation.y > 0) clockwise = true; } else { if (translation.x > 0) clockwise = true; } } else if (right && bottom) { if (ABS(translation.y) > ABS(translation.x)) { if (translation.y > 0) clockwise = true; } else { if (translation.x < 0) clockwise = true; } } else { if (ABS(translation.y) > ABS(translation.x)) { if (translation.y < 0) clockwise = true; } else { if (translation.x < 0) clockwise = true; } } CGFloat delta = CGSqrt(translation.x * translation.x + translation.y * translation.y); CGFloat angleInDegrees = TGRadiansToDegrees(_angle); CGFloat newAngleInDegrees = angleInDegrees + delta * (clockwise * 2 - 1) / (CGFloat)M_PI / 1.15f; _angle = TGDegreesToRadians(newAngleInDegrees); [gestureRecognizer setTranslation:CGPointZero inView:self]; } break; default: break; } [self setNeedsDisplay]; if (self.valueChanged != nil) self.valueChanged(self.centerPoint, self.falloff, self.size, self.angle); } break; case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: case UIGestureRecognizerStateFailed: { _activeControl = TGLinearBlurViewActiveControlNone; [self setSelected:false animated:true]; if (self.interactionEnded != nil) self.interactionEnded(); } break; default: break; } } - (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer { switch (gestureRecognizer.state) { case UIGestureRecognizerStateBegan: { _activeControl = TGLinearBlurViewActiveControlWholeArea; [self setSelected:true animated:true]; } case UIGestureRecognizerStateChanged: { CGFloat scale = gestureRecognizer.scale; self.falloff = MAX(TGLinearBlurMinimumFalloff, self.falloff * scale); self.size = MAX(self.falloff + TGLinearBlurMinimumDifference, self.size * scale); gestureRecognizer.scale = 1.0f; [self setNeedsDisplay]; if (self.valueChanged != nil) self.valueChanged(self.centerPoint, self.falloff, self.size, self.angle); } break; case UIGestureRecognizerStateEnded: { _activeControl = TGLinearBlurViewActiveControlNone; [self setSelected:false animated:true]; } break; case UIGestureRecognizerStateCancelled: case UIGestureRecognizerStateFailed: { _activeControl = TGLinearBlurViewActiveControlNone; [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 radialDistance = CGSqrt(delta.x * delta.x + delta.y * delta.y); CGFloat distance = ABS(delta.x * CGCos(self.angle + (CGFloat)M_PI_2) + delta.y * CGSin(self.angle + (CGFloat)M_PI_2)); CGFloat innerRadius = [self _actualInnerRadius]; CGFloat outerRadius = [self _actualOuterRadius]; bool close = ABS(outerRadius - innerRadius) < TGLinearBlurInsetProximity; CGFloat innerRadiusOuterInset = close ? 0 : TGLinearBlurViewRadiusInset; CGFloat outerRadiusInnerInset = close ? 0 : TGLinearBlurViewRadiusInset; if (radialDistance < TGLinearBlurViewCenterInset && gestureRecognizer == _panGestureRecognizer) return true; else if (distance > innerRadius - TGLinearBlurViewRadiusInset && distance < innerRadius + innerRadiusOuterInset) return true; else if (distance > outerRadius - outerRadiusInnerInset && distance < outerRadius + TGLinearBlurViewRadiusInset) return true; else if ((distance <= innerRadius - TGLinearBlurViewRadiusInset) || distance >= outerRadius + TGLinearBlurViewRadiusInset) 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]; CGContextTranslateCTM(context, centerPoint.x, centerPoint.y); CGContextRotateCTM(context, self.angle); CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); CGContextSetShadowWithColor(context, CGSizeZero, 2.5f, [UIColor colorWithWhite:0.0f alpha:0.3f].CGColor); CGFloat space = 6.0f; CGFloat length = 12.0f; CGFloat thickness = 1.5f; for (NSInteger i = 0; i < 30; i++) { CGContextAddRect(context, CGRectMake(i * (length + space), -innerRadius, length, thickness)); CGContextAddRect(context, CGRectMake(-i * (length + space) - space - length, -innerRadius, length, thickness)); CGContextAddRect(context, CGRectMake(i * (length + space), innerRadius, length, thickness)); CGContextAddRect(context, CGRectMake(-i * (length + space) - space - length, innerRadius, length, thickness)); } length = 6.0f; thickness = 1.5f; for (NSInteger i = 0; i < 64; i++) { CGContextAddRect(context, CGRectMake(i * (length + space), -outerRadius, length, thickness)); CGContextAddRect(context, CGRectMake(-i * (length + space) - space - length, -outerRadius, length, thickness)); CGContextAddRect(context, CGRectMake(i * (length + space), outerRadius, length, thickness)); CGContextAddRect(context, CGRectMake(-i * (length + space) - space - length, outerRadius, length, thickness)); } CGContextFillPath(context); CGContextFillEllipseInRect(context, CGRectMake(-16 / 2, - 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