Add MoonWatch app, widget extension, shared module, and .gitignore
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>
This commit is contained in:
62
Shared/MoonShadowShape.swift
Normal file
62
Shared/MoonShadowShape.swift
Normal file
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user