Adds the real MoonWatch implementation on top of the initial Xcode template: new SwiftUI views (MoonDiskView, StarFieldView), the MoonWatchWidget extension, and a Shared module containing the moon phase calculator, shadow shape, and shared asset catalog. Also adds a Swift/Xcode/macOS .gitignore and untracks per-user xcuserdata files. Co-authored-by: Cursor <cursoragent@cursor.com>
63 lines
2.1 KiB
Swift
63 lines
2.1 KiB
Swift
//
|
|
// MoonShadowShape.swift
|
|
// Shared between MoonWatch and MoonWatchWidget.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
/// Geometric terminator: half-circle along the dark limb stitched to a
|
|
/// half-ellipse whose horizontal axis = cos(2π·t)·R. Renders correctly at
|
|
/// new (full dark), quarter (straight terminator), gibbous, and full
|
|
/// (no shadow). Slight `oversizeFactor` lets a parent `Circle` clip keep
|
|
/// the limb sharp while a small `.blur` softens the terminator.
|
|
public struct MoonShadowShape: Shape {
|
|
public var phaseFraction: Double
|
|
public var oversizeFactor: CGFloat
|
|
|
|
public init(phaseFraction: Double, oversizeFactor: CGFloat = 1.04) {
|
|
self.phaseFraction = phaseFraction
|
|
self.oversizeFactor = oversizeFactor
|
|
}
|
|
|
|
public var animatableData: Double {
|
|
get { phaseFraction }
|
|
set { phaseFraction = newValue }
|
|
}
|
|
|
|
public func path(in rect: CGRect) -> Path {
|
|
let r = min(rect.width, rect.height) / 2
|
|
let outer = r * oversizeFactor
|
|
let cx = rect.midX
|
|
let cy = rect.midY
|
|
let raw = phaseFraction.truncatingRemainder(dividingBy: 1)
|
|
let u = raw < 0 ? raw + 1 : raw
|
|
let cosT = CGFloat(cos(2 * Double.pi * u))
|
|
let waning = u > 0.5
|
|
let bulge: CGFloat = waning ? -cosT * outer : cosT * outer
|
|
let kappa: CGFloat = 0.5522847498
|
|
|
|
var path = Path()
|
|
let top = CGPoint(x: cx, y: cy - outer)
|
|
path.move(to: top)
|
|
path.addArc(
|
|
center: CGPoint(x: cx, y: cy),
|
|
radius: outer,
|
|
startAngle: .degrees(270),
|
|
endAngle: .degrees(90),
|
|
clockwise: !waning
|
|
)
|
|
path.addCurve(
|
|
to: CGPoint(x: cx + bulge, y: cy),
|
|
control1: CGPoint(x: cx + bulge * kappa, y: cy + outer),
|
|
control2: CGPoint(x: cx + bulge, y: cy + outer * kappa)
|
|
)
|
|
path.addCurve(
|
|
to: top,
|
|
control1: CGPoint(x: cx + bulge, y: cy - outer * kappa),
|
|
control2: CGPoint(x: cx + bulge * kappa, y: cy - outer)
|
|
)
|
|
path.closeSubpath()
|
|
return path
|
|
}
|
|
}
|