From 43d8723e351544a710fc3f35aabea9fe94a2d554 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 8 Oct 2025 20:47:52 +0300 Subject: [PATCH] Moved out badge gift tooltip from info profile cover to separated class. --- Telegram/CMakeLists.txt | 2 + .../info/profile/info_profile_badge.cpp | 15 + .../info/profile/info_profile_badge.h | 2 + .../profile/info_profile_badge_tooltip.cpp | 219 +++++++++++++++ .../info/profile/info_profile_badge_tooltip.h | 70 +++++ .../info/profile/info_profile_cover.cpp | 263 +----------------- .../info/profile/info_profile_cover.h | 4 +- 7 files changed, 319 insertions(+), 256 deletions(-) create mode 100644 Telegram/SourceFiles/info/profile/info_profile_badge_tooltip.cpp create mode 100644 Telegram/SourceFiles/info/profile/info_profile_badge_tooltip.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 9fcd52fa52..b143a03161 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1044,6 +1044,8 @@ PRIVATE info/polls/info_polls_results_widget.h info/profile/info_profile_actions.cpp info/profile/info_profile_actions.h + info/profile/info_profile_badge_tooltip.cpp + info/profile/info_profile_badge_tooltip.h info/profile/info_profile_badge.cpp info/profile/info_profile_badge.h info/profile/info_profile_cover.cpp diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp index 8cdfa8c12f..b46275108a 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_badge.h" +#include "data/data_changes.h" #include "data/data_emoji_statuses.h" #include "data/data_peer.h" #include "data/data_session.h" @@ -237,4 +238,18 @@ rpl::producer VerifiedContentForPeer( }); } +rpl::producer BotVerifyBadgeForPeer( + not_null peer) { + return peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::VerifyInfo + ) | rpl::map([=] { + const auto info = peer->botVerifyDetails(); + return Badge::Content{ + .badge = info ? BadgeType::BotVerified : BadgeType::None, + .emojiStatusId = { info ? info->iconId : DocumentId() }, + }; + }); +} + } // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.h b/Telegram/SourceFiles/info/profile/info_profile_badge.h index 387eeadbca..1fb788b632 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_badge.h +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.h @@ -98,5 +98,7 @@ private: not_null peer); [[nodiscard]] rpl::producer VerifiedContentForPeer( not_null peer); +[[nodiscard]] rpl::producer BotVerifyBadgeForPeer( + not_null peer); } // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge_tooltip.cpp b/Telegram/SourceFiles/info/profile/info_profile_badge_tooltip.cpp new file mode 100644 index 0000000000..460b2e75fa --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_badge_tooltip.cpp @@ -0,0 +1,219 @@ +/* +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 "info/profile/info_profile_badge_tooltip.h" + +#include "data/data_emoji_statuses.h" +#include "base/event_filter.h" +#include "ui/painter.h" +#include "ui/ui_utility.h" +#include "styles/style_info.h" + +namespace Info::Profile { +namespace { + +constexpr auto kGlareDurationStep = crl::time(320); +constexpr auto kGlareTimeout = crl::time(1000); + +} // namespace + +BadgeTooltip::BadgeTooltip( + not_null parent, + std::shared_ptr collectible, + not_null pointTo) +: Ui::RpWidget(parent) +, _st(st::infoGiftTooltip) +, _collectible(std::move(collectible)) +, _text(_collectible->title) +, _font(st::infoGiftTooltipFont) +, _inner(_font->width(_text), _font->height) +, _outer(_inner.grownBy(_st.padding)) +, _stroke(st::lineWidth) +, _skip(2 * _stroke) +, _full(_outer + QSize(2 * _skip, _st.arrow + 2 * _skip)) +, _glareSize(_outer.height() * 3) +, _glareRange(_outer.width() + _glareSize) +, _glareDuration(_glareRange * kGlareDurationStep / _glareSize) +, _glareTimer([=] { showGlare(); }) { + resize(_full + QSize(0, _st.shift)); + setupGeometry(pointTo); +} + +void BadgeTooltip::fade(bool shown) { + if (_shown == shown) { + return; + } + show(); + _shown = shown; + _showAnimation.start([=] { + update(); + if (!_showAnimation.animating()) { + if (!_shown) { + hide(); + } else { + showGlare(); + } + } + }, _shown ? 0. : 1., _shown ? 1. : 0., _st.duration, anim::easeInCirc); +} + +void BadgeTooltip::showGlare() { + _glareAnimation.start([=] { + update(); + if (!_glareAnimation.animating()) { + _glareTimer.callOnce(kGlareTimeout); + } + }, 0., 1., _glareDuration); +} + +void BadgeTooltip::finishAnimating() { + _showAnimation.stop(); + if (!_shown) { + hide(); + } +} + +void BadgeTooltip::setOpacity(float64 opacity) { + _opacity = opacity; + update(); +} + +crl::time BadgeTooltip::glarePeriod() const { + return _glareDuration + kGlareTimeout; +} + +void BadgeTooltip::paintEvent(QPaintEvent *e) { + const auto glare = _glareAnimation.value(0.); + _glareRight = anim::interpolate(0, _glareRange, glare); + prepareImage(); + + auto p = QPainter(this); + const auto shown = _showAnimation.value(_shown ? 1. : 0.); + p.setOpacity(shown * _opacity); + const auto imageHeight = _image.height() / _image.devicePixelRatio(); + const auto top = anim::interpolate(0, height() - imageHeight, shown); + p.drawImage(0, top, _image); +} + +void BadgeTooltip::setupGeometry(not_null pointTo) { + auto widget = pointTo.get(); + const auto parent = parentWidget(); + + const auto refresh = [=] { + const auto rect = Ui::MapFrom(parent, pointTo, pointTo->rect()); + const auto point = QPoint(rect.center().x(), rect.y()); + const auto left = point.x() - (width() / 2); + const auto skip = _st.padding.left(); + setGeometry( + std::min(std::max(left, skip), parent->width() - width() - skip), + std::max(point.y() - height() - _st.margin.bottom(), skip), + width(), + height()); + const auto arrowMiddle = point.x() - x(); + if (_arrowMiddle != arrowMiddle) { + _arrowMiddle = arrowMiddle; + update(); + } + }; + refresh(); + while (widget && widget != parent) { + base::install_event_filter(this, widget, [=](not_null e) { + if (e->type() == QEvent::Resize + || e->type() == QEvent::Move + || e->type() == QEvent::ZOrderChange) { + refresh(); + raise(); + } + return base::EventFilterResult::Continue; + }); + widget = widget->parentWidget(); + } +} + +void BadgeTooltip::prepareImage() { + const auto ratio = style::DevicePixelRatio(); + const auto arrow = _st.arrow; + const auto size = _full * ratio; + if (_image.size() != size) { + _image = QImage(size, QImage::Format_ARGB32_Premultiplied); + _image.setDevicePixelRatio(ratio); + } else if (_imageGlareRight == _glareRight + && _imageArrowMiddle == _arrowMiddle) { + return; + } + _imageGlareRight = _glareRight; + _imageArrowMiddle = _arrowMiddle; + _image.fill(Qt::transparent); + + const auto gfrom = _imageGlareRight - _glareSize; + const auto gtill = _imageGlareRight; + + auto path = QPainterPath(); + const auto width = _outer.width(); + const auto height = _outer.height(); + const auto radius = (height + 1) / 2; + const auto diameter = height; + path.moveTo(radius, 0); + path.lineTo(width - radius, 0); + path.arcTo( + QRect(QPoint(width - diameter, 0), QSize(diameter, diameter)), + 90, + -180); + const auto xarrow = _arrowMiddle - _skip; + if (xarrow - arrow <= radius || xarrow + arrow >= width - radius) { + path.lineTo(radius, height); + } else { + path.lineTo(xarrow + arrow, height); + path.lineTo(xarrow, height + arrow); + path.lineTo(xarrow - arrow, height); + path.lineTo(radius, height); + } + path.arcTo( + QRect(QPoint(0, 0), QSize(diameter, diameter)), + -90, + -180); + path.closeSubpath(); + + auto p = QPainter(&_image); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + if (gtill > 0) { + auto gradient = QLinearGradient(gfrom, 0, gtill, 0); + gradient.setStops({ + { 0., _collectible->edgeColor }, + { 0.5, _collectible->centerColor }, + { 1., _collectible->edgeColor }, + }); + p.setBrush(gradient); + } else { + p.setBrush(_collectible->edgeColor); + } + p.translate(_skip, _skip); + p.drawPath(path); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.setBrush(Qt::NoBrush); + auto copy = _collectible->textColor; + copy.setAlpha(0); + if (gtill > 0) { + auto gradient = QLinearGradient(gfrom, 0, gtill, 0); + gradient.setStops({ + { 0., copy }, + { 0.5, _collectible->textColor }, + { 1., copy }, + }); + p.setPen(QPen(gradient, _stroke)); + } else { + p.setPen(QPen(copy, _stroke)); + } + p.drawPath(path); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setFont(_font); + p.setPen(QColor(255, 255, 255)); + p.drawText(_st.padding.left(), _st.padding.top() + _font->ascent, _text); +} + +} // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge_tooltip.h b/Telegram/SourceFiles/info/profile/info_profile_badge_tooltip.h new file mode 100644 index 0000000000..86f30562b4 --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_badge_tooltip.h @@ -0,0 +1,70 @@ +/* +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/rp_widget.h" +#include "ui/effects/animations.h" +#include "base/timer.h" + +namespace style { +struct ImportantTooltip; +} // namespace style + +namespace Data { +struct EmojiStatusCollectible; +} // namespace Data + +namespace Info::Profile { + +class BadgeTooltip final : public Ui::RpWidget { +public: + BadgeTooltip( + not_null parent, + std::shared_ptr collectible, + not_null pointTo); + + void fade(bool shown); + void finishAnimating(); + void setOpacity(float64 opacity); + + [[nodiscard]] crl::time glarePeriod() const; + +private: + void paintEvent(QPaintEvent *e) override; + void setupGeometry(not_null pointTo); + void prepareImage(); + void showGlare(); + + const style::ImportantTooltip &_st; + std::shared_ptr _collectible; + QString _text; + const style::font &_font; + QSize _inner; + QSize _outer; + int _stroke = 0; + int _skip = 0; + QSize _full; + int _glareSize = 0; + int _glareRange = 0; + crl::time _glareDuration = 0; + base::Timer _glareTimer; + + Ui::Animations::Simple _showAnimation; + Ui::Animations::Simple _glareAnimation; + + QImage _image; + int _glareRight = 0; + int _imageGlareRight = 0; + int _arrowMiddle = 0; + int _imageArrowMiddle = 0; + + bool _shown = false; + float64 _opacity = 1.; +}; + +} // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 34c73152cd..7090930857 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_topic.h" #include "data/stickers/data_custom_emoji.h" #include "info/profile/info_profile_badge.h" +#include "info/profile/info_profile_badge_tooltip.h" #include "info/profile/info_profile_emoji_status_panel.h" #include "info/profile/info_profile_music_button.h" #include "info/profile/info_profile_status_label.h" @@ -80,16 +81,6 @@ constexpr auto kGlareTimeout = crl::time(1000); : st::infoProfileCover; } -[[nodiscard]] QMargins LargeCustomEmojiMargins() { - const auto ratio = style::DevicePixelRatio(); - const auto emoji = Ui::Emoji::GetSizeLarge() / ratio; - const auto size = Data::FrameSizeFromTag(Data::CustomEmojiSizeTag::Large) - / ratio; - const auto left = (size - emoji) / 2; - const auto right = size - emoji - left; - return { left, left, right, right }; -} - [[nodiscard]] MusicButtonData DocumentMusicButtonData( not_null document) { if (const auto song = document->song()) { @@ -108,238 +99,14 @@ constexpr auto kGlareTimeout = crl::time(1000); } // namespace -class Cover::BadgeTooltip final : public Ui::RpWidget { -public: - BadgeTooltip( - not_null parent, - std::shared_ptr collectible, - not_null pointTo); - - void fade(bool shown); - void finishAnimating(); - - [[nodiscard]] crl::time glarePeriod() const; - -private: - void paintEvent(QPaintEvent *e) override; - - void setupGeometry(not_null pointTo); - void prepareImage(); - void showGlare(); - - const style::ImportantTooltip &_st; - std::shared_ptr _collectible; - QString _text; - const style::font &_font; - QSize _inner; - QSize _outer; - int _stroke = 0; - int _skip = 0; - QSize _full; - int _glareSize = 0; - int _glareRange = 0; - crl::time _glareDuration = 0; - base::Timer _glareTimer; - - Ui::Animations::Simple _showAnimation; - Ui::Animations::Simple _glareAnimation; - - QImage _image; - int _glareRight = 0; - int _imageGlareRight = 0; - int _arrowMiddle = 0; - int _imageArrowMiddle = 0; - - bool _shown = false; - -}; - -Cover::BadgeTooltip::BadgeTooltip( - not_null parent, - std::shared_ptr collectible, - not_null pointTo) -: Ui::RpWidget(parent) -, _st(st::infoGiftTooltip) -, _collectible(std::move(collectible)) -, _text(_collectible->title) -, _font(st::infoGiftTooltipFont) -, _inner(_font->width(_text), _font->height) -, _outer(_inner.grownBy(_st.padding)) -, _stroke(st::lineWidth) -, _skip(2 * _stroke) -, _full(_outer + QSize(2 * _skip, _st.arrow + 2 * _skip)) -, _glareSize(_outer.height() * 3) -, _glareRange(_outer.width() + _glareSize) -, _glareDuration(_glareRange * kGlareDurationStep / _glareSize) -, _glareTimer([=] { showGlare(); }) { - resize(_full + QSize(0, _st.shift)); - setupGeometry(pointTo); -} - -void Cover::BadgeTooltip::fade(bool shown) { - if (_shown == shown) { - return; - } - show(); - _shown = shown; - _showAnimation.start([=] { - update(); - if (!_showAnimation.animating()) { - if (!_shown) { - hide(); - } else { - showGlare(); - } - } - }, _shown ? 0. : 1., _shown ? 1. : 0., _st.duration, anim::easeInCirc); -} - -void Cover::BadgeTooltip::showGlare() { - _glareAnimation.start([=] { - update(); - if (!_glareAnimation.animating()) { - _glareTimer.callOnce(kGlareTimeout); - } - }, 0., 1., _glareDuration); -} - -void Cover::BadgeTooltip::finishAnimating() { - _showAnimation.stop(); - if (!_shown) { - hide(); - } -} - -crl::time Cover::BadgeTooltip::glarePeriod() const { - return _glareDuration + kGlareTimeout; -} - -void Cover::BadgeTooltip::paintEvent(QPaintEvent *e) { - const auto glare = _glareAnimation.value(0.); - _glareRight = anim::interpolate(0, _glareRange, glare); - prepareImage(); - - auto p = QPainter(this); - const auto shown = _showAnimation.value(_shown ? 1. : 0.); - p.setOpacity(shown); - const auto imageHeight = _image.height() / _image.devicePixelRatio(); - const auto top = anim::interpolate(0, height() - imageHeight, shown); - p.drawImage(0, top, _image); -} - -void Cover::BadgeTooltip::setupGeometry(not_null pointTo) { - auto widget = pointTo.get(); - const auto parent = parentWidget(); - - const auto refresh = [=] { - const auto rect = Ui::MapFrom(parent, pointTo, pointTo->rect()); - const auto point = QPoint(rect.center().x(), rect.y()); - const auto left = point.x() - (width() / 2); - const auto skip = _st.padding.left(); - setGeometry( - std::min(std::max(left, skip), parent->width() - width() - skip), - std::max(point.y() - height() - _st.margin.bottom(), skip), - width(), - height()); - const auto arrowMiddle = point.x() - x(); - if (_arrowMiddle != arrowMiddle) { - _arrowMiddle = arrowMiddle; - update(); - } - }; - refresh(); - while (widget && widget != parent) { - base::install_event_filter(this, widget, [=](not_null e) { - if (e->type() == QEvent::Resize || e->type() == QEvent::Move || e->type() == QEvent::ZOrderChange) { - refresh(); - raise(); - } - return base::EventFilterResult::Continue; - }); - widget = widget->parentWidget(); - } -} - -void Cover::BadgeTooltip::prepareImage() { +QMargins LargeCustomEmojiMargins() { const auto ratio = style::DevicePixelRatio(); - const auto arrow = _st.arrow; - const auto size = _full * ratio; - if (_image.size() != size) { - _image = QImage(size, QImage::Format_ARGB32_Premultiplied); - _image.setDevicePixelRatio(ratio); - } else if (_imageGlareRight == _glareRight - && _imageArrowMiddle == _arrowMiddle) { - return; - } - _imageGlareRight = _glareRight; - _imageArrowMiddle = _arrowMiddle; - _image.fill(Qt::transparent); - - const auto gfrom = _imageGlareRight - _glareSize; - const auto gtill = _imageGlareRight; - - auto path = QPainterPath(); - const auto width = _outer.width(); - const auto height = _outer.height(); - const auto radius = (height + 1) / 2; - const auto diameter = height; - path.moveTo(radius, 0); - path.lineTo(width - radius, 0); - path.arcTo( - QRect(QPoint(width - diameter, 0), QSize(diameter, diameter)), - 90, - -180); - const auto xarrow = _arrowMiddle - _skip; - if (xarrow - arrow <= radius || xarrow + arrow >= width - radius) { - path.lineTo(radius, height); - } else { - path.lineTo(xarrow + arrow, height); - path.lineTo(xarrow, height + arrow); - path.lineTo(xarrow - arrow, height); - path.lineTo(radius, height); - } - path.arcTo( - QRect(QPoint(0, 0), QSize(diameter, diameter)), - -90, - -180); - path.closeSubpath(); - - auto p = QPainter(&_image); - auto hq = PainterHighQualityEnabler(p); - p.setPen(Qt::NoPen); - if (gtill > 0) { - auto gradient = QLinearGradient(gfrom, 0, gtill, 0); - gradient.setStops({ - { 0., _collectible->edgeColor }, - { 0.5, _collectible->centerColor }, - { 1., _collectible->edgeColor }, - }); - p.setBrush(gradient); - } else { - p.setBrush(_collectible->edgeColor); - } - p.translate(_skip, _skip); - p.drawPath(path); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.setBrush(Qt::NoBrush); - auto copy = _collectible->textColor; - copy.setAlpha(0); - if (gtill > 0) { - auto gradient = QLinearGradient(gfrom, 0, gtill, 0); - gradient.setStops({ - { 0., copy }, - { 0.5, _collectible->textColor }, - { 1., copy }, - }); - p.setPen(QPen(gradient, _stroke)); - } else { - p.setPen(QPen(copy, _stroke)); - } - p.drawPath(path); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setFont(_font); - p.setPen(QColor(255, 255, 255)); - p.drawText(_st.padding.left(), _st.padding.top() + _font->ascent, _text); + const auto emoji = Ui::Emoji::GetSizeLarge() / ratio; + const auto size = Data::FrameSizeFromTag(Data::CustomEmojiSizeTag::Large) + / ratio; + const auto left = (size - emoji) / 2; + const auto right = size - emoji - left; + return { left, left, right, right }; } TopicIconView::TopicIconView( @@ -549,20 +316,6 @@ Cover::Cover( nullptr) { } -[[nodiscard]] rpl::producer BotVerifyBadgeForPeer( - not_null peer) { - return peer->session().changes().peerFlagsValue( - peer, - Data::PeerUpdate::Flag::VerifyInfo - ) | rpl::map([=] { - const auto info = peer->botVerifyDetails(); - return Badge::Content{ - .badge = info ? BadgeType::BotVerified : BadgeType::None, - .emojiStatusId = { info ? info->iconId : DocumentId() }, - }; - }); -} - Cover::Cover( QWidget *parent, not_null controller, diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index 2ccab14925..fe68be2b95 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -44,11 +44,14 @@ struct InfoProfileCover; namespace Info::Profile { +class BadgeTooltip; class EmojiStatusPanel; class MusicButton; class Badge; class StatusLabel; +[[nodiscard]] QMargins LargeCustomEmojiMargins(); + class TopicIconView final { public: TopicIconView( @@ -129,7 +132,6 @@ public: [[nodiscard]] std::optional updatedPersonalPhoto() const; private: - class BadgeTooltip; Cover( QWidget *parent,