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
 | 
						|
    }
 | 
						|
    @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))
 | 
						|
    }
 | 
						|
}
 |