diff --git a/wled/View/Color+Extensions.swift b/wled/View/Color+Extensions.swift new file mode 100644 index 0000000..8718ce3 --- /dev/null +++ b/wled/View/Color+Extensions.swift @@ -0,0 +1,55 @@ +import SwiftUI +import UIKit + +extension Color { + /// Fixes the color if it is too dark or too bright depending on the dark/light theme. + /// Used primarily for the Card background color. + func fixDisplayColor(for colorScheme: ColorScheme) -> Color { + let uiColor = UIColor(self) + var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 + uiColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a) + + // In Dark mode, ensure at least 0.2 brightness (don't disappear into black) + // In Light mode, ensure max 0.75 brightness (don't disappear into white/be too bright) + b = colorScheme == .dark ? fmax(b, 0.2) : fmin(b, 0.75) + + return Color(UIColor(hue: h, saturation: s, brightness: b, alpha: a)) + } + + /// Adjusts the color to ensure minimum contrast for UI controls (like Toggle switches) + /// which often have white components (knobs). + func ensureContrast(for colorScheme: ColorScheme) -> Color { + // We only really care about Dark Mode issues where White tint on White thumb is problematic. + // In Light mode, fixDisplayColor likely handles the "too bright" case, or system colors work better. + // But the user specifically mentioned Dark Mode issues with White color. + + guard colorScheme == .dark else { return self } + + let uiColor = UIColor(self) + var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 + uiColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a) + + // If color is very bright (close to white), we need to darken it for the track + // to contrast with the White thumb (Brightness 1.0). + // A brightness of 0.6 provides decent contrast with 1.0. + + if b > 0.8 { + // Also check saturation. If it's a vivid color (High Saturation), + // the hue contrast might be enough even if bright? + // But White Thumb has S=0. + // Cyan (S=1, B=1) vs White. Visible. + // Yellow (S=1, B=1) vs White. Harder. + // White (S=0, B=1) vs White. Invisible. + + // So if Saturation is low, we definitely need to darken. + if s < 0.3 { + return Color(UIColor(hue: h, saturation: s, brightness: 0.5, alpha: a)) + } + + // For other high brightness colors, maybe darken slightly just in case? + // Let's stick to the low saturation case first as it's the most obvious issue. + } + + return self + } +} diff --git a/wled/View/DeviceListItemView.swift b/wled/View/DeviceListItemView.swift index e836527..518131e 100644 --- a/wled/View/DeviceListItemView.swift +++ b/wled/View/DeviceListItemView.swift @@ -16,7 +16,7 @@ struct DeviceListItemView: View { @State private var brightness: Double = 0.0 var body: some View { - let fixedDeviceColor = fixColor(device.currentColor) + let fixedDeviceColor = device.currentColor.fixDisplayColor(for: colorScheme) let backgroundColor = isSelected ? fixedDeviceColor.opacity(DeviceSelectionStyle.Style.selectedOpacity) : fixedDeviceColor.opacity(DeviceSelectionStyle.Style.unselectedOpacity) @@ -61,66 +61,6 @@ struct DeviceListItemView: View { onTogglePower(isOn) }) } - - // Fixes the color if it is too dark or too bright depending of the dark/light theme - private func fixColor(_ color: Color) -> Color { - let uiColor = UIColor(color) - var h = CGFloat(0), s = CGFloat(0), b = CGFloat(0), a = CGFloat(0) - uiColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a) - b = colorScheme == .dark ? fmax(b, 0.2) : fmin(b, 0.75) - return Color(UIColor(hue: h, saturation: s, brightness: b, alpha: a)) - } -} - -// MARK: - DeviceSelectionStyle - -struct DeviceSelectionStyle: ViewModifier { - var isSelected: Bool - var color: Color - @Environment(\.colorScheme) var colorScheme - - func body(content: Content) -> some View { - content - // Prevent system from turning text white on selection - .foregroundStyle(.primary) - // Apply Tint/Accent for sliders/toggles - .tint(color) - .accentColor(color) - // Border - .overlay( - RoundedRectangle(cornerRadius: 12) - .stroke( - isSelected ? color : .clear, - lineWidth: isSelected ? Style.selectedBorderWidth : Style.unselectedBorderWidth - ) - ) - // Glow effect - .shadow(color: glowColor, radius: Style.glowRadius, x: 0, y: 0) - } - - // MARK: Helper properties - - private var glowColor: Color { - guard isSelected else { return .clear } - let opacity = colorScheme == .dark ? Style.darkGlowOpacity : Style.lightGlowOpacity - return color.opacity(opacity) - } - - enum Style { - static let selectedOpacity: Double = 1.0 - static let unselectedOpacity: Double = 0.6 - static let selectedBorderWidth: CGFloat = 2.0 - static let unselectedBorderWidth: CGFloat = 0.0 - static let glowRadius: CGFloat = 5.0 - static let darkGlowOpacity: Double = 0.6 - static let lightGlowOpacity: Double = 0.4 - } -} - -extension View { - func applyDeviceSelectionStyle(isSelected: Bool, color: Color) -> some View { - self.modifier(DeviceSelectionStyle(isSelected: isSelected, color: color)) - } } diff --git a/wled/View/DeviceSelectionStyle.swift b/wled/View/DeviceSelectionStyle.swift new file mode 100644 index 0000000..7f0a4de --- /dev/null +++ b/wled/View/DeviceSelectionStyle.swift @@ -0,0 +1,52 @@ +import SwiftUI + +struct DeviceSelectionStyle: ViewModifier { + var isSelected: Bool + var color: Color + @Environment(\.colorScheme) var colorScheme + + func body(content: Content) -> some View { + let contrastColor = color.ensureContrast(for: colorScheme) + + return content + // Prevent system from turning text white on selection + .foregroundStyle(.primary) + // Apply Tint/Accent for sliders/toggles with contrast check + .tint(contrastColor) + .accentColor(contrastColor) + // Border + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke( + isSelected ? color : .clear, + lineWidth: isSelected ? Style.selectedBorderWidth : Style.unselectedBorderWidth + ) + ) + // Glow effect + .shadow(color: glowColor, radius: Style.glowRadius, x: 0, y: 0) + } + + // MARK: Helper properties + + private var glowColor: Color { + guard isSelected else { return .clear } + let opacity = colorScheme == .dark ? Style.darkGlowOpacity : Style.lightGlowOpacity + return color.opacity(opacity) + } + + enum Style { + static let selectedOpacity: Double = 1.0 + static let unselectedOpacity: Double = 0.6 + static let selectedBorderWidth: CGFloat = 2.0 + static let unselectedBorderWidth: CGFloat = 0.0 + static let glowRadius: CGFloat = 5.0 + static let darkGlowOpacity: Double = 0.6 + static let lightGlowOpacity: Double = 0.4 + } +} + +extension View { + func applyDeviceSelectionStyle(isSelected: Bool, color: Color) -> some View { + self.modifier(DeviceSelectionStyle(isSelected: isSelected, color: color)) + } +}