diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index c83af953d..072dc34fb 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -175,7 +175,8 @@ BackgroundPreviewBox::BackgroundPreviewBox( , _controller(controller) , _forPeer(args.forPeer) , _fromMessageId(args.fromMessageId) -, _chatStyle(std::make_unique<Ui::ChatStyle>()) +, _chatStyle(std::make_unique<Ui::ChatStyle>( + controller->session().colorIndicesValue())) , _serviceHistory(_controller->session().data().history( PeerData::kServiceNotificationsId)) , _service(nullptr) diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index 952aa0c49..2bbb1b4a7 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -133,7 +133,8 @@ void AddMessage( state->delegate = std::make_unique<Delegate>( controller, crl::guard(widget, [=] { widget->update(); })); - state->style = std::make_unique<Ui::ChatStyle>(); + state->style = std::make_unique<Ui::ChatStyle>( + controller->session().colorIndicesValue()); state->style->apply(controller->defaultChatTheme().get()); state->icons.lifetimes = std::vector<rpl::lifetime>(2); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 780b6c571..4c03f5d4d 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -889,7 +889,7 @@ bool PeerData::changeColorIndex(uint8 index) { if (_colorIndexCloud && _colorIndex == index) { return false; } - _colorIndexCloud = true; + _colorIndexCloud = 1; _colorIndex = index; return true; } @@ -898,7 +898,7 @@ bool PeerData::clearColorIndex() { if (!_colorIndexCloud) { return false; } - _colorIndexCloud = false; + _colorIndexCloud = 0; _colorIndex = Data::DecideColorIndex(id); return true; } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 1528269e7..5cf6ecb4c 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -848,19 +848,20 @@ void HistoryMessageReply::paint( ? (resolvedMessage->hiddenSenderInfo()->colorIndex + 1) : 0; const auto useColorIndex = colorIndexPlusOne && !context.outbg; - const auto twoColored = colorIndexPlusOne - && Ui::ColorIndexTwoColored(colorIndexPlusOne - 1); + const auto colorPattern = colorIndexPlusOne + ? st->colorPatternIndex(colorIndexPlusOne - 1) + : 0; const auto cache = !inBubble ? (hasQuote - ? st->serviceQuoteCache(twoColored) - : st->serviceReplyCache(twoColored)).get() + ? st->serviceQuoteCache(colorPattern) + : st->serviceReplyCache(colorPattern)).get() : useColorIndex ? (hasQuote ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() : (hasQuote - ? (twoColored ? stm->quoteCacheTwo : stm->quoteCache) - : (twoColored ? stm->replyCacheTwo : stm->replyCache)).get(); + ? stm->quoteCache[colorPattern] + : stm->replyCache[colorPattern]).get(); const auto "eSt = hasQuote ? st::messageTextStyle.blockquote : st::messageQuoteStyle; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index 8d0107157..0b2fad3d7 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -196,7 +196,8 @@ PreviewWrap::PreviewWrap( , _box(box) , _history(history) , _theme(DefaultThemeOn(lifetime())) -, _style(std::make_unique<Ui::ChatStyle>()) +, _style(std::make_unique<Ui::ChatStyle>( + history->session().colorIndicesValue())) , _delegate(std::make_unique<PreviewDelegate>( box, _style.get(), diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index afffc0b2b..3d9142761 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -232,13 +232,12 @@ void ViewButton::draw( Painter &p, const QRect &r, const Ui::ChatPaintContext &context) { + const auto st = context.st; const auto stm = context.messageStyle(); - const auto selected = context.selected(); - const auto twoColored = Ui::ColorIndexTwoColored(_inner->colorIndex); const auto cache = context.outbg - ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() - : context.st->coloredReplyCache(selected, _inner->colorIndex).get(); + ? stm->replyCache[st->colorPatternIndex(_inner->colorIndex)].get() + : st->coloredReplyCache(selected, _inner->colorIndex).get(); const auto radius = st::historyPagePreview.radius; if (_inner->ripple && !_inner->ripple->empty()) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index 9f09e28db..a91f8350d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -220,9 +220,8 @@ void Game::draw(Painter &p, const PaintContext &context) const { const auto colorIndex = parent()->colorIndex(); const auto selected = context.selected(); - const auto twoColored = Ui::ColorIndexTwoColored(colorIndex); const auto cache = context.outbg - ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() + ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() : st->coloredReplyCache(selected, colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 9079ac6e0..311e9bd88 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -344,13 +344,13 @@ void Giveaway::paintChannels( const auto size = _channels[0].geometry.height(); const auto ratio = style::DevicePixelRatio(); + const auto st = context.st; const auto stm = context.messageStyle(); const auto selected = context.selected(); const auto colorIndex = parent()->colorIndex(); - const auto twoColored = Ui::ColorIndexTwoColored(colorIndex); const auto cache = context.outbg - ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() - : context.st->coloredReplyCache(selected, colorIndex).get(); + ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() + : st->coloredReplyCache(selected, colorIndex).get(); if (_channelCorners[0].isNull() || _channelBg != cache->bg) { _channelBg = cache->bg; _channelCorners = Images::CornersMask(size / 2); diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 7e9ff2b35..f91e7ad55 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -523,9 +523,8 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { const auto selected = context.selected(); const auto colorIndex = parent()->colorIndex(); - const auto twoColored = Ui::ColorIndexTwoColored(colorIndex); const auto cache = context.outbg - ? (twoColored ? stm->replyCacheTwo : stm->replyCache).get() + ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() : st->coloredReplyCache(selected, colorIndex).get(); Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st); diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index b2556600b..3b4b788a1 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "main/main_app_config.h" -#include "main/main_account.h" -#include "base/call_delayed.h" #include "apiwrap.h" +#include "base/call_delayed.h" +#include "main/main_account.h" +#include "ui/chat/chat_style.h" namespace Main { namespace { @@ -27,6 +28,8 @@ AppConfig::AppConfig(not_null<Account*> account) : _account(account) { }, _lifetime); } +AppConfig::~AppConfig() = default; + void AppConfig::start() { _account->mtpMainSessionValue( ) | rpl::start_with_next([=](not_null<MTP::Instance*> instance) { @@ -58,6 +61,7 @@ void AppConfig::refresh() { _data.emplace_or_assign(qs(data.vkey()), data.vvalue()); }); } + parseColorIndices(); DEBUG_LOG(("getAppConfig result handled.")); _refreshed.fire({}); }, [](const MTPDhelp_appConfigNotModified &) {}); @@ -171,6 +175,27 @@ std::vector<std::map<QString, QString>> AppConfig::getStringMapArray( }); } +std::vector<int> AppConfig::getIntArray( + const QString &key, + std::vector<int> &&fallback) const { + return getValue(key, [&](const MTPJSONValue &value) { + return value.match([&](const MTPDjsonArray &data) { + auto result = std::vector<int>(); + result.reserve(data.vvalue().v.size()); + for (const auto &entry : data.vvalue().v) { + if (entry.type() != mtpc_jsonNumber) { + return std::move(fallback); + } + result.push_back( + int(base::SafeRound(entry.c_jsonNumber().vvalue().v))); + } + return result; + }, [&](const auto &data) { + return std::move(fallback); + }); + }); +} + bool AppConfig::suggestionCurrent(const QString &key) const { return !_dismissedSuggestions.contains(key) && ranges::contains( @@ -199,4 +224,121 @@ void AppConfig::dismissSuggestion(const QString &key) { )).send(); } +void AppConfig::parseColorIndices() { + constexpr auto parseColor = [](const MTPJSONValue &color) { + if (color.type() != mtpc_jsonString) { + LOG(("API Error: Bad type for color element.")); + return uint32(); + } + const auto value = color.c_jsonString().vvalue().v; + if (value.size() != 6) { + LOG(("API Error: Bad length for color element: %1" + ).arg(qs(value))); + return uint32(); + } + const auto hex = [](char ch) { + return (ch >= 'a' && ch <= 'f') + ? (ch - 'a' + 10) + : (ch >= 'A' && ch <= 'F') + ? (ch - 'A' + 10) + : (ch >= '0' && ch <= '9') + ? (ch - '0') + : 0; + }; + auto result = (uint32(1) << 24); + for (auto i = 0; i != 6; ++i) { + result |= (uint32(hex(value[i])) << ((5 - i) * 4)); + } + return result; + }; + + struct ParsedColor { + uint8 colorIndex = Ui::kColorIndexCount; + std::array<uint32, Ui::kColorPatternsCount> colors; + + explicit operator bool() const { + return colorIndex < Ui::kColorIndexCount; + } + }; + constexpr auto parseColors = [](const MTPJSONObjectValue &element) { + const auto &data = element.data(); + if (data.vvalue().type() != mtpc_jsonArray) { + LOG(("API Error: Bad value for peer_colors element.")); + return ParsedColor(); + } + const auto &list = data.vvalue().c_jsonArray().vvalue().v; + if (list.empty() || list.size() > Ui::kColorPatternsCount) { + LOG(("API Error: Bad count for peer_colors element: %1" + ).arg(list.size())); + return ParsedColor(); + } + const auto index = data.vkey().v.toInt(); + if (index < Ui::kSimpleColorIndexCount + || index >= Ui::kColorIndexCount) { + LOG(("API Error: Bad index for peer_colors element: %1" + ).arg(qs(data.vkey().v))); + return ParsedColor(); + } + auto result = ParsedColor{ .colorIndex = uint8(index) }; + auto fill = result.colors.data(); + for (const auto &color : list) { + *fill++ = parseColor(color); + } + return result; + }; + constexpr auto checkColorsObjectType = [](const MTPJSONValue &value) { + if (value.type() != mtpc_jsonObject) { + if (value.type() != mtpc_jsonArray + || !value.c_jsonArray().vvalue().v.empty()) { + LOG(("API Error: Bad value for [dark_]peer_colors.")); + } + return false; + } + return true; + }; + + auto colors = std::make_shared< + std::array<Ui::ColorIndexData, Ui::kColorIndexCount>>(); + getValue(u"peer_colors"_q, [&](const MTPJSONValue &value) { + if (!checkColorsObjectType(value)) { + return; + } + for (const auto &element : value.c_jsonObject().vvalue().v) { + if (const auto parsed = parseColors(element)) { + auto &fields = (*colors)[parsed.colorIndex]; + fields.dark = fields.light = parsed.colors; + } + } + }); + getValue(u"dark_peer_colors"_q, [&](const MTPJSONValue &value) { + if (!checkColorsObjectType(value)) { + return; + } + for (const auto &element : value.c_jsonObject().vvalue().v) { + if (const auto parsed = parseColors(element)) { + (*colors)[parsed.colorIndex].dark = parsed.colors; + } + } + }); + + if (!_colorIndicesCurrent) { + _colorIndicesCurrent = std::make_unique<Ui::ColorIndicesCompressed>( + Ui::ColorIndicesCompressed{ std::move(colors) }); + _colorIndicesChanged.fire({}); + } else if (*_colorIndicesCurrent->colors != *colors) { + _colorIndicesCurrent->colors = std::move(colors); + _colorIndicesChanged.fire({}); + } +} + +auto AppConfig::colorIndicesValue() const +-> rpl::producer<Ui::ColorIndicesCompressed> { + return rpl::single(_colorIndicesCurrent + ? *_colorIndicesCurrent + : Ui::ColorIndicesCompressed() + ) | rpl::then(_colorIndicesChanged.events() | rpl::map([=] { + return *_colorIndicesCurrent; + })); +} + } // namespace Main diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index b0273b8de..04c00dcfe 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" #include "base/algorithm.h" +namespace Ui { +struct ColorIndicesCompressed; +} // namespace Ui + namespace Main { class Account; @@ -17,6 +21,7 @@ class Account; class AppConfig final { public: explicit AppConfig(not_null<Account*> account); + ~AppConfig(); void start(); @@ -30,6 +35,8 @@ public: return getString(key, fallback); } else if constexpr (std::is_same_v<Type, std::vector<QString>>) { return getStringArray(key, std::move(fallback)); + } else if constexpr (std::is_same_v<Type, std::vector<int>>) { + return getIntArray(key, std::move(fallback)); } else if constexpr (std::is_same_v< Type, std::vector<std::map<QString, QString>>>) { @@ -47,10 +54,14 @@ public: const QString &key) const; void dismissSuggestion(const QString &key); + [[nodiscard]] auto colorIndicesValue() const + -> rpl::producer<Ui::ColorIndicesCompressed>; + void refresh(); private: void refreshDelayed(); + void parseColorIndices(); template <typename Extractor> [[nodiscard]] auto getValue( @@ -72,6 +83,9 @@ private: [[nodiscard]] std::vector<std::map<QString, QString>> getStringMapArray( const QString &key, std::vector<std::map<QString, QString>> &&fallback) const; + [[nodiscard]] std::vector<int> getIntArray( + const QString &key, + std::vector<int> &&fallback) const; const not_null<Account*> _account; std::optional<MTP::Sender> _api; @@ -80,6 +94,10 @@ private: base::flat_map<QString, MTPJSONValue> _data; rpl::event_stream<> _refreshed; base::flat_set<QString> _dismissedSuggestions; + + rpl::event_stream<> _colorIndicesChanged; + std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index a235f729f..84f48106a 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -476,4 +476,9 @@ Window::SessionController *Session::tryResolveWindow() const { return _windows.front(); } +auto Session::colorIndicesValue() const +-> rpl::producer<Ui::ColorIndicesCompressed> { + return _account->appConfig().colorIndicesValue(); +} + } // namespace Main diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 05dbb9ce5..342c7ced6 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -57,6 +57,10 @@ namespace InlineBots { class AttachWebView; } // namespace InlineBots +namespace Ui { +struct ColorIndicesCompressed; +} // namespace Ui + namespace Main { class Account; @@ -187,6 +191,9 @@ public: [[nodiscard]] Support::Helper &supportHelper() const; [[nodiscard]] Support::Templates &supportTemplates() const; + [[nodiscard]] auto colorIndicesValue() const + -> rpl::producer<Ui::ColorIndicesCompressed>; + private: static constexpr auto kDefaultSaveDelay = crl::time(1000); @@ -227,6 +234,8 @@ private: QByteArray _tmpPassword; TimeId _tmpPasswordValidUntil = 0; + rpl::event_stream<Ui::ColorIndicesCompressed> _colorIndicesChanges; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 2d9e017d5..fd4a0ea8a 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -157,7 +157,7 @@ ReactionView::ReactionView( const Data::SuggestedReaction &reaction) : RpWidget(parent) , _data(reaction) -, _chatStyle(std::make_unique<Ui::ChatStyle>()) +, _chatStyle(std::make_unique<Ui::ChatStyle>(session->colorIndicesValue())) , _pathGradient( std::make_unique<Ui::PathShiftGradient>( st::shadowFg, diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index f34691241..aa1e37d09 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -839,7 +839,9 @@ ForwardsPrivacyController::ForwardsPrivacyController( not_null<Window::SessionController*> controller) : SimpleElementDelegate(controller, [] {}) , _controller(controller) -, _chatStyle(std::make_unique<Ui::ChatStyle>()) { +, _chatStyle( + std::make_unique<Ui::ChatStyle>( + controller->session().colorIndicesValue())) { _chatStyle->apply(controller->defaultChatTheme().get()); } diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index aaf9fec54..75d42760d 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -506,7 +506,8 @@ ConfirmContactBox::ConfirmContactBox( const Contact &data, Fn<void(Qt::KeyboardModifiers)> submit) : SimpleElementDelegate(controller, [=] { update(); }) -, _chatStyle(std::make_unique<Ui::ChatStyle>()) +, _chatStyle(std::make_unique<Ui::ChatStyle>( + history->session().colorIndicesValue())) , _comment(GenerateCommentItem(this, history, data)) , _contact(GenerateContactItem(this, history, data)) , _submit(submit) { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 7050667ae..fd5affd42 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -50,6 +50,7 @@ messageQuoteStyle: QuoteStyle(defaultQuoteStyle) { padding: margins(10px, 2px, 4px, 2px); verticalSkip: 4px; outline: 3px; + outlineShift: 2px; radius: 5px; } messageTextStyle: TextStyle(defaultTextStyle) { diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 1ca60e7a3..b0b23e1d3 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -39,8 +39,7 @@ void EnsureBlockquoteCache( cache = std::make_unique<Text::QuotePaintCache>(); const auto &colors = values(); cache->bg = colors.bg; - cache->outline1 = colors.outline1; - cache->outline2 = colors.outline2; + cache->outlines = colors.outlines; cache->icon = colors.name; } @@ -55,15 +54,15 @@ void EnsurePreCache( const auto bg = bgOverride(); cache->bg = bg.value_or(color->c); if (!bg) { - cache->bg.setAlphaF(0.12); + cache->bg.setAlpha(0.12 * 255); } - cache->outline1 = color->c; - cache->outline1.setAlphaF(0.9); - cache->outline2 = cache->outline1; + cache->outlines[0] = color->c; + cache->outlines[0].setAlpha(0.9 * 255); + cache->outlines[1] = cache->outlines[2] = QColor(0, 0, 0, 0); cache->header = color->c; - cache->header.setAlphaF(0.25); - cache->icon = cache->outline1; - cache->icon.setAlphaF(0.6); + cache->header.setAlpha(0.25 * 255); + cache->icon = cache->outlines[0]; + cache->icon.setAlpha(0.6 * 255); } } // namespace @@ -80,9 +79,8 @@ not_null<Text::QuotePaintCache*> ChatPaintContext::quoteCache( uint8 colorIndex) const { return !outbg ? st->coloredQuoteCache(selected(), colorIndex).get() - : ColorIndexTwoColored(colorIndex) - ? messageStyle()->quoteCacheTwo.get() - : messageStyle()->quoteCache.get(); + : messageStyle()->quoteCache[ + st->colorPatternIndex(colorIndex)].get(); } int HistoryServiceMsgRadius() { @@ -110,85 +108,28 @@ int HistoryServiceMsgInvertedShrink() { return result; } -ColorIndexValues ComputeColorIndexValues( - not_null<const ChatStyle*> st, - bool selected, - uint8 colorIndex) { - if (colorIndex < kSimpleColorIndexCount) { - const style::color list[] = { - st->historyPeer1NameFg(), - st->historyPeer2NameFg(), - st->historyPeer3NameFg(), - st->historyPeer4NameFg(), - st->historyPeer5NameFg(), - st->historyPeer6NameFg(), - st->historyPeer7NameFg(), - st->historyPeer8NameFg(), - }; - const style::color listSelected[] = { - st->historyPeer1NameFgSelected(), - st->historyPeer2NameFgSelected(), - st->historyPeer3NameFgSelected(), - st->historyPeer4NameFgSelected(), - st->historyPeer5NameFgSelected(), - st->historyPeer6NameFgSelected(), - st->historyPeer7NameFgSelected(), - st->historyPeer8NameFgSelected(), - }; - const auto paletteIndex = ColorIndexToPaletteIndex(colorIndex); - auto result = ColorIndexValues{ - .name = (selected ? listSelected : list)[paletteIndex]->c, - }; - result.bg = result.name; - result.bg.setAlphaF(0.12); - result.outline1 = result.name; - result.outline1.setAlphaF(0.9); - result.outline2 = result.outline1; - return result; - } - struct Pair { - QColor outline1; - QColor outline2; - }; - const Pair list[] = { - { QColor(0xE1, 0x50, 0x52), QColor(0xF9, 0xAE, 0x63) }, // Red - { QColor(0xE0, 0x80, 0x2B), QColor(0xFA, 0xC5, 0x34) }, // Orange - { QColor(0xA0, 0x5F, 0xF3), QColor(0xF4, 0x8F, 0xFF) }, // Violet - { QColor(0x27, 0xA9, 0x10), QColor(0xA7, 0xDC, 0x57) }, // Green - { QColor(0x27, 0xAC, 0xCE), QColor(0x82, 0xE8, 0xD6) }, // Cyan - { QColor(0x33, 0x91, 0xD4), QColor(0x7D, 0xD3, 0xF0) }, // Blue - { QColor(0xD1, 0x48, 0x72), QColor(0xFF, 0xBE, 0xA0) }, // Pink - }; - const auto &pair = list[colorIndex - kSimpleColorIndexCount]; - auto bg = pair.outline1; - bg.setAlphaF(0.12); - return { - .name = st->dark() ? pair.outline2 : pair.outline1, - .bg = bg, - .outline1 = pair.outline1, - .outline2 = pair.outline2, - }; -} - -bool ColorIndexTwoColored(uint8 colorIndex) { - return (colorIndex >= kSimpleColorIndexCount); -} - -ColorIndexValues SimpleColorIndexValues(QColor color, bool twoColored) { +ColorIndexValues SimpleColorIndexValues(QColor color, int patternIndex) { auto bg = color; - bg.setAlphaF(0.12); - auto outline1 = color; - outline1.setAlphaF(0.9); - auto outline2 = outline1; - if (twoColored) { - outline2.setAlphaF(0.5); - } - return { + bg.setAlpha(0.12 * 255); + auto result = ColorIndexValues{ .name = color, .bg = bg, - .outline1 = outline1, - .outline2 = outline2, }; + result.outlines[0] = color; + result.outlines[0].setAlpha(0.9 * 255); + if (patternIndex > 1) { + result.outlines[1] = result.outlines[0]; + result.outlines[1].setAlpha(0.3 * 255); + result.outlines[2] = result.outlines[0]; + result.outlines[2].setAlpha(0.6 * 255); + } else if (patternIndex > 0) { + result.outlines[1] = result.outlines[0]; + result.outlines[1].setAlpha(0.5 * 255); + result.outlines[2] = QColor(0, 0, 0, 0); + } else { + result.outlines[1] = result.outlines[2] = QColor(0, 0, 0, 0); + } + return result; } int BackgroundEmojiData::CacheIndex( @@ -202,7 +143,15 @@ int BackgroundEmojiData::CacheIndex( return (base * 2) + (selected ? 1 : 0); }; -ChatStyle::ChatStyle() { +ChatStyle::ChatStyle(rpl::producer<ColorIndicesCompressed> colorIndices) { + if (colorIndices) { + _colorIndicesLifetime = std::move( + colorIndices + ) | rpl::start_with_next([=](ColorIndicesCompressed &&indices) { + _colorIndices = std::move(indices); + }); + } + finalize(); make(_historyPsaForwardPalette, st::historyPsaForwardPalette); make(_imgReplyTextPalette, st::imgReplyTextPalette); @@ -561,7 +510,7 @@ ChatStyle::ChatStyle() { } ChatStyle::ChatStyle(not_null<const style::palette*> isolated) -: ChatStyle() { +: ChatStyle(rpl::producer<ColorIndicesCompressed>()) { assignPalette(isolated); } @@ -631,17 +580,41 @@ std::span<Text::SpecialColor> ChatStyle::highlightColors() const { return _highlightColors; } +void ChatStyle::clearColorIndexCaches() { + for (auto &style : _messageStyles) { + for (auto &cache : style.quoteCache) { + cache = nullptr; + } + for (auto &cache : style.replyCache) { + cache = nullptr; + } + } + for (auto &values : _coloredValues) { + values.reset(); + } + for (auto &palette : _coloredTextPalettes) { + palette.linkFg.reset(); + } + for (auto &cache : _coloredReplyCaches) { + cache = nullptr; + } + for (auto &cache : _coloredQuoteCaches) { + cache = nullptr; + } +} + void ChatStyle::assignPalette(not_null<const style::palette*> palette) { *static_cast<style::palette*>(this) = *palette; style::internal::resetIcons(); + + clearColorIndexCaches(); for (auto &style : _messageStyles) { style.msgBgCornersSmall = {}; style.msgBgCornersLarge = {}; - style.quoteCache = nullptr; - style.quoteCacheTwo = nullptr; - style.replyCache = nullptr; - style.replyCacheTwo = nullptr; style.preCache = nullptr; + style.textPalette.linkAlwaysActive + = style.semiboldPalette.linkAlwaysActive + = (style.textPalette.linkFg->c == style.historyTextFg->c); } for (auto &style : _imageStyles) { style.msgDateImgBgCorners = {}; @@ -657,24 +630,6 @@ void ChatStyle::assignPalette(not_null<const style::palette*> palette) { for (auto &corners : _msgSelectOverlayCorners) { corners = {}; } - - for (auto &stm : _messageStyles) { - stm.textPalette.linkAlwaysActive - = stm.semiboldPalette.linkAlwaysActive - = (stm.textPalette.linkFg->c == stm.historyTextFg->c); - } - for (auto &values : _coloredValues) { - values.reset(); - } - for (auto &palette : _coloredTextPalettes) { - palette.linkFg.reset(); - } - for (auto &cache : _coloredReplyCaches) { - cache = nullptr; - } - for (auto &cache : _coloredQuoteCaches) { - cache = nullptr; - } updateDarkValue(); _paletteChanged.fire({}); @@ -710,19 +665,14 @@ const MessageStyle &ChatStyle::messageStyle(bool outbg, bool selected) const { result.msgBg, &result.msgShadow); const auto &replyBar = result.msgReplyBarColor->c; - EnsureBlockquoteCache( - result.replyCache, - [&] { return SimpleColorIndexValues(replyBar, false); }); - EnsureBlockquoteCache( - result.replyCacheTwo, - [&] { return SimpleColorIndexValues(replyBar, true); }); - if (!result.quoteCache) { - result.quoteCache = std::make_unique<Text::QuotePaintCache>( - *result.replyCache); - } - if (!result.quoteCacheTwo) { - result.quoteCacheTwo = std::make_unique<Text::QuotePaintCache>( - *result.replyCacheTwo); + for (auto i = 0; i != kColorPatternsCount; ++i) { + EnsureBlockquoteCache( + result.replyCache[i], + [&] { return SimpleColorIndexValues(replyBar, i); }); + if (!result.quoteCache[i]) { + result.quoteCache[i] = std::make_unique<Text::QuotePaintCache>( + *result.replyCache[i]); + } } const auto preBgOverride = [&] { @@ -763,6 +713,80 @@ const MessageImageStyle &ChatStyle::imageStyle(bool selected) const { return result; } +int ChatStyle::colorPatternIndex(uint8 colorIndex) const { + Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); + + if (!_colorIndices.colors + || colorIndex < kSimpleColorIndexCount) { + return 0; + } + auto &data = (*_colorIndices.colors)[colorIndex]; + auto &colors = _dark ? data.dark : data.light; + return colors[2] ? 2 : colors[1] ? 1 : 0; +} + +ColorIndexValues ChatStyle::computeColorIndexValues( + bool selected, + uint8 colorIndex) const { + if (!_colorIndices.colors) { + colorIndex %= kSimpleColorIndexCount; + } + if (colorIndex < kSimpleColorIndexCount) { + const auto list = std::array{ + &historyPeer1NameFg(), + &historyPeer2NameFg(), + &historyPeer3NameFg(), + &historyPeer4NameFg(), + &historyPeer5NameFg(), + &historyPeer6NameFg(), + &historyPeer7NameFg(), + &historyPeer8NameFg(), + }; + const auto listSelected = std::array{ + &historyPeer1NameFgSelected(), + &historyPeer2NameFgSelected(), + &historyPeer3NameFgSelected(), + &historyPeer4NameFgSelected(), + &historyPeer5NameFgSelected(), + &historyPeer6NameFgSelected(), + &historyPeer7NameFgSelected(), + &historyPeer8NameFgSelected(), + }; + const auto paletteIndex = ColorIndexToPaletteIndex(colorIndex); + auto result = ColorIndexValues{ + .name = (*(selected ? listSelected : list)[paletteIndex])->c, + }; + result.bg = result.name; + result.bg.setAlphaF(0.12); + result.outlines[0] = result.name; + result.outlines[0].setAlphaF(0.9); + result.outlines[1] = result.outlines[2] = QColor(0, 0, 0, 0); + return result; + } + auto &data = (*_colorIndices.colors)[colorIndex]; + auto &colors = _dark ? data.dark : data.light; + if (!colors[0]) { + return computeColorIndexValues( + selected, + colorIndex % kSimpleColorIndexCount); + } + const auto color = [&](int index) { + const auto v = colors[index]; + return v + ? QColor((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF) + : QColor(0, 0, 0, 0); + }; + auto result = ColorIndexValues{ + .outlines = { color(0), color(1), color(2) } + }; + result.bg = result.outlines[0]; + result.bg.setAlpha(0.12 * 255); + result.name = (_dark && colorPatternIndex(colorIndex) == 1) + ? result.outlines[1] + : result.outlines[0]; + return result; +} + not_null<Text::QuotePaintCache*> ChatStyle::serviceQuoteCache( bool twoColored) const { const auto index = (twoColored ? 1 : 0); @@ -791,7 +815,7 @@ const ColorIndexValues &ChatStyle::coloredValues( const auto shift = (selected ? kColorIndexCount : 0); auto &result = _coloredValues[shift + colorIndex]; if (!result) { - result.emplace(ComputeColorIndexValues(this, selected, colorIndex)); + result.emplace(computeColorIndexValues(selected, colorIndex)); } return *result; } diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 8f2b7639d..c60993df6 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -31,7 +31,8 @@ class ChatTheme; class ChatStyle; struct BubblePattern; -inline constexpr auto kColorIndexCount = uint8(14); +inline constexpr auto kColorPatternsCount = Text::kMaxQuoteOutlines; +inline constexpr auto kColorIndexCount = uint8(1 << 6); inline constexpr auto kSimpleColorIndexCount = uint8(7); struct MessageStyle { @@ -83,10 +84,12 @@ struct MessageStyle { style::icon historyPollChoiceRight = { Qt::Uninitialized }; style::icon historyTranscribeIcon = { Qt::Uninitialized }; style::icon historyTranscribeHide = { Qt::Uninitialized }; - std::unique_ptr<Text::QuotePaintCache> quoteCache; - std::unique_ptr<Text::QuotePaintCache> quoteCacheTwo; - std::unique_ptr<Text::QuotePaintCache> replyCache; - std::unique_ptr<Text::QuotePaintCache> replyCacheTwo; + std::array< + std::unique_ptr<Text::QuotePaintCache>, + kColorPatternsCount> quoteCache; + std::array< + std::unique_ptr<Text::QuotePaintCache>, + kColorPatternsCount> replyCache; std::unique_ptr<Text::QuotePaintCache> preCache; }; @@ -190,22 +193,31 @@ struct ChatPaintContext { [[nodiscard]] int HistoryServiceMsgInvertedRadius(); [[nodiscard]] int HistoryServiceMsgInvertedShrink(); +struct ColorIndexData { + std::array<uint32, kColorPatternsCount> light = {}; + std::array<uint32, kColorPatternsCount> dark = {}; + + friend inline bool operator==( + const ColorIndexData&, + const ColorIndexData&) = default; +}; + +struct ColorIndicesCompressed { + std::shared_ptr<std::array<ColorIndexData, kColorIndexCount>> colors; +}; + struct ColorIndexValues { + std::array<QColor, kColorPatternsCount> outlines; QColor name; QColor bg; - QColor outline1; - QColor outline2; }; -[[nodiscard]] ColorIndexValues ComputeColorIndexValues( - not_null<const ChatStyle*> st, - bool selected, - uint8 colorIndex); -[[nodiscard]] bool ColorIndexTwoColored(uint8 colorIndex); class ChatStyle final : public style::palette { public: - ChatStyle(); + explicit ChatStyle(rpl::producer<ColorIndicesCompressed> colorIndices); explicit ChatStyle(not_null<const style::palette*> isolated); + ChatStyle(const ChatStyle &other) = delete; + ChatStyle &operator=(const ChatStyle &other) = delete; ~ChatStyle(); void apply(not_null<ChatTheme*> theme); @@ -246,6 +258,11 @@ public: bool selected) const; [[nodiscard]] const MessageImageStyle &imageStyle(bool selected) const; + [[nodiscard]] int colorPatternIndex(uint8 colorIndex) const; + [[nodiscard]] ColorIndexValues computeColorIndexValues( + bool selected, + uint8 colorIndex) const; + [[nodiscard]] auto serviceQuoteCache(bool twoColored) const -> not_null<Text::QuotePaintCache*>; [[nodiscard]] auto serviceReplyCache(bool twoColored) const @@ -362,6 +379,7 @@ private: }; void assignPalette(not_null<const style::palette*> palette); + void clearColorIndexCaches(); void updateDarkValue(); [[nodiscard]] not_null<Text::QuotePaintCache*> coloredCache( @@ -462,11 +480,14 @@ private: style::icon _historyPollChoiceRight = { Qt::Uninitialized }; style::icon _historyPollChoiceWrong = { Qt::Uninitialized }; + ColorIndicesCompressed _colorIndices; + bool _dark = false; rpl::event_stream<> _paletteChanged; rpl::lifetime _defaultPaletteChangeLifetime; + rpl::lifetime _colorIndicesLifetime; }; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 11dae1d94..681604d93 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1088,7 +1088,7 @@ SessionController::SessionController( , _invitePeekTimer([=] { checkInvitePeek(); }) , _activeChatsFilter(session->data().chatsFilters().defaultId()) , _defaultChatTheme(std::make_shared<Ui::ChatTheme>()) -, _chatStyle(std::make_unique<Ui::ChatStyle>()) +, _chatStyle(std::make_unique<Ui::ChatStyle>(session->colorIndicesValue())) , _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>()) , _giftPremiumValidator(this) { init(); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 383b5b8f7..611224c52 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 383b5b8f7e629475e5f22445167aaa7669c5cdd6 +Subproject commit 611224c52f3192f616018fc7a2c5930667531084