1136 lines
31 KiB
C++
1136 lines
31 KiB
C++
/*
|
|
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<Text::QuotePaintCache> &cache,
|
|
Fn<ColorIndexValues()> values) {
|
|
if (cache) {
|
|
return;
|
|
}
|
|
cache = std::make_unique<Text::QuotePaintCache>();
|
|
const auto &colors = values();
|
|
cache->bg = colors.bg;
|
|
cache->outlines = colors.outlines;
|
|
cache->icon = colors.name;
|
|
}
|
|
|
|
void EnsurePreCache(
|
|
std::unique_ptr<Text::QuotePaintCache> &cache,
|
|
const style::color &color,
|
|
Fn<std::optional<QColor>()> bgOverride) {
|
|
if (cache) {
|
|
return;
|
|
}
|
|
cache = std::make_unique<Text::QuotePaintCache>();
|
|
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<const MessageStyle*> ChatPaintContext::messageStyle() const {
|
|
return &st->messageStyle(outbg, selected());
|
|
}
|
|
|
|
not_null<const MessageImageStyle*> ChatPaintContext::imageStyle() const {
|
|
return &st->imageStyle(selected());
|
|
}
|
|
|
|
not_null<Text::QuotePaintCache*> 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<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);
|
|
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<const style::palette*> isolated)
|
|
: ChatStyle(rpl::producer<ColorIndicesCompressed>()) {
|
|
assignPalette(isolated);
|
|
}
|
|
|
|
ChatStyle::~ChatStyle() = default;
|
|
|
|
void ChatStyle::apply(not_null<ChatTheme*> 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<Text::SpecialColor> 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<const style::palette*> palette) {
|
|
*static_cast<style::palette*>(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<Text::QuotePaintCache>(
|
|
*result.replyCache[i]);
|
|
}
|
|
}
|
|
|
|
const auto preBgOverride = [&] {
|
|
return _dark ? QColor(0, 0, 0, 192) : std::optional<QColor>();
|
|
};
|
|
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<Text::QuotePaintCache*> 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<Text::QuotePaintCache*> 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<BackgroundEmojiData*> ChatStyle::backgroundEmojiData(
|
|
uint64 id) const {
|
|
return &_backgroundEmojis[id];
|
|
}
|
|
|
|
not_null<Text::QuotePaintCache*> ChatStyle::coloredQuoteCache(
|
|
bool selected,
|
|
uint8 colorIndex) const {
|
|
return coloredCache(_coloredQuoteCaches, selected, colorIndex);
|
|
}
|
|
|
|
not_null<Text::QuotePaintCache*> ChatStyle::coloredReplyCache(
|
|
bool selected,
|
|
uint8 colorIndex) const {
|
|
return coloredCache(_coloredReplyCaches, selected, colorIndex);
|
|
}
|
|
|
|
not_null<Text::QuotePaintCache*> 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<int>(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 <typename Type>
|
|
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 <typename Type>
|
|
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<const ChatStyle*> 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<const ChatStyle*> st,
|
|
QRect rect) {
|
|
PainterHighQualityEnabler hq(p);
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(st->msgSelectOverlay());
|
|
p.drawEllipse(rect);
|
|
}
|
|
|
|
} // namespace Ui
|