Geteilte Listen-Bausteine, drift-sicher als Pendant zur Compose-Lib: - ManaSectionHeader: großer (.title2/SemiBold) Abschnitts-Header für scrollende Listen, optional einklappbar (Chevron). - ManaPinnedGlyph: gezeichneter Fünfzack-Stern (geteilte Geometrie via ManaStarShape), kein SF-Symbol → pixelgleich zum Android-Pendant. - ManaScreenHeader: Titel jetzt fett (.title.bold). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
2.4 KiB
Swift
64 lines
2.4 KiB
Swift
import ManaTokens
|
||
import SwiftUI
|
||
|
||
/// Fünfzackiger Stern als **gezeichneter** Glyph — natives Pendant zur
|
||
/// Android-`ManaPinnedGlyph` (mana-kotlin-ui). Beide Plattformen zeichnen
|
||
/// **dieselbe Geometrie** (5 Außen-/5 Innenzacken, Innenradius = 0,40 · Außen-
|
||
/// radius, erste Spitze nach oben), damit der „Angepinnt"-Marker pixelgleich
|
||
/// aussieht und nicht auseinanderdriftet — wie bei `SparklesGlyph`/`CopyGlyph`.
|
||
///
|
||
/// Bewusst KEIN SF-Symbol (`star.fill`): das System-Symbol weicht in Strich-
|
||
/// stärke/Proportion vom Material-Pendant ab. Eine geteilte Pfad-Definition ist
|
||
/// die einzige Quelle der Wahrheit.
|
||
public struct ManaPinnedGlyph: View {
|
||
@Environment(\.manaTheme) private var theme
|
||
|
||
private let size: CGFloat
|
||
private let color: Color?
|
||
|
||
/// - Parameters:
|
||
/// - size: Kantenlänge des quadratischen Glyphen. Default 16.
|
||
/// - color: Füllfarbe. `nil` → Theme-`primary`.
|
||
public init(size: CGFloat = 16, color: Color? = nil) {
|
||
self.size = size
|
||
self.color = color
|
||
}
|
||
|
||
public var body: some View {
|
||
ManaStarShape()
|
||
.fill(color ?? theme.colors.primary)
|
||
.frame(width: size, height: size)
|
||
.accessibilityLabel("Angepinnt")
|
||
}
|
||
}
|
||
|
||
/// Pfad eines fünfzackigen Sterns, zentriert in `rect`. Geteilte Geometrie mit
|
||
/// der Android-Canvas-Variante: Außenradius = halbe Kantenlänge, Innenradius =
|
||
/// 0,40 davon, 10 Stützpunkte, Start an der oberen Spitze (−90°).
|
||
public struct ManaStarShape: Shape {
|
||
/// Verhältnis Innen- zu Außenradius. Geteilt mit Android.
|
||
public static let innerRatio: CGFloat = 0.40
|
||
/// Anzahl der Zacken.
|
||
public static let points = 5
|
||
|
||
public init() {}
|
||
|
||
public func path(in rect: CGRect) -> Path {
|
||
let center = CGPoint(x: rect.midX, y: rect.midY)
|
||
let outer = min(rect.width, rect.height) / 2
|
||
let inner = outer * Self.innerRatio
|
||
var path = Path()
|
||
let step = Double.pi / Double(Self.points) // halbe Zacken-Teilung
|
||
for i in 0 ..< (Self.points * 2) {
|
||
let radius = i.isMultiple(of: 2) ? outer : inner
|
||
let angle = -Double.pi / 2 + Double(i) * step
|
||
let point = CGPoint(
|
||
x: center.x + CGFloat(cos(angle)) * radius,
|
||
y: center.y + CGFloat(sin(angle)) * radius
|
||
)
|
||
if i == 0 { path.move(to: point) } else { path.addLine(to: point) }
|
||
}
|
||
path.closeSubpath()
|
||
return path
|
||
}
|
||
}
|