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:
2026-05-07 21:05:16 -07:00
parent 1698e3ce15
commit 87f8c7173c
17 changed files with 1211 additions and 24 deletions

View 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
}
}