"Fossies" - the Fresh Open Source Software Archive

Member "tdesktop-4.8.3/Telegram/SourceFiles/window/section_widget.cpp" (1 Jun 2023, 13212 Bytes) of package /linux/misc/tdesktop-4.8.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "section_widget.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.8.1_vs_4.8.3.

    1 /*
    2 This file is part of Telegram Desktop,
    3 the official desktop application for the Telegram messaging service.
    4 
    5 For license and copyright information please follow this link:
    6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
    7 */
    8 #include "window/section_widget.h"
    9 
   10 #include "mainwidget.h"
   11 #include "ui/ui_utility.h"
   12 #include "ui/chat/chat_theme.h"
   13 #include "ui/painter.h"
   14 #include "boxes/premium_preview_box.h"
   15 #include "data/data_peer.h"
   16 #include "data/data_user.h"
   17 #include "data/data_document.h"
   18 #include "data/data_document_media.h"
   19 #include "data/data_changes.h"
   20 #include "data/data_session.h"
   21 #include "data/data_cloud_themes.h"
   22 #include "data/data_message_reactions.h"
   23 #include "data/data_peer_values.h"
   24 #include "history/history.h"
   25 #include "history/history_item.h"
   26 #include "settings/settings_premium.h"
   27 #include "main/main_session.h"
   28 #include "window/section_memento.h"
   29 #include "window/window_slide_animation.h"
   30 #include "window/window_session_controller.h"
   31 #include "window/themes/window_theme.h"
   32 
   33 #include <rpl/range.h>
   34 
   35 namespace Window {
   36 namespace {
   37 
   38 [[nodiscard]] rpl::producer<QString> PeerThemeEmojiValue(
   39         not_null<PeerData*> peer) {
   40     return peer->session().changes().peerFlagsValue(
   41         peer,
   42         Data::PeerUpdate::Flag::ChatThemeEmoji
   43     ) | rpl::map([=] {
   44         return peer->themeEmoji();
   45     });
   46 }
   47 
   48 struct ResolvedPaper {
   49     Data::WallPaper paper;
   50     std::shared_ptr<Data::DocumentMedia> media;
   51 };
   52 
   53 [[nodiscard]] rpl::producer<std::optional<ResolvedPaper>> PeerWallPaperValue(
   54         not_null<PeerData*> peer) {
   55     return peer->session().changes().peerFlagsValue(
   56         peer,
   57         Data::PeerUpdate::Flag::ChatWallPaper
   58     ) | rpl::map([=]() -> rpl::producer<std::optional<ResolvedPaper>> {
   59         const auto paper = peer->wallPaper();
   60         const auto single = [](std::optional<ResolvedPaper> value) {
   61             return rpl::single(std::move(value));
   62         };
   63         if (!paper) {
   64             return single({});
   65         }
   66         const auto document = paper->document();
   67         auto value = ResolvedPaper{
   68             *paper,
   69             document ? document->createMediaView() : nullptr,
   70         };
   71         if (!value.media || value.media->loaded(true)) {
   72             return single(std::move(value));
   73         }
   74         paper->loadDocument();
   75         return single(
   76             value
   77         ) | rpl::then(document->session().downloaderTaskFinished(
   78         ) | rpl::filter([=] {
   79             return value.media->loaded(true);
   80         }) | rpl::take(1) | rpl::map_to(
   81             std::optional<ResolvedPaper>(value)
   82         ));
   83     }) | rpl::flatten_latest();
   84 }
   85 
   86 [[nodiscard]] auto MaybeChatThemeDataValueFromPeer(
   87     not_null<PeerData*> peer)
   88 -> rpl::producer<std::optional<Data::CloudTheme>> {
   89     return PeerThemeEmojiValue(
   90         peer
   91     ) | rpl::map([=](const QString &emoji)
   92     -> rpl::producer<std::optional<Data::CloudTheme>> {
   93         return peer->owner().cloudThemes().themeForEmojiValue(emoji);
   94     }) | rpl::flatten_latest();
   95 }
   96 
   97 [[nodiscard]] rpl::producer<> DebouncedPaletteValue() {
   98     return [=](auto consumer) {
   99         auto lifetime = rpl::lifetime();
  100 
  101         struct State {
  102             base::has_weak_ptr guard;
  103             bool scheduled = false;
  104         };
  105         const auto state = lifetime.make_state<State>();
  106 
  107         consumer.put_next_copy(rpl::empty);
  108         style::PaletteChanged(
  109         ) | rpl::start_with_next([=] {
  110             if (state->scheduled) {
  111                 return;
  112             }
  113             state->scheduled = true;
  114             Ui::PostponeCall(&state->guard, [=] {
  115                 state->scheduled = false;
  116                 consumer.put_next_copy(rpl::empty);
  117             });
  118         }, lifetime);
  119 
  120         return lifetime;
  121     };
  122 }
  123 
  124 struct ResolvedTheme {
  125     std::optional<Data::CloudTheme> theme;
  126     std::optional<ResolvedPaper> paper;
  127     bool dark = false;
  128 };
  129 
  130 [[nodiscard]] auto MaybeCloudThemeValueFromPeer(
  131     not_null<PeerData*> peer)
  132 -> rpl::producer<ResolvedTheme> {
  133     return rpl::combine(
  134         MaybeChatThemeDataValueFromPeer(peer),
  135         PeerWallPaperValue(peer),
  136         Theme::IsThemeDarkValue() | rpl::distinct_until_changed()
  137     ) | rpl::map([](
  138             std::optional<Data::CloudTheme> theme,
  139             std::optional<ResolvedPaper> paper,
  140             bool night) -> rpl::producer<ResolvedTheme> {
  141         if (theme || !paper) {
  142             return rpl::single<ResolvedTheme>({
  143                 std::move(theme),
  144                 std::move(paper),
  145                 night,
  146             });
  147         }
  148         return DebouncedPaletteValue(
  149         ) | rpl::map([=] {
  150             return ResolvedTheme{
  151                 .paper = paper,
  152                 .dark = night,
  153             };
  154         });
  155     }) | rpl::flatten_latest();
  156 }
  157 
  158 } // namespace
  159 
  160 AbstractSectionWidget::AbstractSectionWidget(
  161     QWidget *parent,
  162     not_null<SessionController*> controller,
  163     rpl::producer<PeerData*> peerForBackground)
  164 : RpWidget(parent)
  165 , _controller(controller) {
  166     std::move(
  167         peerForBackground
  168     ) | rpl::map([=](PeerData *peer) -> rpl::producer<> {
  169         if (!peer) {
  170             return rpl::single(rpl::empty) | rpl::then(
  171                 controller->defaultChatTheme()->repaintBackgroundRequests()
  172             );
  173         }
  174         return ChatThemeValueFromPeer(
  175             controller,
  176             peer
  177         ) | rpl::map([](const std::shared_ptr<Ui::ChatTheme> &theme) {
  178             return rpl::single(rpl::empty) | rpl::then(
  179                 theme->repaintBackgroundRequests()
  180             );
  181         }) | rpl::flatten_latest();
  182     }) | rpl::flatten_latest() | rpl::start_with_next([=] {
  183         update();
  184     }, lifetime());
  185 }
  186 
  187 Main::Session &AbstractSectionWidget::session() const {
  188     return _controller->session();
  189 }
  190 
  191 SectionWidget::SectionWidget(
  192     QWidget *parent,
  193     not_null<Window::SessionController*> controller,
  194     rpl::producer<PeerData*> peerForBackground)
  195 : AbstractSectionWidget(parent, controller, std::move(peerForBackground)) {
  196 }
  197 
  198 SectionWidget::SectionWidget(
  199     QWidget *parent,
  200     not_null<Window::SessionController*> controller,
  201     not_null<PeerData*> peerForBackground)
  202 : AbstractSectionWidget(
  203     parent,
  204     controller,
  205     rpl::single(peerForBackground.get())) {
  206 }
  207 
  208 void SectionWidget::setGeometryWithTopMoved(
  209         const QRect &newGeometry,
  210         int topDelta) {
  211     _topDelta = topDelta;
  212     bool willBeResized = (size() != newGeometry.size());
  213     if (geometry() != newGeometry) {
  214         auto weak = Ui::MakeWeak(this);
  215         setGeometry(newGeometry);
  216         if (!weak) {
  217             return;
  218         }
  219     }
  220     if (!willBeResized) {
  221         resizeEvent(nullptr);
  222     }
  223     _topDelta = 0;
  224 }
  225 
  226 void SectionWidget::showAnimated(
  227         SlideDirection direction,
  228         const SectionSlideParams &params) {
  229     if (_showAnimation) {
  230         return;
  231     }
  232 
  233     showChildren();
  234     auto myContentCache = grabForShowAnimation(params);
  235     hideChildren();
  236     showAnimatedHook(params);
  237 
  238     _showAnimation = std::make_unique<SlideAnimation>();
  239     _showAnimation->setDirection(direction);
  240     _showAnimation->setRepaintCallback([this] { update(); });
  241     _showAnimation->setFinishedCallback([this] { showFinished(); });
  242     _showAnimation->setPixmaps(
  243         params.oldContentCache,
  244         myContentCache);
  245     _showAnimation->setTopBarShadow(params.withTopBarShadow);
  246     _showAnimation->setWithFade(params.withFade);
  247     _showAnimation->setTopSkip(params.topSkip);
  248     _showAnimation->setTopBarMask(params.topMask);
  249     _showAnimation->start();
  250 
  251     show();
  252 }
  253 
  254 std::shared_ptr<SectionMemento> SectionWidget::createMemento() {
  255     return nullptr;
  256 }
  257 
  258 void SectionWidget::showFast() {
  259     show();
  260     showFinished();
  261 }
  262 
  263 QPixmap SectionWidget::grabForShowAnimation(
  264         const SectionSlideParams &params) {
  265     return Ui::GrabWidget(this);
  266 }
  267 
  268 void SectionWidget::PaintBackground(
  269         not_null<Window::SessionController*> controller,
  270         not_null<Ui::ChatTheme*> theme,
  271         not_null<QWidget*> widget,
  272         QRect clip) {
  273     PaintBackground(
  274         theme,
  275         widget,
  276         controller->content()->height(),
  277         controller->content()->backgroundFromY(),
  278         clip);
  279 }
  280 
  281 void SectionWidget::PaintBackground(
  282         not_null<Ui::ChatTheme*> theme,
  283         not_null<QWidget*> widget,
  284         int fillHeight,
  285         int fromy,
  286         QRect clip) {
  287     auto p = QPainter(widget);
  288     if (fromy) {
  289         p.translate(0, fromy);
  290         clip = clip.translated(0, -fromy);
  291     }
  292     PaintBackground(p, theme, QSize(widget->width(), fillHeight), clip);
  293 }
  294 
  295 void SectionWidget::PaintBackground(
  296         QPainter &p,
  297         not_null<Ui::ChatTheme*> theme,
  298         QSize fill,
  299         QRect clip) {
  300     const auto &background = theme->background();
  301     if (background.colorForFill) {
  302         p.fillRect(clip, *background.colorForFill);
  303         return;
  304     }
  305     const auto &gradient = background.gradientForFill;
  306     auto state = theme->backgroundState(fill);
  307     const auto paintCache = [&](const Ui::CachedBackground &cache) {
  308         const auto to = QRect(
  309             QPoint(cache.x, cache.y),
  310             cache.pixmap.size() / cIntRetinaFactor());
  311         if (cache.waitingForNegativePattern) {
  312             // While we wait for pattern being loaded we paint just gradient.
  313             // But in case of negative patter opacity we just fill-black.
  314             p.fillRect(to, Qt::black);
  315         } else if (cache.area == fill) {
  316             p.drawPixmap(to, cache.pixmap);
  317         } else {
  318             const auto sx = fill.width() / float64(cache.area.width());
  319             const auto sy = fill.height() / float64(cache.area.height());
  320             const auto round = [](float64 value) -> int {
  321                 return (value >= 0.)
  322                     ? int(std::ceil(value))
  323                     : int(std::floor(value));
  324             };
  325             const auto sto = QPoint(round(to.x() * sx), round(to.y() * sy));
  326             p.drawPixmap(
  327                 sto.x(),
  328                 sto.y(),
  329                 round((to.x() + to.width()) * sx) - sto.x(),
  330                 round((to.y() + to.height()) * sy) - sto.y(),
  331                 cache.pixmap);
  332         }
  333     };
  334     const auto hasNow = !state.now.pixmap.isNull();
  335     const auto goodNow = hasNow && (state.now.area == fill);
  336     const auto useCache = goodNow || !gradient.isNull();
  337     if (useCache) {
  338         const auto fade = (state.shown < 1. && !gradient.isNull());
  339         if (fade) {
  340             paintCache(state.was);
  341             p.setOpacity(state.shown);
  342         }
  343         paintCache(state.now);
  344         if (fade) {
  345             p.setOpacity(1.);
  346         }
  347         return;
  348     }
  349     const auto &prepared = background.prepared;
  350     if (prepared.isNull()) {
  351         return;
  352     } else if (background.isPattern) {
  353         const auto w = prepared.width() * fill.height() / prepared.height();
  354         const auto cx = qCeil(fill.width() / float64(w));
  355         const auto cols = (cx / 2) * 2 + 1;
  356         const auto xshift = (fill.width() - w * cols) / 2;
  357         for (auto i = 0; i != cols; ++i) {
  358             p.drawImage(
  359                 QRect(xshift + i * w, 0, w, fill.height()),
  360                 prepared,
  361                 QRect(QPoint(), prepared.size()));
  362         }
  363     } else if (background.tile) {
  364         const auto &tiled = background.preparedForTiled;
  365         const auto left = clip.left();
  366         const auto top = clip.top();
  367         const auto right = clip.left() + clip.width();
  368         const auto bottom = clip.top() + clip.height();
  369         const auto w = tiled.width() / cRetinaFactor();
  370         const auto h = tiled.height() / cRetinaFactor();
  371         const auto sx = qFloor(left / w);
  372         const auto sy = qFloor(top / h);
  373         const auto cx = qCeil(right / w);
  374         const auto cy = qCeil(bottom / h);
  375         for (auto i = sx; i < cx; ++i) {
  376             for (auto j = sy; j < cy; ++j) {
  377                 p.drawImage(QPointF(i * w, j * h), tiled);
  378             }
  379         }
  380     } else {
  381         const auto hq = PainterHighQualityEnabler(p);
  382         const auto rects = Ui::ComputeChatBackgroundRects(
  383             fill,
  384             prepared.size());
  385         p.drawImage(rects.to, prepared, rects.from);
  386     }
  387 }
  388 
  389 void SectionWidget::paintEvent(QPaintEvent *e) {
  390     if (_showAnimation) {
  391         auto p = QPainter(this);
  392         _showAnimation->paintContents(p);
  393     }
  394 }
  395 
  396 bool SectionWidget::animatingShow() const {
  397     return (_showAnimation != nullptr);
  398 }
  399 
  400 void SectionWidget::showFinished() {
  401     _showAnimation.reset();
  402     if (isHidden()) return;
  403 
  404     showChildren();
  405     showFinishedHook();
  406 
  407     setInnerFocus();
  408 }
  409 
  410 rpl::producer<int> SectionWidget::desiredHeight() const {
  411     return rpl::single(height());
  412 }
  413 
  414 SectionWidget::~SectionWidget() = default;
  415 
  416 auto ChatThemeValueFromPeer(
  417     not_null<SessionController*> controller,
  418     not_null<PeerData*> peer)
  419 -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
  420     auto cloud = MaybeCloudThemeValueFromPeer(
  421         peer
  422     ) | rpl::map([=](ResolvedTheme resolved)
  423     -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
  424         if (!resolved.theme && !resolved.paper) {
  425             return rpl::single(controller->defaultChatTheme());
  426         }
  427         const auto theme = resolved.theme.value_or(Data::CloudTheme());
  428         const auto paper = resolved.paper
  429             ? resolved.paper->paper
  430             : Data::WallPaper(0);
  431         const auto type = resolved.dark
  432             ? Data::CloudThemeType::Dark
  433             : Data::CloudThemeType::Light;
  434         if (paper.document()
  435             && resolved.paper->media
  436             && !resolved.paper->media->loaded()
  437             && !controller->chatThemeAlreadyCached(theme, paper, type)) {
  438             return rpl::single(controller->defaultChatTheme());
  439         }
  440         return controller->cachedChatThemeValue(theme, paper, type);
  441     }) | rpl::flatten_latest(
  442     ) | rpl::distinct_until_changed();
  443 
  444     return rpl::combine(
  445         std::move(cloud),
  446         controller->peerThemeOverrideValue()
  447     ) | rpl::map([=](
  448             std::shared_ptr<Ui::ChatTheme> &&cloud,
  449             PeerThemeOverride &&overriden) {
  450         return (overriden.peer == peer.get()
  451             && Ui::Emoji::Find(peer->themeEmoji()) != overriden.emoji)
  452             ? std::move(overriden.theme)
  453             : std::move(cloud);
  454     });
  455 }
  456 
  457 bool ShowSendPremiumError(
  458         not_null<SessionController*> controller,
  459         not_null<DocumentData*> document) {
  460     if (!document->isPremiumSticker()
  461         || document->session().premium()) {
  462         return false;
  463     }
  464     ShowStickerPreviewBox(controller, document);
  465     return true;
  466 }
  467 
  468 bool ShowReactPremiumError(
  469         not_null<SessionController*> controller,
  470         not_null<HistoryItem*> item,
  471         const Data::ReactionId &id) {
  472     if (controller->session().premium()
  473         || ranges::contains(item->chosenReactions(), id)) {
  474         return false;
  475     }
  476     const auto &list = controller->session().data().reactions().list(
  477         Data::Reactions::Type::Active);
  478     const auto i = ranges::find(list, id, &Data::Reaction::id);
  479     if (i == end(list) || !i->premium) {
  480         if (!id.custom()) {
  481             return false;
  482         }
  483     }
  484     ShowPremiumPreviewBox(controller, PremiumPreview::InfiniteReactions);
  485     return true;
  486 }
  487 
  488 } // namespace Window