import SwiftUI /// A physical key. `latched` keeps it looking pressed (REC while recording). public struct KeyButtonStyle: ButtonStyle { public enum Kind { case standard, record } private let kind: Kind private let latched: Bool public init(kind: Kind = .standard, latched: Bool = false) { self.kind = kind self.latched = latched } public func makeBody(configuration: Configuration) -> some View { KeyBody(configuration: configuration, kind: kind, latched: latched) } private struct KeyBody: View { let configuration: Configuration let kind: Kind let latched: Bool @Environment(\.isEnabled) private var isEnabled @Environment(\.accessibilityReduceMotion) private var reduceMotion private var pressed: Bool { configuration.isPressed || latched } private var face: AnyShapeStyle { switch (kind, pressed) { case (.standard, false): AnyShapeStyle(LinearGradient(colors: [Palette.surfaceKeyHigh, Palette.surfaceKeyLow], startPoint: .top, endPoint: .bottom)) case (.standard, true): AnyShapeStyle(Palette.surfaceKeyPressed) case (.record, false): AnyShapeStyle(Palette.accentRecord) case (.record, true): AnyShapeStyle(Palette.accentRecord.opacity(Opacity.pressedRecord)) } } var body: some View { configuration.label .textStyle(Typography.control) .foregroundStyle(kind == .record ? Palette.inkInverse : Palette.inkPrimary) .padding(.horizontal, Space.s4) .frame( minWidth: kind == .record ? Size.keyRecWidth : Size.keyMinWidth, minHeight: kind == .record ? Size.keyRecHeight : Size.controlHeight) .background(PhysicalSurface( fill: face, cornerRadius: Radius.sm, shadows: pressed ? Shadows.keyPressed : Shadows.keyRaised)) .offset(y: pressed ? Shadows.keyPressedOffset : 0) .opacity(isEnabled ? 1 : Opacity.disabled) .animation( reduceMotion ? nil : .timingCurve( Motion.easingMechanical.0, Motion.easingMechanical.1, Motion.easingMechanical.2, Motion.easingMechanical.3, duration: pressed ? Motion.keyPress : Motion.keyRelease), value: pressed) } } } /// A round indicator lamp. The record lamp has a small glow; text never does. public struct LampView: View { public enum Tint { case record, amber } private let isOn: Bool private let tint: Tint private let large: Bool @Environment(\.colorScheme) private var scheme @Environment(\.accessibilityReduceMotion) private var reduceMotion public init(isOn: Bool, tint: Tint = .record, large: Bool = false) { self.isOn = isOn self.tint = tint self.large = large } private var color: Color { switch (tint, isOn) { case (.record, true): Palette.accentRecord case (.record, false): Palette.accentRecordUnlit case (.amber, true): Palette.levelAmber case (.amber, false): Palette.levelAmberUnlit } } public var body: some View { let diameter = large ? Size.lampLarge : Size.lampDiameter let glow = Shadows.lampOn[0] Circle() .fill(color) .overlay( Circle().fill(RadialGradient( colors: [Color.white.opacity(isOn ? Opacity.specularOn : Opacity.specularOff), .clear], center: UnitPoint(x: 0.35, y: 0.3), startRadius: 0, endRadius: diameter * 0.45))) .overlay(Circle().strokeBorder(Palette.lineSeamDark, lineWidth: Border.hairline)) .frame(width: diameter, height: diameter) .shadow( color: isOn && tint == .record ? glow.color(scheme) : .clear, radius: glow.blur / 2) .animation( reduceMotion ? nil : .easeOut(duration: isOn ? Motion.lampOn : Motion.lampOff), value: isOn) .accessibilityHidden(true) } }