import SwiftUI import OnyxDesign /// Einstellung der Auslösefläche — mit Vorschau. /// /// Zwei Zahlen in Punkten sagen niemandem etwas: „24 Punkte unter der Notch" /// ist keine Vorstellung, die man im Kopf hat. Ein nachgebildeter Bildschirm /// mit der Notch und der eingefärbten Fläche darüber schon — man sieht sofort, /// wie weit man greifen muss. public struct TriggerAreaSettings: View { @Binding private var trigger: TriggerArea public init(trigger: Binding) { self._trigger = trigger } public var body: some View { VStack(alignment: .leading, spacing: 12) { Text("trigger.hint", bundle: .module) .font(.callout) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) TriggerPreview(trigger: trigger) .frame(height: 150) .frame(maxWidth: .infinity) LabeledContent { Slider(value: Binding( get: { trigger.extraWidth }, set: { trigger = TriggerArea(extraWidth: $0, extraHeight: trigger.extraHeight) }), in: 0...TriggerArea.maximumExtraWidth) } label: { Text("trigger.width \(Int(trigger.extraWidth))", bundle: .module) .monospacedDigit() } LabeledContent { Slider(value: Binding( get: { trigger.extraHeight }, set: { trigger = TriggerArea(extraWidth: trigger.extraWidth, extraHeight: $0) }), in: 0...TriggerArea.maximumExtraHeight) } label: { Text("trigger.height \(Int(trigger.extraHeight))", bundle: .module) .monospacedDigit() } Button { trigger = .standard } label: { Text("trigger.reset", bundle: .module) } .buttonStyle(.borderless) } } } /// Der nachgebildete Bildschirm. /// /// Absichtlich karg: es geht nur darum, wo die Notch sitzt und wie weit die /// Fläche darum herum reicht. Ein Schreibtischhintergrund oder Fenster darin /// wären Beiwerk, das von der einen Frage ablenkt. private struct TriggerPreview: View { let trigger: TriggerArea /// Maße eines MacBook Pro mit Notch, auf die Vorschau heruntergerechnet. private let screenAspect: CGFloat = 1800 / 1169 private let notchWidthFraction: CGFloat = 220 / 1800 private let notchHeightFraction: CGFloat = 38 / 1169 var body: some View { GeometryReader { geometry in let height = min(geometry.size.height, geometry.size.width / screenAspect) let width = height * screenAspect let scale = width / 1800 let notchWidth = width * notchWidthFraction let notchHeight = height * notchHeightFraction let areaWidth = notchWidth + 2 * trigger.extraWidth * scale let areaHeight = notchHeight + trigger.extraHeight * scale ZStack(alignment: .top) { RoundedRectangle(cornerRadius: 8, style: .continuous) .fill(Onyx.Color.surface) .overlay { RoundedRectangle(cornerRadius: 8, style: .continuous) .strokeBorder(Onyx.Color.hairline, lineWidth: 1) } // Die Menüleiste als dünner Streifen: sie erklärt, warum die // Fläche nicht beliebig breit werden sollte. VStack(spacing: 0) { Rectangle() .fill(.white.opacity(0.06)) .frame(height: notchHeight) Spacer(minLength: 0) } // Die Auslösefläche. Rectangle() .fill(Onyx.Color.accent.opacity(0.3)) .overlay { Rectangle().strokeBorder(Onyx.Color.accent, lineWidth: 1) } .frame(width: areaWidth, height: areaHeight) // Die Notch selbst — undurchsichtig, wie in echt. UnevenRoundedRectangle(bottomLeadingRadius: 5, bottomTrailingRadius: 5, style: .continuous) .fill(.black) .frame(width: notchWidth, height: notchHeight) } .frame(width: width, height: height) .frame(maxWidth: .infinity, maxHeight: .infinity) .animation(Onyx.Motion.value, value: areaWidth) .animation(Onyx.Motion.value, value: areaHeight) } } }