Start non-unique ChatTheme.

This commit is contained in:
John Preston 2021-08-26 16:25:48 +03:00
parent f3dd8c68b3
commit 3cd0f9d189
40 changed files with 954 additions and 599 deletions

View File

@ -1100,6 +1100,8 @@ PRIVATE
window/window_slide_animation.cpp
window/window_slide_animation.h
window/window_top_bar_wrap.h
window/themes/window_chat_theme.cpp
window/themes/window_chat_theme.h
window/themes/window_theme.cpp
window/themes/window_theme.h
window/themes/window_theme_editor.cpp

View File

@ -3505,7 +3505,7 @@ void ApiWrap::sharedMediaDone(
parsed.fullCount
));
if (type == SharedMediaType::Pinned && !parsed.messageIds.empty()) {
peer->setHasPinnedMessages(true);
peer->owner().history(peer)->setHasPinnedMessages(true);
}
}

View File

@ -590,6 +590,7 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
const auto height1 = _text1->height();
const auto height2 = _text2->height();
const auto context = HistoryView::PaintContext{
.st = style::main_palette::get(),
.bubblesPattern = nullptr, // #TODO bubbles
.viewport = rect(),
.clip = rect(),

View File

@ -26,7 +26,7 @@ object_ptr<Window::SectionWidget> TabbedMemento::createWidget(
TabbedSection::TabbedSection(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Window::SectionWidget(parent, controller, PaintedBackground::Custom)
: Window::SectionWidget(parent, controller)
, _selector(controller->tabbedSelector()) {
_selector->setParent(this);
_selector->setRoundRadius(0);

View File

@ -57,7 +57,7 @@ struct PeerUpdate {
Notifications = (1U << 4),
Migration = (1U << 5),
UnavailableReason = (1U << 6),
PinnedMessages = (1U << 7),
ChatThemeEmoji = (1U << 7),
IsBlocked = (1U << 8),
MessagesTTL = (1U << 9),
@ -118,8 +118,9 @@ struct HistoryUpdate {
BotKeyboard = (1U << 11),
CloudDraft = (1U << 12),
LocalDraftSet = (1U << 13),
PinnedMessages = (1U << 14),
LastUsedBit = (1U << 13),
LastUsedBit = (1U << 14),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@ -37,10 +37,9 @@ CloudTheme CloudTheme::Parse(
const auto document = data.vdocument();
const auto paper = [&]() -> std::optional<WallPaper> {
if (const auto settings = data.vsettings()) {
settings->match([&](const MTPDthemeSettings &data) {
return settings->match([&](const MTPDthemeSettings &data) {
return data.vwallpaper()
? std::make_optional(
WallPaper::Create(session, *data.vwallpaper()))
? WallPaper::Create(session, *data.vwallpaper())
: std::nullopt;
});
}
@ -61,12 +60,27 @@ CloudTheme CloudTheme::Parse(
};
const auto accentColor = [&]() -> std::optional<QColor> {
if (const auto settings = data.vsettings()) {
settings->match([&](const MTPDthemeSettings &data) {
return settings->match([&](const MTPDthemeSettings &data) {
return ColorFromSerialized(data.vaccent_color().v);
});
}
return {};
};
const auto basedOnDark = [&] {
if (const auto settings = data.vsettings()) {
return settings->match([&](const MTPDthemeSettings &data) {
return data.vbase_theme().match([](
const MTPDbaseThemeNight &) {
return true;
}, [](const MTPDbaseThemeTinted &) {
return true;
}, [](const auto &) {
return false;
});
});
}
return false;
};
return {
.id = data.vid().v,
.accessHash = data.vaccess_hash().v,
@ -82,6 +96,7 @@ CloudTheme CloudTheme::Parse(
.outgoingMessagesColors = (parseSettings
? outgoingMessagesColors()
: std::vector<QColor>()),
.basedOnDark = parseSettings && basedOnDark(),
};
}
@ -368,6 +383,24 @@ std::optional<ChatTheme> CloudThemes::themeForEmoji(
return (i != end(_chatThemes)) ? std::make_optional(*i) : std::nullopt;
}
rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
const QString &emoji) {
if (emoji.isEmpty()) {
return rpl::single<std::optional<ChatTheme>>(std::nullopt);
} else if (auto result = themeForEmoji(emoji)) {
return rpl::single(std::move(result));
}
refreshChatThemes();
return rpl::single<std::optional<ChatTheme>>(
std::nullopt
) | rpl::then(chatThemesUpdated(
) | rpl::map([=] {
return themeForEmoji(emoji);
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
return theme.has_value();
}) | rpl::take(1));
}
void CloudThemes::parseChatThemes(const QVector<MTPChatTheme> &list) {
_chatThemes.clear();
_chatThemes.reserve(list.size());

View File

@ -32,9 +32,11 @@ struct CloudTheme {
DocumentId documentId = 0;
UserId createdBy = 0;
int usersCount = 0;
std::optional<WallPaper> paper;
std::optional<QColor> accentColor;
std::vector<QColor> outgoingMessagesColors;
bool basedOnDark = false;
static CloudTheme Parse(
not_null<Main::Session*> session,
@ -69,6 +71,8 @@ public:
[[nodiscard]] rpl::producer<> chatThemesUpdated() const;
[[nodiscard]] std::optional<ChatTheme> themeForEmoji(
const QString &emoji) const;
[[nodiscard]] rpl::producer<std::optional<ChatTheme>> themeForEmojiValue(
const QString &emoji);
void applyUpdate(const MTPTheme &theme);

View File

@ -528,15 +528,6 @@ bool PeerData::canEditMessagesIndefinitely() const {
Unexpected("Peer type in PeerData::canEditMessagesIndefinitely.");
}
bool PeerData::hasPinnedMessages() const {
return _hasPinnedMessages;
}
void PeerData::setHasPinnedMessages(bool has) {
_hasPinnedMessages = has;
session().changes().peerUpdated(this, UpdateFlag::PinnedMessages);
}
bool PeerData::canExportChatHistory() const {
if (isRepliesChat()) {
return false;
@ -1014,10 +1005,14 @@ PeerId PeerData::groupCallDefaultJoinAs() const {
}
void PeerData::setThemeEmoji(const QString &emoji) {
if (_themeEmoji == emoji) {
return;
}
_themeEmoji = emoji;
if (!emoji.isEmpty() && !owner().cloudThemes().themeForEmoji(emoji)) {
owner().cloudThemes().refreshChatThemes();
}
session().changes().peerUpdated(this, UpdateFlag::ChatThemeEmoji);
}
const QString &PeerData::themeEmoji() const {
@ -1166,7 +1161,7 @@ void SetTopPinnedMessageId(not_null<PeerData*> peer, MsgId messageId) {
Storage::SharedMediaType::Pinned,
messageId,
{ messageId, ServerMaxMsgId }));
peer->setHasPinnedMessages(true);
peer->owner().history(peer)->setHasPinnedMessages(true);
}
FullMsgId ResolveTopPinnedId(

View File

@ -406,9 +406,6 @@ public:
[[nodiscard]] bool canPinMessages() const;
[[nodiscard]] bool canEditMessagesIndefinitely() const;
[[nodiscard]] bool hasPinnedMessages() const;
void setHasPinnedMessages(bool has);
[[nodiscard]] bool canExportChatHistory() const;
// Returns true if about text was changed.
@ -511,7 +508,6 @@ private:
crl::time _lastFullUpdate = 0;
TimeId _ttlPeriod = 0;
bool _hasPinnedMessages = false;
Settings _settings = PeerSettings(PeerSetting::Unknown);
BlockStatus _blockStatus = BlockStatus::Unknown;

View File

@ -181,12 +181,6 @@ WallPaperId WallPaper::id() const {
return _id;
}
std::optional<QColor> WallPaper::backgroundColor() const {
return _backgroundColors.empty()
? std::nullopt
: std::make_optional(_backgroundColors.front());
}
const std::vector<QColor> WallPaper::backgroundColors() const {
return _backgroundColors;
}

View File

@ -35,7 +35,6 @@ public:
void setLocalImageAsThumbnail(std::shared_ptr<Image> image);
[[nodiscard]] WallPaperId id() const;
[[nodiscard]] std::optional<QColor> backgroundColor() const;
[[nodiscard]] const std::vector<QColor> backgroundColors() const;
[[nodiscard]] DocumentData *document() const;
[[nodiscard]] Image *localThumbnail() const;

View File

@ -164,7 +164,7 @@ void Widget::BottomButton::paintEvent(QPaintEvent *e) {
Widget::Widget(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Window::AbstractSectionWidget(parent, controller, PaintedBackground::Custom)
: Window::AbstractSectionWidget(parent, controller, nullptr)
, _api(&controller->session().mtp())
, _searchControls(this)
, _mainMenuToggle(_searchControls, st::dialogsMenuToggle)

View File

@ -915,6 +915,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
auto viewport = QRect(); // #TODO bubbles
auto top = itemTop(from->get());
auto context = HistoryView::PaintContext{
.st = style::main_palette::get(),
.bubblesPattern = nullptr,
.viewport = viewport.translated(0, -top),
.clip = clip.translated(0, -top),

View File

@ -275,7 +275,7 @@ Widget::Widget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<ChannelData*> channel)
: Window::SectionWidget(parent, controller, PaintedBackground::Section)
: Window::SectionWidget(parent, controller, rpl::single<PeerData*>(channel))
, _scroll(this, st::historyScroll, false)
, _fixedBar(this, controller, channel)
, _fixedBarShadow(this)
@ -306,11 +306,6 @@ Widget::Widget(
updateAdaptiveLayout();
}, lifetime());
controller->repaintBackgroundRequests(
) | rpl::start_with_next([=] {
update();
}, lifetime());
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller, channel));
_inner->showSearchSignal(
) | rpl::start_with_next([=] {
@ -475,7 +470,11 @@ void Widget::paintEvent(QPaintEvent *e) {
//auto ms = crl::now();
//_historyDownShown.step(ms);
SectionWidget::PaintBackground(controller(), this, e->rect());
SectionWidget::PaintBackground(
controller(),
controller()->defaultChatTheme().get(), // #TODO themes
this,
e->rect());
}
void Widget::onScroll() {

View File

@ -458,7 +458,7 @@ void History::unpinAllMessages() {
Storage::SharedMediaRemoveAll(
peer->id,
Storage::SharedMediaType::Pinned));
peer->setHasPinnedMessages(false);
setHasPinnedMessages(false);
for (const auto &message : _messages) {
if (message->isPinned()) {
message->setIsPinned(false);
@ -751,7 +751,7 @@ not_null<HistoryItem*> History::addNewToBack(
item->id,
{ from, till }));
if (sharedMediaTypes.test(Storage::SharedMediaType::Pinned)) {
peer->setHasPinnedMessages(true);
setHasPinnedMessages(true);
}
}
}
@ -1023,7 +1023,7 @@ void History::applyServiceChanges(
Storage::SharedMediaType::Pinned,
{ id },
{ id, ServerMaxMsgId }));
peer->setHasPinnedMessages(true);
setHasPinnedMessages(true);
}
});
}
@ -1401,7 +1401,7 @@ void History::addToSharedMedia(
std::move(medias[i]),
{ from, till }));
if (type == Storage::SharedMediaType::Pinned) {
peer->setHasPinnedMessages(true);
setHasPinnedMessages(true);
}
}
}
@ -3123,6 +3123,15 @@ void History::removeBlock(not_null<HistoryBlock*> block) {
}
}
bool History::hasPinnedMessages() const {
return _hasPinnedMessages;
}
void History::setHasPinnedMessages(bool has) {
_hasPinnedMessages = has;
session().changes().historyUpdated(this, UpdateFlag::PinnedMessages);
}
History::~History() = default;
HistoryBlock::HistoryBlock(not_null<History*> history)

View File

@ -412,6 +412,9 @@ public:
void setInboxReadTill(MsgId upTo);
std::optional<int> countStillUnreadLocal(MsgId readTillId) const;
[[nodiscard]] bool hasPinnedMessages() const;
void setHasPinnedMessages(bool has);
// Still public data.
std::deque<std::unique_ptr<HistoryBlock>> blocks;
@ -581,6 +584,7 @@ private:
bool _unreadMark = false;
bool _fakeUnreadWhileOpened = false;
bool _hasPinnedMessages = false;
// A pointer to the block that is currently being built.
// We hold this pointer so we can destroy it while building

View File

@ -615,7 +615,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto item = view->data();
auto top = mtop + block->y() + view->y();
auto context = _controller->bubblesContext({
auto context = _controller->preparePaintContext({
.theme = _controller->defaultChatTheme().get(), // #TODO themes
.visibleAreaTop = _visibleAreaTop,
.visibleAreaTopGlobal = visibleAreaTopGlobal,
.clip = clip,
@ -663,7 +664,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto item = view->data();
auto readTill = (HistoryItem*)nullptr;
auto top = htop + block->y() + view->y();
auto context = _controller->bubblesContext({
auto context = _controller->preparePaintContext({
.theme = _controller->defaultChatTheme().get(), // #TODO themes
.visibleAreaTop = _visibleAreaTop,
.visibleAreaTopGlobal = visibleAreaTopGlobal,
.visibleAreaWidth = width(),

View File

@ -364,7 +364,7 @@ void HistoryItem::setIsPinned(bool pinned) {
Storage::SharedMediaType::Pinned,
id,
{ id, id }));
history()->peer->setHasPinnedMessages(true);
history()->setHasPinnedMessages(true);
} else {
_flags &= ~MessageFlag::Pinned;
history()->session().storage().remove(Storage::SharedMediaRemoveOne(
@ -553,7 +553,7 @@ void HistoryItem::indexAsNewItem() {
types,
id));
if (types.test(Storage::SharedMediaType::Pinned)) {
_history->peer->setHasPinnedMessages(true);
_history->setHasPinnedMessages(true);
}
}
}

View File

@ -116,6 +116,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "window/window_slide_animation.h"
#include "window/window_peer_menu.h"
#include "window/themes/window_chat_theme.h"
#include "inline_bots/inline_results_widget.h"
#include "info/profile/info_profile_values.h" // SharedMediaCountValue.
#include "chat_helpers/emoji_suggestions_widget.h"
@ -166,12 +167,24 @@ const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
crl::time(1000) * 8);
}
[[nodiscard]] rpl::producer<PeerData*> ActivePeerValue(
not_null<Window::SessionController*> controller) {
return controller->activeChatValue(
) | rpl::map([](const Dialogs::Key &key) {
const auto history = key.history();
return history ? history->peer.get() : nullptr;
});
}
} // namespace
HistoryWidget::HistoryWidget(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Window::AbstractSectionWidget(parent, controller, PaintedBackground::Section)
: Window::AbstractSectionWidget(
parent,
controller,
ActivePeerValue(controller))
, _api(&controller->session().mtp())
, _updateEditTimeLeftDisplay([=] { updateField(); })
, _fieldBarCancel(this, st::historyReplyCancel)
@ -432,11 +445,6 @@ HistoryWidget::HistoryWidget(
}
}, lifetime());
controller->repaintBackgroundRequests(
) | rpl::start_with_next([=] {
update();
}, lifetime());
session().data().newItemAdded(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
newItemAdded(item);
@ -504,40 +512,52 @@ HistoryWidget::HistoryWidget(
}
}, lifetime());
using HistoryUpdateFlag = Data::HistoryUpdate::Flag;
session().changes().historyUpdates(
Data::HistoryUpdate::Flag::MessageSent
| Data::HistoryUpdate::Flag::ForwardDraft
| Data::HistoryUpdate::Flag::BotKeyboard
| Data::HistoryUpdate::Flag::CloudDraft
| Data::HistoryUpdate::Flag::UnreadMentions
| Data::HistoryUpdate::Flag::UnreadView
| Data::HistoryUpdate::Flag::TopPromoted
| Data::HistoryUpdate::Flag::LocalMessages
HistoryUpdateFlag::MessageSent
| HistoryUpdateFlag::ForwardDraft
| HistoryUpdateFlag::BotKeyboard
| HistoryUpdateFlag::CloudDraft
| HistoryUpdateFlag::UnreadMentions
| HistoryUpdateFlag::UnreadView
| HistoryUpdateFlag::TopPromoted
| HistoryUpdateFlag::LocalMessages
| HistoryUpdateFlag::PinnedMessages
) | rpl::filter([=](const Data::HistoryUpdate &update) {
if (_migrated && update.history.get() == _migrated) {
if (_pinnedTracker
&& (update.flags & HistoryUpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
}
return (_history == update.history.get());
}) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
if (update.flags & Data::HistoryUpdate::Flag::MessageSent) {
const auto flags = update.flags;
if (flags & HistoryUpdateFlag::MessageSent) {
synteticScrollToY(_scroll->scrollTopMax());
}
if (update.flags & Data::HistoryUpdate::Flag::ForwardDraft) {
if (flags & HistoryUpdateFlag::ForwardDraft) {
updateForwarding();
}
if (update.flags & Data::HistoryUpdate::Flag::BotKeyboard) {
if (flags & HistoryUpdateFlag::BotKeyboard) {
updateBotKeyboard(update.history);
}
if (update.flags & Data::HistoryUpdate::Flag::CloudDraft) {
if (flags & HistoryUpdateFlag::CloudDraft) {
applyCloudDraft(update.history);
}
if (update.flags & Data::HistoryUpdate::Flag::LocalMessages) {
if (flags & HistoryUpdateFlag::LocalMessages) {
updateSendButtonType();
}
if (update.flags & Data::HistoryUpdate::Flag::UnreadMentions) {
if (flags & HistoryUpdateFlag::UnreadMentions) {
updateUnreadMentionsVisibility();
}
if (update.flags & Data::HistoryUpdate::Flag::UnreadView) {
if (flags & HistoryUpdateFlag::UnreadView) {
unreadCountUpdated();
}
if (update.flags & Data::HistoryUpdate::Flag::TopPromoted) {
if (_pinnedTracker && (flags & HistoryUpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
if (flags & HistoryUpdateFlag::TopPromoted) {
updateHistoryGeometry();
updateControlsVisibility();
updateControlsGeometry();
@ -545,25 +565,27 @@ HistoryWidget::HistoryWidget(
}
}, lifetime());
using MessageUpdateFlag = Data::MessageUpdate::Flag;
session().changes().messageUpdates(
Data::MessageUpdate::Flag::Destroyed
| Data::MessageUpdate::Flag::Edited
| Data::MessageUpdate::Flag::ReplyMarkup
| Data::MessageUpdate::Flag::BotCallbackSent
MessageUpdateFlag::Destroyed
| MessageUpdateFlag::Edited
| MessageUpdateFlag::ReplyMarkup
| MessageUpdateFlag::BotCallbackSent
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
if (update.flags & Data::MessageUpdate::Flag::Destroyed) {
const auto flags = update.flags;
if (flags & MessageUpdateFlag::Destroyed) {
itemRemoved(update.item);
return;
}
if (update.flags & Data::MessageUpdate::Flag::Edited) {
if (flags & MessageUpdateFlag::Edited) {
itemEdited(update.item);
}
if (update.flags & Data::MessageUpdate::Flag::ReplyMarkup) {
if (flags & MessageUpdateFlag::ReplyMarkup) {
if (_keyboard->forMsgId() == update.item->fullId()) {
updateBotKeyboard(update.item->history(), true);
}
}
if (update.flags & Data::MessageUpdate::Flag::BotCallbackSent) {
if (flags & MessageUpdateFlag::BotCallbackSent) {
botCallbackSent(update.item);
}
}, lifetime());
@ -574,45 +596,38 @@ HistoryWidget::HistoryWidget(
}
});
using UpdateFlag = Data::PeerUpdate::Flag;
using PeerUpdateFlag = Data::PeerUpdate::Flag;
session().changes().peerUpdates(
UpdateFlag::Rights
| UpdateFlag::Migration
| UpdateFlag::UnavailableReason
| UpdateFlag::IsBlocked
| UpdateFlag::Admins
| UpdateFlag::Members
| UpdateFlag::OnlineStatus
| UpdateFlag::Notifications
| UpdateFlag::ChannelAmIn
| UpdateFlag::ChannelLinkedChat
| UpdateFlag::Slowmode
| UpdateFlag::BotStartToken
| UpdateFlag::PinnedMessages
| UpdateFlag::MessagesTTL
PeerUpdateFlag::Rights
| PeerUpdateFlag::Migration
| PeerUpdateFlag::UnavailableReason
| PeerUpdateFlag::IsBlocked
| PeerUpdateFlag::Admins
| PeerUpdateFlag::Members
| PeerUpdateFlag::OnlineStatus
| PeerUpdateFlag::Notifications
| PeerUpdateFlag::ChannelAmIn
| PeerUpdateFlag::ChannelLinkedChat
| PeerUpdateFlag::Slowmode
| PeerUpdateFlag::BotStartToken
| PeerUpdateFlag::MessagesTTL
) | rpl::filter([=](const Data::PeerUpdate &update) {
if (_migrated && update.peer.get() == _migrated->peer) {
if (_pinnedTracker
&& (update.flags & UpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
}
return (update.peer.get() == _peer);
}) | rpl::map([](const Data::PeerUpdate &update) {
return update.flags;
}) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) {
if (flags & UpdateFlag::Rights) {
if (flags & PeerUpdateFlag::Rights) {
checkPreview();
updateStickersByEmoji();
updateFieldPlaceholder();
}
if (flags & UpdateFlag::Migration) {
if (flags & PeerUpdateFlag::Migration) {
handlePeerMigration();
}
if (flags & UpdateFlag::Notifications) {
if (flags & PeerUpdateFlag::Notifications) {
updateNotifyControls();
}
if (flags & UpdateFlag::UnavailableReason) {
if (flags & PeerUpdateFlag::UnavailableReason) {
const auto unavailable = _peer->computeUnavailableReason();
if (!unavailable.isEmpty()) {
controller->showBackFromStack();
@ -620,26 +635,23 @@ HistoryWidget::HistoryWidget(
return;
}
}
if (flags & UpdateFlag::BotStartToken) {
if (flags & PeerUpdateFlag::BotStartToken) {
updateControlsVisibility();
updateControlsGeometry();
}
if (flags & UpdateFlag::Slowmode) {
if (flags & PeerUpdateFlag::Slowmode) {
updateSendButtonType();
}
if (flags & (UpdateFlag::IsBlocked
| UpdateFlag::Admins
| UpdateFlag::Members
| UpdateFlag::OnlineStatus
| UpdateFlag::Rights
| UpdateFlag::ChannelAmIn
| UpdateFlag::ChannelLinkedChat)) {
if (flags & (PeerUpdateFlag::IsBlocked
| PeerUpdateFlag::Admins
| PeerUpdateFlag::Members
| PeerUpdateFlag::OnlineStatus
| PeerUpdateFlag::Rights
| PeerUpdateFlag::ChannelAmIn
| PeerUpdateFlag::ChannelLinkedChat)) {
handlePeerUpdate();
}
if (_pinnedTracker && (flags & UpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
if (flags & UpdateFlag::MessagesTTL) {
if (flags & PeerUpdateFlag::MessagesTTL) {
checkMessagesTTL();
}
}, lifetime());
@ -4930,7 +4942,7 @@ void HistoryWidget::startItemRevealAnimations() {
HistoryView::ListWidget::kItemRevealDuration,
anim::easeOutCirc);
if (item->out() || _history->peer->isSelf()) {
controller()->rotateComplexGradientBackground();
controller()->defaultChatTheme()->rotateComplexGradientBackground(); // #TODO themes
}
}
}
@ -6874,7 +6886,11 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
updateListSize();
}
Window::SectionWidget::PaintBackground(controller(), this, e->rect());
Window::SectionWidget::PaintBackground(
controller(),
controller()->defaultChatTheme().get(), // #TODO themes
this,
e->rect());
Painter p(this);
const auto clip = e->rect();

View File

@ -193,6 +193,7 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
};
struct PaintContext {
not_null<const style::palette*> st;
const Ui::BubblePattern *bubblesPattern = nullptr;
QRect viewport;
QRect clip;

View File

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_adaptive.h"
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "window/themes/window_chat_theme.h"
#include "main/main_session.h"
#include "boxes/confirm_box.h"
#include "ui/widgets/popup_menu.h"
@ -1445,7 +1446,7 @@ void ListWidget::startItemRevealAnimations() {
kItemRevealDuration,
anim::easeOutCirc);
if (view->data()->out()) {
controller()->rotateComplexGradientBackground();
controller()->defaultChatTheme()->rotateComplexGradientBackground(); // #TODO themes
}
}
}
@ -1614,6 +1615,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
auto viewport = QRect(); // #TODO bubbles
auto top = itemTop(from->get());
auto context = HistoryView::PaintContext{
.st = style::main_palette::get(),
.bubblesPattern = nullptr,
.viewport = viewport.translated(0, -top),
.clip = clip.translated(0, -top),

View File

@ -90,7 +90,7 @@ PinnedWidget::PinnedWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<History*> history)
: Window::SectionWidget(parent, controller, PaintedBackground::Section)
: Window::SectionWidget(parent, controller, history->peer)
, _history(history->migrateToOrMe())
, _migratedPeer(_history->peer->migrateFrom())
, _topBar(this, controller)
@ -457,7 +457,11 @@ void PinnedWidget::paintEvent(QPaintEvent *e) {
const auto aboveHeight = _topBar->height();
const auto bg = e->rect().intersected(
QRect(0, aboveHeight, width(), height() - aboveHeight));
SectionWidget::PaintBackground(controller(), this, bg);
SectionWidget::PaintBackground(
controller(),
controller()->defaultChatTheme().get(), // #TODO themes
this,
bg);
}
void PinnedWidget::onScroll() {

View File

@ -31,19 +31,21 @@ PinnedTracker::PinnedTracker(not_null<History*> history)
: _history(history->migrateToOrMe())
, _migratedPeer(_history->peer->migrateFrom()) {
using namespace rpl::mappers;
const auto has = [&](PeerData *peer) -> rpl::producer<bool> {
const auto has = [&](History *history) -> rpl::producer<bool> {
auto &changes = _history->session().changes();
const auto flag = Data::PeerUpdate::Flag::PinnedMessages;
if (!peer) {
const auto flag = Data::HistoryUpdate::Flag::PinnedMessages;
if (!history) {
return rpl::single(false);
}
return changes.peerFlagsValue(peer, flag) | rpl::map([=] {
return peer->hasPinnedMessages();
return changes.historyFlagsValue(history, flag) | rpl::map([=] {
return history->hasPinnedMessages();
});
};
rpl::combine(
has(_history->peer),
has(_migratedPeer),
has(_history),
has(_migratedPeer
? _history->owner().history(_migratedPeer).get()
: nullptr),
_1 || _2
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool has) {
@ -97,9 +99,10 @@ void PinnedTracker::refreshViewer() {
}
refreshCurrentFromSlice();
if (_slice.fullCount == 0) {
_history->peer->setHasPinnedMessages(false);
_history->setHasPinnedMessages(false);
if (_migratedPeer) {
_migratedPeer->setHasPinnedMessages(false);
const auto to = _history->owner().history(_migratedPeer);
to->setHasPinnedMessages(false);
}
}
}, _dataLifetime);

View File

@ -146,7 +146,7 @@ RepliesWidget::RepliesWidget(
not_null<Window::SessionController*> controller,
not_null<History*> history,
MsgId rootId)
: Window::SectionWidget(parent, controller, PaintedBackground::Section)
: Window::SectionWidget(parent, controller, history->peer)
, _history(history)
, _rootId(rootId)
, _root(lookupRoot())
@ -1511,7 +1511,11 @@ void RepliesWidget::paintEvent(QPaintEvent *e) {
const auto aboveHeight = _topBar->height();
const auto bg = e->rect().intersected(
QRect(0, aboveHeight, width(), height() - aboveHeight));
SectionWidget::PaintBackground(controller(), this, bg);
SectionWidget::PaintBackground(
controller(),
controller()->defaultChatTheme().get(), // #TODO themes
this,
bg);
}
void RepliesWidget::onScroll() {

View File

@ -90,7 +90,7 @@ ScheduledWidget::ScheduledWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<History*> history)
: Window::SectionWidget(parent, controller, PaintedBackground::Section)
: Window::SectionWidget(parent, controller, history->peer)
, _history(history)
, _scroll(this, st::historyScroll, false)
, _topBar(this, controller)
@ -996,7 +996,11 @@ void ScheduledWidget::paintEvent(QPaintEvent *e) {
//auto ms = crl::now();
//_historyDownShown.step(ms);
SectionWidget::PaintBackground(controller(), this, e->rect());
SectionWidget::PaintBackground(
controller(),
controller()->defaultChatTheme().get(), // #TODO themes
this,
e->rect());
}
void ScheduledWidget::onScroll() {

View File

@ -24,7 +24,7 @@ SectionWidget::SectionWidget(
not_null<Window::SessionController*> window,
Wrap wrap,
not_null<Memento*> memento)
: Window::SectionWidget(parent, window, PaintedBackground::Custom)
: Window::SectionWidget(parent, window)
, _content(this, window, wrap, memento) {
init();
}
@ -34,7 +34,7 @@ SectionWidget::SectionWidget(
not_null<Window::SessionController*> window,
Wrap wrap,
not_null<MoveMemento*> memento)
: Window::SectionWidget(parent, window, PaintedBackground::Custom)
: Window::SectionWidget(parent, window)
, _content(memento->takeContent(this, wrap)) {
init();
}

View File

@ -59,7 +59,7 @@ WrapWidget::WrapWidget(
not_null<Window::SessionController*> window,
Wrap wrap,
not_null<Memento*> memento)
: SectionWidget(parent, window, PaintedBackground::Custom)
: SectionWidget(parent, window, rpl::producer<PeerData*>())
, _wrap(wrap)
, _controller(createController(window, memento->content()))
, _topShadow(this) {

View File

@ -137,23 +137,9 @@ void MainWindow::setupNativeWindowFrame() {
Core::App().settings().nativeWindowFrameChanges()
);
using BackgroundUpdate = Window::Theme::BackgroundUpdate;
auto themeChanges = Window::Theme::Background()->updates(
) | rpl::filter([=](const BackgroundUpdate &update) {
return update.type == BackgroundUpdate::Type::ApplyingTheme;
}) | rpl::to_empty;
auto nightMode = rpl::single(
rpl::empty_value()
) | rpl::then(
std::move(themeChanges)
) | rpl::map([=] {
return Window::Theme::IsNightMode();
}) | rpl::distinct_until_changed();
rpl::combine(
std::move(nativeFrame),
std::move(nightMode)
Window::Theme::IsNightModeValue()
) | rpl::skip(1) | rpl::start_with_next([=](bool native, bool night) {
validateWindowTheme(native, night);
}, lifetime());

View File

@ -706,11 +706,17 @@ object_ptr<Ui::RpWidget> ForwardsPrivacyController::setupAboveWidget(
widget->paintRequest(
) | rpl::start_with_next([=](QRect rect) {
Window::SectionWidget::PaintBackground(_controller, widget, rect);
// #TODO themes
Window::SectionWidget::PaintBackground(
_controller,
_controller->defaultChatTheme().get(), // #TODO themes
widget,
rect);
Painter p(widget);
const auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr, // #TODO bubbles
.st = style::main_palette::get(),
.bubblesPattern = nullptr,
.viewport = widget->rect(),
.clip = widget->rect(),
.now = crl::now(),

View File

@ -565,6 +565,7 @@ void ConfirmContactBox::paintEvent(QPaintEvent *e) {
p.fillRect(e->rect(), st::boxBg);
const auto context = HistoryView::PaintContext{
.st = style::main_palette::get(),
.bubblesPattern = nullptr, // #TODO bubbles
.viewport = rect(),
.clip = rect(),

View File

@ -9,27 +9,101 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h"
#include "ui/ui_utility.h"
#include "data/data_peer.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_cloud_themes.h"
#include "main/main_session.h"
#include "window/section_memento.h"
#include "window/window_slide_animation.h"
#include "window/themes/window_theme.h"
#include "window/window_session_controller.h"
#include "window/themes/window_theme.h"
#include "window/themes/window_chat_theme.h"
#include <rpl/range.h>
namespace Window {
namespace {
[[nodiscard]] rpl::producer<QString> PeerThemeEmojiValue(
not_null<PeerData*> peer) {
return peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::ChatThemeEmoji
) | rpl::map([=] {
return peer->themeEmoji();
});
}
[[nodiscard]] auto MaybeChatThemeDataValueFromPeer(
not_null<PeerData*> peer)
-> rpl::producer<std::optional<Data::ChatTheme>> {
return PeerThemeEmojiValue(
peer
) | rpl::map([=](const QString &emoji)
-> rpl::producer<std::optional<Data::ChatTheme>> {
return peer->owner().cloudThemes().themeForEmojiValue(emoji);
}) | rpl::flatten_latest();
}
[[nodiscard]] auto MaybeCloudThemeValueFromPeer(
not_null<PeerData*> peer)
-> rpl::producer<std::optional<Data::CloudTheme>> {
return rpl::combine(
MaybeChatThemeDataValueFromPeer(peer),
Theme::IsNightModeValue()
) | rpl::map([](std::optional<Data::ChatTheme> theme, bool night) {
return !theme
? std::nullopt
: night
? std::make_optional(std::move(theme->dark))
: std::make_optional(std::move(theme->light));
});
}
[[nodiscard]] auto ChatThemeValueFromPeer(
not_null<SessionController*> controller,
not_null<PeerData*> peer)
-> rpl::producer<std::shared_ptr<Theme::ChatTheme>> {
return MaybeCloudThemeValueFromPeer(
peer
) | rpl::map([=](std::optional<Data::CloudTheme> theme)
-> rpl::producer<std::shared_ptr<Theme::ChatTheme>> {
if (!theme) {
return rpl::single(controller->defaultChatTheme());
}
return controller->cachedChatThemeValue(*theme);
}) | rpl::flatten_latest(
) | rpl::distinct_until_changed();
}
} // namespace
AbstractSectionWidget::AbstractSectionWidget(
QWidget *parent,
not_null<SessionController*> controller,
PaintedBackground paintedBackground)
rpl::producer<PeerData*> peerForBackground)
: RpWidget(parent)
, _controller(controller) {
if (paintedBackground == PaintedBackground::Section) {
controller->repaintBackgroundRequests(
) | rpl::start_with_next([=] {
update();
}, lifetime());
}
std::move(
peerForBackground
) | rpl::map([=](PeerData *peer) -> rpl::producer<> {
if (!peer) {
return rpl::never<>();
}
return ChatThemeValueFromPeer(
controller,
peer
) | rpl::map([](const std::shared_ptr<Theme::ChatTheme> &theme) {
return rpl::single(
rpl::empty_value()
) | rpl::then(
theme->repaintBackgroundRequests()
);
}) | rpl::flatten_latest();
}) | rpl::flatten_latest() | rpl::start_with_next([=] {
update();
}, lifetime());
}
Main::Session &AbstractSectionWidget::session() const {
@ -39,8 +113,18 @@ Main::Session &AbstractSectionWidget::session() const {
SectionWidget::SectionWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
PaintedBackground paintedBackground)
: AbstractSectionWidget(parent, controller, paintedBackground) {
rpl::producer<PeerData*> peerForBackground)
: AbstractSectionWidget(parent, controller, std::move(peerForBackground)) {
}
SectionWidget::SectionWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peerForBackground)
: AbstractSectionWidget(
parent,
controller,
rpl::single(peerForBackground.get())) {
}
void SectionWidget::setGeometryWithTopMoved(
@ -101,6 +185,7 @@ QPixmap SectionWidget::grabForShowAnimation(
void SectionWidget::PaintBackground(
not_null<Window::SessionController*> controller,
not_null<Window::Theme::ChatTheme*> theme,
not_null<QWidget*> widget,
QRect clip) {
Painter p(widget);
@ -113,8 +198,8 @@ void SectionWidget::PaintBackground(
const auto gradient = background->gradientForFill();
const auto fill = QSize(widget->width(), controller->content()->height());
auto fromy = controller->content()->backgroundFromY();
auto state = controller->backgroundState(fill);
const auto paintCache = [&](const CachedBackground &cache) {
auto state = theme->backgroundState(fill);
const auto paintCache = [&](const Theme::CachedBackground &cache) {
const auto to = QRect(
QPoint(cache.x, fromy + cache.y),
cache.pixmap.size() / cIntRetinaFactor());

View File

@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h"
#include "window/window_section_common.h"
class PeerData;
namespace Main {
class Session;
} // namespace Main
@ -22,6 +24,10 @@ namespace Ui {
class LayerWidget;
} // namespace Ui
namespace Window::Theme {
class ChatTheme;
} // namespace Window::Theme
namespace Window {
class SessionController;
@ -40,14 +46,10 @@ class AbstractSectionWidget
, public Media::Player::FloatSectionDelegate
, protected base::Subscriber {
public:
enum class PaintedBackground {
Section,
Custom,
};
AbstractSectionWidget(
QWidget *parent,
not_null<SessionController*> controller,
PaintedBackground paintedBackground);
rpl::producer<PeerData*> peerForBackground);
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<SessionController*> controller() const {
@ -87,7 +89,11 @@ public:
SectionWidget(
QWidget *parent,
not_null<SessionController*> controller,
PaintedBackground paintedBackground);
rpl::producer<PeerData*> peerForBackground = nullptr);
SectionWidget(
QWidget *parent,
not_null<SessionController*> controller,
not_null<PeerData*> peerForBackground);
virtual Dialogs::RowDescriptor activeChat() const {
return {};
@ -158,6 +164,7 @@ public:
static void PaintBackground(
not_null<SessionController*> controller,
not_null<Window::Theme::ChatTheme*> theme,
not_null<QWidget*> widget,
QRect clip);

View File

@ -0,0 +1,390 @@
/*
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 "window/themes/window_chat_theme.h"
#include "window/themes/window_theme.h"
#include "ui/image/image_prepare.h"
#include "ui/ui_utility.h"
#include "ui/chat/message_bubble.h"
#include "history/view/history_view_element.h"
#include <QtGui/QGuiApplication>
namespace Window::Theme {
namespace {
constexpr auto kMaxChatEntryHistorySize = 50;
constexpr auto kCacheBackgroundTimeout = 3 * crl::time(1000);
constexpr auto kCacheBackgroundFastTimeout = crl::time(200);
constexpr auto kBackgroundFadeDuration = crl::time(200);
[[nodiscard]] CacheBackgroundResult CacheBackground(
const CacheBackgroundRequest &request) {
const auto gradient = request.gradient.isNull()
? QImage()
: request.recreateGradient
? Images::GenerateGradient(
request.gradient.size(),
request.gradientColors,
request.gradientRotation)
: request.gradient;
if (request.isPattern || request.tile || request.prepared.isNull()) {
auto result = gradient.isNull()
? QImage(
request.area * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied)
: gradient.scaled(
request.area * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
result.setDevicePixelRatio(cRetinaFactor());
if (!request.prepared.isNull()) {
QPainter p(&result);
if (!gradient.isNull()) {
if (request.patternOpacity >= 0.) {
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(request.patternOpacity);
} else {
p.setCompositionMode(
QPainter::CompositionMode_DestinationIn);
}
}
const auto tiled = request.isPattern
? request.prepared.scaled(
request.area.height() * cIntRetinaFactor(),
request.area.height() * cIntRetinaFactor(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation)
: request.preparedForTiled;
const auto w = tiled.width() / cRetinaFactor();
const auto h = tiled.height() / cRetinaFactor();
const auto cx = qCeil(request.area.width() / w);
const auto cy = qCeil(request.area.height() / h);
const auto rows = cy;
const auto cols = request.isPattern ? (((cx / 2) * 2) + 1) : cx;
const auto xshift = request.isPattern
? (request.area.width() - cols * w) / 2
: 0;
for (auto y = 0; y != rows; ++y) {
for (auto x = 0; x != cols; ++x) {
p.drawImage(QPointF(xshift + x * w, y * h), tiled);
}
}
if (!gradient.isNull()
&& request.patternOpacity < 0.
&& request.patternOpacity > -1.) {
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setOpacity(1. + request.patternOpacity);
p.fillRect(QRect(QPoint(), request.area), Qt::black);
}
}
return {
.image = std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied),
.gradient = gradient,
.area = request.area,
};
} else {
const auto rects = ComputeBackgroundRects(
request.area,
request.prepared.size());
auto result = request.prepared.copy(rects.from).scaled(
rects.to.width() * cIntRetinaFactor(),
rects.to.height() * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
result.setDevicePixelRatio(cRetinaFactor());
return {
.image = std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied),
.gradient = gradient,
.area = request.area,
.x = rects.to.x(),
.y = rects.to.y(),
};
}
}
} // namespace
bool operator==(
const CacheBackgroundRequest &a,
const CacheBackgroundRequest &b) {
return (a.prepared.cacheKey() == b.prepared.cacheKey())
&& (a.area == b.area)
&& (a.gradientRotation == b.gradientRotation)
&& (a.tile == b.tile)
&& (a.recreateGradient == b.recreateGradient)
&& (a.gradient.cacheKey() == b.gradient.cacheKey())
&& (a.gradientProgress == b.gradientProgress)
&& (a.patternOpacity == b.patternOpacity);
}
bool operator!=(
const CacheBackgroundRequest &a,
const CacheBackgroundRequest &b) {
return !(a == b);
}
CachedBackground::CachedBackground(CacheBackgroundResult &&result)
: pixmap(Ui::PixmapFromImage(std::move(result.image)))
, area(result.area)
, x(result.x)
, y(result.y) {
}
ChatTheme::ChatTheme() {
Background()->updates(
) | rpl::start_with_next([=](const BackgroundUpdate &update) {
if (update.type == BackgroundUpdate::Type::New
|| update.type == BackgroundUpdate::Type::Changed) {
clearCachedBackground();
}
}, _lifetime);
}
// Runs from background thread.
ChatTheme::ChatTheme(const Data::CloudTheme &theme)
: _id(theme.id)
, _palette(std::make_unique<style::palette>()) {
}
uint64 ChatTheme::key() const {
return _id;
}
void ChatTheme::setBubblesBackground(QImage image) {
_bubblesBackgroundPrepared = std::move(image);
if (!_bubblesBackground.area.isEmpty()) {
_bubblesBackground = CacheBackground({
.prepared = _bubblesBackgroundPrepared,
.area = _bubblesBackground.area,
});
}
if (!_bubblesBackgroundPattern) {
_bubblesBackgroundPattern = Ui::PrepareBubblePattern();
}
_bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
_repaintBackgroundRequests.fire({});
}
HistoryView::PaintContext ChatTheme::preparePaintContext(
QRect viewport,
QRect clip) {
_bubblesBackground.area = viewport.size();
//if (!_bubblesBackgroundPrepared.isNull()
// && _bubblesBackground.area != viewport.size()
// && !viewport.isEmpty()) {
// // #TODO bubbles delayed caching
// _bubblesBackground = CacheBackground({
// .prepared = _bubblesBackgroundPrepared,
// .area = viewport.size(),
// });
// _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
//}
return {
.st = _palette ? _palette.get() : style::main_palette::get(),
.bubblesPattern = _bubblesBackgroundPattern.get(),
.viewport = viewport,
.clip = clip,
.now = crl::now(),
};
}
const BackgroundState &ChatTheme::backgroundState(QSize area) {
if (!_cacheBackgroundTimer) {
_cacheBackgroundTimer.emplace([=] { cacheBackground(); });
}
_backgroundState.shown = _backgroundFade.value(1.);
if (_backgroundState.now.pixmap.isNull()
&& !Background()->gradientForFill().isNull()) {
// We don't support direct painting of patterned gradients.
// So we need to sync-generate cache image here.
setCachedBackground(CacheBackground(currentCacheRequest(area)));
_cacheBackgroundTimer->cancel();
} else if (_backgroundState.now.area != area) {
if (_willCacheForArea != area
|| (!_cacheBackgroundTimer->isActive()
&& !_backgroundCachingRequest)) {
_willCacheForArea = area;
_lastAreaChangeTime = crl::now();
_cacheBackgroundTimer->callOnce(kCacheBackgroundFastTimeout);
}
}
generateNextBackgroundRotation();
return _backgroundState;
}
bool ChatTheme::readyForBackgroundRotation() const {
Expects(_cacheBackgroundTimer.has_value());
return !anim::Disabled()
&& !_backgroundFade.animating()
&& !_cacheBackgroundTimer->isActive()
&& !_backgroundState.now.pixmap.isNull();
}
void ChatTheme::generateNextBackgroundRotation() {
if (_backgroundCachingRequest
|| !_backgroundNext.image.isNull()
|| !readyForBackgroundRotation()) {
return;
}
const auto background = Background();
if (background->paper().backgroundColors().size() < 3) {
return;
}
constexpr auto kAddRotation = 315;
const auto request = currentCacheRequest(
_backgroundState.now.area,
kAddRotation);
if (!request) {
return;
}
cacheBackgroundAsync(request, [=](CacheBackgroundResult &&result) {
const auto forRequest = base::take(_backgroundCachingRequest);
if (!readyForBackgroundRotation()) {
return;
}
const auto request = currentCacheRequest(
_backgroundState.now.area,
kAddRotation);
if (forRequest == request) {
_backgroundAddRotation
= (_backgroundAddRotation + kAddRotation) % 360;
_backgroundNext = std::move(result);
}
});
}
auto ChatTheme::currentCacheRequest(QSize area, int addRotation) const
-> CacheBackgroundRequest {
const auto background = Background();
if (background->colorForFill()) {
return {};
}
const auto rotation = background->paper().gradientRotation();
const auto gradient = background->gradientForFill();
return {
.prepared = background->prepared(),
.preparedForTiled = background->preparedForTiled(),
.area = area,
.gradientRotation = (rotation
+ _backgroundAddRotation
+ addRotation) % 360,
.tile = background->tile(),
.isPattern = background->paper().isPattern(),
.recreateGradient = (addRotation != 0),
.gradient = gradient,
.gradientColors = (gradient.isNull()
? std::vector<QColor>()
: background->paper().backgroundColors()),
.gradientProgress = 1.,
.patternOpacity = background->paper().patternOpacity(),
};
}
void ChatTheme::cacheBackground() {
Expects(_cacheBackgroundTimer.has_value());
const auto now = crl::now();
if (now - _lastAreaChangeTime < kCacheBackgroundTimeout
&& QGuiApplication::mouseButtons() != 0) {
_cacheBackgroundTimer->callOnce(kCacheBackgroundFastTimeout);
return;
}
cacheBackgroundNow();
}
void ChatTheme::cacheBackgroundNow() {
if (!_backgroundCachingRequest) {
if (const auto request = currentCacheRequest(_willCacheForArea)) {
cacheBackgroundAsync(request);
}
}
}
void ChatTheme::cacheBackgroundAsync(
const CacheBackgroundRequest &request,
Fn<void(CacheBackgroundResult&&)> done) {
_backgroundCachingRequest = request;
const auto weak = base::make_weak(this);
crl::async([=] {
if (!weak) {
return;
}
crl::on_main(weak, [=, result = CacheBackground(request)]() mutable {
if (done) {
done(std::move(result));
} else if (const auto request = currentCacheRequest(
_willCacheForArea)) {
if (_backgroundCachingRequest != request) {
cacheBackgroundAsync(request);
} else {
_backgroundCachingRequest = {};
setCachedBackground(std::move(result));
}
}
});
});
}
void ChatTheme::setCachedBackground(CacheBackgroundResult &&cached) {
_backgroundNext = {};
const auto background = Background();
if (background->gradientForFill().isNull()
|| _backgroundState.now.pixmap.isNull()
|| anim::Disabled()) {
_backgroundFade.stop();
_backgroundState.shown = 1.;
_backgroundState.now = std::move(cached);
return;
}
// #TODO themes compose several transitions.
_backgroundState.was = std::move(_backgroundState.now);
_backgroundState.now = std::move(cached);
_backgroundState.shown = 0.;
const auto callback = [=] {
if (!_backgroundFade.animating()) {
_backgroundState.was = {};
_backgroundState.shown = 1.;
}
_repaintBackgroundRequests.fire({});
};
_backgroundFade.start(
callback,
0.,
1.,
kBackgroundFadeDuration);
}
void ChatTheme::clearCachedBackground() {
_backgroundState = {};
_backgroundAddRotation = 0;
_backgroundNext = {};
_backgroundFade.stop();
if (_cacheBackgroundTimer) {
_cacheBackgroundTimer->cancel();
}
_repaintBackgroundRequests.fire({});
}
rpl::producer<> ChatTheme::repaintBackgroundRequests() const {
return _repaintBackgroundRequests.events();
}
void ChatTheme::rotateComplexGradientBackground() {
if (!_backgroundFade.animating() && !_backgroundNext.image.isNull()) {
Background()->recacheGradientForFill(
std::move(_backgroundNext.gradient));
setCachedBackground(base::take(_backgroundNext));
}
}
} // namespace Window::Theme

View File

@ -0,0 +1,149 @@
/*
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
*/
#pragma once
#include "ui/effects/animations.h"
#include "base/timer.h"
#include "base/weak_ptr.h"
namespace Data {
struct CloudTheme;
} // namespace Data
namespace HistoryView {
struct PaintContext;
} // namespace HistoryView
namespace Ui {
struct BubblePattern;
} // namespace Ui
namespace Window::Theme {
struct CacheBackgroundRequest {
QImage prepared;
QImage preparedForTiled;
QSize area;
int gradientRotation = 0;
bool tile = false;
bool isPattern = false;
bool recreateGradient = false;
QImage gradient;
std::vector<QColor> gradientColors;
float64 gradientProgress = 1.;
float64 patternOpacity = 1.;
explicit operator bool() const {
return !prepared.isNull() || !gradient.isNull();
}
};
bool operator==(
const CacheBackgroundRequest &a,
const CacheBackgroundRequest &b);
bool operator!=(
const CacheBackgroundRequest &a,
const CacheBackgroundRequest &b);
struct CacheBackgroundResult {
QImage image;
QImage gradient;
QSize area;
int x = 0;
int y = 0;
};
struct CachedBackground {
CachedBackground() = default;
CachedBackground(CacheBackgroundResult &&result);
QPixmap pixmap;
QSize area;
int x = 0;
int y = 0;
};
struct BackgroundState {
CachedBackground was;
CachedBackground now;
float64 shown = 1.;
};
struct ChatThemeBackground {
QImage prepared;
QImage gradientForFill;
std::optional<QColor> colorForFill;
};
struct ChatThemeDescriptor {
Fn<void(style::palette&)> preparePalette;
Fn<ChatThemeBackground()> prepareBackground;
std::vector<QColor> backgroundColors;
};
class ChatTheme final : public base::has_weak_ptr {
public:
ChatTheme();
// Runs from background thread.
ChatTheme(const Data::CloudTheme &theme);
[[nodiscard]] uint64 key() const;
[[nodiscard]] not_null<const style::palette*> palette() const {
return _palette.get();
}
void setBackground(ChatThemeBackground);
void setBubblesBackground(QImage image);
const Ui::BubblePattern *bubblesBackgroundPattern() const {
return _bubblesBackgroundPattern.get();
}
[[nodiscard]] HistoryView::PaintContext preparePaintContext(
QRect viewport,
QRect clip);
[[nodiscard]] const BackgroundState &backgroundState(QSize area);
[[nodiscard]] rpl::producer<> repaintBackgroundRequests() const;
void rotateComplexGradientBackground();
private:
void cacheBackground();
void cacheBackgroundNow();
void cacheBackgroundAsync(
const CacheBackgroundRequest &request,
Fn<void(CacheBackgroundResult&&)> done = nullptr);
void clearCachedBackground();
void setCachedBackground(CacheBackgroundResult &&cached);
[[nodiscard]] CacheBackgroundRequest currentCacheRequest(
QSize area,
int addRotation = 0) const;
[[nodiscard]] bool readyForBackgroundRotation() const;
void generateNextBackgroundRotation();
uint64 _id = 0;
std::unique_ptr<style::palette> _palette;
BackgroundState _backgroundState;
Ui::Animations::Simple _backgroundFade;
CacheBackgroundRequest _backgroundCachingRequest;
CacheBackgroundResult _backgroundNext;
int _backgroundAddRotation = 0;
QSize _willCacheForArea;
crl::time _lastAreaChangeTime = 0;
std::optional<base::Timer> _cacheBackgroundTimer;
CachedBackground _bubblesBackground;
QImage _bubblesBackgroundPrepared;
std::unique_ptr<Ui::BubblePattern> _bubblesBackgroundPattern;
rpl::event_stream<> _repaintBackgroundRequests;
rpl::lifetime _lifetime;
};
} // namespace Window::Theme

View File

@ -868,9 +868,9 @@ void ChatBackground::adjustPaletteUsingColor(QColor color) {
std::optional<QColor> ChatBackground::colorForFill() const {
return !_prepared.isNull()
? imageMonoColor()
: !_gradient.isNull()
: (!_gradient.isNull() || _paper.backgroundColors().empty())
? std::nullopt
: _paper.backgroundColor();
: std::make_optional(_paper.backgroundColors().front());
}
QImage ChatBackground::gradientForFill() const {
@ -1390,6 +1390,21 @@ bool IsNightMode() {
return GlobalBackground ? Background()->nightMode() : false;
}
rpl::producer<bool> IsNightModeValue() {
auto changes = Background()->updates(
) | rpl::filter([=](const BackgroundUpdate &update) {
return update.type == BackgroundUpdate::Type::ApplyingTheme;
}) | rpl::to_empty;
return rpl::single(
rpl::empty_value()
) | rpl::then(
std::move(changes)
) | rpl::map([=] {
return IsNightMode();
}) | rpl::distinct_until_changed();
}
void SetNightModeValue(bool nightMode) {
if (GlobalBackground || nightMode) {
Background()->setNightModeValue(nightMode);

View File

@ -78,6 +78,7 @@ void KeepFromEditor(
QString NightThemePath();
[[nodiscard]] bool IsNightMode();
void SetNightModeValue(bool nightMode);
[[nodiscard]] rpl::producer<bool> IsNightModeValue();
void ToggleNightMode();
void ToggleNightMode(const QString &themePath);
void ToggleNightModeWithConfirmation(

View File

@ -14,11 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "window/main_window.h"
#include "window/window_filters_menu.h"
#include "window/themes/window_chat_theme.h"
#include "info/info_memento.h"
#include "info/info_controller.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_replies_section.h"
#include "media/player/media_player_instance.h"
#include "media/view/media_view_open_common.h"
@ -66,102 +66,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h" // st::boxLabel
#include "styles/style_chat.h" // st::historyMessageRadius
#include <QtGui/QGuiApplication>
namespace Window {
namespace {
constexpr auto kMaxChatEntryHistorySize = 50;
constexpr auto kCacheBackgroundTimeout = 3 * crl::time(1000);
constexpr auto kCacheBackgroundFastTimeout = crl::time(200);
constexpr auto kBackgroundFadeDuration = crl::time(200);
[[nodiscard]] CacheBackgroundResult CacheBackground(
const CacheBackgroundRequest &request) {
const auto gradient = request.gradient.isNull()
? QImage()
: request.recreateGradient
? Images::GenerateGradient(
request.gradient.size(),
request.gradientColors,
request.gradientRotation)
: request.gradient;
if (request.isPattern || request.tile || request.prepared.isNull()) {
auto result = gradient.isNull()
? QImage(
request.area * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied)
: gradient.scaled(
request.area * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
result.setDevicePixelRatio(cRetinaFactor());
if (!request.prepared.isNull()) {
QPainter p(&result);
if (!gradient.isNull()) {
if (request.patternOpacity >= 0.) {
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(request.patternOpacity);
} else {
p.setCompositionMode(
QPainter::CompositionMode_DestinationIn);
}
}
const auto tiled = request.isPattern
? request.prepared.scaled(
request.area.height() * cIntRetinaFactor(),
request.area.height() * cIntRetinaFactor(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation)
: request.preparedForTiled;
const auto w = tiled.width() / cRetinaFactor();
const auto h = tiled.height() / cRetinaFactor();
const auto cx = qCeil(request.area.width() / w);
const auto cy = qCeil(request.area.height() / h);
const auto rows = cy;
const auto cols = request.isPattern ? (((cx / 2) * 2) + 1) : cx;
const auto xshift = request.isPattern
? (request.area.width() - cols * w) / 2
: 0;
for (auto y = 0; y != rows; ++y) {
for (auto x = 0; x != cols; ++x) {
p.drawImage(QPointF(xshift + x * w, y * h), tiled);
}
}
if (!gradient.isNull()
&& request.patternOpacity < 0.
&& request.patternOpacity > -1.) {
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setOpacity(1. + request.patternOpacity);
p.fillRect(QRect(QPoint(), request.area), Qt::black);
}
}
return {
.image = std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied),
.gradient = gradient,
.area = request.area,
};
} else {
const auto rects = Window::Theme::ComputeBackgroundRects(
request.area,
request.prepared.size());
auto result = request.prepared.copy(rects.from).scaled(
rects.to.width() * cIntRetinaFactor(),
rects.to.height() * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
result.setDevicePixelRatio(cRetinaFactor());
return {
.image = std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied),
.gradient = gradient,
.area = request.area,
.x = rects.to.x(),
.y = rects.to.y(),
};
}
}
} // namespace
@ -550,32 +458,6 @@ void SessionNavigation::showPollResults(
showSection(std::make_shared<Info::Memento>(poll, contextId), params);
}
bool operator==(
const CacheBackgroundRequest &a,
const CacheBackgroundRequest &b) {
return (a.prepared.cacheKey() == b.prepared.cacheKey())
&& (a.area == b.area)
&& (a.gradientRotation == b.gradientRotation)
&& (a.tile == b.tile)
&& (a.recreateGradient == b.recreateGradient)
&& (a.gradient.cacheKey() == b.gradient.cacheKey())
&& (a.gradientProgress == b.gradientProgress)
&& (a.patternOpacity == b.patternOpacity);
}
bool operator!=(
const CacheBackgroundRequest &a,
const CacheBackgroundRequest &b) {
return !(a == b);
}
CachedBackground::CachedBackground(CacheBackgroundResult &&result)
: pixmap(Ui::PixmapFromImage(std::move(result.image)))
, area(result.area)
, x(result.x)
, y(result.y) {
}
SessionController::SessionController(
not_null<Main::Session*> session,
not_null<Controller*> window)
@ -586,7 +468,7 @@ SessionController::SessionController(
_window->widget(),
this))
, _invitePeekTimer([=] { checkInvitePeek(); })
, _cacheBackgroundTimer([=] { cacheBackground(); }) {
, _defaultChatTheme(std::make_shared<Theme::ChatTheme>()) {
init();
if (Media::Player::instance()->pauseGifByRoundVideo()) {
@ -629,15 +511,6 @@ SessionController::SessionController(
}));
}, _lifetime);
using Update = Window::Theme::BackgroundUpdate;
Window::Theme::Background()->updates(
) | rpl::start_with_next([=](const Update &update) {
if (update.type == Update::Type::New
|| update.type == Update::Type::Changed) {
clearCachedBackground();
}
}, lifetime());
session->addWindow(this);
}
@ -1435,23 +1308,50 @@ void SessionController::openDocument(
session().data().message(contextId));
}
void SessionController::setBubblesBackground(QImage image) {
_bubblesBackgroundPrepared = std::move(image);
if (!_bubblesBackground.area.isEmpty()) {
_bubblesBackground = CacheBackground({
.prepared = _bubblesBackgroundPrepared,
.area = _bubblesBackground.area,
});
auto SessionController::cachedChatThemeValue(
const Data::CloudTheme &data)
-> rpl::producer<std::shared_ptr<Theme::ChatTheme>> {
const auto key = data.id;
if (!key || !data.paper || data.paper->backgroundColors().empty()) {
return rpl::single(_defaultChatTheme);
}
if (!_bubblesBackgroundPattern) {
_bubblesBackgroundPattern = Ui::PrepareBubblePattern();
const auto i = _customChatThemes.find(key);
if (i != end(_customChatThemes) && i->second) {
return rpl::single(i->second);
}
_bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
_repaintBackgroundRequests.fire({});
if (i == end(_customChatThemes)) {
cacheChatTheme(data);
}
using namespace rpl::mappers;
return rpl::single(
_defaultChatTheme
) | rpl::then(_cachedThemesStream.events(
) | rpl::filter([=](const std::shared_ptr<Theme::ChatTheme> &theme) {
return (theme->key() == key);
}) | rpl::take(1));
}
HistoryView::PaintContext SessionController::bubblesContext(
BubblesContextArgs &&args) {
void SessionController::cacheChatTheme(const Data::CloudTheme &data) {
Expects(data.id != 0);
const auto key = data.id;
if (data.paper) {
const auto document = data.paper->document();
}
crl::async([this, data, weak = base::make_weak(this)] {
crl::on_main(weak, [
this,
result = std::make_shared<Theme::ChatTheme>(data)
]() mutable {
_customChatThemes.emplace(result->key(), result);
_cachedThemesStream.fire(std::move(result));
});
});
}
HistoryView::PaintContext SessionController::preparePaintContext(
PaintContextArgs &&args) {
const auto visibleAreaTopLocal = content()->mapFromGlobal(
QPoint(0, args.visibleAreaTopGlobal)).y();
const auto viewport = QRect(
@ -1459,205 +1359,7 @@ HistoryView::PaintContext SessionController::bubblesContext(
args.visibleAreaTop - visibleAreaTopLocal,
args.visibleAreaWidth,
content()->height());
_bubblesBackground.area = viewport.size();
//if (!_bubblesBackgroundPrepared.isNull()
// && _bubblesBackground.area != viewport.size()
// && !viewport.isEmpty()) {
// // #TODO bubbles delayed caching
// _bubblesBackground = CacheBackground({
// .prepared = _bubblesBackgroundPrepared,
// .area = viewport.size(),
// });
// _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
//}
return {
.bubblesPattern = _bubblesBackgroundPattern.get(),
.viewport = viewport,
.clip = args.clip,
.now = crl::now(),
};
}
const BackgroundState &SessionController::backgroundState(QSize area) {
_backgroundState.shown = _backgroundFade.value(1.);
if (_backgroundState.now.pixmap.isNull()
&& !Window::Theme::Background()->gradientForFill().isNull()) {
// We don't support direct painting of patterned gradients.
// So we need to sync-generate cache image here.
setCachedBackground(CacheBackground(currentCacheRequest(area)));
_cacheBackgroundTimer.cancel();
} else if (_backgroundState.now.area != area) {
if (_willCacheForArea != area
|| (!_cacheBackgroundTimer.isActive()
&& !_backgroundCachingRequest)) {
_willCacheForArea = area;
_lastAreaChangeTime = crl::now();
_cacheBackgroundTimer.callOnce(kCacheBackgroundFastTimeout);
}
}
generateNextBackgroundRotation();
return _backgroundState;
}
bool SessionController::readyForBackgroundRotation() const {
return !anim::Disabled()
&& !_backgroundFade.animating()
&& !_cacheBackgroundTimer.isActive()
&& !_backgroundState.now.pixmap.isNull();
}
void SessionController::generateNextBackgroundRotation() {
if (_backgroundCachingRequest
|| !_backgroundNext.image.isNull()
|| !readyForBackgroundRotation()) {
return;
}
const auto background = Window::Theme::Background();
if (background->paper().backgroundColors().size() < 3) {
return;
}
constexpr auto kAddRotation = 315;
const auto request = currentCacheRequest(
_backgroundState.now.area,
kAddRotation);
if (!request) {
return;
}
cacheBackgroundAsync(request, [=](CacheBackgroundResult &&result) {
const auto forRequest = base::take(_backgroundCachingRequest);
if (!readyForBackgroundRotation()) {
return;
}
const auto request = currentCacheRequest(
_backgroundState.now.area,
kAddRotation);
if (forRequest == request) {
_backgroundAddRotation
= (_backgroundAddRotation + kAddRotation) % 360;
_backgroundNext = std::move(result);
}
});
}
auto SessionController::currentCacheRequest(QSize area, int addRotation) const
-> CacheBackgroundRequest {
const auto background = Window::Theme::Background();
if (background->colorForFill()) {
return {};
}
const auto rotation = background->paper().gradientRotation();
const auto gradient = background->gradientForFill();
return {
.prepared = background->prepared(),
.preparedForTiled = background->preparedForTiled(),
.area = area,
.gradientRotation = (rotation
+ _backgroundAddRotation
+ addRotation) % 360,
.tile = background->tile(),
.isPattern = background->paper().isPattern(),
.recreateGradient = (addRotation != 0),
.gradient = gradient,
.gradientColors = (gradient.isNull()
? std::vector<QColor>()
: background->paper().backgroundColors()),
.gradientProgress = 1.,
.patternOpacity = background->paper().patternOpacity(),
};
}
void SessionController::cacheBackground() {
const auto now = crl::now();
if (now - _lastAreaChangeTime < kCacheBackgroundTimeout
&& QGuiApplication::mouseButtons() != 0) {
_cacheBackgroundTimer.callOnce(kCacheBackgroundFastTimeout);
return;
}
cacheBackgroundNow();
}
void SessionController::cacheBackgroundNow() {
if (!_backgroundCachingRequest) {
if (const auto request = currentCacheRequest(_willCacheForArea)) {
cacheBackgroundAsync(request);
}
}
}
void SessionController::cacheBackgroundAsync(
const CacheBackgroundRequest &request,
Fn<void(CacheBackgroundResult&&)> done) {
_backgroundCachingRequest = request;
const auto weak = base::make_weak(this);
crl::async([=] {
if (!weak) {
return;
}
crl::on_main(weak, [=, result = CacheBackground(request)]() mutable {
if (done) {
done(std::move(result));
} else if (const auto request = currentCacheRequest(
_willCacheForArea)) {
if (_backgroundCachingRequest != request) {
cacheBackgroundAsync(request);
} else {
_backgroundCachingRequest = {};
setCachedBackground(std::move(result));
}
}
});
});
}
void SessionController::setCachedBackground(CacheBackgroundResult &&cached) {
_backgroundNext = {};
const auto background = Window::Theme::Background();
if (background->gradientForFill().isNull()
|| _backgroundState.now.pixmap.isNull()
|| anim::Disabled()) {
_backgroundFade.stop();
_backgroundState.shown = 1.;
_backgroundState.now = std::move(cached);
return;
}
// #TODO themes compose several transitions.
_backgroundState.was = std::move(_backgroundState.now);
_backgroundState.now = std::move(cached);
_backgroundState.shown = 0.;
const auto callback = [=] {
if (!_backgroundFade.animating()) {
_backgroundState.was = {};
_backgroundState.shown = 1.;
}
_repaintBackgroundRequests.fire({});
};
_backgroundFade.start(
callback,
0.,
1.,
kBackgroundFadeDuration);
}
void SessionController::clearCachedBackground() {
_backgroundState = {};
_backgroundAddRotation = 0;
_backgroundNext = {};
_backgroundFade.stop();
_cacheBackgroundTimer.cancel();
_repaintBackgroundRequests.fire({});
}
rpl::producer<> SessionController::repaintBackgroundRequests() const {
return _repaintBackgroundRequests.events();
}
void SessionController::rotateComplexGradientBackground() {
if (!_backgroundFade.animating() && !_backgroundNext.image.isNull()) {
Window::Theme::Background()->recacheGradientForFill(
std::move(_backgroundNext.gradient));
setCachedBackground(base::take(_backgroundNext));
}
return args.theme->preparePaintContext(viewport, args.clip);
}
SessionController::~SessionController() {

View File

@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/weak_ptr.h"
#include "base/timer.h"
#include "dialogs/dialogs_key.h"
#include "ui/effects/animations.h"
#include "ui/layers/layer_widget.h"
#include "window/window_adaptive.h"
@ -50,9 +49,16 @@ class FormController;
namespace Ui {
class LayerWidget;
enum class ReportReason;
struct BubblePattern;
} // namespace Ui
namespace Window::Theme {
class ChatTheme;
} // namespace Window::Theme
namespace Data {
struct CloudTheme;
} // namespace Data
namespace Window {
class MainWindow;
@ -60,55 +66,6 @@ class SectionMemento;
class Controller;
class FiltersMenu;
struct CacheBackgroundRequest {
QImage prepared;
QImage preparedForTiled;
QSize area;
int gradientRotation = 0;
bool tile = false;
bool isPattern = false;
bool recreateGradient = false;
QImage gradient;
std::vector<QColor> gradientColors;
float64 gradientProgress = 1.;
float64 patternOpacity = 1.;
explicit operator bool() const {
return !prepared.isNull() || !gradient.isNull();
}
};
bool operator==(
const CacheBackgroundRequest &a,
const CacheBackgroundRequest &b);
bool operator!=(
const CacheBackgroundRequest &a,
const CacheBackgroundRequest &b);
struct CacheBackgroundResult {
QImage image;
QImage gradient;
QSize area;
int x = 0;
int y = 0;
};
struct CachedBackground {
CachedBackground() = default;
CachedBackground(CacheBackgroundResult &&result);
QPixmap pixmap;
QSize area;
int x = 0;
int y = 0;
};
struct BackgroundState {
CachedBackground was;
CachedBackground now;
float64 shown = 1.;
};
enum class GifPauseReason {
Any = 0,
InlineResults = (1 << 0),
@ -445,22 +402,23 @@ public:
void toggleFiltersMenu(bool enabled);
[[nodiscard]] rpl::producer<> filtersMenuChanged() const;
void setBubblesBackground(QImage image);
const Ui::BubblePattern *bubblesBackgroundPattern() const {
return _bubblesBackgroundPattern.get();
[[nodiscard]] auto defaultChatTheme() const
-> const std::shared_ptr<Theme::ChatTheme> & {
return _defaultChatTheme;
}
[[nodiscard]] auto cachedChatThemeValue(
const Data::CloudTheme &data)
-> rpl::producer<std::shared_ptr<Theme::ChatTheme>>;
struct BubblesContextArgs {
struct PaintContextArgs {
not_null<Theme::ChatTheme*> theme;
int visibleAreaTop = 0;
int visibleAreaTopGlobal = 0;
int visibleAreaWidth = 0;
QRect clip;
};
[[nodiscard]] HistoryView::PaintContext bubblesContext(
BubblesContextArgs &&args);
[[nodiscard]] const BackgroundState &backgroundState(QSize area);
[[nodiscard]] rpl::producer<> repaintBackgroundRequests() const;
void rotateComplexGradientBackground();
[[nodiscard]] HistoryView::PaintContext preparePaintContext(
PaintContextArgs &&args);
rpl::lifetime &lifetime() {
return _lifetime;
@ -491,18 +449,7 @@ private:
void checkInvitePeek();
void cacheBackground();
void cacheBackgroundNow();
void cacheBackgroundAsync(
const CacheBackgroundRequest &request,
Fn<void(CacheBackgroundResult&&)> done = nullptr);
void clearCachedBackground();
void setCachedBackground(CacheBackgroundResult &&cached);
[[nodiscard]] CacheBackgroundRequest currentCacheRequest(
QSize area,
int addRotation = 0) const;
[[nodiscard]] bool readyForBackgroundRotation() const;
void generateNextBackgroundRotation();
void cacheChatTheme(const Data::CloudTheme &data);
const not_null<Controller*> _window;
@ -531,19 +478,11 @@ private:
rpl::event_stream<> _filtersMenuChanged;
BackgroundState _backgroundState;
Ui::Animations::Simple _backgroundFade;
CacheBackgroundRequest _backgroundCachingRequest;
CacheBackgroundResult _backgroundNext;
int _backgroundAddRotation = 0;
QSize _willCacheForArea;
crl::time _lastAreaChangeTime = 0;
base::Timer _cacheBackgroundTimer;
CachedBackground _bubblesBackground;
QImage _bubblesBackgroundPrepared;
std::unique_ptr<Ui::BubblePattern> _bubblesBackgroundPattern;
rpl::event_stream<> _repaintBackgroundRequests;
std::shared_ptr<Theme::ChatTheme> _defaultChatTheme;
base::flat_map<
uint64,
std::shared_ptr<Theme::ChatTheme>> _customChatThemes;
rpl::event_stream<std::shared_ptr<Theme::ChatTheme>> _cachedThemesStream;
rpl::lifetime _lifetime;

@ -1 +1 @@
Subproject commit 248614b49cd7d5aff69d75a737f2e35b79fbb119
Subproject commit 13117d03e5683af00f898a330aa319fd17efe8f7

@ -1 +1 @@
Subproject commit 5938cb012fd78f5128fdefa3794fbae6c8f1dcf6
Subproject commit 15ffd051d605be310051645412c54c2b9f87ff01