mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Add liquid voice record button
This commit is contained in:
parent
2d02870d59
commit
ec896bd9ff
@ -2,6 +2,13 @@
|
||||
|
||||
@class TGModernConversationInputMicButton;
|
||||
|
||||
@protocol TGModernConversationInputMicButtonDecoration <NSObject>
|
||||
|
||||
- (void)updateLevel:(CGFloat)level;
|
||||
- (void)tick:(CGFloat)level;
|
||||
|
||||
@end
|
||||
|
||||
@protocol TGModernConversationInputMicButtonPresentation <NSObject>
|
||||
|
||||
- (UIView *)view;
|
||||
@ -26,6 +33,7 @@
|
||||
- (bool)micButtonShouldLock;
|
||||
|
||||
- (id<TGModernConversationInputMicButtonPresentation>)micButtonPresenter;
|
||||
- (UIView<TGModernConversationInputMicButtonDecoration> *)micButtonDecoration;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -135,6 +135,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
UIImage *_icon;
|
||||
|
||||
id<TGModernConversationInputMicButtonPresentation> _presentation;
|
||||
UIView<TGModernConversationInputMicButtonDecoration> *_decoration;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -240,6 +241,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
centerPoint.y += _centerOffset.y;
|
||||
_innerCircleView.center = centerPoint;
|
||||
_outerCircleView.center = centerPoint;
|
||||
_decoration.center = centerPoint;
|
||||
_innerIconWrapperView.center = centerPoint;
|
||||
|
||||
_lockPanelWrapperView.frame = CGRectMake(floor(centerPoint.x - _lockPanelWrapperView.frame.size.width / 2.0f), floor(centerPoint.y - 122.0f - _lockPanelWrapperView.frame.size.height / 2.0f), _lockPanelWrapperView.frame.size.width, _lockPanelWrapperView.frame.size.height);
|
||||
@ -355,7 +357,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
[delegate micButtonInteractionRequestedLockedAction];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
_lockPanelWrapperView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 38.0f, 77.0f)];
|
||||
_lockPanelWrapperView.userInteractionEnabled = false;
|
||||
[[_presentation view] addSubview:_lockPanelWrapperView];
|
||||
@ -377,6 +379,12 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
_innerCircleView.alpha = 0.0f;
|
||||
[[_presentation view] addSubview:_innerCircleView];
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(micButtonDecoration)]) {
|
||||
UIView<TGModernConversationInputMicButtonDecoration> *decoration = [_delegate micButtonDecoration];
|
||||
_decoration = decoration;
|
||||
[[_presentation view] addSubview:_decoration];
|
||||
}
|
||||
|
||||
_outerCircleView = [[UIImageView alloc] initWithImage:[self outerCircleImage:self.pallete != nil ? self.pallete.buttonColor : TGAccentColor()]];
|
||||
_outerCircleView.alpha = 0.0f;
|
||||
_outerCircleView.tag = 0x01f2bca;
|
||||
@ -419,8 +427,10 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
_innerIconWrapperView.transform = CGAffineTransformIdentity;
|
||||
_innerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
|
||||
_outerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
|
||||
_decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
|
||||
_innerCircleView.alpha = 0.2f;
|
||||
_outerCircleView.alpha = 0.2f;
|
||||
_decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
|
||||
|
||||
_lockPanelWrapperView.transform = CGAffineTransformMakeTranslation(0.0f, 100.0f);
|
||||
_lockPanelWrapperView.alpha = 0.0f;
|
||||
@ -429,6 +439,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
[UIView animateWithDuration:0.50 delay:0.0 usingSpringWithDamping:0.55f initialSpringVelocity:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
||||
_innerCircleView.transform = CGAffineTransformIdentity;
|
||||
_outerCircleView.transform = CGAffineTransformMakeScale(outerCircleMinScale, outerCircleMinScale);
|
||||
_decoration.transform = CGAffineTransformIdentity;
|
||||
|
||||
_lockPanelWrapperView.transform = CGAffineTransformIdentity;
|
||||
} completion:nil];
|
||||
@ -438,6 +449,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
self.iconView.alpha = 0.0f;
|
||||
_innerIconWrapperView.alpha = 1.0f;
|
||||
_outerCircleView.alpha = 1.0f;
|
||||
_decoration.alpha = 1.0;
|
||||
|
||||
_lockPanelWrapperView.alpha = 1.0f;
|
||||
}];
|
||||
@ -473,8 +485,10 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
[UIView animateWithDuration:0.18 animations:^{
|
||||
_innerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
|
||||
_outerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
|
||||
_decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
|
||||
_innerCircleView.alpha = 0.0f;
|
||||
_outerCircleView.alpha = 0.0f;
|
||||
_decoration.alpha = 0.0f;
|
||||
self.iconView.alpha = 1.0f;
|
||||
_innerIconWrapperView.alpha = 0.0f;
|
||||
|
||||
@ -764,6 +778,9 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
}
|
||||
|
||||
- (void)displayLinkUpdate {
|
||||
if (_decoration != NULL) {
|
||||
_outerCircleView.image = nil;
|
||||
}
|
||||
NSTimeInterval t = CACurrentMediaTime();
|
||||
if (t > _animationStartTime + 0.5) {
|
||||
_currentLevel = _currentLevel * 0.8f + _inputLevel * 0.2f;
|
||||
@ -783,11 +800,14 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
||||
|
||||
transform = CGAffineTransformScale(translation, _currentScale, _currentScale);
|
||||
_innerCircleView.transform = transform;
|
||||
|
||||
[_decoration tick:_currentLevel];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addMicLevel:(CGFloat)level {
|
||||
_inputLevel = level;
|
||||
[_decoration updateLevel:level];
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)__unused gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)__unused otherGestureRecognizer {
|
||||
|
@ -387,6 +387,10 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
|
||||
return ChatTextInputMediaRecordingButtonPresenter(account: self.account!, presentController: self.presentController)
|
||||
}
|
||||
|
||||
func micButtonDecoration() -> (UIView & TGModernConversationInputMicButtonDecoration)! {
|
||||
return CombinedWaveView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 640.0, height: 640.0)), color: self.theme.chat.inputPanel.actionControlFillColor)
|
||||
}
|
||||
|
||||
private var previousSize = CGSize()
|
||||
func layoutItems() {
|
||||
let size = self.bounds.size
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
@ -26,7 +27,7 @@ public final class TelegramRootController: NavigationController {
|
||||
private var permissionsDisposable: Disposable?
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private var presentationData: PresentationData
|
||||
|
||||
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
||||
@ -120,21 +121,8 @@ public final class TelegramRootController: NavigationController {
|
||||
self.accountSettingsController = accountSettingsController
|
||||
self.rootTabController = tabBarController
|
||||
self.pushViewController(tabBarController, animated: false)
|
||||
|
||||
// let _ = (archivedStickerPacks(account: self.context.account, namespace: .stickers)
|
||||
// |> deliverOnMainQueue).start(next: { [weak self] stickerPacks in
|
||||
// var packs: [(StickerPackCollectionInfo, StickerPackItem?)] = []
|
||||
// for pack in stickerPacks {
|
||||
// packs.append((pack.info, pack.topItems.first))
|
||||
// }
|
||||
//
|
||||
// if let strongSelf = self {
|
||||
// let controller = archivedStickersNoticeController(context: strongSelf.context, archivedStickerPacks: packs)
|
||||
// strongSelf.chatListController?.present(controller, in: .window(.root))
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
|
||||
public func updateRootControllers(showCallsTab: Bool) {
|
||||
guard let rootTabController = self.rootTabController else {
|
||||
return
|
||||
|
468
submodules/TelegramUI/Sources/WaveButtonNode.swift
Normal file
468
submodules/TelegramUI/Sources/WaveButtonNode.swift
Normal file
@ -0,0 +1,468 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import LegacyComponents
|
||||
|
||||
private struct Constants {
|
||||
static let sineWaveSpeed: CGFloat = 0.81
|
||||
static let smallWaveRadius: CGFloat = 0.55
|
||||
static let smallWaveScale: CGFloat = 0.40
|
||||
static let smallWaveScaleSpeed: CGFloat = 0.6
|
||||
static let flingDistance: CGFloat = 0.5
|
||||
|
||||
static let circleRadius: CGFloat = 56.0
|
||||
|
||||
static let animationSpeed: CGFloat = 0.35 * 0.1
|
||||
static let animationSpeedSmall: CGFloat = 0.55 * 0.1
|
||||
|
||||
static let rotationSpeed: CGFloat = 0.36 * 0.1
|
||||
static let waveAngle: CGFloat = 0.03
|
||||
static let randomRadiusSize: CGFloat = 0.3
|
||||
|
||||
static let idleWaveAngle: CGFloat = 0.5
|
||||
static let idleScaleSpeed: CGFloat = 0.3
|
||||
static let idleRotationSpeed: CGFloat = 0.2
|
||||
static let idleRadiusValue: CGFloat = 0.56
|
||||
static let idleRotationDiff: CGFloat = 0.1 * idleRotationSpeed
|
||||
}
|
||||
|
||||
class CombinedWaveView: UIView, TGModernConversationInputMicButtonDecoration {
|
||||
private let bigWaveView: WaveView
|
||||
private let smallWaveView: WaveView
|
||||
|
||||
private var level: CGFloat = 0.0
|
||||
|
||||
init(frame: CGRect, color: UIColor) {
|
||||
let n = 12
|
||||
let bounds = CGRect(origin: CGPoint(), size: frame.size)
|
||||
self.bigWaveView = WaveView(frame: bounds, n: n, amplitudeRadius: 40.0, isBig: true, color: color.withAlphaComponent(0.3))
|
||||
self.smallWaveView = WaveView(frame: bounds, n: n, amplitudeRadius: 35.0, isBig: false, color: color.withAlphaComponent(0.15))
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.bigWaveView.rotation = CGFloat.pi / 6.0
|
||||
self.bigWaveView.amplitudeWaveDif = 0.02 * Constants.sineWaveSpeed * CGFloat.pi / 180.0
|
||||
|
||||
self.smallWaveView.amplitudeWaveDif = 0.026 * Constants.sineWaveSpeed
|
||||
self.smallWaveView.amplitudeRadius = 20.0 + 20.0 * Constants.smallWaveRadius
|
||||
self.smallWaveView.maxScale = 0.3 * Constants.smallWaveScale
|
||||
self.smallWaveView.scaleSpeed = 0.001 * Constants.smallWaveScaleSpeed
|
||||
self.smallWaveView.fling = Constants.flingDistance
|
||||
|
||||
self.addSubview(self.bigWaveView)
|
||||
self.addSubview(self.smallWaveView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateLevel(_ level: CGFloat) {
|
||||
let level = level * 0.45
|
||||
self.level = level
|
||||
self.bigWaveView.setLevel(level)
|
||||
self.smallWaveView.setLevel(level)
|
||||
}
|
||||
|
||||
func tick(_ level: CGFloat) {
|
||||
let radius = 56.0 + 30.0 * level * 0.45
|
||||
self.bigWaveView.tick(circleRadius: radius)
|
||||
self.smallWaveView.tick(circleRadius: radius)
|
||||
}
|
||||
}
|
||||
|
||||
class WaveView : UIView {
|
||||
var fling: CGFloat = 0.0
|
||||
private var animateToAmplitude: CGFloat = 0.0
|
||||
private var amplitude: CGFloat = 0.0
|
||||
private var slowAmplitude: CGFloat = 0.0
|
||||
private var animateAmplitudeDiff: CGFloat = 0.0
|
||||
private var animateAmplitudeSlowDiff: CGFloat = 0.0
|
||||
|
||||
private var lastRadius: CGFloat = 0.0
|
||||
private var radiusDiff: CGFloat = 0.0
|
||||
private var waveDiff: CGFloat = 0.0
|
||||
private var waveAngle: CGFloat = 0.0
|
||||
|
||||
private var incRandomAdditionals = false
|
||||
|
||||
var rotation: CGFloat = 0.0
|
||||
private var idleRotation: CGFloat = 0.0
|
||||
private var innerRotation: CGFloat = 0.0
|
||||
|
||||
var amplitudeWaveDif: CGFloat = 0.0
|
||||
|
||||
var amplitudeRadius: CGFloat
|
||||
private let isBig: Bool
|
||||
|
||||
private var idleRadius: CGFloat = 0.0
|
||||
private var idleRadiusK: CGFloat = 0.15 * Constants.idleWaveAngle
|
||||
private var expandIdleRadius = false
|
||||
private var expandScale = false
|
||||
|
||||
private var isIdle = true
|
||||
private var scale: CGFloat = 1.0
|
||||
private var scaleIdleDif: CGFloat = 0.0
|
||||
private var scaleDif: CGFloat = 0.0
|
||||
var scaleSpeed: CGFloat = 0.00008
|
||||
public var scaleSpeedIdle: CGFloat = 0.0002 * Constants.idleScaleSpeed
|
||||
var maxScale: CGFloat = 0.0
|
||||
|
||||
private var flingRadius: CGFloat = 0.0
|
||||
|
||||
private let randomAdditions: CGFloat = 8.0 * Constants.randomRadiusSize
|
||||
|
||||
private var idleGlobalRadius: CGFloat = 10.0 * Constants.idleRadiusValue
|
||||
private var sineAngleMax: CGFloat = 0.0
|
||||
|
||||
private let n: Int
|
||||
private let l: CGFloat
|
||||
private var additions: [CGFloat]
|
||||
|
||||
var idleStateDiff: CGFloat = 0.0
|
||||
var radius: CGFloat = 60.0;
|
||||
var cubicBezierK: CGFloat = 1.0;
|
||||
|
||||
var randomK: CGFloat = 0.0
|
||||
|
||||
var color: UIColor
|
||||
|
||||
init(frame: CGRect, n: Int, amplitudeRadius: CGFloat, isBig: Bool, color: UIColor) {
|
||||
self.n = n
|
||||
self.amplitudeRadius = amplitudeRadius
|
||||
self.isBig = isBig
|
||||
self.color = color
|
||||
|
||||
self.expandIdleRadius = isBig
|
||||
self.radiusDiff = 34.0 * 0.0012
|
||||
|
||||
self.l = 4.0 / 3.0 * tan(CGFloat.pi / (2.0 * CGFloat(self.n)))
|
||||
self.additions = Array(repeating: 0.0, count: self.n)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundColor = .clear
|
||||
self.isOpaque = false
|
||||
|
||||
self.updateAdditions()
|
||||
}
|
||||
|
||||
func setLevel(_ level: CGFloat) {
|
||||
self.animateToAmplitude = level
|
||||
|
||||
let amplitudeDelta: CGFloat
|
||||
let amplitudeSlowDelta: CGFloat
|
||||
if self.isBig {
|
||||
if self.animateToAmplitude > self.amplitude {
|
||||
amplitudeDelta = 300.0 * Constants.animationSpeed
|
||||
amplitudeSlowDelta = 500.0 * Constants.animationSpeed
|
||||
} else {
|
||||
amplitudeDelta = 500.0 * Constants.animationSpeed
|
||||
amplitudeSlowDelta = 500.0 * Constants.animationSpeed
|
||||
}
|
||||
} else {
|
||||
if self.animateToAmplitude > self.amplitude {
|
||||
amplitudeDelta = 400.0 * Constants.animationSpeedSmall
|
||||
amplitudeSlowDelta = 500.0 * Constants.animationSpeedSmall
|
||||
} else {
|
||||
amplitudeDelta = 500.0 * Constants.animationSpeedSmall
|
||||
amplitudeSlowDelta = 500.0 * Constants.animationSpeedSmall
|
||||
}
|
||||
}
|
||||
|
||||
self.animateAmplitudeDiff = (self.animateToAmplitude - self.amplitude) / (100.0 + amplitudeDelta)
|
||||
self.animateAmplitudeSlowDiff = (self.animateToAmplitude - self.slowAmplitude) / (100.0 + amplitudeSlowDelta)
|
||||
|
||||
let isIdle = level < 0.1
|
||||
if self.isIdle != isIdle && isIdle && self.isBig {
|
||||
//
|
||||
//
|
||||
//
|
||||
}
|
||||
|
||||
self.isIdle = isIdle
|
||||
}
|
||||
|
||||
private var wasFling = false
|
||||
|
||||
private func startFling(delta: CGFloat) {
|
||||
self.pop_removeAnimation(forKey: "fling1")
|
||||
self.pop_removeAnimation(forKey: "fling2")
|
||||
|
||||
let fling = self.fling * 2.0
|
||||
let flingDistance = delta * self.amplitudeRadius * (self.isBig ? 8.0 : 20.0) * 16.0 * fling
|
||||
|
||||
let animation = POPBasicAnimation()
|
||||
animation.property = POPAnimatableProperty.property(withName: "fling1", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! WaveView).flingRadius
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! WaveView).flingRadius = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as? POPAnimatableProperty
|
||||
animation.fromValue = self.flingRadius as NSNumber
|
||||
animation.toValue = flingDistance as NSNumber
|
||||
animation.duration = Double((self.isBig ? 0.2 : 0.35) * fling)
|
||||
animation.completionBlock = { [weak self] _, finished in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let animation = POPBasicAnimation()
|
||||
animation.property = POPAnimatableProperty.property(withName: "fling2", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! WaveView).flingRadius
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! WaveView).flingRadius = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as? POPAnimatableProperty
|
||||
animation.fromValue = flingDistance as NSNumber
|
||||
animation.toValue = 0.0 as NSNumber
|
||||
animation.duration = Double((strongSelf.isBig ? 0.22 : 0.38) * fling)
|
||||
strongSelf.pop_add(animation, forKey: "fling2")
|
||||
}
|
||||
self.pop_add(animation, forKey: "fling1")
|
||||
}
|
||||
|
||||
private var lastUpdateTime: CGFloat?
|
||||
func tick(circleRadius: CGFloat) {
|
||||
let dt: CGFloat
|
||||
let time = CGFloat(CACurrentMediaTime())
|
||||
if let lastUpdateTime = self.lastUpdateTime {
|
||||
dt = (time - lastUpdateTime) * 1000.0
|
||||
} else {
|
||||
dt = 0.0
|
||||
}
|
||||
self.lastUpdateTime = time
|
||||
|
||||
if self.animateToAmplitude != self.amplitude {
|
||||
self.amplitude += self.animateAmplitudeDiff * dt
|
||||
if self.animateAmplitudeDiff > 0.0 {
|
||||
if self.amplitude > self.animateToAmplitude {
|
||||
self.amplitude = self.animateToAmplitude
|
||||
}
|
||||
} else {
|
||||
if self.amplitude < self.animateToAmplitude {
|
||||
self.amplitude = self.animateToAmplitude
|
||||
}
|
||||
}
|
||||
|
||||
if abs(self.amplitude - self.animateToAmplitude) * self.amplitudeRadius < 4.0 {
|
||||
if !self.wasFling {
|
||||
self.startFling(delta: self.animateAmplitudeDiff)
|
||||
self.wasFling = true
|
||||
}
|
||||
} else {
|
||||
self.wasFling = false
|
||||
}
|
||||
}
|
||||
|
||||
if self.animateToAmplitude != self.slowAmplitude {
|
||||
self.slowAmplitude += self.animateAmplitudeSlowDiff * dt
|
||||
if abs(self.slowAmplitude - self.amplitude) > 0.2 {
|
||||
self.slowAmplitude = self.amplitudeRadius + (self.slowAmplitude > self.amplitude ? 0.2 : -0.2)
|
||||
}
|
||||
if self.animateAmplitudeSlowDiff > 0.0 {
|
||||
if self.slowAmplitude > self.animateToAmplitude {
|
||||
self.slowAmplitude = self.animateToAmplitude
|
||||
}
|
||||
} else {
|
||||
if self.slowAmplitude < self.animateToAmplitude {
|
||||
self.slowAmplitude = self.animateToAmplitude
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.idleRadius = circleRadius * self.idleRadiusK
|
||||
if self.expandIdleRadius {
|
||||
self.scaleIdleDif += self.scaleSpeedIdle * dt
|
||||
if self.scaleIdleDif >= 0.05 {
|
||||
self.scaleIdleDif = 0.05
|
||||
self.expandIdleRadius = false
|
||||
}
|
||||
} else {
|
||||
self.scaleIdleDif -= self.scaleSpeedIdle * dt
|
||||
if self.scaleIdleDif < 0.0 {
|
||||
self.scaleIdleDif = 0.0
|
||||
self.expandIdleRadius = true
|
||||
}
|
||||
}
|
||||
|
||||
if self.maxScale > 0.0 {
|
||||
if self.expandScale {
|
||||
self.scaleDif += self.scaleSpeed * dt
|
||||
if self.scaleDif >= self.maxScale {
|
||||
self.scaleDif = self.maxScale
|
||||
self.expandScale = false
|
||||
}
|
||||
} else {
|
||||
self.scaleDif -= self.scaleSpeed * dt
|
||||
if self.scaleDif < 0.0 {
|
||||
self.scaleDif = 0.0
|
||||
self.expandScale = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.sineAngleMax > self.animateToAmplitude {
|
||||
self.sineAngleMax -= 0.25
|
||||
if self.sineAngleMax < self.animateToAmplitude {
|
||||
self.sineAngleMax = self.animateToAmplitude
|
||||
}
|
||||
} else if self.sineAngleMax < self.animateToAmplitude {
|
||||
self.sineAngleMax += 0.25
|
||||
if self.sineAngleMax > self.animateToAmplitude {
|
||||
self.sineAngleMax = self.animateToAmplitude
|
||||
}
|
||||
}
|
||||
|
||||
if !self.isIdle {
|
||||
self.rotation += (Constants.rotationSpeed * 0.5 + Constants.rotationSpeed * 4.0 * (self.amplitude > 0.5 ? 1.0 : self.amplitude / 0.5) * dt) * CGFloat.pi / 180.0
|
||||
while self.rotation > CGFloat.pi * 2.0 {
|
||||
self.rotation -= CGFloat.pi * 2.0
|
||||
}
|
||||
} else {
|
||||
self.idleRotation += Constants.idleRotationDiff * dt * CGFloat.pi / 180.0
|
||||
while self.idleRotation > CGFloat.pi * 2.0 {
|
||||
self.idleRotation -= CGFloat.pi * 2.0
|
||||
}
|
||||
}
|
||||
|
||||
if self.lastRadius < circleRadius {
|
||||
self.lastRadius = circleRadius
|
||||
} else {
|
||||
self.lastRadius -= self.radiusDiff * dt
|
||||
if self.lastRadius < circleRadius {
|
||||
self.lastRadius = circleRadius
|
||||
}
|
||||
}
|
||||
|
||||
self.lastRadius = circleRadius
|
||||
|
||||
if !self.isIdle {
|
||||
self.waveAngle += self.amplitudeWaveDif * self.sineAngleMax * dt
|
||||
if self.isBig {
|
||||
self.waveDiff = cos(self.waveAngle)
|
||||
} else {
|
||||
self.waveDiff = -cos(self.waveAngle)
|
||||
}
|
||||
|
||||
if self.waveDiff > 0.0 && self.incRandomAdditionals {
|
||||
self.updateAdditions()
|
||||
self.incRandomAdditionals = false
|
||||
} else if self.waveDiff < 0.0 && !self.incRandomAdditionals {
|
||||
self.updateAdditions()
|
||||
self.incRandomAdditionals = true
|
||||
}
|
||||
}
|
||||
|
||||
self.prepareDraw()
|
||||
}
|
||||
|
||||
func updateAdditions() {
|
||||
self.additions = (0..<self.n).map { _ in CGFloat(arc4random() % 100) / 100.0 }
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func prepareDraw() {
|
||||
let waveAmplitude = self.amplitude < 0.3 ? self.amplitude / 0.3 : 1.0
|
||||
let radiusDiff: CGFloat = 10.0 + 50.0 * Constants.waveAngle * self.animateToAmplitude
|
||||
|
||||
self.idleStateDiff = self.idleRadius * (1.0 - waveAmplitude)
|
||||
|
||||
let kDiff: CGFloat = 0.35 * waveAmplitude * self.waveDiff
|
||||
self.radiusDiff = radiusDiff * kDiff
|
||||
self.cubicBezierK = 1.0 + abs(kDiff) * waveAmplitude + (1.0 - waveAmplitude) * self.idleRadiusK
|
||||
|
||||
self.radius = (self.lastRadius + self.amplitudeRadius * self.amplitude) + self.idleGlobalRadius + (self.flingRadius * waveAmplitude)
|
||||
|
||||
if self.radius + self.radiusDiff < Constants.circleRadius {
|
||||
self.radiusDiff = Constants.circleRadius - self.radius
|
||||
}
|
||||
|
||||
if self.isBig {
|
||||
self.innerRotation = self.rotation + self.idleRotation
|
||||
} else {
|
||||
self.innerRotation = -self.rotation + self.idleRotation
|
||||
}
|
||||
|
||||
self.randomK = waveAmplitude * self.waveDiff * self.randomAdditions
|
||||
|
||||
self.scale = 1.0 + self.scaleIdleDif * (1.0 - waveAmplitude) + self.scaleDif * waveAmplitude
|
||||
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
let ctx = UIGraphicsGetCurrentContext()!
|
||||
ctx.clear(rect)
|
||||
|
||||
let r1 = self.radius - self.idleStateDiff / 2.0 - self.radiusDiff / 2.0
|
||||
let r2 = self.radius + self.radiusDiff / 2.0 + self.idleStateDiff / 2.0
|
||||
|
||||
let l = self.l * max(r1, r2) * self.cubicBezierK
|
||||
|
||||
let cx = rect.width / 2.0
|
||||
let cy = rect.height / 2.0
|
||||
|
||||
let path = UIBezierPath()
|
||||
|
||||
for i in 0..<self.n {
|
||||
var transform = CGAffineTransform.init(translationX: cx, y: cy)
|
||||
transform = transform.rotated(by: 2 * CGFloat.pi / CGFloat(self.n) * CGFloat(i))
|
||||
transform = transform.translatedBy(x: -cx, y: -cy)
|
||||
|
||||
var r = ((i % 2 == 0) ? r1 : r2) + self.randomK * self.additions[i]
|
||||
|
||||
var p1 = CGPoint(x: cx, y: cy - r)
|
||||
var p2 = CGPoint(x: cx + l + self.randomK * self.additions[i] * self.l, y: cy - r)
|
||||
|
||||
p1 = p1.applying(transform)
|
||||
p2 = p2.applying(transform)
|
||||
|
||||
var j = i + 1
|
||||
if j >= self.n {
|
||||
j = 0
|
||||
}
|
||||
|
||||
r = ((j % 2 == 0) ? r1 : r2) + self.randomK * self.additions[j]
|
||||
|
||||
var p3 = CGPoint(x: cx, y: cy - r)
|
||||
var p4 = CGPoint(x: cx - l + self.randomK * self.additions[j] * self.l, y: cy - r)
|
||||
|
||||
transform = CGAffineTransform.init(translationX: cx, y: cy)
|
||||
transform = transform.rotated(by: 2 * CGFloat.pi / CGFloat(self.n) * CGFloat(j))
|
||||
transform = transform.translatedBy(x: -cx, y: -cy)
|
||||
|
||||
p3 = p3.applying(transform)
|
||||
p4 = p4.applying(transform)
|
||||
|
||||
if i == 0 {
|
||||
path.move(to: p1)
|
||||
}
|
||||
|
||||
path.addCurve(to: p3, controlPoint1: p2, controlPoint2: p4)
|
||||
}
|
||||
|
||||
ctx.setFillColor(self.color.cgColor)
|
||||
|
||||
ctx.saveGState()
|
||||
ctx.translateBy(x: rect.width / 2.0, y: rect.height / 2.0)
|
||||
ctx.scaleBy(x: self.scale, y: self.scale)
|
||||
ctx.rotate(by: self.innerRotation)
|
||||
ctx.translateBy(x: -rect.width / 2.0, y: -rect.height / 2.0)
|
||||
|
||||
ctx.addPath(path.cgPath)
|
||||
ctx.drawPath(using: .fill)
|
||||
ctx.restoreGState()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user