diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 5a1b70ae24..dd87192980 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -1111,3 +1111,7 @@ premiumAccountsLabelRadius: 6; premiumAccountsNameTop: 13px; premiumAccountsPadding: margins(0px, 20px, 0px, 14px); premiumAccountsHeight: 105px; + +gradientButtonGlareDuration: 700; +gradientButtonGlareTimeout: 2000; +gradientButtonGlareWidth: 100px; diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 793a5faf45..7bbda91e1a 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -169,7 +169,7 @@ using Order = std::vector; }; } -[[nodiscard]] not_null CreateSubscribeButton( +[[nodiscard]] not_null CreateSubscribeButton( not_null controller, not_null parent, Fn callback) { @@ -630,6 +630,8 @@ public: [[nodiscard]] QPointer createPinnedToBottom( not_null parent) override; + void showFinished() override; + [[nodiscard]] bool hasFlexibleTopBar() const override; [[nodiscard]] const Ui::RoundRect *bottomSkipRounding() const override; @@ -650,6 +652,7 @@ private: std::optional _bottomSkipRounding; rpl::event_stream<> _showBack; + rpl::event_stream<> _showFinished; }; @@ -884,6 +887,10 @@ QPointer Premium::createPinnedToTop( return Ui::MakeWeak(not_null{ content }); } +void Premium::showFinished() { + _showFinished.fire({}); +} + QPointer Premium::createPinnedToBottom( not_null parent) { const auto content = Ui::CreateChild(parent.get()); @@ -892,6 +899,10 @@ QPointer Premium::createPinnedToBottom( SendScreenAccept(_controller); StartPremiumPayment(_controller, _ref); }); + _showFinished.events( + ) | rpl::take(1) | rpl::start_with_next([=] { + button->startGlareAnimation(); + }, button->lifetime()); auto text = _controller->session().api().premium().statusTextValue(); const auto status = Ui::CreateChild( content, diff --git a/Telegram/SourceFiles/ui/widgets/gradient_round_button.cpp b/Telegram/SourceFiles/ui/widgets/gradient_round_button.cpp index 429b5f063a..7b9f5e94c0 100644 --- a/Telegram/SourceFiles/ui/widgets/gradient_round_button.cpp +++ b/Telegram/SourceFiles/ui/widgets/gradient_round_button.cpp @@ -8,8 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/gradient_round_button.h" #include "ui/image/image_prepare.h" +#include "styles/style_boxes.h" namespace Ui { +namespace { + +constexpr auto kMaxGlareOpaque = 0.5; + +} // namespace GradientButton::GradientButton(QWidget *widget, QGradientStops stops) : RippleButton(widget, st::defaultRippleAnimation) @@ -21,10 +27,41 @@ void GradientButton::paintEvent(QPaintEvent *e) { validateBg(); p.drawImage(0, 0, _bg); + paintGlare(p); + const auto ripple = QColor(0, 0, 0, 36); paintRipple(p, 0, 0, &ripple); } +void GradientButton::paintGlare(QPainter &p) { + if (!_glare.glare.birthTime) { + return; + } + const auto progress = (crl::now() - _glare.glare.birthTime) + / float64(_glare.glare.deathTime - _glare.glare.birthTime); + const auto x = (-_glare.width) + (width() + _glare.width * 2) * progress; + const auto h = height(); + + const auto edgeWidth = _glare.width + st::roundRadiusLarge; + if (x > edgeWidth && x < (width() - edgeWidth)) { + p.drawTiledPixmap(x, 0, _glare.width, h, _glare.pixmap, 0, 0); + } else { + auto frame = QImage( + QSize(_glare.width, h) * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + frame.setDevicePixelRatio(style::DevicePixelRatio()); + frame.fill(Qt::transparent); + + { + Painter q(&frame); + q.drawTiledPixmap(0, 0, _glare.width, h, _glare.pixmap, 0, 0); + q.setCompositionMode(QPainter::CompositionMode_DestinationIn); + q.drawImage(-x, 0, _bg, 0, 0); + } + p.drawImage(x, 0, frame); + } +} + void GradientButton::validateBg() { const auto factor = devicePixelRatio(); if (!_bg.isNull() @@ -44,4 +81,54 @@ void GradientButton::validateBg() { _bg = Images::Round(std::move(_bg), ImageRoundRadius::Large); } +void GradientButton::validateGlare() { + if (anim::Disabled()) { + return; + } + _glare.width = st::gradientButtonGlareWidth; + _glare.animation.init([=](crl::time now) { + if (const auto diff = (now - _glare.glare.deathTime); diff > 0) { + if (diff > st::gradientButtonGlareTimeout) { + _glare.glare = Glare{ + .birthTime = now, + .deathTime = now + st::gradientButtonGlareDuration, + }; + update(); + } + } else { + update(); + } + }); + _glare.animation.start(); + { + auto pixmap = QPixmap(QSize(_glare.width, 1) + * style::DevicePixelRatio()); + pixmap.setDevicePixelRatio(style::DevicePixelRatio()); + pixmap.fill(Qt::transparent); + { + Painter p(&pixmap); + auto gradient = QLinearGradient( + QPointF(0, 0), + QPointF(_glare.width, 0)); + + auto color = st::premiumButtonFg->c; + color.setAlphaF(0); + const auto edge = color; + color.setAlphaF(kMaxGlareOpaque); + const auto middle = color; + gradient.setStops({ + { 0., edge }, + { .5, middle }, + { 1., edge }, + }); + p.fillRect(pixmap.rect(), QBrush(gradient)); + } + _glare.pixmap = std::move(pixmap); + } +} + +void GradientButton::startGlareAnimation() { + validateGlare(); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/gradient_round_button.h b/Telegram/SourceFiles/ui/widgets/gradient_round_button.h index 4aa024e849..c6453a7561 100644 --- a/Telegram/SourceFiles/ui/widgets/gradient_round_button.h +++ b/Telegram/SourceFiles/ui/widgets/gradient_round_button.h @@ -15,13 +15,29 @@ class GradientButton final : public Ui::RippleButton { public: GradientButton(QWidget *widget, QGradientStops stops); + void startGlareAnimation(); + private: void paintEvent(QPaintEvent *e); + void paintGlare(QPainter &p); void validateBg(); + void validateGlare(); + + struct Glare final { + crl::time birthTime = 0; + crl::time deathTime = 0; + }; QGradientStops _stops; QImage _bg; + struct { + Ui::Animations::Basic animation; + Glare glare; + QPixmap pixmap; + int width; + } _glare; + }; } // namespace Ui