mirror of
https://github.com/EmergeTools/Pow.git
synced 2026-03-25 08:55:50 +00:00
139 lines
3.7 KiB
Swift
139 lines
3.7 KiB
Swift
#if !os(tvOS)
|
|
import SwiftUI
|
|
|
|
@available(watchOS 9.0, *)
|
|
struct AngleControl<Label: View>: View {
|
|
@Binding
|
|
var angle: Angle
|
|
|
|
var label: Label
|
|
|
|
init(angle: Binding<Angle>, @ViewBuilder label: () -> Label) {
|
|
self._angle = angle
|
|
self.label = label()
|
|
}
|
|
|
|
@State
|
|
private var lastAngle: Angle = .zero
|
|
|
|
@GestureState
|
|
private var dragAngle: Angle = .zero
|
|
|
|
@Environment(\.controlSize)
|
|
private var controlSize
|
|
|
|
private var dragGesture: some Gesture {
|
|
DragGesture(minimumDistance: 0)
|
|
.updating($dragAngle) { value, state, _ in
|
|
state = .degrees(-value.translation.height * 2)
|
|
}
|
|
.onChanged { value in
|
|
angle = lastAngle + .degrees(-value.translation.height * 2)
|
|
}
|
|
.onEnded { value in
|
|
angle = lastAngle + .degrees(-value.translation.height * 2)
|
|
lastAngle = angle
|
|
}
|
|
}
|
|
|
|
private var size: CGFloat {
|
|
switch controlSize {
|
|
case .mini: return 32
|
|
case .small: return 38
|
|
case .regular: return 44
|
|
case .large: return 54
|
|
#if compiler(>=5.9)
|
|
// ControlSize.extraLarge is only available from Xcode 15 which comes with Swift 5.9
|
|
case .extraLarge: return 54
|
|
#endif
|
|
@unknown default: return 44
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
let content = ZStack {
|
|
Circle()
|
|
.fill(.gray.opacity(dragAngle == .zero ? 0.1 : 0.2))
|
|
.animation(.easeOut(duration: dragAngle == .zero ? 0.3 : 0.05), value: dragAngle == .zero)
|
|
Circle()
|
|
.stroke(.quaternary)
|
|
ZStack(alignment: .leading) {
|
|
Color.clear
|
|
Capsule(style: .continuous)
|
|
.fill(.tint)
|
|
.frame(width: size / 4, height: 2)
|
|
.padding(4)
|
|
}
|
|
.rotationEffect(angle)
|
|
}
|
|
.frame(width: size, height: size)
|
|
|
|
if #available(iOS 16.0, macOS 13, *) {
|
|
LabeledContent {
|
|
content
|
|
} label: {
|
|
label
|
|
}
|
|
.gesture(dragGesture)
|
|
} else {
|
|
content
|
|
.gesture(dragGesture)
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(watchOS 9.0, *)
|
|
extension AngleControl where Label == Text {
|
|
init(_ title: some StringProtocol, angle: Binding<Angle>) {
|
|
self._angle = angle
|
|
self.label = Text(title)
|
|
}
|
|
|
|
init(_ titleKey: LocalizedStringKey, angle: Binding<Angle>) {
|
|
self._angle = angle
|
|
self.label = Text(titleKey)
|
|
}
|
|
|
|
init(angle: Binding<Angle>) {
|
|
self._angle = angle
|
|
let measurement = Measurement<UnitAngle>(value: angle.wrappedValue.degrees, unit: .degrees)
|
|
let formatted = measurement
|
|
.formatted(
|
|
.measurement(
|
|
width: .narrow,
|
|
numberFormatStyle: .number.precision(.fractionLength(0))
|
|
)
|
|
)
|
|
self.label = Text(formatted)
|
|
}
|
|
}
|
|
|
|
@available(watchOS 9.0, *)
|
|
struct AngleControl_Previews: PreviewProvider {
|
|
struct Preview: View {
|
|
@State var angle: Angle = .zero
|
|
|
|
var body: some View {
|
|
VStack {
|
|
Rectangle()
|
|
.fill(.red)
|
|
.frame(width: 100, height: 1)
|
|
.rotationEffect(angle)
|
|
|
|
AngleControl(angle: $angle)
|
|
}
|
|
.monospacedDigit()
|
|
}
|
|
}
|
|
|
|
static var previews: some View {
|
|
VStack(spacing: 32) {
|
|
ForEach(ControlSize.allCases, id: \.self) { size in
|
|
Preview()
|
|
.controlSize(size)
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
}
|
|
#endif
|