Add help view and refine AR tank placement
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Vertrieb-AppIcon-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
@ -12,6 +13,7 @@
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "Vertrieb-AppIcon-Dark-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
@ -23,6 +25,7 @@
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"filename" : "Vertrieb-AppIcon-Tinted-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 825 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 964 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 839 KiB |
@ -15,7 +15,9 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
#Preview {
|
||||
ProjectListView()
|
||||
.modelContainer(PreviewSupport.container)
|
||||
}
|
||||
#endif
|
||||
|
||||
BIN
GasTankAR/Icon/Vertrieb-AppIcon-1024.png
Normal file
BIN
GasTankAR/Icon/Vertrieb-AppIcon-1024.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 825 KiB |
BIN
GasTankAR/Icon/Vertrieb-AppIcon-Dark-1024.png
Normal file
BIN
GasTankAR/Icon/Vertrieb-AppIcon-Dark-1024.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 964 KiB |
BIN
GasTankAR/Icon/Vertrieb-AppIcon-Tinted-1024.png
Normal file
BIN
GasTankAR/Icon/Vertrieb-AppIcon-Tinted-1024.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 839 KiB |
@ -16,6 +16,10 @@ nonisolated struct TankDefinition: Codable, Hashable, Identifiable {
|
||||
let placement: TankPlacement
|
||||
/// Pfad bzw. Dateiname des 3D-Modells im App-Bundle (z. B. `DG-2700_wh.usdz`).
|
||||
let modelPath: String
|
||||
/// X-Koordinate der Domschachtmitte im lokalen Modellraum (Meter).
|
||||
/// Aus der oberen Schachtkante: (kleinstes X + größtes X) / 2.
|
||||
/// Die Tank-Längsachse der gelieferten USDZ-Modelle liegt auf X.
|
||||
let domeCenterX: Float
|
||||
|
||||
/// Stabile Identität aus der Kombination der Auswahlmerkmale.
|
||||
var id: String { "\(size.rawValue)_\(color.rawValue)_\(placement.rawValue)" }
|
||||
|
||||
@ -10,14 +10,14 @@
|
||||
"olive": "Olivgrün"
|
||||
},
|
||||
"tanks": [
|
||||
{ "size": "t1_2", "color": "white", "placement": "aboveGround", "modelPath": "DG-2700_wh.usdz" },
|
||||
{ "size": "t1_2", "color": "green", "placement": "aboveGround", "modelPath": "DG-2700_mi.usdz" },
|
||||
{ "size": "t1_2", "color": "olive", "placement": "belowGround", "modelPath": "DG-2700_ol.usdz" },
|
||||
{ "size": "t2_1", "color": "white", "placement": "aboveGround", "modelPath": "DG-4850_wh.usdz" },
|
||||
{ "size": "t2_1", "color": "green", "placement": "aboveGround", "modelPath": "DG-4850_mi.usdz" },
|
||||
{ "size": "t2_1", "color": "olive", "placement": "belowGround", "modelPath": "DG-4850_ol.usdz" },
|
||||
{ "size": "t2_9", "color": "white", "placement": "aboveGround", "modelPath": "DG-6400_wh.usdz" },
|
||||
{ "size": "t2_9", "color": "green", "placement": "aboveGround", "modelPath": "DG-6400_mi.usdz" },
|
||||
{ "size": "t2_9", "color": "olive", "placement": "belowGround", "modelPath": "DG-6400_ol.usdz" }
|
||||
{ "size": "t1_2", "color": "white", "placement": "aboveGround", "modelPath": "DG-2700_wh.usdz", "domeCenterX": -0.426 },
|
||||
{ "size": "t1_2", "color": "green", "placement": "aboveGround", "modelPath": "DG-2700_mi.usdz", "domeCenterX": -0.426 },
|
||||
{ "size": "t1_2", "color": "olive", "placement": "belowGround", "modelPath": "DG-2700_ol.usdz", "domeCenterX": -0.498 },
|
||||
{ "size": "t2_1", "color": "white", "placement": "aboveGround", "modelPath": "DG-4850_wh.usdz", "domeCenterX": -1.317 },
|
||||
{ "size": "t2_1", "color": "green", "placement": "aboveGround", "modelPath": "DG-4850_mi.usdz", "domeCenterX": -1.317 },
|
||||
{ "size": "t2_1", "color": "olive", "placement": "belowGround", "modelPath": "DG-4850_ol.usdz", "domeCenterX": -1.395 },
|
||||
{ "size": "t2_9", "color": "white", "placement": "aboveGround", "modelPath": "DG-6400_wh.usdz", "domeCenterX": -1.939 },
|
||||
{ "size": "t2_9", "color": "green", "placement": "aboveGround", "modelPath": "DG-6400_mi.usdz", "domeCenterX": -1.939 },
|
||||
{ "size": "t2_9", "color": "olive", "placement": "belowGround", "modelPath": "DG-6400_ol.usdz", "domeCenterX": -2.018 }
|
||||
]
|
||||
}
|
||||
|
||||
@ -117,6 +117,7 @@ struct DocuframeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
#Preview {
|
||||
let sample = PreviewSupport.galleryProjectContainer()
|
||||
return NavigationStack {
|
||||
@ -124,3 +125,4 @@ struct DocuframeView: View {
|
||||
}
|
||||
.modelContainer(sample.container)
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -607,6 +607,8 @@ private struct PhotoLocationCard: View {
|
||||
String(format: "%.5f, %.5f", coordinate.latitude, coordinate.longitude)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
#Preview("Galerie") {
|
||||
let sample = PreviewSupport.galleryProjectContainer()
|
||||
return NavigationStack {
|
||||
@ -619,3 +621,4 @@ private struct PhotoLocationCard: View {
|
||||
let photo = PreviewSupport.samplePhoto()
|
||||
PhotoViewer(photo: photo, photos: [photo]) {}
|
||||
}
|
||||
#endif
|
||||
|
||||
95
GasTankAR/Views/HelpView.swift
Normal file
95
GasTankAR/Views/HelpView.swift
Normal file
@ -0,0 +1,95 @@
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
struct HelpView: View {
|
||||
var body: some View {
|
||||
List {
|
||||
HelpAppInfoSection()
|
||||
|
||||
HelpTopicSection(
|
||||
title: "Projekte und Tankauswahl",
|
||||
systemImage: "folder",
|
||||
explanation: "Tippe in der Projektübersicht auf +, um ein Projekt anzulegen. Öffne ein Projekt, um seine Daten anzusehen und den gewünschten Tank auszuwählen. Über „AR-Modus starten“ wechselst du zur Darstellung in der realen Umgebung."
|
||||
)
|
||||
|
||||
HelpTopicSection(
|
||||
title: "Tank in der Umgebung platzieren",
|
||||
systemImage: "arkit",
|
||||
explanation: "Erlaube den Kamerazugriff und bewege das Gerät langsam, damit die Umgebung erkannt wird. Richte das Fadenkreuz auf den gewünschten Standort und tippe auf „Tank platzieren“. Beachte dabei die Hinweise auf dem Bildschirm."
|
||||
)
|
||||
|
||||
HelpTopicSection(
|
||||
title: "Tank anpassen",
|
||||
systemImage: "move.3d",
|
||||
explanation: "Tippe auf den platzierten Tank, um die Bedienelemente zu öffnen. Damit kannst du seine Position, Höhe und Drehung anpassen oder ihn neu platzieren."
|
||||
)
|
||||
|
||||
HelpTopicSection(
|
||||
title: "Werkzeuge im AR-Modus",
|
||||
systemImage: "ruler",
|
||||
explanation: "Das Schild-Symbol blendet die Sicherheitsabstände ein oder aus. Über das Lineal öffnest du die Funktion zum Ausmessen. Mit „Rohrführung“ kannst du einen Leitungsverlauf darstellen. Folge für die einzelnen Schritte den Hinweisen im AR-Modus."
|
||||
)
|
||||
|
||||
HelpTopicSection(
|
||||
title: "Fotos und Galerie",
|
||||
systemImage: "camera",
|
||||
explanation: "Mit dem Kamera-Symbol im AR-Modus nimmst du die aktuelle Ansicht auf. Die gespeicherten Aufnahmen findest du im jeweiligen Projekt im Reiter „Galerie“."
|
||||
)
|
||||
|
||||
HelpTopicSection(
|
||||
title: "Darstellung und Erkennung",
|
||||
systemImage: "gearshape",
|
||||
explanation: "In den Einstellungen kannst du auf unterstützten Geräten LiDAR-Funktionen aktivieren und die Belichtung der oberirdischen Tanks anpassen. Änderungen werden beim nächsten Start des AR-Modus übernommen. Falls die Umgebung nicht erkannt wird, bewege das Gerät langsam weiter und achte auf gute Beleuchtung und erkennbare Strukturen."
|
||||
)
|
||||
}
|
||||
.navigationTitle("Hilfe")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
||||
private struct HelpAppInfoSection: View {
|
||||
private let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
||||
private let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
|
||||
|
||||
var body: some View {
|
||||
Section("Über die App") {
|
||||
Text("GasTankAR")
|
||||
.font(.headline)
|
||||
Text("Mit GasTankAR kannst du Gastanks in deiner realen Umgebung visualisieren und die Ansichten in Projekten festhalten.")
|
||||
LabeledContent("Version") {
|
||||
if let version, !version.isEmpty {
|
||||
Text(verbatim: version)
|
||||
} else {
|
||||
Text("Nicht verfügbar")
|
||||
}
|
||||
}
|
||||
if let build, !build.isEmpty {
|
||||
LabeledContent("Build") {
|
||||
Text(verbatim: build)
|
||||
}
|
||||
}
|
||||
Text("Diese App wurde von der Firma SoftVisor GmbH erstellt.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct HelpTopicSection: View {
|
||||
let title: LocalizedStringKey
|
||||
let systemImage: String
|
||||
let explanation: LocalizedStringKey
|
||||
|
||||
var body: some View {
|
||||
Section {
|
||||
Text(explanation)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
} header: {
|
||||
Label(title, systemImage: systemImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationStack {
|
||||
HelpView()
|
||||
}
|
||||
}
|
||||
@ -58,6 +58,7 @@ struct ProjectDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
#Preview {
|
||||
let sample = PreviewSupport.singleProjectContainer()
|
||||
|
||||
@ -66,3 +67,4 @@ struct ProjectDetailView: View {
|
||||
}
|
||||
.modelContainer(sample.container)
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -90,6 +90,7 @@ struct ProjectListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
#Preview("Gefüllt") {
|
||||
ProjectListView()
|
||||
.modelContainer(PreviewSupport.container)
|
||||
@ -99,3 +100,4 @@ struct ProjectListView: View {
|
||||
ProjectListView()
|
||||
.modelContainer(PreviewSupport.makeEmptyContainer())
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -8,15 +8,30 @@
|
||||
import SwiftUI
|
||||
import ARKit
|
||||
|
||||
/// Zentraler Schlüssel für die LiDAR-Einstellung (in AppStorage/UserDefaults).
|
||||
/// Zentrale Schlüssel für AppStorage/UserDefaults.
|
||||
enum SettingsKeys {
|
||||
static let lidarFeaturesEnabled = "lidarFeaturesEnabled"
|
||||
static let arExposure = "arExposure"
|
||||
static let devTankResizingEnabled = "devTankResizingEnabled"
|
||||
}
|
||||
|
||||
/// Eine gespeicherte Entwickler-Option darf in Release niemals die Tankgröße ändern.
|
||||
enum TankResizePolicy {
|
||||
static func isEnabled(devFlag: Bool) -> Bool {
|
||||
#if DEBUG
|
||||
devFlag
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@AppStorage(SettingsKeys.lidarFeaturesEnabled) private var lidarEnabled = false
|
||||
@AppStorage(SettingsKeys.arExposure) private var arExposure = 1.0
|
||||
#if DEBUG
|
||||
@AppStorage(SettingsKeys.devTankResizingEnabled) private var devTankResizingEnabled = false
|
||||
#endif
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
/// LiDAR ist verfügbar, wenn das Gerät Scene-Reconstruction (Mesh) unterstützt.
|
||||
@ -56,6 +71,22 @@ struct SettingsView: View {
|
||||
} footer: {
|
||||
Text("Passt die zusätzliche Beleuchtung der oberirdischen Tanks an. Änderungen greifen beim nächsten Start des AR-Modus.")
|
||||
}
|
||||
#if DEBUG
|
||||
Section {
|
||||
Toggle("Tankgröße per Geste ändern", isOn: $devTankResizingEnabled)
|
||||
} header: {
|
||||
Text("Entwicklung")
|
||||
} footer: {
|
||||
Text("Nur im Debug-Build verfügbar. Die Änderung greift beim nächsten Start des AR-Modus.")
|
||||
}
|
||||
#endif
|
||||
Section {
|
||||
NavigationLink {
|
||||
HelpView()
|
||||
} label: {
|
||||
Label("Hilfe", systemImage: "questionmark.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Einstellungen")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
||||
@ -15,6 +15,9 @@ struct ARExperienceView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@AppStorage(SettingsKeys.lidarFeaturesEnabled) private var lidarEnabled = false
|
||||
@AppStorage(SettingsKeys.arExposure) private var arExposure = 1.0
|
||||
#if DEBUG
|
||||
@AppStorage(SettingsKeys.devTankResizingEnabled) private var devTankResizingEnabled = false
|
||||
#endif
|
||||
|
||||
let project: Project
|
||||
|
||||
@ -95,10 +98,16 @@ struct ARExperienceView: View {
|
||||
.task {
|
||||
guard controller == nil else { return }
|
||||
let persistence = ProjectPersistenceAdapter(project: project, modelContext: modelContext)
|
||||
#if DEBUG
|
||||
let devResizing = devTankResizingEnabled
|
||||
#else
|
||||
let devResizing = false
|
||||
#endif
|
||||
controller = ARSessionController(
|
||||
persistence: persistence,
|
||||
useLiDAR: lidarEnabled,
|
||||
arExposure: arExposure
|
||||
arExposure: arExposure,
|
||||
devTankResizingEnabled: devResizing
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,8 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
|
||||
}
|
||||
/// Ob die Sicherheitsabstände (Ringe um den Tank) eingeblendet sind.
|
||||
private(set) var showSafetyZones = false
|
||||
/// Nur für Debug-Builds mit eingeschalteter Entwickler-Option.
|
||||
let allowsTankResizing: Bool
|
||||
/// Wird bei jedem erfolgreichen Foto erhöht – die UI nutzt das für ein kurzes Aufblitzen.
|
||||
private(set) var snapshotFlashCounter = 0
|
||||
/// Angelegte Strecken (nur für die laufende Sitzung gehalten).
|
||||
@ -127,9 +129,15 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
|
||||
private static let anchorDetectionTimeout: Duration = .seconds(20)
|
||||
private static let relocationTimeout: Duration = .seconds(15)
|
||||
|
||||
init(persistence: ARProjectPersisting, useLiDAR: Bool, arExposure: Double = 1.0) {
|
||||
init(
|
||||
persistence: ARProjectPersisting,
|
||||
useLiDAR: Bool,
|
||||
arExposure: Double = 1.0,
|
||||
devTankResizingEnabled: Bool = false
|
||||
) {
|
||||
self.persistence = persistence
|
||||
self.useLiDAR = useLiDAR
|
||||
self.allowsTankResizing = TankResizePolicy.isEnabled(devFlag: devTankResizingEnabled)
|
||||
self.showsPit = persistence.tank.placement == .belowGround && !persistence.tank.showLidOnly
|
||||
self.entityFactory = TankEntityFactory(aboveGroundLightMultiplier: Float(arExposure))
|
||||
self.configFactory = SessionConfigurationFactory(
|
||||
@ -255,7 +263,7 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
|
||||
|
||||
/// Setzt die Tank-Skalierung auf die Originalgröße zurück.
|
||||
func resetTankScale() {
|
||||
guard let tankEntity else { return }
|
||||
guard allowsTankResizing, let tankEntity else { return }
|
||||
tankEntity.scale = SIMD3<Float>(repeating: 1)
|
||||
updateSafetyZones()
|
||||
updateTankDimensionValues()
|
||||
@ -967,6 +975,10 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
|
||||
}
|
||||
|
||||
container.transform = Transform(matrix: worldTransform)
|
||||
// Ältere Projekte können eine gespeicherte Skalierung enthalten.
|
||||
if !allowsTankResizing {
|
||||
container.scale = SIMD3<Float>(repeating: 1)
|
||||
}
|
||||
updateDepthFadeGroundY()
|
||||
updateSceneOcclusion()
|
||||
updateSafetyZones()
|
||||
@ -992,30 +1004,21 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
|
||||
safetyZoneEntity = entity
|
||||
}
|
||||
|
||||
// Der Katalogwert ist im Modellraum gemessen. w = 0 vermeidet eine
|
||||
// zweite Translation; Rotation und Skalierung des Tanks bleiben erhalten.
|
||||
let domeCenterX = entityFactory.domeCenterX(for: persistence.tank)
|
||||
let localOffset = SIMD4<Float>(domeCenterX, 0, 0, 0)
|
||||
let transformedOffset = tankEntity.transformMatrix(relativeTo: tankAnchor) * localOffset
|
||||
let tankPosition = tankEntity.position(relativeTo: tankAnchor)
|
||||
let localDomeOffset = SIMD4<Float>(0, 0, safetyZoneDomeOffset, 0)
|
||||
let tankTransform = tankEntity.transformMatrix(relativeTo: tankAnchor)
|
||||
let domeOffset = tankTransform * localDomeOffset
|
||||
|
||||
entity.position = tankPosition + SIMD3(domeOffset.x, domeOffset.y, domeOffset.z)
|
||||
}
|
||||
|
||||
/// Abstand vom Tankmittelpunkt zum seitlichen Domschacht entlang der lokalen
|
||||
/// Tank-Längsachse (+Z), abgeleitet aus den Vorgabemaßen.
|
||||
private var safetyZoneDomeOffset: Float {
|
||||
switch persistence.tank.size {
|
||||
case .t1_2:
|
||||
return 2.460 / 2
|
||||
case .t2_1:
|
||||
return 4.255 / 2
|
||||
case .t2_9:
|
||||
return 5.500 / 2
|
||||
}
|
||||
entity.position = tankPosition + SIMD3(transformedOffset.x, 0, transformedOffset.z)
|
||||
}
|
||||
|
||||
private func installManipulationGestures(on entity: ModelEntity) {
|
||||
guard let arView else { return }
|
||||
let recognizers = arView.installGestures([.translation, .rotation, .scale], for: entity)
|
||||
let gestures: ARView.EntityGestures = allowsTankResizing
|
||||
? [.translation, .rotation, .scale]
|
||||
: [.translation, .rotation]
|
||||
let recognizers = arView.installGestures(gestures, for: entity)
|
||||
for recognizer in recognizers {
|
||||
recognizer.addTarget(self, action: #selector(handleManipulationChanged(_:)))
|
||||
}
|
||||
|
||||
@ -9,13 +9,13 @@ import RealityKit
|
||||
import UIKit
|
||||
|
||||
/// Ein Sicherheitsabstand rund um den Tank: ein Kreis mit gegebenem Radius und Farbe,
|
||||
/// dessen Mittelpunkt der Tank ist.
|
||||
/// dessen Mittelpunkt über dem Domschacht liegt.
|
||||
///
|
||||
/// Modular gehalten: Ein weiterer Sicherheitsabstand wird schlicht durch Hinzufügen
|
||||
/// eines Eintrags zu ``SafetyZone/all`` ergänzt – die Darstellung folgt automatisch.
|
||||
struct SafetyZone: Identifiable {
|
||||
let id = UUID()
|
||||
/// Radius des Kreises in Metern (Abstand vom Tank-Mittelpunkt).
|
||||
/// Radius des Kreises in Metern (Abstand von der Domschachtmitte).
|
||||
let radius: Float
|
||||
/// Farbe des Rings.
|
||||
let color: UIColor
|
||||
|
||||
@ -42,6 +42,12 @@ final class TankEntityFactory {
|
||||
self.aboveGroundLightMultiplier = aboveGroundLightMultiplier
|
||||
}
|
||||
|
||||
/// Domschachtposition der tatsächlich geladenen Variante, gemessen ab dem
|
||||
/// Ursprung des USDZ-Modells. Bei fehlendem Modell bleibt der Ersatzkörper zentriert.
|
||||
func domeCenterX(for tank: Tank) -> Float {
|
||||
catalog.resolvedDefinition(for: tank)?.domeCenterX ?? 0
|
||||
}
|
||||
|
||||
/// Lädt das zum Tank passende Modell asynchron vor (blockiert den Main-Actor nicht).
|
||||
func preload(_ tank: Tank) {
|
||||
guard let name = catalog.resolvedDefinition(for: tank)?.modelResourceName else { return }
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
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.
|
||||
/// (vor/zurück/links/rechts) und Drehen (⟲⟳). Erscheint von unten.
|
||||
/// Buttons unterstützen Gedrückt-halten für kontinuierliche Anpassung.
|
||||
struct TankManipulationPanel: View {
|
||||
let controller: ARSessionController
|
||||
@ -109,10 +109,12 @@ struct TankManipulationPanel: View {
|
||||
|
||||
private var actionButtons: some View {
|
||||
HStack(spacing: 10) {
|
||||
Button("Skalierung zurücksetzen", systemImage: "arrow.counterclockwise") {
|
||||
controller.resetTankScale()
|
||||
if controller.allowsTankResizing {
|
||||
Button("Skalierung zurücksetzen", systemImage: "arrow.counterclockwise") {
|
||||
controller.resetTankScale()
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
||||
Button("Neu platzieren", systemImage: "arrow.up.and.down.and.arrow.left.and.right") {
|
||||
controller.reposition()
|
||||
|
||||
Reference in New Issue
Block a user