diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 936f3002b..5eab26160 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index a3904d74f..65e99ddd8 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -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); } } diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index a142eeb02..4c4ff770f 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -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(), diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp index a5c6833f7..3759697e3 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp @@ -26,7 +26,7 @@ object_ptr TabbedMemento::createWidget( TabbedSection::TabbedSection( QWidget *parent, not_null controller) -: Window::SectionWidget(parent, controller, PaintedBackground::Custom) +: Window::SectionWidget(parent, controller) , _selector(controller->tabbedSelector()) { _selector->setParent(this); _selector->setRoundRadius(0); diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index bede05e23..b94f7f7f5 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -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; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_cloud_themes.cpp b/Telegram/SourceFiles/data/data_cloud_themes.cpp index 15520241d..994775fd7 100644 --- a/Telegram/SourceFiles/data/data_cloud_themes.cpp +++ b/Telegram/SourceFiles/data/data_cloud_themes.cpp @@ -37,10 +37,9 @@ CloudTheme CloudTheme::Parse( const auto document = data.vdocument(); const auto paper = [&]() -> std::optional { 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 { 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()), + .basedOnDark = parseSettings && basedOnDark(), }; } @@ -368,6 +383,24 @@ std::optional CloudThemes::themeForEmoji( return (i != end(_chatThemes)) ? std::make_optional(*i) : std::nullopt; } +rpl::producer> CloudThemes::themeForEmojiValue( + const QString &emoji) { + if (emoji.isEmpty()) { + return rpl::single>(std::nullopt); + } else if (auto result = themeForEmoji(emoji)) { + return rpl::single(std::move(result)); + } + refreshChatThemes(); + return rpl::single>( + std::nullopt + ) | rpl::then(chatThemesUpdated( + ) | rpl::map([=] { + return themeForEmoji(emoji); + }) | rpl::filter([](const std::optional &theme) { + return theme.has_value(); + }) | rpl::take(1)); +} + void CloudThemes::parseChatThemes(const QVector &list) { _chatThemes.clear(); _chatThemes.reserve(list.size()); diff --git a/Telegram/SourceFiles/data/data_cloud_themes.h b/Telegram/SourceFiles/data/data_cloud_themes.h index 59a994de8..e6e6ae7cb 100644 --- a/Telegram/SourceFiles/data/data_cloud_themes.h +++ b/Telegram/SourceFiles/data/data_cloud_themes.h @@ -32,9 +32,11 @@ struct CloudTheme { DocumentId documentId = 0; UserId createdBy = 0; int usersCount = 0; + std::optional paper; std::optional accentColor; std::vector outgoingMessagesColors; + bool basedOnDark = false; static CloudTheme Parse( not_null session, @@ -69,6 +71,8 @@ public: [[nodiscard]] rpl::producer<> chatThemesUpdated() const; [[nodiscard]] std::optional themeForEmoji( const QString &emoji) const; + [[nodiscard]] rpl::producer> themeForEmojiValue( + const QString &emoji); void applyUpdate(const MTPTheme &theme); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 390703388..07ec4c5f6 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -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 peer, MsgId messageId) { Storage::SharedMediaType::Pinned, messageId, { messageId, ServerMaxMsgId })); - peer->setHasPinnedMessages(true); + peer->owner().history(peer)->setHasPinnedMessages(true); } FullMsgId ResolveTopPinnedId( diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 60dc63807..59fd57420 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -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; diff --git a/Telegram/SourceFiles/data/data_wall_paper.cpp b/Telegram/SourceFiles/data/data_wall_paper.cpp index 73ef7c659..55082b0d2 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.cpp +++ b/Telegram/SourceFiles/data/data_wall_paper.cpp @@ -181,12 +181,6 @@ WallPaperId WallPaper::id() const { return _id; } -std::optional WallPaper::backgroundColor() const { - return _backgroundColors.empty() - ? std::nullopt - : std::make_optional(_backgroundColors.front()); -} - const std::vector WallPaper::backgroundColors() const { return _backgroundColors; } diff --git a/Telegram/SourceFiles/data/data_wall_paper.h b/Telegram/SourceFiles/data/data_wall_paper.h index 98dfe5bba..795b88fdc 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.h +++ b/Telegram/SourceFiles/data/data_wall_paper.h @@ -35,7 +35,6 @@ public: void setLocalImageAsThumbnail(std::shared_ptr image); [[nodiscard]] WallPaperId id() const; - [[nodiscard]] std::optional backgroundColor() const; [[nodiscard]] const std::vector backgroundColors() const; [[nodiscard]] DocumentData *document() const; [[nodiscard]] Image *localThumbnail() const; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index d6342a486..853971dbc 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -164,7 +164,7 @@ void Widget::BottomButton::paintEvent(QPaintEvent *e) { Widget::Widget( QWidget *parent, not_null controller) -: Window::AbstractSectionWidget(parent, controller, PaintedBackground::Custom) +: Window::AbstractSectionWidget(parent, controller, nullptr) , _api(&controller->session().mtp()) , _searchControls(this) , _mainMenuToggle(_searchControls, st::dialogsMenuToggle) diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 28161ff97..6940ca7a5 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -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), diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index eb23ebd45..2dca653a7 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -275,7 +275,7 @@ Widget::Widget( QWidget *parent, not_null controller, not_null channel) -: Window::SectionWidget(parent, controller, PaintedBackground::Section) +: Window::SectionWidget(parent, controller, rpl::single(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(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() { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 49a842daa..003cb904c 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -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 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 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) diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index c86dfd921..9f1939f68 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -412,6 +412,9 @@ public: void setInboxReadTill(MsgId upTo); std::optional countStillUnreadLocal(MsgId readTillId) const; + [[nodiscard]] bool hasPinnedMessages() const; + void setHasPinnedMessages(bool has); + // Still public data. std::deque> 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 diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c1e719b09..a7461a572 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -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(), diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index dcbac45dc..9d37710dc 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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); } } } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 9fa941299..32aedd932 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -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 ActivePeerValue( + not_null 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 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 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(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 5d332226d..6e848bd84 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -193,6 +193,7 @@ struct DateBadge : public RuntimeComponent { }; struct PaintContext { + not_null st; const Ui::BubblePattern *bubblesPattern = nullptr; QRect viewport; QRect clip; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 51a3ec1de..c3f7b487a 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -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), diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index d58b93ed1..99e21ed34 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -90,7 +90,7 @@ PinnedWidget::PinnedWidget( QWidget *parent, not_null controller, not_null 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() { diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp index 8e31bac8d..874a951bc 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp @@ -31,19 +31,21 @@ PinnedTracker::PinnedTracker(not_null history) : _history(history->migrateToOrMe()) , _migratedPeer(_history->peer->migrateFrom()) { using namespace rpl::mappers; - const auto has = [&](PeerData *peer) -> rpl::producer { + const auto has = [&](History *history) -> rpl::producer { 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); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 1e0ed722d..fd3565701 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -146,7 +146,7 @@ RepliesWidget::RepliesWidget( not_null controller, not_null 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() { diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 296ae3d17..bed6a4ca8 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -90,7 +90,7 @@ ScheduledWidget::ScheduledWidget( QWidget *parent, not_null controller, not_null 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() { diff --git a/Telegram/SourceFiles/info/info_section_widget.cpp b/Telegram/SourceFiles/info/info_section_widget.cpp index e8b405c7f..188432308 100644 --- a/Telegram/SourceFiles/info/info_section_widget.cpp +++ b/Telegram/SourceFiles/info/info_section_widget.cpp @@ -24,7 +24,7 @@ SectionWidget::SectionWidget( not_null window, Wrap wrap, not_null 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, Wrap wrap, not_null memento) -: Window::SectionWidget(parent, window, PaintedBackground::Custom) +: Window::SectionWidget(parent, window) , _content(memento->takeContent(this, wrap)) { init(); } diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 09e4f126e..2b6079c7b 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -59,7 +59,7 @@ WrapWidget::WrapWidget( not_null window, Wrap wrap, not_null memento) -: SectionWidget(parent, window, PaintedBackground::Custom) +: SectionWidget(parent, window, rpl::producer()) , _wrap(wrap) , _controller(createController(window, memento->content())) , _topShadow(this) { diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index d7edc9442..fbac6d87f 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -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()); diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 1f64d7589..881c48289 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -706,11 +706,17 @@ object_ptr 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(), diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index 513f098ec..46b157146 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -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(), diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 2ac6cea5f..0c26c857d 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -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 namespace Window { +namespace { + +[[nodiscard]] rpl::producer PeerThemeEmojiValue( + not_null peer) { + return peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::ChatThemeEmoji + ) | rpl::map([=] { + return peer->themeEmoji(); + }); +} + +[[nodiscard]] auto MaybeChatThemeDataValueFromPeer( + not_null peer) +-> rpl::producer> { + return PeerThemeEmojiValue( + peer + ) | rpl::map([=](const QString &emoji) + -> rpl::producer> { + return peer->owner().cloudThemes().themeForEmojiValue(emoji); + }) | rpl::flatten_latest(); +} + +[[nodiscard]] auto MaybeCloudThemeValueFromPeer( + not_null peer) +-> rpl::producer> { + return rpl::combine( + MaybeChatThemeDataValueFromPeer(peer), + Theme::IsNightModeValue() + ) | rpl::map([](std::optional 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 controller, + not_null peer) +-> rpl::producer> { + return MaybeCloudThemeValueFromPeer( + peer + ) | rpl::map([=](std::optional theme) + -> rpl::producer> { + 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 controller, - PaintedBackground paintedBackground) + rpl::producer 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) { + 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 controller, - PaintedBackground paintedBackground) -: AbstractSectionWidget(parent, controller, paintedBackground) { + rpl::producer peerForBackground) +: AbstractSectionWidget(parent, controller, std::move(peerForBackground)) { +} + +SectionWidget::SectionWidget( + QWidget *parent, + not_null controller, + not_null peerForBackground) +: AbstractSectionWidget( + parent, + controller, + rpl::single(peerForBackground.get())) { } void SectionWidget::setGeometryWithTopMoved( @@ -101,6 +185,7 @@ QPixmap SectionWidget::grabForShowAnimation( void SectionWidget::PaintBackground( not_null controller, + not_null theme, not_null 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()); diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index 68f1036c4..f24dfb5f3 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -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 controller, - PaintedBackground paintedBackground); + rpl::producer peerForBackground); [[nodiscard]] Main::Session &session() const; [[nodiscard]] not_null controller() const { @@ -87,7 +89,11 @@ public: SectionWidget( QWidget *parent, not_null controller, - PaintedBackground paintedBackground); + rpl::producer peerForBackground = nullptr); + SectionWidget( + QWidget *parent, + not_null controller, + not_null peerForBackground); virtual Dialogs::RowDescriptor activeChat() const { return {}; @@ -158,6 +164,7 @@ public: static void PaintBackground( not_null controller, + not_null theme, not_null widget, QRect clip); diff --git a/Telegram/SourceFiles/window/themes/window_chat_theme.cpp b/Telegram/SourceFiles/window/themes/window_chat_theme.cpp new file mode 100644 index 000000000..92ac29ef4 --- /dev/null +++ b/Telegram/SourceFiles/window/themes/window_chat_theme.cpp @@ -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 + +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()) { +} + +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() + : 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 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 diff --git a/Telegram/SourceFiles/window/themes/window_chat_theme.h b/Telegram/SourceFiles/window/themes/window_chat_theme.h new file mode 100644 index 000000000..b427d6626 --- /dev/null +++ b/Telegram/SourceFiles/window/themes/window_chat_theme.h @@ -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 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 colorForFill; +}; + +struct ChatThemeDescriptor { + Fn preparePalette; + Fn prepareBackground; + std::vector 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 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 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 _palette; + BackgroundState _backgroundState; + Ui::Animations::Simple _backgroundFade; + CacheBackgroundRequest _backgroundCachingRequest; + CacheBackgroundResult _backgroundNext; + int _backgroundAddRotation = 0; + QSize _willCacheForArea; + crl::time _lastAreaChangeTime = 0; + std::optional _cacheBackgroundTimer; + CachedBackground _bubblesBackground; + QImage _bubblesBackgroundPrepared; + std::unique_ptr _bubblesBackgroundPattern; + + rpl::event_stream<> _repaintBackgroundRequests; + + rpl::lifetime _lifetime; + +}; + +} // namespace Window::Theme diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index b95f18966..d1877a84b 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -868,9 +868,9 @@ void ChatBackground::adjustPaletteUsingColor(QColor color) { std::optional 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 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); diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index 23b2d140a..895ecb27b 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -78,6 +78,7 @@ void KeepFromEditor( QString NightThemePath(); [[nodiscard]] bool IsNightMode(); void SetNightModeValue(bool nightMode); +[[nodiscard]] rpl::producer IsNightModeValue(); void ToggleNightMode(); void ToggleNightMode(const QString &themePath); void ToggleNightModeWithConfirmation( diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index a6773c5ff..d7ffba729 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -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 - 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(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 session, not_null window) @@ -586,7 +468,7 @@ SessionController::SessionController( _window->widget(), this)) , _invitePeekTimer([=] { checkInvitePeek(); }) -, _cacheBackgroundTimer([=] { cacheBackground(); }) { +, _defaultChatTheme(std::make_shared()) { 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> { + 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) { + 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(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() - : 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 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() { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index a806f31f6..b5ae93b9e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -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 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 & { + return _defaultChatTheme; } + [[nodiscard]] auto cachedChatThemeValue( + const Data::CloudTheme &data) + -> rpl::producer>; - struct BubblesContextArgs { + struct PaintContextArgs { + not_null 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 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 _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 _bubblesBackgroundPattern; - - rpl::event_stream<> _repaintBackgroundRequests; + std::shared_ptr _defaultChatTheme; + base::flat_map< + uint64, + std::shared_ptr> _customChatThemes; + rpl::event_stream> _cachedThemesStream; rpl::lifetime _lifetime; diff --git a/Telegram/codegen b/Telegram/codegen index 248614b49..13117d03e 160000 --- a/Telegram/codegen +++ b/Telegram/codegen @@ -1 +1 @@ -Subproject commit 248614b49cd7d5aff69d75a737f2e35b79fbb119 +Subproject commit 13117d03e5683af00f898a330aa319fd17efe8f7 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 5938cb012..15ffd051d 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 5938cb012fd78f5128fdefa3794fbae6c8f1dcf6 +Subproject commit 15ffd051d605be310051645412c54c2b9f87ff01