/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/image/image_prepare.h" // ImageRoundRadius #include "ui/text/text_custom_emoji.h" #include "ui/color_contrast.h" #include "ui/painter.h" #include "ui/ui_utility.h" #include "styles/style_chat.h" #include "styles/style_dialogs.h" #include "styles/style_widgets.h" namespace Ui { namespace { void EnsureCorners( CornersPixmaps &corners, int radius, const style::color &color, const style::color *shadow = nullptr) { if (corners.p[0].isNull()) { corners = PrepareCornerPixmaps(radius, color, shadow); } } void EnsureBlockquoteCache( std::unique_ptr &cache, Fn values) { if (cache) { return; } cache = std::make_unique(); const auto &colors = values(); cache->bg = colors.bg; cache->outlines = colors.outlines; cache->icon = colors.name; } void EnsurePreCache( std::unique_ptr &cache, const style::color &color, Fn()> bgOverride) { if (cache) { return; } cache = std::make_unique(); const auto bg = bgOverride(); cache->bg = bg.value_or(color->c); if (!bg) { cache->bg.setAlpha(kDefaultBgOpacity * 255); } cache->outlines[0] = color->c; cache->outlines[0].setAlpha(kDefaultOutline1Opacity * 255); cache->outlines[1] = cache->outlines[2] = QColor(0, 0, 0, 0); cache->header = color->c; cache->header.setAlpha(kDefaultOutline2Opacity * 255); cache->icon = cache->outlines[0]; cache->icon.setAlpha(kDefaultOutline3Opacity * 255); } } // namespace not_null ChatPaintContext::messageStyle() const { return &st->messageStyle(outbg, selected()); } not_null ChatPaintContext::imageStyle() const { return &st->imageStyle(selected()); } not_null ChatPaintContext::quoteCache( uint8 colorIndex) const { return !outbg ? st->coloredQuoteCache(selected(), colorIndex).get() : messageStyle()->quoteCache[ st->colorPatternIndex(colorIndex)].get(); } int HistoryServiceMsgRadius() { static const auto result = [] { const auto minMessageHeight = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); return minMessageHeight / 2; }(); return result; } int HistoryServiceMsgInvertedRadius() { static const auto result = [] { const auto minRowHeight = st::msgServiceFont->height; return minRowHeight - HistoryServiceMsgRadius(); }(); return result; } int HistoryServiceMsgInvertedShrink() { static const auto result = [] { return (HistoryServiceMsgInvertedRadius() * 2) / 3; }(); return result; } ColorIndexValues SimpleColorIndexValues(QColor color, int patternIndex) { auto bg = color; bg.setAlpha(kDefaultBgOpacity * 255); auto result = ColorIndexValues{ .name = color, .bg = bg, }; result.outlines[0] = color; result.outlines[0].setAlpha(kDefaultOutline1Opacity * 255); if (patternIndex > 1) { result.outlines[1] = result.outlines[0]; result.outlines[1].setAlpha(kDefaultOutline2Opacity * 255); result.outlines[2] = result.outlines[0]; result.outlines[2].setAlpha(kDefaultOutline3Opacity * 255); } else if (patternIndex > 0) { result.outlines[1] = result.outlines[0]; result.outlines[1].setAlpha(kDefaultOutlineOpacitySecond * 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( bool selected, bool outbg, bool inbubble, uint8 colorIndexPlusOne) { const auto base = colorIndexPlusOne ? (colorIndexPlusOne - 1) : (kColorIndexCount + (!inbubble ? 0 : outbg ? 1 : 2)); return (base * 2) + (selected ? 1 : 0); }; ChatStyle::ChatStyle(rpl::producer 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); make(_serviceTextPalette, st::serviceTextPalette); make(_historyRepliesInvertedIcon, st::historyRepliesInvertedIcon); make(_historyViewsInvertedIcon, st::historyViewsInvertedIcon); make(_historyViewsSendingIcon, st::historyViewsSendingIcon); make( _historyViewsSendingInvertedIcon, st::historyViewsSendingInvertedIcon); make(_historyPinInvertedIcon, st::historyPinInvertedIcon); make(_historySendingIcon, st::historySendingIcon); make(_historySendingInvertedIcon, st::historySendingInvertedIcon); make(_historySentInvertedIcon, st::historySentInvertedIcon); make(_historyReceivedInvertedIcon, st::historyReceivedInvertedIcon); make(_msgBotKbUrlIcon, st::msgBotKbUrlIcon); make(_msgBotKbPaymentIcon, st::msgBotKbPaymentIcon); make(_msgBotKbSwitchPmIcon, st::msgBotKbSwitchPmIcon); make(_msgBotKbWebviewIcon, st::msgBotKbWebviewIcon); make(_historyFastCommentsIcon, st::historyFastCommentsIcon); make(_historyFastShareIcon, st::historyFastShareIcon); make(_historyFastTranscribeIcon, st::historyFastTranscribeIcon); make(_historyGoToOriginalIcon, st::historyGoToOriginalIcon); make(_historyMapPoint, st::historyMapPoint); make(_historyMapPointInner, st::historyMapPointInner); make(_youtubeIcon, st::youtubeIcon); make(_videoIcon, st::videoIcon); make(_historyPollChoiceRight, st::historyPollChoiceRight); make(_historyPollChoiceWrong, st::historyPollChoiceWrong); make( &MessageStyle::msgBg, st::msgInBg, st::msgInBgSelected, st::msgOutBg, st::msgOutBgSelected); make( &MessageStyle::msgShadow, st::msgInShadow, st::msgInShadowSelected, st::msgOutShadow, st::msgOutShadowSelected); make( &MessageStyle::msgServiceFg, st::msgInServiceFg, st::msgInServiceFgSelected, st::msgOutServiceFg, st::msgOutServiceFgSelected); make( &MessageStyle::msgDateFg, st::msgInDateFg, st::msgInDateFgSelected, st::msgOutDateFg, st::msgOutDateFgSelected); make( &MessageStyle::msgFileThumbLinkFg, st::msgFileThumbLinkInFg, st::msgFileThumbLinkInFgSelected, st::msgFileThumbLinkOutFg, st::msgFileThumbLinkOutFgSelected); make( &MessageStyle::msgFileBg, st::msgFileInBg, st::msgFileInBgSelected, st::msgFileOutBg, st::msgFileOutBgSelected); make( &MessageStyle::msgReplyBarColor, st::msgInReplyBarColor, st::msgInReplyBarSelColor, st::msgOutReplyBarColor, st::msgOutReplyBarSelColor); make( &MessageStyle::msgWaveformActive, st::msgWaveformInActive, st::msgWaveformInActiveSelected, st::msgWaveformOutActive, st::msgWaveformOutActiveSelected); make( &MessageStyle::msgWaveformInactive, st::msgWaveformInInactive, st::msgWaveformInInactiveSelected, st::msgWaveformOutInactive, st::msgWaveformOutInactiveSelected); make( &MessageStyle::historyTextFg, st::historyTextInFg, st::historyTextInFgSelected, st::historyTextOutFg, st::historyTextOutFgSelected); make( &MessageStyle::historyFileNameFg, st::historyFileNameInFg, st::historyFileNameInFgSelected, st::historyFileNameOutFg, st::historyFileNameOutFgSelected); make( &MessageStyle::historyFileRadialFg, st::historyFileInRadialFg, st::historyFileInRadialFgSelected, st::historyFileOutRadialFg, st::historyFileOutRadialFgSelected); make( &MessageStyle::mediaFg, st::mediaInFg, st::mediaInFgSelected, st::mediaOutFg, st::mediaOutFgSelected); make( &MessageStyle::textPalette, st::inTextPalette, st::inTextPaletteSelected, st::outTextPalette, st::outTextPaletteSelected); make( &MessageStyle::semiboldPalette, st::inSemiboldPalette, st::inTextPaletteSelected, st::outSemiboldPalette, st::outTextPaletteSelected); make( &MessageStyle::fwdTextPalette, st::inFwdTextPalette, st::inFwdTextPaletteSelected, st::outFwdTextPalette, st::outFwdTextPaletteSelected); make( &MessageStyle::replyTextPalette, st::inReplyTextPalette, st::inReplyTextPaletteSelected, st::outReplyTextPalette, st::outReplyTextPaletteSelected); make( &MessageStyle::tailLeft, st::historyBubbleTailInLeft, st::historyBubbleTailInLeftSelected, st::historyBubbleTailOutLeft, st::historyBubbleTailOutLeftSelected); make( &MessageStyle::tailRight, st::historyBubbleTailInRight, st::historyBubbleTailInRightSelected, st::historyBubbleTailOutRight, st::historyBubbleTailOutRightSelected); make( &MessageStyle::historyRepliesIcon, st::historyRepliesInIcon, st::historyRepliesInSelectedIcon, st::historyRepliesOutIcon, st::historyRepliesOutSelectedIcon); make( &MessageStyle::historyViewsIcon, st::historyViewsInIcon, st::historyViewsInSelectedIcon, st::historyViewsOutIcon, st::historyViewsOutSelectedIcon); make( &MessageStyle::historyPinIcon, st::historyPinInIcon, st::historyPinInSelectedIcon, st::historyPinOutIcon, st::historyPinOutSelectedIcon); make( &MessageStyle::historySentIcon, st::historySentIcon, st::historySentSelectedIcon, st::historySentIcon, st::historySentSelectedIcon); make( &MessageStyle::historyReceivedIcon, st::historyReceivedIcon, st::historyReceivedSelectedIcon, st::historyReceivedIcon, st::historyReceivedSelectedIcon); make( &MessageStyle::historyPsaIcon, st::historyPsaIconIn, st::historyPsaIconInSelected, st::historyPsaIconOut, st::historyPsaIconOutSelected); make( &MessageStyle::historyCommentsOpen, st::historyCommentsOpenIn, st::historyCommentsOpenInSelected, st::historyCommentsOpenOut, st::historyCommentsOpenOutSelected); make( &MessageStyle::historyComments, st::historyCommentsIn, st::historyCommentsInSelected, st::historyCommentsOut, st::historyCommentsOutSelected); make( &MessageStyle::historyCallArrow, st::historyCallArrowIn, st::historyCallArrowInSelected, st::historyCallArrowOut, st::historyCallArrowOutSelected); make( &MessageStyle::historyCallArrowMissed, st::historyCallArrowMissedIn, st::historyCallArrowMissedInSelected, st::historyCallArrowMissedIn, st::historyCallArrowMissedInSelected); make( &MessageStyle::historyCallIcon, st::historyCallInIcon, st::historyCallInIconSelected, st::historyCallOutIcon, st::historyCallOutIconSelected); make( &MessageStyle::historyCallCameraIcon, st::historyCallCameraInIcon, st::historyCallCameraInIconSelected, st::historyCallCameraOutIcon, st::historyCallCameraOutIconSelected); make( &MessageStyle::historyFilePlay, st::historyFileInPlay, st::historyFileInPlaySelected, st::historyFileOutPlay, st::historyFileOutPlaySelected); make( &MessageStyle::historyFileWaiting, st::historyFileInWaiting, st::historyFileInWaitingSelected, st::historyFileOutWaiting, st::historyFileOutWaitingSelected); make( &MessageStyle::historyFileDownload, st::historyFileInDownload, st::historyFileInDownloadSelected, st::historyFileOutDownload, st::historyFileOutDownloadSelected); make( &MessageStyle::historyFileCancel, st::historyFileInCancel, st::historyFileInCancelSelected, st::historyFileOutCancel, st::historyFileOutCancelSelected); make( &MessageStyle::historyFilePause, st::historyFileInPause, st::historyFileInPauseSelected, st::historyFileOutPause, st::historyFileOutPauseSelected); make( &MessageStyle::historyFileImage, st::historyFileInImage, st::historyFileInImageSelected, st::historyFileOutImage, st::historyFileOutImageSelected); make( &MessageStyle::historyFileDocument, st::historyFileInDocument, st::historyFileInDocumentSelected, st::historyFileOutDocument, st::historyFileOutDocumentSelected); make( &MessageStyle::historyAudioDownload, st::historyAudioInDownload, st::historyAudioInDownloadSelected, st::historyAudioOutDownload, st::historyAudioOutDownloadSelected); make( &MessageStyle::historyAudioCancel, st::historyAudioInCancel, st::historyAudioInCancelSelected, st::historyAudioOutCancel, st::historyAudioOutCancelSelected); make( &MessageStyle::historyQuizTimer, st::historyQuizTimerIn, st::historyQuizTimerInSelected, st::historyQuizTimerOut, st::historyQuizTimerOutSelected); make( &MessageStyle::historyQuizExplain, st::historyQuizExplainIn, st::historyQuizExplainInSelected, st::historyQuizExplainOut, st::historyQuizExplainOutSelected); make( &MessageStyle::historyPollChosen, st::historyPollInChosen, st::historyPollInChosenSelected, st::historyPollOutChosen, st::historyPollOutChosenSelected); make( &MessageStyle::historyPollChoiceRight, st::historyPollInChoiceRight, st::historyPollInChoiceRightSelected, st::historyPollOutChoiceRight, st::historyPollOutChoiceRightSelected); make( &MessageStyle::historyTranscribeIcon, st::historyTranscribeInIcon, st::historyTranscribeInIconSelected, st::historyTranscribeOutIcon, st::historyTranscribeOutIconSelected); make( &MessageStyle::historyTranscribeHide, st::historyTranscribeInHide, st::historyTranscribeInHideSelected, st::historyTranscribeOutHide, st::historyTranscribeOutHideSelected); make( &MessageImageStyle::msgDateImgBg, st::msgDateImgBg, st::msgDateImgBgSelected); make( &MessageImageStyle::msgServiceBg, st::msgServiceBg, st::msgServiceBgSelected); make( &MessageImageStyle::msgShadow, st::msgInShadow, st::msgInShadowSelected); make( &MessageImageStyle::historyFileThumbRadialFg, st::historyFileThumbRadialFg, st::historyFileThumbRadialFgSelected); make( &MessageImageStyle::historyFileThumbPlay, st::historyFileThumbPlay, st::historyFileThumbPlaySelected); make( &MessageImageStyle::historyFileThumbWaiting, st::historyFileThumbWaiting, st::historyFileThumbWaitingSelected); make( &MessageImageStyle::historyFileThumbDownload, st::historyFileThumbDownload, st::historyFileThumbDownloadSelected); make( &MessageImageStyle::historyFileThumbCancel, st::historyFileThumbCancel, st::historyFileThumbCancelSelected); make( &MessageImageStyle::historyFileThumbPause, st::historyFileThumbPause, st::historyFileThumbPauseSelected); make( &MessageImageStyle::historyVideoDownload, st::historyVideoDownload, st::historyVideoDownloadSelected); make( &MessageImageStyle::historyVideoCancel, st::historyVideoCancel, st::historyVideoCancelSelected); make( &MessageImageStyle::historyVideoMessageMute, st::historyVideoMessageMute, st::historyVideoMessageMuteSelected); make( &MessageImageStyle::historyPageEnlarge, st::historyPageEnlarge, st::historyPageEnlargeSelected); updateDarkValue(); } ChatStyle::ChatStyle(not_null isolated) : ChatStyle(rpl::producer()) { assignPalette(isolated); } ChatStyle::~ChatStyle() = default; void ChatStyle::apply(not_null theme) { applyCustomPalette(theme->palette()); } void ChatStyle::updateDarkValue() { const auto withBg = [&](const QColor &color) { return CountContrast(windowBg()->c, color); }; _dark = (withBg({ 0, 0, 0 }) < withBg({ 255, 255, 255 })); } void ChatStyle::applyCustomPalette(const style::palette *palette) { assignPalette(palette ? palette : style::main_palette::get().get()); if (palette) { _defaultPaletteChangeLifetime.destroy(); } else { style::PaletteChanged( ) | rpl::start_with_next([=] { assignPalette(style::main_palette::get()); }, _defaultPaletteChangeLifetime); } } void ChatStyle::applyAdjustedServiceBg(QColor serviceBg) { auto r = 0, g = 0, b = 0, a = 0; serviceBg.getRgb(&r, &g, &b, &a); msgServiceBg().set(uchar(r), uchar(g), uchar(b), uchar(a)); } std::span ChatStyle::highlightColors() const { if (_highlightColors.empty()) { const auto push = [&](const style::color &color) { _highlightColors.push_back({ &color->p, &color->p }); }; // comment, block-comment, prolog, doctype, cdata push(statisticsChartLineLightblue()); // punctuation push(statisticsChartLineRed()); // property, tag, boolean, number, // constant, symbol, deleted push(statisticsChartLineRed()); // selector, attr-name, string, char, builtin, inserted push(statisticsChartLineOrange()); // operator, entity, url push(statisticsChartLineRed()); // atrule, attr-value, keyword, function push(statisticsChartLineBlue()); // class-name push(statisticsChartLinePurple()); //push(statisticsChartLineLightgreen()); //push(statisticsChartLineGreen()); //push(statisticsChartLineGolden()); } 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 palette) { *static_cast(this) = *palette; style::internal::resetIcons(); clearColorIndexCaches(); for (auto &style : _messageStyles) { style.msgBgCornersSmall = {}; style.msgBgCornersLarge = {}; style.preCache = nullptr; style.textPalette.linkAlwaysActive = style.semiboldPalette.linkAlwaysActive = (style.textPalette.linkFg->c == style.historyTextFg->c); } for (auto &style : _imageStyles) { style.msgDateImgBgCorners = {}; style.msgServiceBgCornersSmall = {}; style.msgServiceBgCornersLarge = {}; style.msgShadowCornersSmall = {}; style.msgShadowCornersLarge = {}; } _serviceBgCornersNormal = {}; _serviceBgCornersInverted = {}; _msgBotKbOverBgAddCornersSmall = {}; _msgBotKbOverBgAddCornersLarge = {}; for (auto &corners : _msgSelectOverlayCorners) { corners = {}; } updateDarkValue(); _paletteChanged.fire({}); } const CornersPixmaps &ChatStyle::serviceBgCornersNormal() const { EnsureCorners( _serviceBgCornersNormal, HistoryServiceMsgRadius(), msgServiceBg()); return _serviceBgCornersNormal; } const CornersPixmaps &ChatStyle::serviceBgCornersInverted() const { if (_serviceBgCornersInverted.p[0].isNull()) { _serviceBgCornersInverted = PrepareInvertedCornerPixmaps( HistoryServiceMsgInvertedRadius(), msgServiceBg()); } return _serviceBgCornersInverted; } const MessageStyle &ChatStyle::messageStyle(bool outbg, bool selected) const { auto &result = messageStyleRaw(outbg, selected); EnsureCorners( result.msgBgCornersSmall, BubbleRadiusSmall(), result.msgBg, &result.msgShadow); EnsureCorners( result.msgBgCornersLarge, BubbleRadiusLarge(), result.msgBg, &result.msgShadow); const auto &replyBar = result.msgReplyBarColor->c; 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( *result.replyCache[i]); } } const auto preBgOverride = [&] { return _dark ? QColor(0, 0, 0, 192) : std::optional(); }; EnsurePreCache( result.preCache, (selected ? result.textPalette.selectMonoFg : result.textPalette.monoFg), preBgOverride); return result; } const MessageImageStyle &ChatStyle::imageStyle(bool selected) const { auto &result = imageStyleRaw(selected); EnsureCorners( result.msgDateImgBgCorners, (st::msgDateImgPadding.y() * 2 + st::normalFont->height) / 2, result.msgDateImgBg); EnsureCorners( result.msgServiceBgCornersSmall, BubbleRadiusSmall(), result.msgServiceBg); EnsureCorners( result.msgServiceBgCornersLarge, BubbleRadiusLarge(), result.msgServiceBg); EnsureCorners( result.msgShadowCornersSmall, BubbleRadiusSmall(), result.msgShadow); EnsureCorners( result.msgShadowCornersLarge, BubbleRadiusLarge(), result.msgShadow); 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.setAlpha(kDefaultBgOpacity * 255); result.outlines[0] = result.name; result.outlines[0].setAlpha(kDefaultOutline1Opacity * 255); 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(kDefaultBgOpacity * 255); result.name = result.outlines[0]; return result; } not_null ChatStyle::serviceQuoteCache( bool twoColored) const { const auto index = (twoColored ? 1 : 0); const auto &service = msgServiceFg()->c; EnsureBlockquoteCache( _serviceQuoteCache[index], [&] { return SimpleColorIndexValues(service, twoColored); }); return _serviceQuoteCache[index].get(); } not_null ChatStyle::serviceReplyCache( bool twoColored) const { const auto index = (twoColored ? 1 : 0); const auto &service = msgServiceFg()->c; EnsureBlockquoteCache( _serviceReplyCache[index], [&] { return SimpleColorIndexValues(service, twoColored); }); return _serviceReplyCache[index].get(); } const ColorIndexValues &ChatStyle::coloredValues( bool selected, uint8 colorIndex) const { Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); const auto shift = (selected ? kColorIndexCount : 0); auto &result = _coloredValues[shift + colorIndex]; if (!result) { result.emplace(computeColorIndexValues(selected, colorIndex)); } return *result; } const style::TextPalette &ChatStyle::coloredTextPalette( bool selected, uint8 colorIndex) const { Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); const auto shift = (selected ? kColorIndexCount : 0); auto &result = _coloredTextPalettes[shift + colorIndex]; if (!result.linkFg) { result.linkFg.emplace(coloredValues(selected, colorIndex).name); make( result.data, (selected ? st::inReplyTextPaletteSelected : st::inReplyTextPalette)); result.data.linkFg = result.linkFg->color(); result.data.selectLinkFg = result.data.linkFg; } return result.data; } not_null ChatStyle::backgroundEmojiData( uint64 id) const { return &_backgroundEmojis[id]; } not_null ChatStyle::coloredQuoteCache( bool selected, uint8 colorIndex) const { return coloredCache(_coloredQuoteCaches, selected, colorIndex); } not_null ChatStyle::coloredReplyCache( bool selected, uint8 colorIndex) const { return coloredCache(_coloredReplyCaches, selected, colorIndex); } not_null ChatStyle::coloredCache( ColoredQuotePaintCaches &caches, bool selected, uint8 colorIndex) const { Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); const auto shift = (selected ? kColorIndexCount : 0); auto &cache = caches[shift + colorIndex]; EnsureBlockquoteCache(cache, [&] { return coloredValues(selected, colorIndex); }); return cache.get(); } const CornersPixmaps &ChatStyle::msgBotKbOverBgAddCornersSmall() const { EnsureCorners( _msgBotKbOverBgAddCornersSmall, BubbleRadiusSmall(), msgBotKbOverBgAdd()); return _msgBotKbOverBgAddCornersSmall; } const CornersPixmaps &ChatStyle::msgBotKbOverBgAddCornersLarge() const { EnsureCorners( _msgBotKbOverBgAddCornersLarge, BubbleRadiusLarge(), msgBotKbOverBgAdd()); return _msgBotKbOverBgAddCornersLarge; } const CornersPixmaps &ChatStyle::msgSelectOverlayCorners( CachedCornerRadius radius) const { const auto index = static_cast(radius); Assert(index >= 0 && index < int(CachedCornerRadius::kCount)); EnsureCorners( _msgSelectOverlayCorners[index], CachedCornerRadiusValue(radius), msgSelectOverlay()); return _msgSelectOverlayCorners[index]; } MessageStyle &ChatStyle::messageStyleRaw(bool outbg, bool selected) const { return _messageStyles[(outbg ? 2 : 0) + (selected ? 1 : 0)]; } MessageStyle &ChatStyle::messageIn() { return messageStyleRaw(false, false); } MessageStyle &ChatStyle::messageInSelected() { return messageStyleRaw(false, true); } MessageStyle &ChatStyle::messageOut() { return messageStyleRaw(true, false); } MessageStyle &ChatStyle::messageOutSelected() { return messageStyleRaw(true, true); } MessageImageStyle &ChatStyle::imageStyleRaw(bool selected) const { return _imageStyles[selected ? 1 : 0]; } MessageImageStyle &ChatStyle::image() { return imageStyleRaw(false); } MessageImageStyle &ChatStyle::imageSelected() { return imageStyleRaw(true); } void ChatStyle::make(style::color &my, const style::color &original) const { my = _colors[style::main_palette::indexOfColor(original)]; } void ChatStyle::make(style::icon &my, const style::icon &original) const { my = original.withPalette(*this); } void ChatStyle::make( style::TextPalette &my, const style::TextPalette &original) const { my.linkAlwaysActive = original.linkAlwaysActive; make(my.linkFg, original.linkFg); make(my.monoFg, original.monoFg); make(my.spoilerFg, original.spoilerFg); make(my.selectBg, original.selectBg); make(my.selectFg, original.selectFg); make(my.selectLinkFg, original.selectLinkFg); make(my.selectMonoFg, original.selectMonoFg); make(my.selectSpoilerFg, original.selectSpoilerFg); make(my.selectOverlay, original.selectOverlay); } void ChatStyle::make( style::TwoIconButton &my, const style::TwoIconButton &original) const { my = original; make(my.iconBelow, original.iconBelow); make(my.iconAbove, original.iconAbove); make(my.iconBelowOver, original.iconBelowOver); make(my.iconAboveOver, original.iconAboveOver); make(my.ripple.color, original.ripple.color); } void ChatStyle::make( style::ScrollArea &my, const style::ScrollArea &original) const { my = original; make(my.bg, original.bg); make(my.bgOver, original.bgOver); make(my.barBg, original.barBg); make(my.barBgOver, original.barBgOver); make(my.shColor, original.shColor); } template void ChatStyle::make( Type MessageStyle::*my, const Type &originalIn, const Type &originalInSelected, const Type &originalOut, const Type &originalOutSelected) { make(messageIn().*my, originalIn); make(messageInSelected().*my, originalInSelected); make(messageOut().*my, originalOut); make(messageOutSelected().*my, originalOutSelected); } template void ChatStyle::make( Type MessageImageStyle::*my, const Type &original, const Type &originalSelected) { make(image().*my, original); make(imageSelected().*my, originalSelected); } uint8 DecideColorIndex(uint64 id) { return id % kSimpleColorIndexCount; } uint8 ColorIndexToPaletteIndex(uint8 colorIndex) { Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); const int8 map[] = { 0, 7, 4, 1, 6, 3, 5 }; return map[colorIndex % kSimpleColorIndexCount]; } QColor FromNameFg( not_null st, bool selected, uint8 colorIndex) { return st->coloredValues(selected, colorIndex).name; } void FillComplexOverlayRect( QPainter &p, QRect rect, const style::color &color, const CornersPixmaps &corners) { using namespace Images; const auto pix = corners.p; const auto fillRect = [&](QRect rect) { p.fillRect(rect, color); }; if (pix[kTopLeft].isNull() && pix[kTopRight].isNull() && pix[kBottomLeft].isNull() && pix[kBottomRight].isNull()) { fillRect(rect); return; } const auto ratio = style::DevicePixelRatio(); const auto fillCorner = [&](int left, int top, int index) { p.drawPixmap(left, top, pix[index]); }; const auto cornerSize = [&](int index) { const auto &p = pix[index]; return p.isNull() ? 0 : p.width() / ratio; }; const auto verticalSkip = [&](int left, int right) { return std::max(cornerSize(left), cornerSize(right)); }; const auto top = verticalSkip(kTopLeft, kTopRight); const auto bottom = verticalSkip(kBottomLeft, kBottomRight); if (top) { const auto left = cornerSize(kTopLeft); const auto right = cornerSize(kTopRight); if (left) { fillCorner(rect.left(), rect.top(), kTopLeft); if (const auto add = top - left) { fillRect({ rect.left(), rect.top() + left, left, add }); } } if (const auto fill = rect.width() - left - right; fill > 0) { fillRect({ rect.left() + left, rect.top(), fill, top }); } if (right) { fillCorner( rect.left() + rect.width() - right, rect.top(), kTopRight); if (const auto add = top - right) { fillRect({ rect.left() + rect.width() - right, rect.top() + right, right, add, }); } } } if (const auto h = rect.height() - top - bottom; h > 0) { fillRect({ rect.left(), rect.top() + top, rect.width(), h }); } if (bottom) { const auto left = cornerSize(kBottomLeft); const auto right = cornerSize(kBottomRight); if (left) { fillCorner( rect.left(), rect.top() + rect.height() - left, kBottomLeft); if (const auto add = bottom - left) { fillRect({ rect.left(), rect.top() + rect.height() - bottom, left, add, }); } } if (const auto fill = rect.width() - left - right; fill > 0) { fillRect({ rect.left() + left, rect.top() + rect.height() - bottom, fill, bottom, }); } if (right) { fillCorner( rect.left() + rect.width() - right, rect.top() + rect.height() - right, kBottomRight); if (const auto add = bottom - right) { fillRect({ rect.left() + rect.width() - right, rect.top() + rect.height() - bottom, right, add, }); } } } } void FillComplexEllipse( QPainter &p, not_null st, QRect rect) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(st->msgSelectOverlay()); p.drawEllipse(rect); } } // namespace Ui