Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 20 additions & 6 deletions Sources/Bonsplit/Internal/Styling/TabBarColors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,27 @@ private extension NSColor {
if hex.hasPrefix("#") {
hex.removeFirst()
}
guard hex.count == 6 else { return nil }
guard hex.count == 6 || hex.count == 8 else { return nil }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Blend alpha colors before deriving text contrast

Allowing 8-digit hex values here introduces translucent chrome colors, but text/icon contrast is still computed from raw RGB in effectiveTextColor/isBonsplitLightColor without considering alpha compositing against the fallback background. With inputs like #00000000 (or any low-alpha dark color) on a light window, the code picks white foreground text even though the rendered background is effectively light, making tab labels hard to read.

Useful? React with 👍 / 👎.

guard hex.unicodeScalars.allSatisfy({ Self.bonsplitHexDigits.contains($0) }) else { return nil }
guard let rgb = UInt64(hex, radix: 16) else { return nil }
let red = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
let green = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
let blue = CGFloat(rgb & 0x0000FF) / 255.0
self.init(red: red, green: green, blue: blue, alpha: 1.0)
guard let raw = UInt64(hex, radix: 16) else { return nil }
let red: CGFloat
let green: CGFloat
let blue: CGFloat
let alpha: CGFloat

if hex.count == 8 {
red = CGFloat((raw & 0xFF00_0000) >> 24) / 255.0
green = CGFloat((raw & 0x00FF_0000) >> 16) / 255.0
blue = CGFloat((raw & 0x0000_FF00) >> 8) / 255.0
alpha = CGFloat(raw & 0x0000_00FF) / 255.0
} else {
red = CGFloat((raw & 0xFF0000) >> 16) / 255.0
green = CGFloat((raw & 0x00FF00) >> 8) / 255.0
blue = CGFloat(raw & 0x0000FF) / 255.0
alpha = 1.0
}

self.init(red: red, green: green, blue: blue, alpha: alpha)
}

var isBonsplitLightColor: Bool {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Bonsplit/Public/BonsplitConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ extension BonsplitConfiguration {

public struct Appearance: Sendable {
public struct ChromeColors: Sendable {
/// Optional hex color (`#RRGGBB`) for tab/pane chrome backgrounds.
/// Optional hex color (`#RRGGBB` or `#RRGGBBAA`) for tab/pane chrome backgrounds.
/// When unset, Bonsplit uses native system colors.
public var backgroundHex: String?

Expand Down
18 changes: 18 additions & 0 deletions Tests/BonsplitTests/BonsplitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,24 @@ final class BonsplitTests: XCTestCase {
XCTAssertEqual(Int(round(alpha * 255)), 255)
}

func testChromeBackgroundHexOverrideParsesRGBAForPaneBackground() {
let appearance = BonsplitConfiguration.Appearance(
chromeColors: .init(backgroundHex: "#11223380")
)
let color = TabBarColors.nsColorPaneBackground(for: appearance).usingColorSpace(.sRGB)!

var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

XCTAssertEqual(Int(round(red * 255)), 17)
XCTAssertEqual(Int(round(green * 255)), 34)
XCTAssertEqual(Int(round(blue * 255)), 51)
XCTAssertEqual(Int(round(alpha * 255)), 128)
}

func testInvalidChromeBackgroundHexFallsBackToPaneDefaultColor() {
let appearance = BonsplitConfiguration.Appearance(
chromeColors: .init(backgroundHex: "#ZZZZZZ")
Expand Down