Der Blitz war ein Loch in der Füllung — über dem gefüllten Teil liest man ihn, über dem leeren ist es Transparenz auf Transparenz. Bei wenig Ladung sitzt er fast ganz im Leeren und verschwindet. Statt zwischen Schwarz und Weiß zu faden geht er den Weg von macOS: geteilt gezeichnet, über der Füllung ausgestanzt, daneben ausgemalt. Damit ist er bei jedem Ladestand lesbar, ohne Bewegung in der Menüleiste. Ein Test prüft, dass sich die beiden Teile an der Kante treffen statt zu überlappen — sonst wäre der Blitz an der Nahtstelle dicker. Die Auslösefläche der Notch lässt sich jetzt einstellen, mit Vorschau. Wie genau jemand die Notch trifft, hängt an Hand und Zeigegerät; feste Werte im Code waren dafür die falsche Antwort. Die Vorschau zeigt einen nachgebildeten Bildschirm mit Notch und eingefärbter Fläche — „24 Punkte unter der Notch" ist keine Vorstellung, die jemand im Kopf hat. Die Menüleistenbreiten folgen jetzt dem, was dasteht. „Temp 53°" stand in einer Box für „Temp 100 °C", und in der Menüleiste ist Platz das knappste Gut. Dieselbe Hysterese wie beim Netzwerk: sofort wachsen, zögernd schrumpfen. Das Mixer-Symbol war ausgeschaltet auf 55 % abgeblendet. Das war meine Idee und ein Fehlgriff: über einem hellen Schreibtischhintergrund ist es damit praktisch weg. Ob der Mixer läuft, erfährt man beim Klick — dafür muss man das Symbol aber erst finden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
123 lines
4.7 KiB
Swift
123 lines
4.7 KiB
Swift
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<TriggerArea>) {
|
|
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)
|
|
}
|
|
}
|
|
}
|