From 535b42a7b8304340182ca1367aa42f047eb06bff Mon Sep 17 00:00:00 2001 From: Tommaso Negri Date: Mon, 27 Jul 2026 16:18:34 +0200 Subject: scaffold basic structure --- Sources/phbar/Models/HEX.swift | 114 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Sources/phbar/Models/HEX.swift (limited to 'Sources/phbar/Models/HEX.swift') diff --git a/Sources/phbar/Models/HEX.swift b/Sources/phbar/Models/HEX.swift new file mode 100644 index 0000000..fbbdf39 --- /dev/null +++ b/Sources/phbar/Models/HEX.swift @@ -0,0 +1,114 @@ +import SwiftUI + +/// A color described by a hexadecimal color code. +/// +/// `HEX` is a lightweight, string-backed representation: the code is stored +/// verbatim and parsed only when converted to a SwiftUI color via `Color(hex:)` +/// or `Color.hex(_:)`, which resolve it in the sRGB color space. +/// +/// Supported formats (the leading `#` is optional): +/// - `#RGB` +/// - `#RGBA` +/// - `#RRGGBB` +/// - `#RRGGBBAA` +/// +/// Single-digit components are expanded by repeating the digit; for example, +/// `#3aF` is read as `#33AAFF`. When present, the trailing digits encode alpha +/// (opacity); otherwise opacity defaults to `1`. +struct HEX { + /// The raw hex code, exactly as supplied. It is not validated or normalized; + /// parsing is performed lazily by `Color.hex(_:)`. + var code: String + + /// Creates a hex color from the given code. + /// + /// - Parameter code: A hex color code. Stored verbatim; see ``HEX`` for the + /// accepted formats. + init(code: String) { + self.code = code + } +} + +extension SwiftUI.Color { + /// Creates a color from a ``HEX`` value. + init(hex: HEX) { + self = .hex(hex.code) + } + + /// Creates a color from a hex code, resolving it in the sRGB color space. + /// + /// Supported formats (the leading `#` is optional): `#RGB`, `#RGBA`, + /// `#RRGGBB`, and `#RRGGBBAA`. Single-digit components are expanded by + /// repeating the digit. When present, the trailing digits encode alpha + /// (opacity); otherwise opacity defaults to `1`. Malformed input resolves to + /// opaque black. + /// + /// - Parameter code: A hex color code. + static func hex(_ code: String) -> SwiftUI.Color { + var sanitized = code.trimmingCharacters(in: .whitespacesAndNewlines) + if sanitized.hasPrefix("#") { + sanitized.removeFirst() + } + + var rgba: UInt64 = 0 + Scanner(string: sanitized).scanHexInt64(&rgba) + + let red: Double + let green: Double + let blue: Double + let opacity: Double + + switch sanitized.count { + case 8: // RRGGBBAA + red = Double((rgba >> 24) & 0xFF) / 255.0 + green = Double((rgba >> 16) & 0xFF) / 255.0 + blue = Double((rgba >> 8) & 0xFF) / 255.0 + opacity = Double(rgba & 0xFF) / 255.0 + case 6: // RRGGBB + red = Double((rgba >> 16) & 0xFF) / 255.0 + green = Double((rgba >> 8) & 0xFF) / 255.0 + blue = Double(rgba & 0xFF) / 255.0 + opacity = 1.0 + case 4: // RGBA + red = Double(((rgba >> 12) & 0xF) * 0x11) / 255.0 + green = Double(((rgba >> 8) & 0xF) * 0x11) / 255.0 + blue = Double(((rgba >> 4) & 0xF) * 0x11) / 255.0 + opacity = Double((rgba & 0xF) * 0x11) / 255.0 + case 3: // RGB + red = Double(((rgba >> 8) & 0xF) * 0x11) / 255.0 + green = Double(((rgba >> 4) & 0xF) * 0x11) / 255.0 + blue = Double((rgba & 0xF) * 0x11) / 255.0 + opacity = 1.0 + default: + red = 0.0 + green = 0.0 + blue = 0.0 + opacity = 1.0 + } + + return Color(.sRGB, red: red, green: green, blue: blue, opacity: opacity) + } +} + +extension SwiftUI.Color.Resolved { + /// The hex code for this resolved color. + /// + /// Components are clamped to `0...1` and quantized to 8 bits. The result is a + /// 6-digit `#RRGGBB` code, or an 8-digit `#RRGGBBAA` code when the color is + /// not fully opaque. + var hex: HEX { + let r = Int((min(max(Double(red), 0), 1) * 255).rounded()) + let g = Int((min(max(Double(green), 0), 1) * 255).rounded()) + let b = Int((min(max(Double(blue), 0), 1) * 255).rounded()) + + let code: String + if opacity < 1 { + let a = Int((min(max(Double(opacity), 0), 1) * 255).rounded()) + code = String(format: "#%02X%02X%02X%02X", r, g, b, a) + } else { + code = String(format: "#%02X%02X%02X", r, g, b) + } + + return HEX(code: code) + } +} -- cgit v1.2.3