more ui jumpiness
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
@@ -250,14 +250,104 @@ struct PreBox: View {
|
|||||||
|
|
||||||
struct Spinner: View {
|
struct Spinner: View {
|
||||||
var color: Color
|
var color: Color
|
||||||
private static let frames = Array("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
|
@Environment(\.font) private var font
|
||||||
|
@Environment(\.fontResolutionContext) private var fontContext
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineView(.periodic(from: .now, by: 0.08)) { context in
|
Text("⠋")
|
||||||
let i = Int(context.date.timeIntervalSinceReferenceDate / 0.08) % Self.frames.count
|
.hidden()
|
||||||
Text(String(Self.frames[i]))
|
.overlay {
|
||||||
.foregroundStyle(color)
|
SpinnerGlyphs(font: (font ?? .body).resolve(in: fontContext).ctFont, color: color)
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SpinnerGlyphs: NSViewRepresentable {
|
||||||
|
let font: CTFont
|
||||||
|
let color: Color
|
||||||
|
|
||||||
|
func makeNSView(context: Context) -> SpinnerView {
|
||||||
|
SpinnerView()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSView(_ v: SpinnerView, context: Context) {
|
||||||
|
v.font = font
|
||||||
|
v.color = color.resolve(in: context.environment).cgColor
|
||||||
|
v.needsLayout = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class SpinnerView: NSView {
|
||||||
|
private struct Key: Equatable {
|
||||||
|
let size: CGSize
|
||||||
|
let scale: CGFloat
|
||||||
|
let font: CTFont
|
||||||
|
let color: CGColor
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let frames = Array("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
|
||||||
|
var font: CTFont?
|
||||||
|
var color: CGColor?
|
||||||
|
private let glyph = CALayer()
|
||||||
|
private var drawn: Key?
|
||||||
|
|
||||||
|
override init(frame: NSRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
wantsLayer = true
|
||||||
|
layer?.addSublayer(glyph)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidChangeBackingProperties() {
|
||||||
|
super.viewDidChangeBackingProperties()
|
||||||
|
needsLayout = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layout() {
|
||||||
|
super.layout()
|
||||||
|
guard let font, let color, let window, bounds.width > 0, bounds.height > 0 else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
let key = Key(size: bounds.size, scale: window.backingScaleFactor, font: font, color: color)
|
||||||
|
if key == drawn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
drawn = key
|
||||||
|
let images = Self.frames.map { render(String($0), key) }
|
||||||
|
CATransaction.begin()
|
||||||
|
CATransaction.setDisableActions(true)
|
||||||
|
glyph.frame = bounds
|
||||||
|
glyph.contentsScale = key.scale
|
||||||
|
glyph.contents = images[0]
|
||||||
|
let spin = CAKeyframeAnimation(keyPath: "contents")
|
||||||
|
spin.values = images
|
||||||
|
spin.keyTimes = (0...images.count).map { NSNumber(value: Double($0) / Double(images.count)) }
|
||||||
|
spin.calculationMode = .discrete
|
||||||
|
spin.duration = 0.08 * Double(images.count)
|
||||||
|
spin.repeatCount = .infinity
|
||||||
|
spin.isRemovedOnCompletion = false
|
||||||
|
glyph.add(spin, forKey: "spin")
|
||||||
|
CATransaction.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func render(_ s: String, _ key: Key) -> CGImage {
|
||||||
|
let ctx = CGContext(data: nil, width: Int(ceil(key.size.width * key.scale)), height: Int(ceil(key.size.height * key.scale)), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
|
||||||
|
ctx.scaleBy(x: key.scale, y: key.scale)
|
||||||
|
let attrs: [NSAttributedString.Key: Any] = [
|
||||||
|
NSAttributedString.Key(kCTFontAttributeName as String): key.font,
|
||||||
|
NSAttributedString.Key(kCTForegroundColorAttributeName as String): key.color,
|
||||||
|
]
|
||||||
|
let line = CTLineCreateWithAttributedString(NSAttributedString(string: s, attributes: attrs))
|
||||||
|
var ascent: CGFloat = 0
|
||||||
|
var descent: CGFloat = 0
|
||||||
|
let width = CTLineGetTypographicBounds(line, &ascent, &descent, nil)
|
||||||
|
ctx.textPosition = CGPoint(x: (key.size.width - width) / 2, y: (key.size.height - ascent - descent) / 2 + descent)
|
||||||
|
CTLineDraw(line, ctx)
|
||||||
|
return ctx.makeImage()!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user