Support server-side colors by index, up to three.

This commit is contained in:
John Preston 2023-10-26 23:40:53 +04:00
parent cc8408d11c
commit 5d335341ab
21 changed files with 391 additions and 167 deletions

View File

@ -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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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 &quoteSt = hasQuote
? st::messageTextStyle.blockquote
: st::messageQuoteStyle;

View File

@ -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(),

View File

@ -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()) {

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;
};

View File

@ -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

View File

@ -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;
};

View File

@ -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,

View File

@ -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());
}

View File

@ -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) {

View File

@ -50,6 +50,7 @@ messageQuoteStyle: QuoteStyle(defaultQuoteStyle) {
padding: margins(10px, 2px, 4px, 2px);
verticalSkip: 4px;
outline: 3px;
outlineShift: 2px;
radius: 5px;
}
messageTextStyle: TextStyle(defaultTextStyle) {

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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();

@ -1 +1 @@
Subproject commit 383b5b8f7e629475e5f22445167aaa7669c5cdd6
Subproject commit 611224c52f3192f616018fc7a2c5930667531084