first commit

This commit is contained in:
fototeddy
2026-09-04 21:30:51 +02:00
commit da64b1b6bd
160 changed files with 20664 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
import SwiftUI
/// Zeigt die Neigung am Fahrzeug selbst, statt an einer abstrakten Blase.
///
/// Zwei Ansichten, jede um ihre Achse gekippt:
///
/// * **Seitenansicht** für die Längsneigung. Die Front zeigt nach links, das
/// Heck nach rechts.
/// * **Heckansicht** für die Querneigung. Sie teilt die Blickrichtung des
/// Fahrers, links im Bild ist also links am Fahrzeug bei einer
/// Frontansicht wäre es seitenverkehrt.
///
/// In beiden Fällen wird gegen den mathematischen Drehsinn gekippt: Steht das
/// Heck höher, muss die rechte Bildseite nach oben.
struct VehicleTiltView: View {
let pitch: Double?
let roll: Double?
var body: some View {
VStack(spacing: 20) {
tiltPanel(image: "VehicleSide",
angle: pitch,
title: "Längs",
lowerLabel: "Front",
upperLabel: "Heck",
aspect: VehicleTilt.sideAspect)
tiltPanel(image: "VehicleRear",
angle: roll,
title: "Quer",
lowerLabel: "links",
upperLabel: "rechts",
aspect: VehicleTilt.rearAspect)
// Ohne diesen Hinweis nähme man den Bildwinkel für den echten.
Text(String(format: "Neigung %.0f-fach überhöht dargestellt "
+ "sonst wäre sie kaum zu erkennen. Die Gradzahlen sind echt.",
VehicleTilt.exaggeration))
.font(.caption2)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
}
}
private func tiltPanel(image: String,
angle: Double?,
title: String,
lowerLabel: String,
upperLabel: String,
aspect: Double) -> some View {
VStack(spacing: 8) {
HStack {
Text(title)
.font(.subheadline.weight(.medium))
Spacer()
Text(angle.map { String(format: "%.1f°", $0) } ?? "")
.font(.subheadline.weight(.semibold).monospacedDigit())
.foregroundStyle(VehicleTilt.colour(for: angle))
}
ZStack {
// Waagerechte als Bezug ohne sie ist eine kleine Neigung
// nicht einzuschätzen.
Rectangle()
.fill(Color.secondary.opacity(0.35))
.frame(height: 1)
Image(image)
.resizable()
.scaledToFit()
.foregroundStyle(VehicleTilt.colour(for: angle))
.aspectRatio(aspect, contentMode: .fit)
.rotationEffect(.degrees(-(angle ?? 0) * VehicleTilt.exaggeration))
.animation(.spring(duration: 0.4), value: angle)
.opacity(angle == nil ? 0.3 : 1)
}
.frame(height: 110)
HStack {
Text(lowerLabel)
Spacer()
Text(upperLabel)
}
.font(.caption2)
.foregroundStyle(.secondary)
}
}
}
/// Umschalter zwischen den beiden Darstellungen, gemerkt über Starts hinweg.
enum LevelDisplayStyle: String, CaseIterable, Identifiable {
case bubble
case vehicle
var id: String { rawValue }
var title: String {
switch self {
case .bubble: return "Libelle"
case .vehicle: return "Fahrzeug"
}
}
}