From ae5a3047e09fab1cb518708d2a2ff5a550e32eeb Mon Sep 17 00:00:00 2001 From: erdivartanovich Date: Fri, 10 Jul 2026 20:18:41 +0800 Subject: [PATCH] Add configurable selected-item colors New optional color parameters for the currently selected item: list_color_selected, dialog_color_selected, history_name_color_selected and history_text_color_selected (bg/fg each). When set, the selected chat list row, list dialog item and selected message name/text use the specified color pair in addition to the corresponding _attr_selected attribute. When left empty (default) only attribute styling is applied, preserving existing behavior. Selected color takes precedence over unread/shaded/usercolor for the highlighted item. Enables palette-consistent selection styling for terminal theming setups where reverse video is undesirable. --- README.md | 14 ++++++++++++++ src/uicolorconfig.cpp | 13 +++++++++++++ src/uicolorconfig.h | 1 + src/uihistoryview.cpp | 16 ++++++++++++++++ src/uilistdialog.cpp | 16 +++++++++++++++- src/uilistview.cpp | 21 ++++++++++++++++++++- 6 files changed, 79 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a16110ad4..63203a131 100644 --- a/README.md +++ b/README.md @@ -941,6 +941,8 @@ This configuration file holds user interface color settings. Default content: dialog_attr_selected=reverse dialog_color_bg= dialog_color_fg= + dialog_color_selected_bg= + dialog_color_selected_fg= entry_attr= entry_color_bg= entry_color_fg= @@ -949,6 +951,8 @@ This configuration file holds user interface color settings. Default content: help_color_fg=white history_name_attr=bold history_name_attr_selected=reverse + history_name_color_selected_bg= + history_name_color_selected_fg= history_name_recv_color_bg= history_name_recv_color_fg= history_name_recv_group_color_bg= @@ -959,6 +963,8 @@ This configuration file holds user interface color settings. Default content: history_text_attachment_color_fg=gray history_text_attr= history_text_attr_selected=reverse + history_text_color_selected_bg= + history_text_color_selected_fg= history_text_quoted_color_bg= history_text_quoted_color_fg=gray history_text_reaction_color_bg= @@ -973,6 +979,8 @@ This configuration file holds user interface color settings. Default content: list_attr_selected=reverse list_color_bg= list_color_fg= + list_color_selected_bg= + list_color_selected_fg= list_color_unread_bg= list_color_unread_fg= listborder_attr= @@ -1020,6 +1028,12 @@ parameters also supports the special value `usercolor`. When set, nchat will determine which color to use for a user, based on a hash of their user id used to pick a color from the list in `~/.config/nchat/usercolor.conf`. +The `list_color_selected`, `dialog_color_selected`, `history_name_color_selected` +and `history_text_color_selected` parameters allow specifying explicit colors +for the currently selected item, instead of (or in addition to) the +corresponding `_attr_selected` attribute. When left empty (default), only the +attribute styling is applied, preserving previous behavior. + Themes ------ Color themes are available in the source package under the `themes` directory diff --git a/src/uicolorconfig.cpp b/src/uicolorconfig.cpp index 5dadae9cf..8802d191b 100644 --- a/src/uicolorconfig.cpp +++ b/src/uicolorconfig.cpp @@ -50,6 +50,8 @@ void UiColorConfig::Init() { "list_attr_selected", "reverse" }, { "list_color_bg", "" }, { "list_color_fg", "" }, + { "list_color_selected_bg", "" }, + { "list_color_selected_fg", "" }, { "list_color_unread_fg", "" }, { "list_color_unread_bg", "" }, { "listborder_attr", "" }, @@ -58,6 +60,8 @@ void UiColorConfig::Init() { "history_text_attr", "" }, { "history_text_attr_selected", "reverse" }, + { "history_text_color_selected_bg", "" }, + { "history_text_color_selected_fg", "" }, { "history_text_sent_color_bg", "" }, { "history_text_sent_color_fg", defaultSentColor }, { "history_text_reaction_color_bg", "" }, @@ -73,6 +77,8 @@ void UiColorConfig::Init() { "history_name_attr", "bold" }, { "history_name_attr_selected", "reverse" }, + { "history_name_color_selected_bg", "" }, + { "history_name_color_selected_fg", "" }, { "history_name_sent_color_bg", "" }, { "history_name_sent_color_fg", defaultSentColor }, { "history_name_recv_color_bg", "" }, @@ -84,6 +90,8 @@ void UiColorConfig::Init() { "dialog_attr_selected", "reverse" }, { "dialog_color_bg", "" }, { "dialog_color_fg", "" }, + { "dialog_color_selected_bg", "" }, + { "dialog_color_selected_fg", "" }, { "dialog_shaded_color_bg", "" }, { "dialog_shaded_color_fg", defaultShadedColor }, @@ -128,6 +136,11 @@ int UiColorConfig::GetColorPair(const std::string& p_Param) return colorPairs[p_Param]; } +bool UiColorConfig::HasColor(const std::string& p_Param) +{ + return !m_Config.Get(p_Param + "_fg").empty() || !m_Config.Get(p_Param + "_bg").empty(); +} + int UiColorConfig::GetUserColorPair(const std::string& p_Param, const std::string& p_UserId) { if (!has_colors()) return 0; diff --git a/src/uicolorconfig.h b/src/uicolorconfig.h index 08acde9f0..3f70e54be 100644 --- a/src/uicolorconfig.h +++ b/src/uicolorconfig.h @@ -18,6 +18,7 @@ class UiColorConfig static void Init(); static void Cleanup(); static int GetColorPair(const std::string& p_Param); // ex: "top_color" + static bool HasColor(const std::string& p_Param); // ex: "list_color_selected" static int GetUserColorPair(const std::string& p_Param, const std::string& p_UserId); static bool IsUserColor(const std::string& p_Param); // ex: "top_color" static int GetAttribute(const std::string& p_Param); // ex: "top_attr" diff --git a/src/uihistoryview.cpp b/src/uihistoryview.cpp index ef9da4043..3b06dc0a6 100644 --- a/src/uihistoryview.cpp +++ b/src/uihistoryview.cpp @@ -105,6 +105,14 @@ void UiHistoryView::Draw() int attributeText = isSelectedMessage ? attributeTextSelected : attributeTextNormal; int colorPairText = [&]() { + // selected color takes precedence over sent/recv/usercolor for the selected message + static bool hasColorSelected = UiColorConfig::HasColor("history_text_color_selected"); + if (isSelectedMessage && hasColorSelected) + { + static int colorPairSelected = UiColorConfig::GetColorPair("history_text_color_selected"); + return colorPairSelected; + } + if (msg.isOutgoing) return colorPairTextSent; if (msg.senderId == currentChat.second) return colorPairTextRecv; @@ -365,6 +373,14 @@ void UiHistoryView::Draw() int attributeName = isSelectedMessage ? attributeNameSelected : attributeNameNormal; int colorPairName = [&]() { + // selected color takes precedence over sent/recv/usercolor for the selected message + static bool hasColorSelected = UiColorConfig::HasColor("history_name_color_selected"); + if (isSelectedMessage && hasColorSelected) + { + static int colorPairSelected = UiColorConfig::GetColorPair("history_name_color_selected"); + return colorPairSelected; + } + if (msg.isOutgoing) return colorPairNameSent; if (msg.senderId == currentChat.second) return colorPairNameRecv; diff --git a/src/uilistdialog.cpp b/src/uilistdialog.cpp index 9de0dfe4d..fe6da8978 100644 --- a/src/uilistdialog.cpp +++ b/src/uilistdialog.cpp @@ -180,6 +180,8 @@ void UiListDialog::Draw() static int shadedColorPair = UiColorConfig::GetColorPair("dialog_shaded_color"); static int attribute = UiColorConfig::GetAttribute("dialog_attr"); static int attributeSelected = UiColorConfig::GetAttribute("dialog_attr_selected"); + static bool hasColorSelected = UiColorConfig::HasColor("dialog_color_selected"); + static int colorPairSelected = hasColorSelected ? UiColorConfig::GetColorPair("dialog_color_selected") : colorPair; static std::wstring hiddenIndicator = L"."; werase(m_Win); @@ -190,7 +192,9 @@ void UiListDialog::Draw() for (int i = offset; i < std::min((offset + m_H), (int)m_Items.size()); ++i) { const std::wstring& wdisp = m_Items.at(i); - const bool isShaded = m_ShadeHidden && (wdisp.rfind(hiddenIndicator, 0) == 0); + // selected color takes precedence over shaded color for the highlighted item + const bool isSelectedColor = (i == m_Index) && hasColorSelected; + const bool isShaded = m_ShadeHidden && !isSelectedColor && (wdisp.rfind(hiddenIndicator, 0) == 0); if (isShaded) { @@ -202,6 +206,11 @@ void UiListDialog::Draw() { wattroff(m_Win, attribute); wattron(m_Win, attributeSelected); + if (isSelectedColor) + { + wattroff(m_Win, colorPair); + wattron(m_Win, colorPairSelected); + } } mvwaddnwstr(m_Win, (i - offset), 0, wdisp.c_str(), std::min((int)wdisp.size(), m_W)); @@ -210,6 +219,11 @@ void UiListDialog::Draw() { wattroff(m_Win, attributeSelected); wattron(m_Win, attribute); + if (isSelectedColor) + { + wattroff(m_Win, colorPairSelected); + wattron(m_Win, colorPair); + } } if (isShaded) diff --git a/src/uilistview.cpp b/src/uilistview.cpp index e3a916017..0c35f6c18 100644 --- a/src/uilistview.cpp +++ b/src/uilistview.cpp @@ -52,6 +52,8 @@ void UiListView::Draw() static int attribute = UiColorConfig::GetAttribute("list_attr"); static int attributeSelected = UiColorConfig::GetAttribute("list_attr_selected"); static int colorPairUnread = UiColorConfig::GetColorPair("list_color_unread"); + static bool hasColorSelected = UiColorConfig::HasColor("list_color_selected"); + static int colorPairSelected = hasColorSelected ? UiColorConfig::GetColorPair("list_color_selected") : colorPair; int index = std::max(0, m_Model->GetCurrentChatIndexLocked()); const std::vector>& p_ChatVec = m_Model->GetChatVecLocked(); @@ -84,6 +86,11 @@ void UiListView::Draw() { wattroff(m_PaddedWin, attribute); wattron(m_PaddedWin, attributeSelected); + if (hasColorSelected) + { + wattroff(m_PaddedWin, colorPair); + wattron(m_PaddedWin, colorPairSelected); + } } int y = i - offset; @@ -96,8 +103,11 @@ void UiListView::Draw() std::wstring wname = StrUtil::ToWString(name).substr(0, m_PaddedW); wname = StrUtil::TrimPadWString(wname, m_PaddedW); - if (unreads[i]) + // selected color takes precedence over unread color for the highlighted row + const bool useUnreadColor = unreads[i] && !((i == index) && hasColorSelected); + if (useUnreadColor) { + wattroff(m_PaddedWin, colorPair); wattron(m_PaddedWin, colorPairUnread); } @@ -108,7 +118,11 @@ void UiListView::Draw() static const std::string unreadIndicator = " " + UiConfig::GetStr("unread_indicator"); static const std::wstring wunread = StrUtil::ToWString(unreadIndicator); mvwaddnwstr(m_PaddedWin, y, (m_PaddedW - StrUtil::WStringWidth(wunread)), wunread.c_str(), wunread.size()); + } + if (useUnreadColor) + { + wattroff(m_PaddedWin, colorPairUnread); wattron(m_PaddedWin, colorPair); } @@ -116,6 +130,11 @@ void UiListView::Draw() { wattroff(m_PaddedWin, attributeSelected); wattron(m_PaddedWin, attribute); + if (hasColorSelected) + { + wattroff(m_PaddedWin, colorPairSelected); + wattron(m_PaddedWin, colorPair); + } } } }