Add customizable sidebar selection highlight color (#1824)
* Add customizable sidebar selection highlight color Expose a `sidebarSelectionColorHex` user default that overrides the hardcoded blue (#0091FF) selection highlight in the sidebar. Add a "Selection Highlight" color picker in Settings > Workspace Colors, following the same pattern as existing tint color pickers. Falls back to the default accent color when no custom color is set. Closes #1753 * Fix review feedback: reactivity, reset button, localization - Add @AppStorage subscription in TabItemView so sidebar selection color updates reactively when changed in Settings - Add Reset button in Settings > Workspace Colors > Selection Highlight - Localize debug panel strings for Selection Color picker - Clear sidebarSelectionColorHex in resetAllSettings() * Add customizable notification badge color in sidebar Add `sidebarNotificationBadgeColorHex` user default to override the unread notification badge color on workspace tabs. Add a "Notification Badge" color picker in Settings > Workspace Colors, following the same pattern as the selection highlight picker. Falls back to the default accent color when no custom color is set.
This commit is contained in:
parent
63904811f9
commit
609a02c3f9
2 changed files with 140 additions and 4 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<Color> {
|
||||
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<Color> {
|
||||
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<Color> {
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue