609 lines
25 KiB
Swift
609 lines
25 KiB
Swift
//
|
|
// PrivadoLoader.swift
|
|
// PrivadoVPN
|
|
//
|
|
// Created by Murad Shabanov on 08.09.2021.
|
|
// Copyright © 2021 Privado LLC. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import AppKit
|
|
|
|
final class PrivadoLoader: NSControl {
|
|
|
|
public var arcColor = NSColor(red: 80, green: 90, blue: 190) {
|
|
didSet {
|
|
self.updateLayer()
|
|
}
|
|
}
|
|
|
|
public var keyholeColor = NSColor(red: 80, green: 90, blue: 190) {
|
|
didSet {
|
|
self.updateLayer()
|
|
}
|
|
}
|
|
|
|
public var arcWidth: CGFloat = 5 {
|
|
didSet {
|
|
self.updateLayer()
|
|
}
|
|
}
|
|
|
|
public var backCircleColor: NSColor = .clear {
|
|
didSet {
|
|
self.updateLayer()
|
|
}
|
|
}
|
|
|
|
public var hasShadow: Bool = false {
|
|
didSet {
|
|
self.updateLayer()
|
|
}
|
|
}
|
|
|
|
public var connecting: Bool = false
|
|
|
|
public var isDisconnected = true {
|
|
didSet {
|
|
if isDisconnected {
|
|
self.startTaphereTimer()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Initialization
|
|
|
|
override init(frame frameRect: NSRect) {
|
|
super.init(frame: frameRect)
|
|
}
|
|
|
|
init(frame frameRect: NSRect, isButton: Bool) {
|
|
super.init(frame: frameRect)
|
|
self.wantsLayer = true
|
|
self.isEnabled = false
|
|
self.layer?.masksToBounds = false
|
|
self.layer?.removeAllAnimations()
|
|
self.layer?.sublayers?.forEach { $0.removeAllAnimations() }
|
|
self.bindUI()
|
|
self.setup()
|
|
self.arcLayers = [upperLargeArc, rightLargeArc, leftLargeArc,
|
|
rightMiddleArc, leftMiddleArc, leftLowArc,
|
|
upperLowArc, bottomLowArc]
|
|
self.largeArcLayers = [upperLargeArc, rightLargeArc, leftLargeArc]
|
|
self.middleArcLayers = [leftMiddleArc, rightMiddleArc]
|
|
self.lowArcLayers = [leftLowArc, upperLowArc, bottomLowArc]
|
|
if isButton {
|
|
self.startTaphereTimer()
|
|
}
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
|
|
private var backgroundCircle = CAShapeLayer()
|
|
private var taphereLayer = CAShapeLayer()
|
|
|
|
private var upperLargeArc = ConnectionLayer()
|
|
private var rightLargeArc = ConnectionLayer()
|
|
private var leftLargeArc = ConnectionLayer()
|
|
|
|
private var rightMiddleArc = ConnectionLayer()
|
|
private var leftMiddleArc = ConnectionLayer()
|
|
|
|
private var leftLowArc = ConnectionLayer()
|
|
private var upperLowArc = ConnectionLayer()
|
|
private var bottomLowArc = ConnectionLayer()
|
|
|
|
private var keyholeLayer = ConnectionLayer()
|
|
|
|
private var arcLayers = [ConnectionLayer]()
|
|
private var largeArcLayers = [ConnectionLayer]()
|
|
private var middleArcLayers = [ConnectionLayer]()
|
|
private var lowArcLayers = [ConnectionLayer]()
|
|
|
|
private var trackingArea: NSTrackingArea?
|
|
private var wasAnimated = false
|
|
private var clockwise = false
|
|
private var currentColor: NSColor?
|
|
private var totalDuration = 0.0
|
|
private let duration = 1.0
|
|
private let secondDuration = 0.5
|
|
|
|
weak var taphereTimer: Timer?
|
|
|
|
private let loaderBack = NSColor(red: 46, green: 51, blue: 80)
|
|
private let tapHereBackColor = NSColor(red: 71, green: 80, blue: 130)
|
|
private let whiteColor = NSColor.white
|
|
let yellowColor = NSColor(red: 255, green: 216, blue: 8)
|
|
let redColor = NSColor(red: 229, green: 93, blue: 129)
|
|
let orangeColor = NSColor(red: 254, green: 147, blue: 67)
|
|
let ligtGreenColor = NSColor(red: 148, green: 215, blue: 80)
|
|
let mediumGreenColor = NSColor(red: 94, green: 215, blue: 117)
|
|
let greenColor = NSColor(red: 40, green: 215, blue: 153)
|
|
|
|
public func startAnimation(clockwise: Bool) {
|
|
|
|
if !self.wasAnimated {
|
|
self.animation(clockwise: clockwise)
|
|
}
|
|
}
|
|
|
|
public func colorChangeAnimaton(duration: Double = 2, color: NSColor) {
|
|
if self.currentColor != color {
|
|
self.keyholeColorChangeAnimation(duration: duration, color: color)
|
|
self.currentColor = color
|
|
}
|
|
}
|
|
|
|
public func animation(clockwise: Bool) {
|
|
self.firstRotation(for: self.upperLargeArc, clockwise: clockwise)
|
|
self.firstRotation(for: self.leftLargeArc, clockwise: clockwise)
|
|
self.firstRotation(for: self.rightLargeArc, clockwise: clockwise)
|
|
self.firstRotation(for: self.leftMiddleArc, clockwise: !clockwise)
|
|
self.firstRotation(for: self.rightMiddleArc, clockwise: !clockwise)
|
|
self.firstRotation(for: self.leftLowArc, clockwise: clockwise)
|
|
self.firstRotation(for: self.bottomLowArc, clockwise: clockwise)
|
|
self.firstRotation(for: self.upperLowArc, clockwise: clockwise)
|
|
self.wasAnimated = true
|
|
self.clockwise = clockwise
|
|
}
|
|
|
|
// maybe will be needed
|
|
public func stopAnimation() {
|
|
// self.pauseLayer(self.lowRingLayer)
|
|
// self.pauseLayer(self.middleRingLayer)
|
|
// self.pauseLayer(self.largeRingLayer)
|
|
}
|
|
|
|
override func updateLayer() {
|
|
super.updateLayer()
|
|
self.updateViewAppearance()
|
|
}
|
|
|
|
override func layout() {
|
|
super.layout()
|
|
self.setup()
|
|
}
|
|
|
|
private func bindUI() {
|
|
self.layer?.addSublayer(self.backgroundCircle)
|
|
self.backgroundCircle.addSublayer(self.rightLargeArc)
|
|
self.backgroundCircle.addSublayer(self.leftLargeArc)
|
|
self.backgroundCircle.addSublayer(self.upperLargeArc)
|
|
self.backgroundCircle.addSublayer(self.leftMiddleArc)
|
|
self.backgroundCircle.addSublayer(self.rightMiddleArc)
|
|
self.backgroundCircle.addSublayer(self.upperLowArc)
|
|
self.backgroundCircle.addSublayer(self.leftLowArc)
|
|
self.backgroundCircle.addSublayer(self.bottomLowArc)
|
|
self.backgroundCircle.addSublayer(self.keyholeLayer)
|
|
}
|
|
|
|
private func setup() {
|
|
self.setupArc(self.upperLargeArc, radius: self.bounds.height * 0.48, startAngle: 150, endAngle: 35)
|
|
self.setupArc(self.leftLargeArc, radius: self.bounds.height * 0.48, startAngle: 225, endAngle: 165)
|
|
self.setupArc(self.rightLargeArc, radius: self.bounds.height * 0.48, startAngle: 15, endAngle: 240)
|
|
self.setupArc(self.leftMiddleArc, radius: self.bounds.height * 0.4, startAngle: 270, endAngle: 75)
|
|
self.setupArc(self.rightMiddleArc, radius: self.bounds.height * 0.4, startAngle: 60, endAngle: 285)
|
|
self.setupArc(self.upperLowArc, radius: self.bounds.height * 0.3, startAngle: 150, endAngle: 330)
|
|
self.setupArc(self.leftLowArc, radius: self.bounds.height * 0.3, startAngle: 225, endAngle: 180)
|
|
self.setupArc(self.bottomLowArc, radius: self.bounds.height * 0.3, startAngle: 315, endAngle: 240)
|
|
self.setupBackCircle()
|
|
self.setupKeyholeLayer()
|
|
}
|
|
|
|
private func setupKeyholeLayer() {
|
|
let path = NSBezierPath()
|
|
path.appendArc(withCenter: NSPoint(x: self.bounds.width * 0.5, y: self.bounds.height * 0.54),
|
|
radius: self.bounds.height * 0.08,
|
|
startAngle: 240, endAngle: 300, clockwise: true)
|
|
|
|
path.line(to: NSPoint(x: self.bounds.width * 0.54, y: self.bounds.height * 0.42))
|
|
|
|
path.appendArc( withCenter: NSPoint(x: self.bounds.width * 0.50, y: self.bounds.height * 0.42), radius: self.bounds.height * 0.04, startAngle: 0, endAngle: 180, clockwise: true)
|
|
|
|
let circle = NSBezierPath()
|
|
circle.appendArc(withCenter: NSPoint(x: self.bounds.midX, y: self.bounds.midY), radius: self.bounds.height * 0.23, startAngle: 0, endAngle: 360)
|
|
|
|
let allPathes = NSBezierPath()
|
|
allPathes.append(path)
|
|
allPathes.append(circle)
|
|
|
|
self.keyholeLayer.frame = self.bounds
|
|
self.keyholeLayer.path = allPathes.CGPath
|
|
self.keyholeLayer.fillColor = self.keyholeColor.cgColor
|
|
self.keyholeLayer.fillRule = .evenOdd
|
|
}
|
|
|
|
private func updateViewAppearance() {
|
|
self.backgroundCircle.fillColor = self.backCircleColor.cgColor
|
|
self.keyholeLayer.fillColor = self.keyholeColor.cgColor
|
|
}
|
|
|
|
private func setupBackCircle() {
|
|
let circle = NSBezierPath()
|
|
circle.appendArc(withCenter: self.bounds.center, radius: self.bounds.height * 0.6, startAngle: 0, endAngle: 360)
|
|
self.backgroundCircle.shadowRadius = 9
|
|
self.backgroundCircle.shadowColor = self.hasShadow ? .black : .clear
|
|
self.backgroundCircle.shadowOpacity = 0.6
|
|
self.backgroundCircle.shadowOffset = CGSize(width: 0, height: 0)
|
|
self.backgroundCircle.lineWidth = 2
|
|
self.backgroundCircle.strokeColor = .clear
|
|
self.backgroundCircle.path = circle.CGPath
|
|
self.backgroundCircle.frame = self.bounds
|
|
}
|
|
|
|
private func setupArc(_ layer: ConnectionLayer, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) {
|
|
let arc = NSBezierPath()
|
|
arc.appendArc(withCenter: NSPoint(x: self.bounds.midX, y: self.bounds.midY), radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
|
|
layer.path = arc.CGPath
|
|
layer.fillColor = nil
|
|
layer.frame = self.bounds
|
|
layer.lineCap = .round
|
|
layer.newColor = self.arcColor
|
|
layer.arcWidth = self.arcWidth
|
|
}
|
|
|
|
// MARK: TaphereTimer
|
|
|
|
func startTaphereTimer() {
|
|
// self.taphereTimer?.invalidate()
|
|
|
|
// self.taphereTimer = Timer.scheduledTimer(timeInterval: 35.0, target: self, selector: #selector(self.timerHandler), userInfo: nil, repeats: false)
|
|
}
|
|
|
|
func stopTaphereTimer() {
|
|
// self.taphereTimer?.invalidate()
|
|
}
|
|
|
|
@objc func timerHandler() {
|
|
if self.isDisconnected {
|
|
self.tapHereAnimation(duration: 1.0, repeatCount: 2.0)
|
|
self.startTaphereTimer()
|
|
} else {
|
|
self.stopTaphereTimer()
|
|
}
|
|
}
|
|
|
|
// MARK: Animations
|
|
|
|
private func keyholeColorChangeAnimation(duration: Double, color: NSColor) {
|
|
let fillColorAnimation = CABasicAnimation(keyPath: "fillColor")
|
|
fillColorAnimation.duration = duration
|
|
fillColorAnimation.fromValue = self.keyholeLayer.fillColor
|
|
fillColorAnimation.toValue = color.cgColor
|
|
self.keyholeColor = color
|
|
self.keyholeLayer.add(fillColorAnimation, forKey: "fillColor")
|
|
}
|
|
|
|
private func arcChangeColorAnimation(layer: ConnectionLayer, duration: Double, delay: Double, from color: NSColor, to newColor: NSColor, repeatCount: Float) {
|
|
|
|
let strokeColorAnimation = CABasicAnimation(keyPath: "strokeColor")
|
|
strokeColorAnimation.duration = duration
|
|
strokeColorAnimation.fromValue = color.cgColor
|
|
strokeColorAnimation.toValue = newColor.cgColor
|
|
strokeColorAnimation.beginTime = 0
|
|
layer.newColor = newColor
|
|
|
|
let animation = CABasicAnimation(keyPath: "strokeColor")
|
|
animation.duration = duration
|
|
animation.fromValue = newColor.cgColor
|
|
animation.toValue = color.cgColor
|
|
animation.beginTime = duration
|
|
layer.newColor = color
|
|
|
|
let fakeAnimation = CABasicAnimation(keyPath: "strokeColor")
|
|
fakeAnimation.duration = delay - duration * 2
|
|
fakeAnimation.fromValue = color.cgColor
|
|
fakeAnimation.toValue = color.cgColor
|
|
fakeAnimation.beginTime = duration * 2
|
|
layer.newColor = color
|
|
|
|
let animationGroup = CAAnimationGroup()
|
|
|
|
animationGroup.animations = [strokeColorAnimation, animation, fakeAnimation]
|
|
animationGroup.repeatCount = repeatCount
|
|
animationGroup.duration = delay + duration * 2
|
|
|
|
layer.add(animationGroup, forKey: "connectingAnimation")
|
|
}
|
|
|
|
private func firstRotation(for layer: CALayer, clockwise: Bool) {
|
|
|
|
let angle = clockwise ? -CGFloat.pi * 2: CGFloat.pi * 2
|
|
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
|
rotateAnimation.fromValue = 0
|
|
rotateAnimation.toValue = angle
|
|
rotateAnimation.duration = 9
|
|
rotateAnimation.repeatCount = .infinity
|
|
layer.add(rotateAnimation, forKey: "layer")
|
|
return
|
|
}
|
|
|
|
private func changeRotationSpeed(speed: Float) {
|
|
self.arcLayers.forEach { layer in
|
|
layer.timeOffset = layer.convertTime(CACurrentMediaTime(), from: nil)
|
|
layer.beginTime = CACurrentMediaTime()
|
|
layer.speed = speed
|
|
}
|
|
}
|
|
|
|
private func removeConnectingAnimation() {
|
|
self.largeArcLayers.forEach { $0.removeAnimation(forKey: "connectingAnimation") }
|
|
self.middleArcLayers.forEach { $0.removeAnimation(forKey: "connectingAnimation") }
|
|
self.lowArcLayers.forEach { $0.removeAnimation(forKey: "connectingAnimation") }
|
|
}
|
|
|
|
public func startConnectionAnimation(completion: @escaping () -> Void) {
|
|
self.isDisconnected = false
|
|
self.backgroundCircle.removeAnimation(forKey: "taphereanimation")
|
|
|
|
self.changeRotationSpeed(speed: 1.8)
|
|
|
|
// pulse red color
|
|
self.arcChangeColorAnimation(layer: self.rightLargeArc, duration: self.duration, delay: 0.0, from: self.arcColor, to: self.redColor, repeatCount: 1)
|
|
self.arcChangeColorAnimation(layer: self.leftMiddleArc, duration: self.duration, delay: 0.0, from: self.arcColor, to: self.redColor, repeatCount: 1)
|
|
self.arcChangeColorAnimation(layer: self.bottomLowArc, duration: self.duration, delay: 0.0, from: self.arcColor, to: self.redColor, repeatCount: 1)
|
|
self.totalDuration += duration * 2
|
|
|
|
// pulse orange color
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + self.totalDuration) {
|
|
guard self.connecting else { return }
|
|
self.keyholeColorChangeAnimation(duration: self.duration, color: self.orangeColor)
|
|
|
|
self.arcChangeColorAnimation(layer: self.rightLargeArc, duration: self.duration, delay: self.totalDuration, from: self.arcColor, to: self.orangeColor, repeatCount: 1)
|
|
self.arcChangeColorAnimation(layer: self.leftMiddleArc, duration: self.duration, delay: self.totalDuration, from: self.arcColor, to: self.orangeColor, repeatCount: 1)
|
|
self.arcChangeColorAnimation(layer: self.bottomLowArc, duration: self.duration, delay: self.totalDuration, from: self.arcColor, to: self.orangeColor, repeatCount: 1)
|
|
}
|
|
|
|
self.totalDuration += duration * 2
|
|
|
|
// pulse yellow color
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + self.totalDuration) {
|
|
guard self.connecting else { return }
|
|
|
|
self.keyholeColorChangeAnimation(duration: self.duration, color: self.yellowColor)
|
|
|
|
self.arcChangeColorAnimation(layer: self.rightLargeArc, duration: self.duration, delay: self.totalDuration, from: self.arcColor, to: self.yellowColor, repeatCount: 1)
|
|
self.arcChangeColorAnimation(layer: self.leftMiddleArc, duration: self.duration, delay: self.totalDuration, from: self.arcColor, to: self.yellowColor, repeatCount: 1)
|
|
self.arcChangeColorAnimation(layer: self.bottomLowArc, duration: self.duration, delay: self.totalDuration, from: self.arcColor, to: self.yellowColor, repeatCount: 1)
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + self.duration * 2) {
|
|
completion()
|
|
}
|
|
}
|
|
|
|
self.totalDuration = 0.0
|
|
}
|
|
|
|
public func tapHereAnimation(duration: Double, repeatCount: Float) {
|
|
|
|
self.configureTapHereLayer()
|
|
|
|
self.backgroundCircle.lineWidth = 2
|
|
|
|
self.backgroundCircle.name = "backCircle"
|
|
self.keyholeLayer.name = "keyholeLayer"
|
|
self.taphereLayer.name = "taphereLayer"
|
|
|
|
// change background circle animation
|
|
let backFillColorAnimation = CABasicAnimation(keyPath: "fillColor")
|
|
backFillColorAnimation.duration = duration
|
|
backFillColorAnimation.fromValue = self.loaderBack.cgColor
|
|
backFillColorAnimation.toValue = self.tapHereBackColor.cgColor
|
|
backFillColorAnimation.beginTime = 0
|
|
|
|
// change keyhole layer animation
|
|
let fillColorAnimation = CABasicAnimation(keyPath: "sublayers.keyholeLayer.fillColor")
|
|
fillColorAnimation.duration = duration
|
|
fillColorAnimation.fromValue = self.keyholeColor.cgColor
|
|
fillColorAnimation.toValue = self.whiteColor.cgColor
|
|
fillColorAnimation.beginTime = 0
|
|
|
|
// pulse stroke line background circle
|
|
let backCircleStrokePulse = CABasicAnimation(keyPath: "strokeColor")
|
|
backCircleStrokePulse.duration = duration
|
|
backCircleStrokePulse.fromValue = NSColor.clear.cgColor
|
|
backCircleStrokePulse.toValue = NSColor.white.cgColor
|
|
backCircleStrokePulse.beginTime = 0
|
|
|
|
// tap here layer appear animatino
|
|
let tapHereShowAniamtion = CABasicAnimation(keyPath: "sublayers.taphereLayer.opacity")
|
|
tapHereShowAniamtion.duration = duration
|
|
tapHereShowAniamtion.fromValue = 0
|
|
tapHereShowAniamtion.toValue = 1
|
|
tapHereShowAniamtion.beginTime = 0
|
|
|
|
|
|
// and evetything back
|
|
let oneAnimation = CABasicAnimation(keyPath: "fillColor")
|
|
oneAnimation.duration = duration
|
|
oneAnimation.fromValue = self.tapHereBackColor.cgColor
|
|
oneAnimation.toValue = self.loaderBack.cgColor
|
|
oneAnimation.beginTime = duration
|
|
|
|
let secondAnimation = CABasicAnimation(keyPath: "sublayers.keyholeLayer.fillColor")
|
|
secondAnimation.duration = duration
|
|
secondAnimation.fromValue = self.whiteColor.cgColor
|
|
secondAnimation.toValue = self.keyholeColor.cgColor
|
|
secondAnimation.beginTime = duration
|
|
|
|
let thirdAnimation = CABasicAnimation(keyPath: "strokeColor")
|
|
thirdAnimation.duration = duration
|
|
thirdAnimation.fromValue = NSColor.white.cgColor
|
|
thirdAnimation.toValue = NSColor.clear.cgColor
|
|
thirdAnimation.beginTime = duration
|
|
|
|
let forthAnimation = CABasicAnimation(keyPath: "sublayers.taphereLayer.opacity")
|
|
forthAnimation.duration = duration
|
|
forthAnimation.fromValue = 1
|
|
forthAnimation.toValue = 0
|
|
forthAnimation.beginTime = duration
|
|
|
|
let animationGroup = CAAnimationGroup()
|
|
animationGroup.animations = [fillColorAnimation, secondAnimation, backCircleStrokePulse, thirdAnimation, tapHereShowAniamtion, forthAnimation, backFillColorAnimation, oneAnimation]
|
|
animationGroup.duration = duration * 2
|
|
animationGroup.repeatCount = repeatCount
|
|
self.backgroundCircle.add(animationGroup, forKey: "taphereanimation")
|
|
}
|
|
|
|
private func configureTapHereLayer() {
|
|
|
|
let backRect = CGRect(x: self.bounds.width / 4, y: self.bounds.height / 7, width: self.bounds.width / 2, height: self.bounds.height / 6)
|
|
|
|
let backLayer = CAShapeLayer()
|
|
let path = NSBezierPath(roundedRect: backRect, xRadius: 7, yRadius: 7)
|
|
backLayer.path = path.CGPath
|
|
backLayer.fillColor = .white
|
|
backLayer.opacity = 0
|
|
|
|
let textLayer = CATextLayer()
|
|
|
|
let width = backRect.width
|
|
let heigght = backRect.height * 0.8
|
|
let x = backRect.origin.x * 1.3
|
|
let y = backRect.origin.y
|
|
|
|
textLayer.frame = CGRect(x: x, y: y, width: width, height: heigght)
|
|
textLayer.string = "Tap here"
|
|
textLayer.fontSize = 7
|
|
if let scale = NSScreen.main?.backingScaleFactor {
|
|
textLayer.contentsScale = scale
|
|
}
|
|
textLayer.foregroundColor = .black
|
|
|
|
backLayer.addSublayer(textLayer)
|
|
self.taphereLayer = backLayer
|
|
self.backgroundCircle.addSublayer(self.taphereLayer)
|
|
}
|
|
|
|
public func connectingAnimation() {
|
|
self.removeConnectingAnimation()
|
|
|
|
var delay = 0.0
|
|
self.largeArcLayers.forEach { layer in
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
|
guard self.connecting else { return }
|
|
self.arcChangeColorAnimation(layer: layer, duration: self.duration, delay: self.duration * 4, from: self.arcColor, to: self.yellowColor, repeatCount: .infinity)
|
|
}
|
|
delay += self.duration * 2
|
|
}
|
|
|
|
var middleDelay = 0.0
|
|
self.middleArcLayers.forEach { layer in
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + middleDelay) {
|
|
guard self.connecting else { return }
|
|
self.arcChangeColorAnimation(layer: layer, duration: self.duration, delay: self.duration * 2, from: self.arcColor, to: self.yellowColor, repeatCount: .infinity)
|
|
}
|
|
middleDelay += self.duration * 2
|
|
}
|
|
|
|
var lowDelay = 0.0
|
|
self.lowArcLayers.forEach { layer in
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + lowDelay) {
|
|
guard self.connecting else { return }
|
|
self.arcChangeColorAnimation(layer: layer, duration: self.duration, delay: self.duration * 4, from: self.arcColor, to: self.yellowColor, repeatCount: .infinity)
|
|
}
|
|
lowDelay += self.duration * 2
|
|
}
|
|
}
|
|
|
|
public func connectedAnimation() {
|
|
self.removeConnectingAnimation()
|
|
|
|
self.changeRotationSpeed(speed: 1.0)
|
|
|
|
self.totalDuration = 0.0
|
|
// pulse green small
|
|
self.keyholeColorChangeAnimation(duration: self.duration, color: self.ligtGreenColor)
|
|
self.lowArcLayers.forEach { self.arcChangeColorAnimation(layer: $0, duration: self.duration, delay: 0, from: self.arcColor, to: self.ligtGreenColor, repeatCount: 1) }
|
|
self.totalDuration += secondDuration
|
|
|
|
// pulse green medium
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + totalDuration) {
|
|
guard !self.isDisconnected else { return }
|
|
self.keyholeColorChangeAnimation(duration: self.duration, color: self.mediumGreenColor)
|
|
self.middleArcLayers.forEach { self.arcChangeColorAnimation(layer: $0, duration: self.duration, delay: 0, from: self.arcColor, to: self.mediumGreenColor, repeatCount: 1) }
|
|
}
|
|
self.totalDuration += secondDuration
|
|
|
|
// pulse green large
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + totalDuration) {
|
|
guard !self.isDisconnected else { return }
|
|
self.keyholeColorChangeAnimation(duration: self.duration, color: self.greenColor)
|
|
self.largeArcLayers.forEach { self.arcChangeColorAnimation(layer: $0, duration: self.duration, delay: 0, from: self.arcColor, to: self.greenColor, repeatCount: 1) }
|
|
}
|
|
self.totalDuration = 0.0
|
|
}
|
|
|
|
public func disconnectAnimation() {
|
|
self.isDisconnected = true
|
|
self.changeRotationSpeed(speed: 1.0)
|
|
self.removeConnectingAnimation()
|
|
self.largeArcLayers.forEach {self.arcChangeColorAnimation(layer: $0, duration: 0.5, delay: 0.0, from: $0.newColor, to: self.redColor, repeatCount: 0)}
|
|
self.middleArcLayers.forEach {self.arcChangeColorAnimation(layer: $0, duration: 0.5, delay: 0.0, from: $0.newColor, to: self.redColor, repeatCount: 0)}
|
|
self.lowArcLayers.forEach {self.arcChangeColorAnimation(layer: $0, duration: 0.5, delay: 0.0, from: $0.newColor, to: self.redColor, repeatCount: 0)}
|
|
self.keyholeColorChangeAnimation(duration: 0.5, color: self.redColor)
|
|
}
|
|
|
|
// maybe will be needed
|
|
private func continueRotation(for layer: CAShapeLayer, clockwise: Bool) {
|
|
|
|
guard let presentation = layer.presentation() else { return }
|
|
let angle = clockwise ? -CGFloat.pi * 2: CGFloat.pi * 2
|
|
let transform = presentation.transform
|
|
let current = atan2(transform.m12, transform.m11)
|
|
|
|
// layer.transform = CATransform3DRotate(largeRingLayer.transform, current, 0, 0, 1)
|
|
layer.setAffineTransform(presentation.affineTransform())
|
|
|
|
layer.removeAllAnimations()
|
|
|
|
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
|
|
|
rotateAnimation.fromValue = current
|
|
rotateAnimation.toValue = current + angle
|
|
rotateAnimation.duration = 5
|
|
rotateAnimation.repeatCount = .infinity
|
|
|
|
layer.add(rotateAnimation, forKey: "continue")
|
|
}
|
|
|
|
private func pauseLayer(_ layer: CALayer) {
|
|
let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil)
|
|
layer.speed = 0
|
|
layer.timeOffset = pausedTime
|
|
}
|
|
|
|
private func resumeLayer(_ layer: CALayer) {
|
|
let pausedTime = layer.timeOffset
|
|
layer.speed = 1
|
|
layer.timeOffset = 0
|
|
layer.beginTime = 0
|
|
let timeSincePaused = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
|
|
layer.beginTime = timeSincePaused
|
|
}
|
|
|
|
// MARK: Event Response
|
|
|
|
override func mouseDown(with event: NSEvent) {
|
|
guard self.isEnabled else { return }
|
|
if let selector = self.action {
|
|
NSApp.sendAction(selector, to: self.target, from: self)
|
|
}
|
|
}
|
|
|
|
override func updateTrackingAreas() {
|
|
super.updateTrackingAreas()
|
|
|
|
if let trackingArea = self.trackingArea {
|
|
self.removeTrackingArea(trackingArea)
|
|
}
|
|
|
|
let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways]
|
|
let trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil)
|
|
self.addTrackingArea(trackingArea)
|
|
}
|
|
}
|