diff --git a/indra/newview/alavatargroups.cpp b/indra/newview/alavatargroups.cpp index b376427ac2..ab7bb5bc55 100644 --- a/indra/newview/alavatargroups.cpp +++ b/indra/newview/alavatargroups.cpp @@ -26,6 +26,7 @@ // lib includes #include "llavatarname.h" #include "llavatarnamecache.h" +#include "llchat.h" #include "lluicolor.h" #include "lluicolortable.h" #include "lluuid.h" @@ -34,6 +35,7 @@ // viewer includes #include "llagent.h" #include "llcallingcard.h" +#include "llinstantmessage.h" // SYSTEM_FROM #include "llmutelist.h" #include "llviewercontrol.h" #include "rlvactions.h" @@ -294,3 +296,99 @@ std::string ALAvatarGroups::getAvatarColorName(const LLUUID& id, std::string_vie return out_color_name; } + +bool ALAvatarGroups::getIRCChatColor(const LLChat& chat, LLUIColor& color) +{ + static LLCachedControl enabled(gSavedSettings, "AlchemyChatIRCColorsEnabled", false); + if (!enabled) + { + return false; + } + + // Only override "other" speakers; self, system, objects and owner-say keep + // their user-configured chat colors from the existing switch. + if (chat.mSourceType == CHAT_SOURCE_AGENT + && chat.mFromID.notNull() + && chat.mFromID != gAgentID + && SYSTEM_FROM != chat.mFromName + // A stable per-avatar color would defeat @shownames, so skip it while + // restricted from seeing this speaker's name. + && RlvActions::canShowName(RlvActions::SNC_DEFAULT, chat.mFromID)) + { + color = deterministicAgentColor(chat.mFromID); + return true; + } + + return false; +} + +bool ALAvatarGroups::getIRCNameColor(const LLChat& chat, LLUIColor& color) +{ + static LLCachedControl enabled(gSavedSettings, "AlchemyChatIRCColorsEnabled", false); + if (!enabled) + { + return false; + } + + // Keep the default name styling while restricted from seeing this speaker's + // name, matching getIRCChatColor. + if (!RlvActions::canShowName(RlvActions::SNC_DEFAULT, chat.mFromID)) + { + return false; + } + + color = nameColor(chat.mFromID); + return true; +} + +bool ALAvatarGroups::getIRCNameTagColor(const LLUUID& id, LLColor4& color) +{ + static LLCachedControl enabled(gSavedSettings, "AlchemyChatIRCColorsEnabled", false); + static LLCachedControl name_tags(gSavedSettings, "AlchemyChatIRCColorNameTags", false); + if (!enabled || !name_tags) + { + return false; + } + + // Mirror the chat-color override: only other agents get a per-avatar color, + // self keeps the user-configured name tag colors. Skip while restricted from + // seeing this avatar's name so the color can't defeat @shownames. The name + // lightness applied to chat names is applied here too so tags match. + if (id.notNull() && id != gAgentID + && RlvActions::canShowName(RlvActions::SNC_DEFAULT, id)) + { + color = nameColor(id); + return true; + } + + return false; +} + +LLColor4 ALAvatarGroups::deterministicAgentColor(const LLUUID& id) +{ + static LLCachedControl saturation(gSavedSettings, "AlchemyChatIRCAgentSaturation", 0.7f); + static LLCachedControl lightness(gSavedSettings, "AlchemyChatIRCAgentLightness", 0.9f); + + LLColor4 color; + color.setHSL(static_cast(id.getCRC32() % 360) / 360.f, saturation(), lightness()); + color.mV[VALPHA] = 1.f; + + return color; +} + +LLColor4 ALAvatarGroups::nameColor(const LLUUID& id) +{ + static LLCachedControl saturation(gSavedSettings, "AlchemyChatIRCAgentSaturation", 0.7f); + static LLCachedControl name_lightness(gSavedSettings, "AlchemyChatIRCNameLightness", 0.7f); + + // Same hue and saturation as the per-agent chat color, but with the name's + // own independent lightness. Built from the same inputs as the chat color + // rather than derived from it, so the chat Lightness slider doesn't bleed + // into the name (a very light/dark chat color loses saturation, and hue at + // the extremes, when round-tripped through RGB). + LLColor4 result; + result.setHSL(static_cast(id.getCRC32() % 360) / 360.f, saturation(), name_lightness()); + result.mV[VALPHA] = 1.f; + + return result; +} diff --git a/indra/newview/alavatargroups.h b/indra/newview/alavatargroups.h index 625d703b1e..1ea3894576 100644 --- a/indra/newview/alavatargroups.h +++ b/indra/newview/alavatargroups.h @@ -24,7 +24,9 @@ #include #include +class LLChat; class LLColor4; +class LLUIColor; class LLUUID; class ALAvatarGroups final : public LLSingleton < ALAvatarGroups > @@ -54,4 +56,16 @@ class ALAvatarGroups final : public LLSingleton < ALAvatarGroups > LLColor4 getAvatarColor(const LLUUID& id, LLColor4 default_color, EColorType color_type); std::string getAvatarColorName(const LLUUID& id, std::string_view color_name, EColorType color_type); + + // IRC-style chat coloring: when enabled, gives every other speaker a stable + // deterministic color and dims the displayed name relative to its text color. + // Other message categories (self, system, objects, owner-say) keep their + // user-configured chat colors. + bool getIRCChatColor(const LLChat& chat, LLUIColor& color); + bool getIRCNameColor(const LLChat& chat, LLUIColor& color); + bool getIRCNameTagColor(const LLUUID& id, LLColor4& color); + +private: + LLColor4 deterministicAgentColor(const LLUUID& id); + LLColor4 nameColor(const LLUUID& id); }; diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index b323c0f901..0cf4980b66 100644 --- a/indra/newview/app_settings/settings_alchemy.xml +++ b/indra/newview/app_settings/settings_alchemy.xml @@ -387,6 +387,61 @@ Value /tp2cam + AlchemyChatIRCColorsEnabled + + Comment + Use IRC-style deterministic colors for local chat names and text. + Persist + 1 + Type + Boolean + Value + 0 + + AlchemyChatIRCColorNameTags + + Comment + Overwrite each other agent's name tag with their IRC-style per-agent chat color. + Persist + 1 + Type + Boolean + Value + 0 + + AlchemyChatIRCAgentSaturation + + Comment + Saturation of the per-agent IRC-style chat colors (0-1). + Persist + 1 + Type + F32 + Value + 0.7 + + AlchemyChatIRCAgentLightness + + Comment + Lightness of the per-agent IRC-style chat colors (0-1). + Persist + 1 + Type + F32 + Value + 0.9 + + AlchemyChatIRCNameLightness + + Comment + Lightness of the displayed name in IRC-style chat colors; shares the chat color's hue and saturation (0-1). + Persist + 1 + Type + F32 + Value + 0.7 + AlchemyChatMarkUnnamedObjects Comment diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 39a31fb630..1c567b58e8 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -69,6 +69,7 @@ #include "llviewercontrol.h" #include "llviewermenu.h" #include "llviewerobjectlist.h" +#include "alavatargroups.h" // [SL:KB] - Patch: Chat-Alerts | Checked: 2012-07-10 (Catznip-3.3) #include "llaudioengine.h" #include "lltextparser.h" @@ -1338,6 +1339,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL LLUIColor txt_color = LLUIColorTable::instance().getColor("White"); LLUIColor name_color = LLUIColorTable::instance().getColor("ChatHeaderDisplayNameColor"); // LLViewerChat::getChatColor(chat, txt_color, alpha); + const bool irc_name_color = ALAvatarGroups::instance().getIRCNameColor(chat, name_color); LLFontGL* fontp = LLViewerChat::getChatFont(); std::string font_name = LLFontGL::nameFromFont(fontp); @@ -1463,6 +1465,11 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL static LLUIColor link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); link_params.color = link_color; link_params.readonly_color = link_color; + if (irc_name_color) + { + link_params.color = name_color; + link_params.readonly_color = name_color; + } link_params.is_link = true; link_params.link_href = url; @@ -1500,6 +1507,14 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL { LLStyle::Params link_params(body_message_params); link_params.overwriteFrom(LLStyleMap::instance().lookupAgent(chat.mFromID)); + if (irc_name_color) + { + // Keep our dimmed name color instead of letting the agent + // SLURL re-parse to the default HTMLLinkColor link style. + link_params.use_default_link_style = false; + link_params.color = name_color; + link_params.readonly_color = name_color; + } if (use_irssi_text_chat_history) { diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 3b286af56b..7b21bb68c6 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -34,7 +34,12 @@ #include "llfloaterpreference.h" +#include "alavatargroups.h" #include "llaudioengine.h" +#include "llchat.h" +#include "llstyle.h" +#include "lluicolortable.h" +#include "llviewerchat.h" #include "message.h" #include "llfloaterautoreplacesettings.h" #include "llagent.h" @@ -74,6 +79,8 @@ #include "llscrolllistitem.h" #include "llsliderctrl.h" #include "lltabcontainer.h" +#include "lltextbox.h" +#include "lltexteditor.h" #include "lltrans.h" #include "lluri.h" #include "llviewercontrol.h" @@ -3066,6 +3073,162 @@ class LLPanelPreferencePrivacy : public LLPanelPreference static LLPanelInjector t_pref_graph("panel_preference_graphics"); static LLPanelInjector t_pref_privacy("panel_preference_privacy"); static LLPanelInjector t_pref_sound("panel_preference_sound"); +static LLPanelInjector t_pref_colors("panel_preference_colors"); + +bool LLPanelPreferenceColors::postBuild() +{ + mPreviewEditor = getChild("irc_chat_preview"); + updatePreview(); + return LLPanelPreference::postBuild(); +} + +void LLPanelPreferenceColors::draw() +{ + // The chat-color swatches commit straight into LLUIColorTable (no settings + // signal to listen to), so poll a cheap signature of everything the preview + // depends on and re-render only when it actually changes. + static const char* const color_names[] = { + "UserChatColor", "AgentChatColor", "FriendChatColor", "SystemChatColor", + "ObjectChatColor", "llOwnerSayChatColor", "LindenChatColor", "HTMLLinkColor", + }; + std::string signature; + for (const char* name : color_names) + { + const LLColor4& c = LLUIColorTable::instance().getColor(name).get(); + signature += llformat("%.3f,%.3f,%.3f;", c.mV[VRED], c.mV[VGREEN], c.mV[VBLUE]); + } + signature += llformat("%d;%.3f;%.3f;%.3f;%d", + gSavedSettings.getBOOL("AlchemyChatIRCColorsEnabled") ? 1 : 0, + gSavedSettings.getF32("AlchemyChatIRCAgentSaturation"), + gSavedSettings.getF32("AlchemyChatIRCAgentLightness"), + gSavedSettings.getF32("AlchemyChatIRCNameLightness"), + gSavedSettings.getBOOL("Use24HourClock") ? 1 : 0); + + if (signature != mPreviewSignature) + { + mPreviewSignature = signature; + updatePreview(); + } + + LLPanelPreference::draw(); +} + +void LLPanelPreferenceColors::updatePreview() +{ + if (!mPreviewEditor) + return; + + // One line per chat category the panel above exposes. Each carries the base + // color the real getChatColor() switch would pick (friend/Linden can't be + // detected from a fake id, so we set it explicitly), plus a stable id so the + // per-avatar IRC color stays constant between refreshes. The IRC override and + // name dimming are then applied through the real ALAvatarGroups calls, so the + // preview tracks the live colors and settings exactly like in-world local chat. + enum EKind { K_SYSTEM, K_SELF, K_RESIDENT, K_FRIEND, K_LINDEN, K_OBJECT, K_OWNER }; + static const struct + { + EKind kind; + const char* color; // base text color name + const char* id; // stable per-avatar id (empty for system/self) + S32 hour; // mock 24h timestamp, formatted per Use24HourClock + S32 minute; + const char* name; // speaker name ("" hides the name prefix) + const char* text; + } samples[] = { + { K_SYSTEM, "SystemChatColor", "", 13, 42, "", "Welcome to Quirk Island. Mind the dancing llamas." }, + { K_SELF, "UserChatColor", "", 13, 43, "You", "ok who moved my virtual cheese" }, + { K_RESIDENT, "AgentChatColor", "a1a1a1a1-0000-0000-0000-000000000001", 13, 44, "Bartholomew Bumblecrash", "anyone else seeing the sky do the wobble thing?" }, + { K_RESIDENT, "AgentChatColor", "f6f6f6f6-0000-0000-0000-000000000006", 13, 45, "Wandering Pixel", "https://marketplace.secondlife.com/" }, + { K_FRIEND, "FriendChatColor", "b2b2b2b2-0000-0000-0000-000000000002", 13, 46, "Glittertoes McSparkle", "omg hi!! brb taming a baby dragon" }, + { K_LINDEN, "LindenChatColor", "c3c3c3c3-0000-0000-0000-000000000003", 13, 47, "Governor Linden", "Rolling restart in 5, hold onto your hats." }, + { K_OBJECT, "ObjectChatColor", "d4d4d4d4-0000-0000-0000-000000000004", 13, 48, "Object", "Welcome to Help Island!" }, + { K_OWNER, "llOwnerSayChatColor", "e5e5e5e5-0000-0000-0000-000000000005", 13, 49, "Animation HUD", "Memory Free: 2167 KiB :)" }, + }; + + static const LLUIColor time_color = LLUIColorTable::instance().getColor("ChatHeaderTimestampColor"); + const bool use_24h = gSavedSettings.getBOOL("Use24HourClock"); + + mPreviewEditor->clear(); // clear before re-appending + + bool prepend_newline = false; // newline before every line except the first + for (const auto& s : samples) + { + LLChat chat; + chat.mFromName = s.name; + chat.mText = s.text; + switch (s.kind) + { + case K_SYSTEM: + chat.mSourceType = CHAT_SOURCE_SYSTEM; + break; + case K_SELF: + chat.mSourceType = CHAT_SOURCE_AGENT; + chat.mFromID = gAgentID; + break; + case K_OBJECT: + chat.mSourceType = CHAT_SOURCE_OBJECT; + chat.mFromID = LLUUID(s.id); + break; + case K_OWNER: + chat.mSourceType = CHAT_SOURCE_OBJECT; + chat.mChatType = CHAT_TYPE_OWNER; + chat.mFromID = LLUUID(s.id); + break; + default: // resident / friend / Linden are all "other agents" + chat.mSourceType = CHAT_SOURCE_AGENT; + chat.mFromID = LLUUID(s.id); + break; + } + + // Base category color, then the live IRC overrides (mirrors getChatColor + + // getIRCNameColor in llchathistory.cpp). getIRCChatColor only recolors other + // agents, so self/system/object/owner keep their configured color. Names are + // clickable SLURLs in chat, so by default they take the link color; when IRC + // coloring is on, getIRCNameColor overrides that with the per-agent name color. + LLUIColor txt_color = LLUIColorTable::instance().getColor(s.color); + ALAvatarGroups::instance().getIRCChatColor(chat, txt_color); + + LLUIColor name_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + ALAvatarGroups::instance().getIRCNameColor(chat, name_color); + + // Timestamp prefix, like nearby chat: "[hh:mm] ", honoring the + // Use24HourClock pref the way LLFloaterIMSessionTab::appendTime() does. + std::string time_str; + if (use_24h) + { + time_str = llformat("%d:%02d", s.hour, s.minute); + } + else + { + S32 h12 = s.hour % 12; + if (h12 == 0) h12 = 12; + time_str = llformat("%d:%02d %s", h12, s.minute, s.hour < 12 ? "AM" : "PM"); + } + + LLStyle::Params time_style; + time_style.color(time_color); + time_style.readonly_color(time_color); + mPreviewEditor->appendText("[" + time_str + "] ", prepend_newline, time_style); + prepend_newline = false; // keep the rest of the line attached to the timestamp + + if (!chat.mFromName.empty()) + { + LLStyle::Params name_style; + name_style.color(name_color); + name_style.readonly_color(name_color); + mPreviewEditor->appendText(chat.mFromName + ": ", prepend_newline, name_style); + } + + // parse_urls is set on the widget, so any URL in the text is linkified + // (and colored HTMLLinkColor) automatically by appendText. + LLStyle::Params text_style; + text_style.color(txt_color); + text_style.readonly_color(txt_color); + mPreviewEditor->appendText(chat.mText, prepend_newline, text_style); + + prepend_newline = true; + } +} bool LLPanelPreferenceSound::postBuild() { diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index 310bd4bb6e..567c2adc4c 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -53,6 +53,7 @@ class LLScrollListCell; class LLSliderCtrl; class LLSD; class LLTextBox; +class LLTextEditor; struct skin_t; namespace ll @@ -388,6 +389,27 @@ class LLPanelPreferenceSound : public LLPanelPreference boost::signals2::scoped_connection mDevicesChangedConn; }; +// Colors > Chat preferences panel. Renders a live mock-chat preview that +// re-colors whenever a chat-color swatch or any AlchemyChatIRC* setting +// changes, using the real chat-color path so it matches in-world local chat. +class LLPanelPreferenceColors : public LLPanelPreference +{ + LOG_CLASS(LLPanelPreferenceColors); +public: + bool postBuild() override; + void draw() override; + +private: + void updatePreview(); + + LLTextEditor* mPreviewEditor = nullptr; + + // Signature of the colors/settings the preview depends on. The chat-color + // swatches commit straight into LLUIColorTable (no settings signal), so + // draw() polls this and re-renders only when it actually changes. + std::string mPreviewSignature; +}; + class LLPanelPreferenceControls : public LLPanelPreference, public LLKeyBindResponderInterface { LOG_CLASS(LLPanelPreferenceControls); diff --git a/indra/newview/llviewerchat.cpp b/indra/newview/llviewerchat.cpp index 5c0e0358ab..b3942f2960 100644 --- a/indra/newview/llviewerchat.cpp +++ b/indra/newview/llviewerchat.cpp @@ -51,6 +51,8 @@ void LLViewerChat::getChatColor(const LLChat& chat, LLUIColor& r_color, F32& r_c } else { + if (!ALAvatarGroups::instance().getIRCChatColor(chat, r_color)) + { switch(chat.mSourceType) { case CHAT_SOURCE_SYSTEM: @@ -96,6 +98,7 @@ void LLViewerChat::getChatColor(const LLChat& chat, LLUIColor& r_color, F32& r_c default: r_color = LLUIColorTable::instance().getColor("White"); } + } if (!chat.mPosAgent.isExactlyZero()) { diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 7334477a8c..49f790b46e 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -3999,7 +3999,10 @@ LLColor4 LLVOAvatar::getNameTagColor(bool is_friend) #endif static LLUIColor name_tag_match = LLUIColorTable::instance().getColor("NameTagMatch"); LLColor4 color_name = name_tag_match; - color_name = ALAvatarGroups::instance().getAvatarColor(getID(), color_name, ALAvatarGroups::COLOR_NAMETAG); + if (!ALAvatarGroups::instance().getIRCNameTagColor(getID(), color_name)) + { + color_name = ALAvatarGroups::instance().getAvatarColor(getID(), color_name, ALAvatarGroups::COLOR_NAMETAG); + } return color_name; } diff --git a/indra/newview/skins/default/xui/en/floater_preferences.xml b/indra/newview/skins/default/xui/en/floater_preferences.xml index e012770cd2..9fa6f983a7 100644 --- a/indra/newview/skins/default/xui/en/floater_preferences.xml +++ b/indra/newview/skins/default/xui/en/floater_preferences.xml @@ -144,7 +144,7 @@ help_topic="preferences_msgs_tab" name="msgs" /> - Lindens + + IRC-style chat colors: + + + + + + + + Compact Chat Preview: + +