Initial commit for first version of project

This commit is contained in:
Dennis Nemec
2026-09-02 20:54:36 +02:00
parent 3d9f1ab6bc
commit 992bb32025
56 changed files with 5139 additions and 31 deletions

View File

@ -0,0 +1,144 @@
//
// TankManipulationPanel.swift
// GasTankAR
//
// Created by Dennis Nemec on 28.08.26.
//
import SwiftUI
/// Steuer-Panel für den ausgewählten Tank: Höhe (↑↓), Verschieben auf dem Boden
/// (vor/zurück/links/rechts) und Drehen (⟲⟳). Erscheint von unten; Pinch bleibt möglich.
/// Buttons unterstützen Gedrückt-halten für kontinuierliche Anpassung.
struct TankManipulationPanel: View {
let controller: ARSessionController
var body: some View {
VStack(spacing: 14) {
HStack {
Text("Tank anpassen")
.font(.headline)
Spacer()
Button {
controller.deselectTank()
} label: {
Image(systemName: "xmark.circle.fill")
.font(.title2)
.foregroundStyle(.secondary)
}
.buttonStyle(.plain)
.accessibilityLabel("Schließen")
}
HStack(alignment: .center, spacing: 24) {
heightGroup
moveGroup
rotateGroup
}
Button("Neu platzieren", systemImage: "arrow.up.and.down.and.arrow.left.and.right") {
controller.reposition()
}
.buttonStyle(.bordered)
}
.padding()
.background(.ultraThinMaterial, in: .rect(cornerRadius: 16))
}
// MARK: - Gruppen
private var heightGroup: some View {
controlGroup(title: "Höhe") {
VStack(spacing: 8) {
holdButton("chevron.up", label: "Höher") { controller.adjustTankHeight(up: true) }
holdButton("chevron.down", label: "Tiefer") { controller.adjustTankHeight(up: false) }
}
}
}
private var moveGroup: some View {
controlGroup(title: "Verschieben") {
VStack(spacing: 8) {
holdButton("arrow.up", label: "Vorwärts") { controller.moveTank(forward: true) }
HStack(spacing: 8) {
holdButton("arrow.left", label: "Nach links") { controller.moveTank(left: true) }
holdButton("arrow.right", label: "Nach rechts") { controller.moveTank(left: false) }
}
holdButton("arrow.down", label: "Rückwärts") { controller.moveTank(forward: false) }
}
}
}
private var rotateGroup: some View {
controlGroup(title: "Drehen") {
HStack(spacing: 8) {
holdButton("rotate.left", label: "Gegen den Uhrzeigersinn") { controller.rotateTank(clockwise: false) }
holdButton("rotate.right", label: "Im Uhrzeigersinn") { controller.rotateTank(clockwise: true) }
}
}
}
private func controlGroup<Content: View>(title: String, @ViewBuilder content: () -> Content) -> some View {
VStack(spacing: 8) {
content()
Text(title)
.font(.caption)
.foregroundStyle(.secondary)
}
}
private func holdButton(_ systemImage: String, label: String, action: @escaping () -> Void) -> some View {
HoldRepeatButton(
systemImage: systemImage,
accessibilityLabel: label,
action: action,
onRelease: { controller.commitTankEdit() }
)
}
}
/// Runder Button, der bei Gedrückt-halten die Aktion wiederholt (Tastenwiederholung).
/// Einzel-Tap = ein Schritt; Halten = fortlaufend. Beim Loslassen wird `onRelease` aufgerufen.
private struct HoldRepeatButton: View {
let systemImage: String
let accessibilityLabel: String
let action: () -> Void
let onRelease: () -> Void
@State private var repeatTask: Task<Void, Never>?
private static let initialDelay: Duration = .milliseconds(350)
private static let repeatInterval: Duration = .milliseconds(60)
var body: some View {
Image(systemName: systemImage)
.font(.title3)
.frame(width: 48, height: 48)
.background(.ultraThinMaterial, in: .circle)
.contentShape(.circle)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in if repeatTask == nil { start() } }
.onEnded { _ in stop() }
)
.accessibilityLabel(accessibilityLabel)
.accessibilityAddTraits(.isButton)
}
private func start() {
action() // sofortiger erster Schritt (Einzel-Tap)
repeatTask = Task { @MainActor in
try? await Task.sleep(for: Self.initialDelay)
while !Task.isCancelled {
action()
try? await Task.sleep(for: Self.repeatInterval)
}
}
}
private func stop() {
repeatTask?.cancel()
repeatTask = nil
onRelease()
}
}