diff --git a/Coder-Desktop/Coder-Desktop/Views/CircularProgressView.swift b/Coder-Desktop/Coder-Desktop/Views/CircularProgressView.swift index fc359e83..7b143969 100644 --- a/Coder-Desktop/Coder-Desktop/Views/CircularProgressView.swift +++ b/Coder-Desktop/Coder-Desktop/Views/CircularProgressView.swift @@ -8,45 +8,35 @@ struct CircularProgressView: View { var primaryColor: Color = .secondary var backgroundColor: Color = .secondary.opacity(0.3) - @State private var rotation = 0.0 - @State private var trimAmount: CGFloat = 0.15 - var autoCompleteThreshold: Float? var autoCompleteDuration: TimeInterval? var body: some View { ZStack { - // Background circle - Circle() - .stroke(backgroundColor, style: StrokeStyle(lineWidth: strokeWidth, lineCap: .round)) - .frame(width: diameter, height: diameter) - Group { - if let value { - // Determinate gauge + if let value { + ZStack { + Circle() + .stroke(backgroundColor, style: StrokeStyle(lineWidth: strokeWidth, lineCap: .round)) + Circle() .trim(from: 0, to: CGFloat(displayValue(for: value))) .stroke(primaryColor, style: StrokeStyle(lineWidth: strokeWidth, lineCap: .round)) - .frame(width: diameter, height: diameter) .rotationEffect(.degrees(-90)) .animation(autoCompleteAnimation(for: value), value: value) - } else { - // Indeterminate gauge - Circle() - .trim(from: 0, to: trimAmount) - .stroke(primaryColor, style: StrokeStyle(lineWidth: strokeWidth, lineCap: .round)) - .frame(width: diameter, height: diameter) - .rotationEffect(.degrees(rotation)) } + .frame(width: diameter, height: diameter) + + } else { + IndeterminateSpinnerView( + diameter: diameter, + strokeWidth: strokeWidth, + primaryColor: NSColor(primaryColor), + backgroundColor: NSColor(backgroundColor) + ) + .frame(width: diameter, height: diameter) } } .frame(width: diameter + strokeWidth * 2, height: diameter + strokeWidth * 2) - .onAppear { - if value == nil { - withAnimation(.linear(duration: 0.8).repeatForever(autoreverses: false)) { - rotation = 360 - } - } - } } private func displayValue(for value: Float) -> Float { @@ -78,3 +68,55 @@ extension CircularProgressView { return view } } + +// We note a constant >10% CPU usage when using a SwiftUI rotation animation that +// repeats forever, while this implementation, using Core Animation, uses <1% CPU. +struct IndeterminateSpinnerView: NSViewRepresentable { + var diameter: CGFloat + var strokeWidth: CGFloat + var primaryColor: NSColor + var backgroundColor: NSColor + + func makeNSView(context _: Context) -> NSView { + let view = NSView(frame: NSRect(x: 0, y: 0, width: diameter, height: diameter)) + view.wantsLayer = true + + guard let viewLayer = view.layer else { return view } + + let fullPath = NSBezierPath( + ovalIn: NSRect(x: 0, y: 0, width: diameter, height: diameter) + ).cgPath + + let backgroundLayer = CAShapeLayer() + backgroundLayer.path = fullPath + backgroundLayer.strokeColor = backgroundColor.cgColor + backgroundLayer.fillColor = NSColor.clear.cgColor + backgroundLayer.lineWidth = strokeWidth + viewLayer.addSublayer(backgroundLayer) + + let foregroundLayer = CAShapeLayer() + + foregroundLayer.frame = viewLayer.bounds + foregroundLayer.path = fullPath + foregroundLayer.strokeColor = primaryColor.cgColor + foregroundLayer.fillColor = NSColor.clear.cgColor + foregroundLayer.lineWidth = strokeWidth + foregroundLayer.lineCap = .round + foregroundLayer.strokeStart = 0 + foregroundLayer.strokeEnd = 0.15 + viewLayer.addSublayer(foregroundLayer) + + let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation") + rotationAnimation.fromValue = 0 + rotationAnimation.toValue = 2 * Double.pi + rotationAnimation.duration = 1.0 + rotationAnimation.repeatCount = .infinity + rotationAnimation.isRemovedOnCompletion = false + + foregroundLayer.add(rotationAnimation, forKey: "rotationAnimation") + + return view + } + + func updateNSView(_: NSView, context _: Context) {} +}
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: