/* 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/section_widget.h" #include "mainwidget.h" #include "ui/ui_utility.h" #include "ui/chat/chat_theme.h" #include "ui/toasts/common_toasts.h" #include "data/data_peer.h" #include "data/data_user.h" #include "data/data_document.h" #include "data/data_changes.h" #include "data/data_session.h" #include "data/data_cloud_themes.h" #include "data/data_message_reactions.h" #include "history/history_item.h" #include "main/main_session.h" #include "window/section_memento.h" #include "window/window_slide_animation.h" #include "window/window_session_controller.h" #include "window/themes/window_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(); } struct ResolvedTheme { std::optional theme; bool dark = false; }; [[nodiscard]] auto MaybeCloudThemeValueFromPeer( not_null peer) -> rpl::producer { return rpl::combine( MaybeChatThemeDataValueFromPeer(peer), Theme::IsThemeDarkValue() | rpl::distinct_until_changed() ) | rpl::map([](std::optional theme, bool night) { return ResolvedTheme{ std::move(theme), night }; }); } } // namespace AbstractSectionWidget::AbstractSectionWidget( QWidget *parent, not_null controller, rpl::producer peerForBackground) : RpWidget(parent) , _controller(controller) { std::move( peerForBackground ) | rpl::map([=](PeerData *peer) -> rpl::producer<> { if (!peer) { return rpl::single(rpl::empty) | rpl::then( controller->defaultChatTheme()->repaintBackgroundRequests() ); } return ChatThemeValueFromPeer( controller, peer ) | rpl::map([](const std::shared_ptr &theme) { return rpl::single(rpl::empty) | rpl::then( theme->repaintBackgroundRequests() ); }) | rpl::flatten_latest(); }) | rpl::flatten_latest() | rpl::start_with_next([=] { update(); }, lifetime()); } Main::Session &AbstractSectionWidget::session() const { return _controller->session(); } SectionWidget::SectionWidget( QWidget *parent, not_null controller, 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( const QRect &newGeometry, int topDelta) { _topDelta = topDelta; bool willBeResized = (size() != newGeometry.size()); if (geometry() != newGeometry) { auto weak = Ui::MakeWeak(this); setGeometry(newGeometry); if (!weak) { return; } } if (!willBeResized) { resizeEvent(nullptr); } _topDelta = 0; } void SectionWidget::showAnimated( SlideDirection direction, const SectionSlideParams ¶ms) { if (_showAnimation) return; showChildren(); auto myContentCache = grabForShowAnimation(params); hideChildren(); showAnimatedHook(params); _showAnimation = std::make_unique(); _showAnimation->setDirection(direction); _showAnimation->setRepaintCallback([this] { update(); }); _showAnimation->setFinishedCallback([this] { showFinished(); }); _showAnimation->setPixmaps( params.oldContentCache, myContentCache); _showAnimation->setTopBarShadow(params.withTopBarShadow); _showAnimation->setWithFade(params.withFade); _showAnimation->setTopSkip(params.topSkip); _showAnimation->start(); show(); } std::shared_ptr SectionWidget::createMemento() { return nullptr; } void SectionWidget::showFast() { show(); showFinished(); } QPixmap SectionWidget::grabForShowAnimation( const SectionSlideParams ¶ms) { return Ui::GrabWidget(this); } void SectionWidget::PaintBackground( not_null controller, not_null theme, not_null widget, QRect clip) { Painter p(widget); const auto &background = theme->background(); if (background.colorForFill) { p.fillRect(clip, *background.colorForFill); return; } const auto &gradient = background.gradientForFill; const auto fill = QSize(widget->width(), controller->content()->height()); auto fromy = controller->content()->backgroundFromY(); auto state = theme->backgroundState(fill); const auto paintCache = [&](const Ui::CachedBackground &cache) { const auto to = QRect( QPoint(cache.x, fromy + cache.y), cache.pixmap.size() / cIntRetinaFactor()); if (cache.waitingForNegativePattern) { // While we wait for pattern being loaded we paint just gradient. // But in case of negative patter opacity we just fill-black. p.fillRect(to, Qt::black); } else if (cache.area == fill) { p.drawPixmap(to, cache.pixmap); } else { const auto sx = fill.width() / float64(cache.area.width()); const auto sy = fill.height() / float64(cache.area.height()); const auto round = [](float64 value) -> int { return (value >= 0.) ? int(std::ceil(value)) : int(std::floor(value)); }; const auto sto = QPoint(round(to.x() * sx), round(to.y() * sy)); p.drawPixmap( sto.x(), sto.y(), round((to.x() + to.width()) * sx) - sto.x(), round((to.y() + to.height()) * sy) - sto.y(), cache.pixmap); } }; const auto hasNow = !state.now.pixmap.isNull(); const auto goodNow = hasNow && (state.now.area == fill); const auto useCache = goodNow || !gradient.isNull(); if (useCache) { if (state.shown < 1. && !gradient.isNull()) { paintCache(state.was); p.setOpacity(state.shown); } paintCache(state.now); return; } const auto &prepared = background.prepared; if (prepared.isNull()) { return; } else if (background.isPattern) { const auto w = prepared.width() * fill.height() / prepared.height(); const auto cx = qCeil(fill.width() / float64(w)); const auto cols = (cx / 2) * 2 + 1; const auto xshift = (fill.width() - w * cols) / 2; for (auto i = 0; i != cols; ++i) { p.drawImage( QRect(xshift + i * w, 0, w, fill.height()), prepared, QRect(QPoint(), prepared.size())); } } else if (background.tile) { const auto &tiled = background.preparedForTiled; const auto left = clip.left(); const auto top = clip.top(); const auto right = clip.left() + clip.width(); const auto bottom = clip.top() + clip.height(); const auto w = tiled.width() / cRetinaFactor(); const auto h = tiled.height() / cRetinaFactor(); const auto sx = qFloor(left / w); const auto sy = qFloor((top - fromy) / h); const auto cx = qCeil(right / w); const auto cy = qCeil((bottom - fromy) / h); for (auto i = sx; i < cx; ++i) { for (auto j = sy; j < cy; ++j) { p.drawImage(QPointF(i * w, fromy + j * h), tiled); } } } else { const auto hq = PainterHighQualityEnabler(p); const auto rects = Ui::ComputeChatBackgroundRects( fill, prepared.size()); auto to = rects.to; to.moveTop(to.top() + fromy); p.drawImage(to, prepared, rects.from); } } void SectionWidget::paintEvent(QPaintEvent *e) { if (_showAnimation) { Painter p(this); _showAnimation->paintContents(p); } } bool SectionWidget::animatingShow() const { return (_showAnimation != nullptr); } void SectionWidget::showFinished() { _showAnimation.reset(); if (isHidden()) return; showChildren(); showFinishedHook(); setInnerFocus(); } rpl::producer SectionWidget::desiredHeight() const { return rpl::single(height()); } SectionWidget::~SectionWidget() = default; auto ChatThemeValueFromPeer( not_null controller, not_null peer) -> rpl::producer> { auto cloud = MaybeCloudThemeValueFromPeer( peer ) | rpl::map([=](ResolvedTheme resolved) -> rpl::producer> { return resolved.theme ? controller->cachedChatThemeValue( *resolved.theme, (resolved.dark ? Data::CloudThemeType::Dark : Data::CloudThemeType::Light)) : rpl::single(controller->defaultChatTheme()); }) | rpl::flatten_latest( ) | rpl::distinct_until_changed(); return rpl::combine( std::move(cloud), controller->peerThemeOverrideValue() ) | rpl::map([=]( std::shared_ptr &&cloud, PeerThemeOverride &&overriden) { return (overriden.peer == peer.get()) ? std::move(overriden.theme) : std::move(cloud); }); } bool ShowSendPremiumError( not_null controller, not_null document) { if (!document->isPremiumSticker() || document->session().user()->isPremium()) { return false; } Ui::ShowMultilineToast({ .text = { u"Premium sticker."_q }, }); return true; } bool ShowReactPremiumError( not_null controller, not_null item, const QString &emoji) { if (item->chosenReaction() == emoji || controller->session().user()->isPremium()) { return false; } const auto &list = controller->session().data().reactions().list( Data::Reactions::Type::Active); const auto i = ranges::find(list, emoji, &Data::Reaction::emoji); if (i == end(list) || !i->premium) { return false; } Ui::ShowMultilineToast({ .text = { u"Premium reaction."_q }, }); return true; } } // namespace Window