diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 72b3f922..fcd3a6be 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -113,7 +113,11 @@ enum SidebarRemoteErrorCopySupport { } func sidebarSelectedWorkspaceBackgroundNSColor(for colorScheme: ColorScheme) -> NSColor { - cmuxAccentNSColor(for: colorScheme) + if let hex = UserDefaults.standard.string(forKey: "sidebarSelectionColorHex"), + let parsed = NSColor(hex: hex) { + return parsed + } + return cmuxAccentNSColor(for: colorScheme) } func sidebarSelectedWorkspaceForegroundNSColor(opacity: CGFloat) -> NSColor { @@ -11026,6 +11030,8 @@ private struct TabItemView: View, Equatable { private var sidebarHideAllDetails = SidebarWorkspaceDetailSettings.defaultHideAllDetails @AppStorage(SidebarActiveTabIndicatorSettings.styleKey) private var activeTabIndicatorStyleRaw = SidebarActiveTabIndicatorSettings.defaultStyle.rawValue + @AppStorage("sidebarSelectionColorHex") private var sidebarSelectionColorHex: String? + @AppStorage("sidebarNotificationBadgeColorHex") private var sidebarNotificationBadgeColorHex: String? var isMultiSelected: Bool { selectedTabIds.contains(tab.id) @@ -11083,7 +11089,10 @@ private struct TabItemView: View, Equatable { } private var activeUnreadBadgeFillColor: Color { - usesInvertedActiveForeground ? Color.white.opacity(0.25) : cmuxAccentColor() + if let hex = sidebarNotificationBadgeColorHex, let nsColor = NSColor(hex: hex) { + return Color(nsColor: nsColor) + } + return usesInvertedActiveForeground ? Color.white.opacity(0.25) : cmuxAccentColor() } private var activeProgressTrackColor: Color { @@ -11828,14 +11837,21 @@ private struct TabItemView: View, Equatable { .disabled(!hasReadNotifications(in: targetIds)) } + private var selectionBackgroundColor: NSColor { + if let hex = sidebarSelectionColorHex, let parsed = NSColor(hex: hex) { + return parsed + } + return cmuxAccentNSColor(for: colorScheme) + } + private var backgroundColor: Color { switch activeTabIndicatorStyle { case .leftRail: - if isActive { return Color(nsColor: sidebarSelectedWorkspaceBackgroundNSColor(for: colorScheme)) } + if isActive { return Color(nsColor: selectionBackgroundColor) } if isMultiSelected { return cmuxAccentColor().opacity(0.25) } return Color.clear case .solidFill: - if isActive { return Color(nsColor: sidebarSelectedWorkspaceBackgroundNSColor(for: colorScheme)) } + if isActive { return Color(nsColor: selectionBackgroundColor) } if let custom = resolvedCustomTabColor { if isMultiSelected { return custom.opacity(0.35) } return custom.opacity(0.7) diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index d70a7303..48d210a9 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2885,6 +2885,7 @@ private struct SidebarDebugView: View { private var showSidebarDevBuildBanner = DevBuildBannerDebugSettings.defaultShowSidebarBanner @AppStorage(SidebarActiveTabIndicatorSettings.styleKey) private var sidebarActiveTabIndicatorStyle = SidebarActiveTabIndicatorSettings.defaultStyle.rawValue + @AppStorage("sidebarSelectionColorHex") private var sidebarSelectionColorHex: String? private var selectedSidebarIndicatorStyle: SidebarActiveTabIndicatorStyle { SidebarActiveTabIndicatorSettings.resolvedStyle(rawValue: sidebarActiveTabIndicatorStyle) @@ -2897,6 +2898,21 @@ private struct SidebarDebugView: View { ) } + private var selectionColorBinding: Binding { + Binding( + get: { + if let hex = sidebarSelectionColorHex, let nsColor = NSColor(hex: hex) { + return Color(nsColor: nsColor) + } + return cmuxAccentColor() + }, + set: { newColor in + let nsColor = NSColor(newColor) + sidebarSelectionColorHex = nsColor.hexString() + } + ) + } + var body: some View { ScrollView { VStack(alignment: .leading, spacing: 14) { @@ -3004,6 +3020,15 @@ private struct SidebarDebugView: View { Text(style.displayName).tag(style.rawValue) } } + + ColorPicker(String(localized: "sidebar.debug.selectionColor", defaultValue: "Selection Color"), selection: selectionColorBinding, supportsOpacity: false) + + if sidebarSelectionColorHex != nil { + Button(String(localized: "sidebar.debug.resetSelectionColor", defaultValue: "Reset to Default")) { + sidebarSelectionColorHex = nil + } + .font(.caption) + } } .padding(.top, 2) } @@ -3039,6 +3064,7 @@ private struct SidebarDebugView: View { } Button("Reset Active Indicator") { sidebarActiveTabIndicatorStyle = SidebarActiveTabIndicatorSettings.defaultStyle.rawValue + sidebarSelectionColorHex = nil } } @@ -3857,6 +3883,8 @@ struct SettingsView: View { @AppStorage(SidebarBranchLayoutSettings.key) private var sidebarBranchVerticalLayout = SidebarBranchLayoutSettings.defaultVerticalLayout @AppStorage(SidebarActiveTabIndicatorSettings.styleKey) private var sidebarActiveTabIndicatorStyle = SidebarActiveTabIndicatorSettings.defaultStyle.rawValue + @AppStorage("sidebarSelectionColorHex") private var sidebarSelectionColorHex: String? + @AppStorage("sidebarNotificationBadgeColorHex") private var sidebarNotificationBadgeColorHex: String? @AppStorage("sidebarShowBranchDirectory") private var sidebarShowBranchDirectory = true @AppStorage("sidebarShowPullRequest") private var sidebarShowPullRequest = true @AppStorage(BrowserLinkOpenSettings.openSidebarPullRequestLinksInCmuxBrowserKey) @@ -3969,6 +3997,36 @@ struct SettingsView: View { ) } + private var selectionColorBinding: Binding { + Binding( + get: { + if let hex = sidebarSelectionColorHex, let nsColor = NSColor(hex: hex) { + return Color(nsColor: nsColor) + } + return cmuxAccentColor() + }, + set: { newColor in + let nsColor = NSColor(newColor) + sidebarSelectionColorHex = nsColor.hexString() + } + ) + } + + private var notificationBadgeColorBinding: Binding { + Binding( + get: { + if let hex = sidebarNotificationBadgeColorHex, let nsColor = NSColor(hex: hex) { + return Color(nsColor: nsColor) + } + return cmuxAccentColor() + }, + set: { newColor in + let nsColor = NSColor(newColor) + sidebarNotificationBadgeColorHex = nsColor.hexString() + } + ) + } + private var selectedSocketControlMode: SocketControlMode { SocketControlSettings.migrateMode(socketControlMode) } @@ -4836,6 +4894,66 @@ struct SettingsView: View { SettingsCardDivider() + SettingsCardRow( + String(localized: "settings.workspaceColors.selectionColor", defaultValue: "Selection Highlight"), + subtitle: String(localized: "settings.workspaceColors.selectionColor.subtitle", defaultValue: "Background color of the selected workspace in the sidebar.") + ) { + HStack(spacing: 8) { + if sidebarSelectionColorHex != nil { + Button(String(localized: "settings.workspaceColors.selectionColor.reset", defaultValue: "Reset")) { + sidebarSelectionColorHex = nil + } + .buttonStyle(.bordered) + .controlSize(.small) + } + + ColorPicker( + "", + selection: selectionColorBinding, + supportsOpacity: false + ) + .labelsHidden() + .frame(width: 38) + + Text(sidebarSelectionColorHex ?? String(localized: "settings.sidebarAppearance.defaultLabel", defaultValue: "Default")) + .font(.system(size: 12, weight: .medium, design: .monospaced)) + .foregroundStyle(.secondary) + .frame(width: 76, alignment: .trailing) + } + } + + SettingsCardDivider() + + SettingsCardRow( + String(localized: "settings.workspaceColors.notificationBadgeColor", defaultValue: "Notification Badge"), + subtitle: String(localized: "settings.workspaceColors.notificationBadgeColor.subtitle", defaultValue: "Color of the unread notification badge on workspace tabs.") + ) { + HStack(spacing: 8) { + if sidebarNotificationBadgeColorHex != nil { + Button(String(localized: "settings.workspaceColors.notificationBadgeColor.reset", defaultValue: "Reset")) { + sidebarNotificationBadgeColorHex = nil + } + .buttonStyle(.bordered) + .controlSize(.small) + } + + ColorPicker( + "", + selection: notificationBadgeColorBinding, + supportsOpacity: false + ) + .labelsHidden() + .frame(width: 38) + + Text(sidebarNotificationBadgeColorHex ?? String(localized: "settings.sidebarAppearance.defaultLabel", defaultValue: "Default")) + .font(.system(size: 12, weight: .medium, design: .monospaced)) + .foregroundStyle(.secondary) + .frame(width: 76, alignment: .trailing) + } + } + + SettingsCardDivider() + SettingsCardNote(String(localized: "settings.workspaceColors.paletteNote", defaultValue: "Customize the workspace color palette used by Sidebar > Workspace Color. \"Choose Custom Color...\" entries are persisted below.")) ForEach(Array(workspaceTabDefaultEntries.enumerated()), id: \.element.name) { index, entry in @@ -5660,6 +5778,8 @@ struct SettingsView: View { sidebarShowNotificationMessage = SidebarWorkspaceDetailSettings.defaultShowNotificationMessage sidebarBranchVerticalLayout = SidebarBranchLayoutSettings.defaultVerticalLayout sidebarActiveTabIndicatorStyle = SidebarActiveTabIndicatorSettings.defaultStyle.rawValue + sidebarSelectionColorHex = nil + sidebarNotificationBadgeColorHex = nil sidebarShowBranchDirectory = true sidebarShowPullRequest = true openSidebarPullRequestLinksInCmuxBrowser = BrowserLinkOpenSettings.defaultOpenSidebarPullRequestLinksInCmuxBrowser