Added text based huds progress

This commit is contained in:
Hariharan Mudaliar 2025-10-26 12:50:54 +05:30
commit a135c8a590
6 changed files with 317 additions and 157 deletions

View file

@ -4609,8 +4609,9 @@
"Color Picker Panel:" : {
},
"Color-coded displays are only available in Gradient mode. Switch Progressbar style to Gradient to enable this feature." : {
"Color-coded fills and smooth gradients are unavailable in Segmented mode. Switch to Hierarchical or Gradient to adjust these options." : {
"comment" : "An explanatory text that appears when the user tries to enable color-coded color gradients in Segmented progress bars. It instructs the user to switch to Hierarchical or Gradient progress bars to enable these features.",
"isCommentAutoGenerated" : true
},
"Color-Coded Progress Bars" : {
"comment" : "A section header that lists the options for color-coded progress bars.",
@ -5800,8 +5801,8 @@
}
}
},
"Discrete: Battery/Volume uses 3 distinct colors only\nBattery: Green (high), Yellow (medium), Red (low)\nVolume: Green (quiet), Yellow (medium), Red (loud)" : {
"comment" : "A description of the \"Discrete\" color-coded progress bar style, including the colors used for battery and volume levels.",
"Discrete transitions snap between Green (0–60%), Yellow (60–85%), and Red (85–100%)." : {
"comment" : "A description of how the color-coded progress bars work when not using smooth transitions.",
"isCommentAutoGenerated" : true
},
"Disk Read" : {
@ -10761,6 +10762,7 @@
}
},
"Mic %@" : {
"extractionState" : "stale",
"localizations" : {
"ar" : {
"stringUnit" : {
@ -10860,6 +10862,14 @@
}
}
},
"Mic muted" : {
"comment" : "A description of the state when the microphone is muted.",
"isCommentAutoGenerated" : true
},
"Mic unmuted" : {
"comment" : "A description of the state when the microphone is unmuted.",
"isCommentAutoGenerated" : true
},
"Microphone" : {
"comment" : "A label displayed next to the microphone icon in the privacy indicator detail view.",
"isCommentAutoGenerated" : true
@ -15307,8 +15317,8 @@
}
}
},
"Smooth: Battery/Volume gradually transitions through all colors\nBattery: Green (high) → Yellow-Green → Yellow → Orange → Red (low)\nVolume: Green (quiet) → Yellow → Orange → Red (loud)" : {
"comment" : "A detailed description of the \"Smooth\" color-coded gradient display feature.",
"Smooth transitions blend Green (0–60%), Yellow (60–85%), and Red (85–100%) through the entire fill." : {
"comment" : "A description of how the color-coding feature works.",
"isCommentAutoGenerated" : true
},
"Sneak Peek shows the media title and artist under the notch for a few seconds." : {

View file

@ -9,16 +9,31 @@
import SwiftUI
import Defaults
enum ColorCodingMode {
case volume
case battery
func intensity(for value: CGFloat) -> CGFloat {
let clamped = min(max(value, 0), 1)
switch self {
case .volume:
return clamped
case .battery:
return 1 - clamped
}
}
}
struct ColorCodedProgressBar: View {
let value: CGFloat // 0.0 to 1.0
let reversed: Bool // If true: green at low, red at high (for volume)
let mode: ColorCodingMode
let width: CGFloat
let height: CGFloat
let smoothGradient: Bool // If true: smooth gradient, if false: discrete colors
init(value: CGFloat, reversed: Bool = false, width: CGFloat = 100, height: CGFloat = 4, smoothGradient: Bool = true) {
init(value: CGFloat, mode: ColorCodingMode = .battery, width: CGFloat = 100, height: CGFloat = 4, smoothGradient: Bool = true) {
self.value = min(max(value, 0), 1) // Clamp between 0 and 1
self.reversed = reversed
self.mode = mode
self.width = width
self.height = height
self.smoothGradient = smoothGradient
@ -34,100 +49,19 @@ struct ColorCodedProgressBar: View {
// Filled track with color gradient or solid color
RoundedRectangle(cornerRadius: height / 2)
.fill(smoothGradient ?
AnyShapeStyle(LinearGradient(
gradient: gradientForValue,
startPoint: .leading,
endPoint: .trailing
)) :
AnyShapeStyle(colorForPercentage(value, reversed: reversed))
)
.fill(fillStyle)
.frame(width: width * value, height: height)
}
}
.frame(width: width, height: height)
}
/// Generates a gradient based on the current value
private var gradientForValue: Gradient {
let color = colorForPercentage(value, reversed: reversed)
// Create a gradient that transitions smoothly
let startColor = colorForPercentage(max(value - 0.1, 0), reversed: reversed)
let endColor = color
return Gradient(colors: [startColor, endColor])
}
/// Calculates discrete color for a given percentage (no smooth transitions)
/// - Parameters:
/// - percentage: Value from 0.0 to 1.0
/// - reversed: If true, reverses the color scale (green at low, red at high)
/// - Returns: Discrete color (red, yellow, or green only)
private func discreteColorForPercentage(_ percentage: CGFloat, reversed: Bool) -> Color {
let normalized = reversed ? (1.0 - percentage) : percentage
switch normalized {
case 0.0..<0.33:
// Red zone (critical/low)
return Color(red: 1.0, green: 0.23, blue: 0.19)
case 0.33..<0.67:
// Yellow zone (medium)
return Color(red: 1.0, green: 0.8, blue: 0.0)
default:
// Green zone (healthy/high)
return Color(red: 0.2, green: 0.78, blue: 0.35)
}
}
/// Calculates color for a given percentage with smooth transitions
/// - Parameters:
/// - percentage: Value from 0.0 to 1.0
/// - reversed: If true, reverses the color scale (green at low, red at high)
/// - Returns: Color with smooth gradient transitions
private func colorForPercentage(_ percentage: CGFloat, reversed: Bool) -> Color {
let normalized = reversed ? (1.0 - percentage) : percentage
switch normalized {
case 0.0..<0.2:
// Pure Red zone (critical/low)
return Color(red: 1.0, green: 0.23, blue: 0.19)
case 0.2..<0.35:
// Red → Orange transition
let t = (normalized - 0.2) / 0.15
let red: CGFloat = 1.0
let green: CGFloat = 0.23 + (0.37 * t) // 0.23 → 0.6
let blue: CGFloat = 0.19
return Color(red: red, green: green, blue: blue)
case 0.35..<0.5:
// Orange → Yellow transition
let t = (normalized - 0.35) / 0.15
let red: CGFloat = 1.0
let green: CGFloat = 0.6 + (0.4 * t) // 0.6 → 1.0
let blue: CGFloat = 0.19
return Color(red: red, green: green, blue: blue)
case 0.5..<0.65:
// Yellow → Yellow-Green transition
let t = (normalized - 0.5) / 0.15
let red: CGFloat = 1.0 - (0.4 * t) // 1.0 → 0.6
let green: CGFloat = 1.0
let blue: CGFloat = 0.19 + (0.4 * t) // 0.19 → 0.59
return Color(red: red, green: green, blue: blue)
case 0.65..<0.8:
// Yellow-Green → Green transition
let t = (normalized - 0.65) / 0.15
let red: CGFloat = 0.6 - (0.4 * t) // 0.6 → 0.2
let green: CGFloat = 1.0 - (0.22 * t) // 1.0 → 0.78
let blue: CGFloat = 0.59 - (0.24 * t) // 0.59 → 0.35
return Color(red: red, green: green, blue: blue)
default:
// Pure Green zone (healthy/high)
return Color(red: 0.2, green: 0.78, blue: 0.35)
private var fillStyle: AnyShapeStyle {
let intensity = mode.intensity(for: value)
if smoothGradient {
return AnyShapeStyle(ColorCodedPalette.gradient(for: intensity))
} else {
return AnyShapeStyle(ColorCodedPalette.discreteColor(for: intensity))
}
}
}
@ -137,12 +71,78 @@ struct ColorCodedProgressBar: View {
extension ColorCodedProgressBar {
/// Creates a battery-style progress bar (green at high, red at low)
static func battery(value: CGFloat, width: CGFloat = 100, height: CGFloat = 4, smoothGradient: Bool = true) -> some View {
ColorCodedProgressBar(value: value, reversed: false, width: width, height: height, smoothGradient: smoothGradient)
ColorCodedProgressBar(value: value, mode: .battery, width: width, height: height, smoothGradient: smoothGradient)
}
/// Creates a volume-style progress bar (red at high, green at low)
static func volume(value: CGFloat, width: CGFloat = 100, height: CGFloat = 4, smoothGradient: Bool = true) -> some View {
ColorCodedProgressBar(value: value, reversed: true, width: width, height: height, smoothGradient: smoothGradient)
ColorCodedProgressBar(value: value, mode: .volume, width: width, height: height, smoothGradient: smoothGradient)
}
/// Shared helper for other progress bar implementations that need the same palette.
static func shapeStyle(for value: CGFloat, mode: ColorCodingMode, smoothGradient: Bool) -> AnyShapeStyle {
let intensity = mode.intensity(for: min(max(value, 0), 1))
if smoothGradient {
return AnyShapeStyle(ColorCodedPalette.gradient(for: intensity))
}
return AnyShapeStyle(ColorCodedPalette.discreteColor(for: intensity))
}
}
// MARK: - Palette Helpers
private enum ColorCodedPalette {
private static let greenComponents: (CGFloat, CGFloat, CGFloat) = (0.2, 0.78, 0.35)
private static let yellowComponents: (CGFloat, CGFloat, CGFloat) = (0.93, 0.75, 0.2)
private static let redComponents: (CGFloat, CGFloat, CGFloat) = (0.95, 0.36, 0.3)
static func discreteColor(for intensity: CGFloat) -> Color {
let clamped = min(max(intensity, 0), 1)
switch clamped {
case ..<0.6:
return color(from: greenComponents)
case ..<0.85:
return color(from: yellowComponents)
default:
return color(from: redComponents)
}
}
static func gradient(for intensity: CGFloat) -> LinearGradient {
let endColor = smoothColor(for: intensity)
let startColor = smoothColor(for: max(intensity - 0.15, 0))
return LinearGradient(colors: [startColor, endColor], startPoint: .leading, endPoint: .trailing)
}
private static func smoothColor(for intensity: CGFloat) -> Color {
let clamped = min(max(intensity, 0), 1)
switch clamped {
case ..<0.6:
// Stay green within the comfort zone
return color(from: greenComponents)
case ..<0.85:
let t = (clamped - 0.6) / 0.25
return blend(from: greenComponents, to: yellowComponents, fraction: t)
default:
let t = (clamped - 0.85) / 0.15
return blend(from: yellowComponents, to: redComponents, fraction: t)
}
}
private static func blend(from start: (CGFloat, CGFloat, CGFloat), to end: (CGFloat, CGFloat, CGFloat), fraction: CGFloat) -> Color {
let t = min(max(fraction, 0), 1)
let red = lerp(start.0, end.0, t)
let green = lerp(start.1, end.1, t)
let blue = lerp(start.2, end.2, t)
return color(from: (red, green, blue))
}
private static func lerp(_ a: CGFloat, _ b: CGFloat, _ t: CGFloat) -> CGFloat {
a + (b - a) * t
}
private static func color(from components: (CGFloat, CGFloat, CGFloat)) -> Color {
Color(red: Double(components.0), green: Double(components.1), blue: Double(components.2))
}
}

View file

@ -20,6 +20,7 @@ struct InlineHUD: View {
@Default(.useColorCodedVolumeDisplay) var useColorCodedVolumeDisplay
@Default(.useSmoothColorGradient) var useSmoothColorGradient
@Default(.progressBarStyle) var progressBarStyle
@Default(.showProgressPercentages) var showProgressPercentages
@ObservedObject var bluetoothManager = BluetoothAudioManager.shared
@State private var displayName: String = ""
@ -130,14 +131,15 @@ struct InlineHUD: View {
// Bluetooth device battery display
HStack(spacing: 4) {
if value > 0 {
// Show color-coded battery bar if battery info available
// Color-coding disabled for segmented or hierarchical mode
if useColorCodedBatteryDisplay && progressBarStyle == .gradient {
ColorCodedProgressBar.battery(value: value, width: 60, height: 4, smoothGradient: useSmoothColorGradient)
} else {
DraggableProgressBar(value: .constant(value))
.frame(width: 60)
Group {
if useColorCodedBatteryDisplay && progressBarStyle != .segmented {
DraggableProgressBar(value: .constant(value), colorMode: .battery)
} else {
DraggableProgressBar(value: .constant(value))
}
}
.frame(width: 60)
.allowsHitTesting(false)
Text("\(Int(value * 100))%")
.font(.caption)
.fontWeight(.medium)
@ -155,26 +157,35 @@ struct InlineHUD: View {
.frame(maxWidth: .infinity, alignment: .trailing)
} else {
// Volume and brightness displays
HStack {
// Color-coding only available in gradient mode, not hierarchical or segmented
if type == .volume && useColorCodedVolumeDisplay && progressBarStyle == .gradient {
// Color-coded volume (reversed: red at high, green at low)
ColorCodedProgressBar.volume(value: value, width: 70, height: 4, smoothGradient: useSmoothColorGradient)
Group {
if type == .volume {
Group {
if value.isZero {
Text("muted")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.gray)
.lineLimit(1)
.allowsTightening(true)
.multilineTextAlignment(.trailing)
.contentTransition(.numericText())
} else {
HStack(spacing: 6) {
DraggableProgressBar(value: $value, colorMode: .volume)
PercentageLabel(value: value, isVisible: showProgressPercentages)
}
.transition(.opacity.combined(with: .scale))
}
}
.animation(.smooth(duration: 0.2), value: value.isZero)
} else {
// Default draggable progress bar
DraggableProgressBar(value: $value)
}
if (type == .volume && value.isZero) {
Text("muted")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.gray)
.lineLimit(1)
.allowsTightening(true)
.multilineTextAlignment(.trailing)
HStack(spacing: 6) {
DraggableProgressBar(value: $value)
PercentageLabel(value: value, isVisible: showProgressPercentages)
}
}
}
.frame(maxWidth: .infinity, alignment: .trailing)
}
}
.padding(.trailing, 4)

View file

@ -22,6 +22,7 @@ struct SystemEventIndicatorModifier: View {
@Binding var icon: String
let showSlider: Bool = false
var sendEventBack: (CGFloat) -> Void
@Default(.showProgressPercentages) private var showProgressPercentages
var body: some View {
HStack(spacing: 14) {
@ -58,13 +59,21 @@ struct SystemEventIndicatorModifier: View {
default:
EmptyView()
}
if (eventType != .mic) {
DraggableProgressBar(value: $value)
} else {
Text("Mic \(value > 0 ? "unmuted" : "muted")")
switch eventType {
case .mic:
Text(value > 0 ? "Mic unmuted" : "Mic muted")
.foregroundStyle(.gray)
.lineLimit(1)
.allowsTightening(true)
.contentTransition(.numericText())
case .volume:
VolumeProgressSection(value: $value, showPercentages: showProgressPercentages)
case .brightness:
ProgressSection(value: $value, showPercentages: showProgressPercentages)
case .backlight:
ProgressSection(value: $value, showPercentages: showProgressPercentages)
default:
ProgressSection(value: $value, showPercentages: showProgressPercentages)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
@ -88,18 +97,106 @@ struct SystemEventIndicatorModifier: View {
}
}
struct ProgressSection: View {
@Binding var value: CGFloat
var showPercentages: Bool
var colorMode: ProgressColorMode? = nil
var body: some View {
HStack(spacing: 8) {
DraggableProgressBar(value: $value, colorMode: colorMode)
PercentageLabel(value: value, isVisible: showPercentages)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
struct VolumeProgressSection: View {
@Binding var value: CGFloat
var showPercentages: Bool
var body: some View {
Group {
if value.isZero {
Text("muted")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.gray)
.contentTransition(.numericText())
} else {
ProgressSection(value: $value, showPercentages: showPercentages, colorMode: .volume)
.transition(.opacity.combined(with: .scale))
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.animation(.smooth(duration: 0.2), value: value.isZero)
}
}
struct PercentageLabel: View {
let value: CGFloat
let isVisible: Bool
@Default(.progressBarStyle) private var progressBarStyle
var body: some View {
Group {
if shouldShow {
Text(formattedValue)
.font(.caption)
.fontWeight(.semibold)
.monospacedDigit()
.foregroundStyle(.white.opacity(0.9))
.contentTransition(.numericText())
.frame(width: 28, alignment: .trailing)
}
}
}
private var shouldShow: Bool {
isVisible && progressBarStyle != .segmented
}
private var formattedValue: String {
let raw = max(0, min(100, Int(round(value * 100))))
return String(format: "%3d", raw)
}
}
enum ProgressColorMode {
case volume
case battery
}
private extension ProgressColorMode {
var colorCodingMode: ColorCodingMode {
switch self {
case .volume:
return .volume
case .battery:
return .battery
}
}
}
struct DraggableProgressBar: View {
@EnvironmentObject var vm: DynamicIslandViewModel
@Binding var value: CGFloat
var colorMode: ProgressColorMode? = nil
@State private var isDragging = false
@State private var dragOffset: CGFloat = 0
@Default(.progressBarStyle) private var progressBarStyle
@Default(.useColorCodedVolumeDisplay) private var useColorCodedVolumeDisplay
@Default(.useColorCodedBatteryDisplay) private var useColorCodedBatteryDisplay
@Default(.useSmoothColorGradient) private var useSmoothColorGradient
@Default(.systemEventIndicatorUseAccent) private var useAccentColor
@Default(.systemEventIndicatorShadow) private var useShadow
@Default(.inlineHUD) private var inlineHUD
var body: some View {
VStack {
GeometryReader { geo in
Group {
if Defaults[.progressBarStyle] == .segmented {
if progressBarStyle == .segmented {
// Segmented progress bar - completely different layout
SegmentedProgressContent(value: value, geometry: geo)
} else {
@ -107,23 +204,8 @@ struct DraggableProgressBar: View {
ZStack(alignment: .leading) {
Capsule()
.fill(.tertiary)
Group {
switch Defaults[.progressBarStyle] {
case .gradient:
Capsule()
.fill(LinearGradient(colors: Defaults[.systemEventIndicatorUseAccent] ? [Defaults[.accentColor], Defaults[.accentColor].ensureMinimumBrightness(factor: 0.2)] : [.white, .white.opacity(0.2)], startPoint: .trailing, endPoint: .leading))
.frame(width: max(0, min(geo.size.width * value, geo.size.width)))
.shadow(color: Defaults[.systemEventIndicatorShadow] ? Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.7) : .white : .clear, radius: 8, x: 3)
case .hierarchical:
Capsule()
.fill(Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor] : .white)
.frame(width: max(0, min(geo.size.width * value, geo.size.width)))
.shadow(color: Defaults[.systemEventIndicatorShadow] ? Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.7) : .white : .clear, radius: 8, x: 3)
case .segmented:
EmptyView() // This case won't be reached due to the outer if condition
}
}
.opacity(value.isZero ? 0 : 1)
progressFill(width: geo.size.width)
.opacity(value.isZero ? 0 : 1)
}
}
}
@ -142,7 +224,7 @@ struct DraggableProgressBar: View {
}
)
}
.frame(height: Defaults[.inlineHUD] ? isDragging ? 8 : 5 : isDragging ? 9 : 6)
.frame(height: inlineHUD ? (isDragging ? 8 : 5) : (isDragging ? 9 : 6))
}
}
@ -152,6 +234,59 @@ struct DraggableProgressBar: View {
value = max(0, min(newValue, 1))
}
private func progressFill(width: CGFloat) -> some View {
let clampedWidth = max(0, min(width * value, width))
let accentColor = Defaults[.accentColor]
let shadowColor: Color = {
guard useShadow else { return .clear }
if useAccentColor {
return accentColor.ensureMinimumBrightness(factor: 0.7)
}
return .white
}()
return Group {
if shouldUseColorCoding, let mode = colorMode?.colorCodingMode {
Capsule()
.fill(ColorCodedProgressBar.shapeStyle(for: value, mode: mode, smoothGradient: useSmoothColorGradient))
.frame(width: clampedWidth)
.shadow(color: shadowColor, radius: useShadow ? 8 : 0, x: 3)
} else {
switch progressBarStyle {
case .gradient:
Capsule()
.fill(LinearGradient(
colors: useAccentColor
? [accentColor, accentColor.ensureMinimumBrightness(factor: 0.2)]
: [.white, .white.opacity(0.2)],
startPoint: .trailing,
endPoint: .leading
))
.frame(width: clampedWidth)
.shadow(color: shadowColor, radius: useShadow ? 8 : 0, x: 3)
case .hierarchical:
Capsule()
.fill(useAccentColor ? accentColor : .white)
.frame(width: clampedWidth)
.shadow(color: shadowColor, radius: useShadow ? 8 : 0, x: 3)
case .segmented:
EmptyView()
}
}
}
}
private var shouldUseColorCoding: Bool {
guard let colorMode else { return false }
guard progressBarStyle != .segmented else { return false }
switch colorMode {
case .volume:
return useColorCodedVolumeDisplay
case .battery:
return useColorCodedBatteryDisplay
}
}
}
struct SegmentedProgressContent: View {
@ -164,8 +299,8 @@ struct SegmentedProgressContent: View {
var body: some View {
let spacing: CGFloat = 1.5
let computed = (geometry.size.width - CGFloat(segmentCount - 1) * spacing) / CGFloat(segmentCount)
let barWidth = max(3.0, min(6.0, computed))
let computed = (geometry.size.width - CGFloat(segmentCount - 1) * spacing) / CGFloat(segmentCount)
let barWidth = max(3.0, computed)
let activeCount = Int(round(value * CGFloat(segmentCount)))
HStack(spacing: spacing) {

View file

@ -675,27 +675,30 @@ struct HUD: View {
}
Section {
let colorCodingDisabled = progressBarStyle == .segmented
Defaults.Toggle("Color-coded battery display", key: .useColorCodedBatteryDisplay)
.disabled(progressBarStyle != .gradient)
.disabled(colorCodingDisabled)
Defaults.Toggle("Color-coded volume display", key: .useColorCodedVolumeDisplay)
.disabled(progressBarStyle != .gradient)
.disabled(colorCodingDisabled)
if progressBarStyle == .gradient && (Defaults[.useColorCodedBatteryDisplay] || Defaults[.useColorCodedVolumeDisplay]) {
if !colorCodingDisabled && (Defaults[.useColorCodedBatteryDisplay] || Defaults[.useColorCodedVolumeDisplay]) {
Defaults.Toggle("Smooth color transitions", key: .useSmoothColorGradient)
}
Defaults.Toggle("Show percentages beside progress bars", key: .showProgressPercentages)
} header: {
Text("Color-Coded Progress Bars")
} footer: {
if progressBarStyle != .gradient {
Text("Color-coded displays are only available in Gradient mode. Switch Progressbar style to Gradient to enable this feature.")
if progressBarStyle == .segmented {
Text("Color-coded fills and smooth gradients are unavailable in Segmented mode. Switch to Hierarchical or Gradient to adjust these options.")
.foregroundStyle(.secondary)
.font(.caption)
} else if Defaults[.useSmoothColorGradient] {
Text("Smooth: Battery/Volume gradually transitions through all colors\nBattery: Green (high) → Yellow-Green → Yellow → Orange → Red (low)\nVolume: Green (quiet) → Yellow → Orange → Red (loud)")
Text("Smooth transitions blend Green (0–60%), Yellow (60–85%), and Red (85–100%) through the entire fill.")
.foregroundStyle(.secondary)
.font(.caption)
} else {
Text("Discrete: Battery/Volume uses 3 distinct colors only\nBattery: Green (high), Yellow (medium), Red (low)\nVolume: Green (quiet), Yellow (medium), Red (loud)")
Text("Discrete transitions snap between Green (0–60%), Yellow (60–85%), and Red (85–100%).")
.foregroundStyle(.secondary)
.font(.caption)
}

View file

@ -362,6 +362,7 @@ extension Defaults.Keys {
static let enableGradient = Key<Bool>("enableGradient", default: false)
static let systemEventIndicatorShadow = Key<Bool>("systemEventIndicatorShadow", default: false)
static let systemEventIndicatorUseAccent = Key<Bool>("systemEventIndicatorUseAccent", default: false)
static let showProgressPercentages = Key<Bool>("showProgressPercentages", default: false)
// MARK: Shelf
static let dynamicShelf = Key<Bool>("dynamicShelf", default: true)