Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions wled/View/Color+Extensions.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
62 changes: 1 addition & 61 deletions wled/View/DeviceListItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
}
}


Expand Down
52 changes: 52 additions & 0 deletions wled/View/DeviceSelectionStyle.swift
Original file line number Diff line number Diff line change
@@ -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))
}
}