97 lines
3.2 KiB
Swift
97 lines
3.2 KiB
Swift
//
|
|
// SpinWheelView.swift
|
|
// SodaLive
|
|
//
|
|
// Created by klaus on 2023/12/07.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct Triangle: Shape {
|
|
public func path(in rect: CGRect) -> Path {
|
|
var path = Path()
|
|
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
|
|
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
|
|
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
|
|
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
|
|
path.addCurve(to: CGPoint(x: rect.midX, y: rect.minY), control1: CGPoint(x: rect.maxX, y: rect.minY), control2: CGPoint(x: rect.midX, y: rect.minY))
|
|
return path
|
|
}
|
|
}
|
|
|
|
struct SpinWheelPointer: View {
|
|
var pointerColor: Color
|
|
var body: some View {
|
|
Triangle().frame(width: 50, height: 50)
|
|
.foregroundColor(pointerColor).cornerRadius(24)
|
|
.rotationEffect(.init(degrees: 180))
|
|
.shadow(color: Color(hex: "212121").opacity(0.5), radius: 5, x: 0.0, y: 1.0)
|
|
}
|
|
}
|
|
|
|
struct SpinWheelBolt: View {
|
|
var body: some View {
|
|
ZStack {
|
|
Circle().frame(width: 28, height: 28)
|
|
.foregroundColor(Color(hex: "F4C25B"))
|
|
Circle().frame(width: 18, height: 18)
|
|
.foregroundColor(Color(hex: "FFD25A"))
|
|
.shadow(color: Color(hex: "404040").opacity(0.35), radius: 3, x: 0.0, y: 1.0)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SpinWheelView: View {
|
|
|
|
var data: [Double], labels: [String]
|
|
|
|
private let colors: [Color]
|
|
private let sliceOffset: Double = -.pi / 2
|
|
@available(macOS 10.15, *)
|
|
|
|
init(data: [Double], labels: [String], colors: [Color]) {
|
|
self.data = data
|
|
self.labels = labels
|
|
self.colors = colors.shuffled()
|
|
}
|
|
@available(macOS 10.15.0, *)
|
|
|
|
var body: some View {
|
|
GeometryReader { geo in
|
|
ZStack(alignment: .center) {
|
|
ForEach(0..<data.count, id: \.self) { index in
|
|
SpinWheelCell(startAngle: startAngle(for: index), endAngle: endAngle(for: index))
|
|
.fill(colors[index % colors.count])
|
|
Text(labels[index]).font(.custom(Font.medium.rawValue, size: 13)).foregroundColor(Color.white)
|
|
.offset(viewOffset(for: index, in: geo.size)).zIndex(1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func startAngle(for index: Int) -> Double {
|
|
switch index {
|
|
case 0: return sliceOffset
|
|
default:
|
|
let ratio: Double = data[..<index].reduce(0.0, +) / data.reduce(0.0, +)
|
|
return sliceOffset + 2 * .pi * ratio
|
|
}
|
|
}
|
|
|
|
private func endAngle(for index: Int) -> Double {
|
|
switch index {
|
|
case data.count - 1: return sliceOffset + 2 * .pi
|
|
default:
|
|
let ratio: Double = data[..<(index + 1)].reduce(0.0, +) / data.reduce(0.0, +)
|
|
return sliceOffset + 2 * .pi * ratio
|
|
}
|
|
}
|
|
|
|
private func viewOffset(for index: Int, in size: CGSize) -> CGSize {
|
|
let radius = min(size.width, size.height) / 3
|
|
let dataRatio = (2 * data[..<index].reduce(0, +) + data[index]) / (2 * data.reduce(0, +))
|
|
let angle = CGFloat(sliceOffset + 2 * .pi * dataRatio)
|
|
return CGSize(width: radius * cos(angle), height: radius * sin(angle))
|
|
}
|
|
}
|