diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index f8ff6ec7c..35bedab59 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_premium.h" #include "ui/basic_click_handlers.h" // UrlClickHandler::Open. #include "ui/effects/premium_graphics.h" +#include "ui/effects/premium_stars.h" #include "ui/layers/generic_box.h" #include "ui/special_buttons.h" #include "ui/text/format_values.h" @@ -89,6 +90,76 @@ GiftOptions GiftOptionFromTL( return result; } +class ColoredMiniStars final { +public: + ColoredMiniStars(not_null<Ui::RpWidget*> parent); + + void setSize(const QSize &size); + void setPosition(QPoint position); + void paint(Painter &p); + +private: + Ui::Premium::MiniStars _ministars; + QRectF _ministarsRect; + QImage _frame; + QImage _mask; + QSize _size; + QPoint _position; + +}; + +ColoredMiniStars::ColoredMiniStars(not_null<Ui::RpWidget*> parent) +: _ministars([=](const QRect &r) { + parent->update(r.translated(_position)); +}, true) { +} + +void ColoredMiniStars::setSize(const QSize &size) { + _frame = QImage( + size * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + _frame.setDevicePixelRatio(style::DevicePixelRatio()); + + _mask = _frame; + _mask.fill(Qt::transparent); + { + Painter p(&_mask); + auto gradient = QLinearGradient(0, 0, size.width(), 0); + gradient.setStops(Ui::Premium::GiftGradientStops()); + p.setPen(Qt::NoPen); + p.setBrush(gradient); + p.drawRect(0, 0, size.width(), size.height()); + } + + _size = size; + + { + const auto s = _size / Ui::Premium::MiniStars::kSizeFactor; + const auto margins = QMarginsF( + s.width() / 2., + s.height() / 2., + s.width() / 2., + s.height() / 2.); + _ministarsRect = QRectF(QPointF(), _size) - margins; + } +} + +void ColoredMiniStars::setPosition(QPoint position) { + _position = std::move(position); +} + +void ColoredMiniStars::paint(Painter &p) { + _frame.fill(Qt::transparent); + { + Painter q(&_frame); + _ministars.paint(q, _ministarsRect); + q.setCompositionMode(QPainter::CompositionMode_SourceIn); + q.drawImage(0, 0, _mask); + } + + p.drawImage(_position, _frame); +} + void GiftBox( not_null<Ui::GenericBox*> box, not_null<Window::SessionController*> controller, @@ -111,6 +182,8 @@ void GiftBox( + userpicPadding.bottom() + st::defaultUserpicButton.size.height())); + const auto stars = box->lifetime().make_state<ColoredMiniStars>(top); + const auto userpic = Ui::CreateChild<Ui::UserpicButton>( top, user, @@ -122,8 +195,26 @@ void GiftBox( userpic->moveToLeft( (width - userpic->width()) / 2, userpicPadding.top()); + + const auto center = top->rect().center(); + const auto size = QSize( + userpic->width() * Ui::Premium::MiniStars::kSizeFactor, + userpic->height()); + const auto ministarsRect = QRect( + QPoint(center.x() - size.width(), center.y() - size.height()), + QPoint(center.x() + size.width(), center.y() + size.height())); + stars->setPosition(ministarsRect.topLeft()); + stars->setSize(ministarsRect.size()); }, userpic->lifetime()); + top->paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + Painter p(top); + + p.fillRect(r, Qt::transparent); + stars->paint(p); + }, top->lifetime()); + const auto close = Ui::CreateChild<Ui::IconButton>( buttonsParent, st::infoTopBarClose); diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 1e7d68f80..3afde7749 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_premium.h" -#include "base/random.h" #include "core/application.h" #include "core/click_handler_types.h" #include "data/data_peer_values.h" @@ -19,9 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_premium.h" #include "ui/abstract_button.h" #include "ui/basic_click_handlers.h" -#include "ui/effects/animation_value_f.h" #include "ui/effects/gradient.h" #include "ui/effects/premium_graphics.h" +#include "ui/effects/premium_stars.h" #include "ui/text/text_utilities.h" #include "ui/text/format_values.h" #include "ui/layers/generic_box.h" @@ -46,8 +45,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_settings.h" -#include <QSvgRenderer> - namespace Settings { namespace { @@ -58,8 +55,6 @@ constexpr auto kTitleAnimationPart = 0.15; constexpr auto kTitleAdditionalScale = 0.15; -constexpr auto kDeformationMax = 0.1; - struct Entry { const style::icon *icon; rpl::producer<QString> title; @@ -230,187 +225,6 @@ void SendScreenAccept(not_null<Window::SessionController*> controller) { MTP_jsonNull()); } -class MiniStars final { -public: - MiniStars(Fn<void(const QRect &r)> updateCallback); - - void paint(Painter &p, const QRectF &rect); - void setPaused(bool paused); - -private: - struct MiniStar { - crl::time birthTime = 0; - crl::time deathTime = 0; - int angle = 0; - float64 size = 0.; - float64 alpha = 0.; - float64 sinFactor = 0.; - }; - - struct Interval { - int from = 0; - int length = 0; - }; - - void createStar(crl::time now); - [[nodiscard]] int angle() const; - [[nodiscard]] crl::time timeNow() const; - [[nodiscard]] int randomInterval(const Interval &interval) const; - - const std::vector<Interval> _availableAngles; - const Interval _lifeLength; - const Interval _deathTime; - const Interval _size; - const Interval _alpha; - const Interval _sinFactor; - - const float64 _appearProgressTill; - const float64 _disappearProgressAfter; - const float64 _distanceProgressStart; - - QSvgRenderer _sprite; - - Ui::Animations::Basic _animation; - - std::vector<MiniStar> _ministars; - - crl::time _nextBirthTime = 0; - bool _paused = false; - - QRect _rectToUpdate; - -}; - -MiniStars::MiniStars(Fn<void(const QRect &r)> updateCallback) -: _availableAngles({ - Interval{ -10, 40 }, - Interval{ 180 + 10 - 40, 40 }, - Interval{ 180 + 15, 50 }, - Interval{ -15 - 50, 50 }, -}) -, _lifeLength({ 150, 200 }) -, _deathTime({ 1500, 2000 }) -, _size({ 10, 20 }) -, _alpha({ 40, 60 }) -, _sinFactor({ 10, 190 }) -, _appearProgressTill(0.2) -, _disappearProgressAfter(0.8) -, _distanceProgressStart(0.5) -, _sprite(u":/gui/icons/settings/starmini.svg"_q) -, _animation([=](crl::time now) { - if (now > _nextBirthTime && !_paused) { - createStar(now); - _nextBirthTime = now + randomInterval(_lifeLength); - } - if (_rectToUpdate.isValid()) { - updateCallback(base::take(_rectToUpdate)); - } -}) { - if (anim::Disabled()) { - const auto from = _deathTime.from + _deathTime.length; - for (auto i = -from; i < 0; i += randomInterval(_lifeLength)) { - createStar(i); - } - updateCallback(_rectToUpdate); - } else { - _animation.start(); - } -} - -int MiniStars::randomInterval(const Interval &interval) const { - return interval.from + base::RandomIndex(interval.length); -} - -crl::time MiniStars::timeNow() const { - return anim::Disabled() ? 0 : crl::now(); -} - -void MiniStars::paint(Painter &p, const QRectF &rect) { - const auto center = rect.center(); - const auto opacity = p.opacity(); - for (const auto &ministar : _ministars) { - const auto progress = (timeNow() - ministar.birthTime) - / float64(ministar.deathTime - ministar.birthTime); - if (progress > 1.) { - continue; - } - const auto appearProgress = std::clamp( - progress / _appearProgressTill, - 0., - 1.); - const auto rsin = float(std::sin(ministar.angle * M_PI / 180.)); - const auto rcos = float(std::cos(ministar.angle * M_PI / 180.)); - const auto end = QPointF( - rect.width() / 1.5 * rcos, - rect.height() / 1.5 * rsin); - - const auto alphaProgress = 1. - - (std::clamp(progress - _disappearProgressAfter, 0., 1.) - / (1. - _disappearProgressAfter)); - p.setOpacity(ministar.alpha - * alphaProgress - * appearProgress - * opacity); - - const auto deformResult = progress * 360; - const auto rsinDeform = float( - std::sin(ministar.sinFactor * deformResult * M_PI / 180.)); - const auto deformH = 1. + kDeformationMax * rsinDeform; - const auto deformW = 1. / deformH; - - const auto distanceProgress = _distanceProgressStart + progress; - const auto starSide = ministar.size * appearProgress; - const auto widthFade = (std::abs(rcos) >= std::abs(rsin)); - const auto starWidth = starSide - * (widthFade ? alphaProgress : 1.) - * deformW; - const auto starHeight = starSide - * (!widthFade ? alphaProgress : 1.) - * deformH; - const auto renderRect = QRectF( - center.x() - + anim::interpolateF(0, end.x(), distanceProgress) - - starWidth / 2., - center.y() - + anim::interpolateF(0, end.y(), distanceProgress) - - starHeight / 2., - starWidth, - starHeight); - _sprite.render(&p, renderRect); - _rectToUpdate |= renderRect.toRect(); - } - p.setOpacity(opacity); -} - -void MiniStars::setPaused(bool paused) { - _paused = paused; -} - -int MiniStars::angle() const { - const auto &interval = _availableAngles[ - base::RandomIndex(_availableAngles.size())]; - return base::RandomIndex(interval.length) + interval.from; -} - -void MiniStars::createStar(crl::time now) { - auto ministar = MiniStar{ - .birthTime = now, - .deathTime = now + randomInterval(_deathTime), - .angle = angle(), - .size = float64(randomInterval(_size)), - .alpha = float64(randomInterval(_alpha)) / 100., - .sinFactor = randomInterval(_sinFactor) / 100. - * (base::RandomIndex(2) == 1 ? 1. : -1.), - }; - for (auto i = 0; i < _ministars.size(); i++) { - if (ministar.birthTime > _ministars[i].deathTime) { - _ministars[i] = ministar; - return; - } - } - _ministars.push_back(ministar); -} - class TopBar final : public Ui::RpWidget { public: TopBar( @@ -435,7 +249,7 @@ private: const style::font &_titleFont; const style::margins &_titlePadding; object_ptr<Ui::FlatLabel> _about; - MiniStars _ministars; + Ui::Premium::MiniStars _ministars; QSvgRenderer _star; struct { diff --git a/Telegram/SourceFiles/ui/effects/premium_stars.cpp b/Telegram/SourceFiles/ui/effects/premium_stars.cpp new file mode 100644 index 000000000..ff5c20e75 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/premium_stars.cpp @@ -0,0 +1,150 @@ +/* +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 "ui/effects/premium_stars.h" + +#include "base/random.h" +#include "ui/effects/animation_value_f.h" + +namespace Ui { +namespace Premium { + +constexpr auto kDeformationMax = 0.1; + +MiniStars::MiniStars(Fn<void(const QRect &r)> updateCallback, bool opaque) +: _availableAngles({ + Interval{ -10, 40 }, + Interval{ 180 + 10 - 40, 40 }, + Interval{ 180 + 15, 50 }, + Interval{ -15 - 50, 50 }, +}) +, _lifeLength({ 150, 200 }) +, _deathTime({ 1500, 2000 }) +, _size({ 10, 20 }) +, _alpha({ opaque ? 100 : 40, opaque ? 100 : 60 }) +, _sinFactor({ 10, 190 }) +, _appearProgressTill(0.2) +, _disappearProgressAfter(0.8) +, _distanceProgressStart(0.5) +, _sprite(u":/gui/icons/settings/starmini.svg"_q) +, _animation([=](crl::time now) { + if (now > _nextBirthTime && !_paused) { + createStar(now); + _nextBirthTime = now + randomInterval(_lifeLength); + } + if (_rectToUpdate.isValid()) { + updateCallback(base::take(_rectToUpdate)); + } +}) { + if (anim::Disabled()) { + const auto from = _deathTime.from + _deathTime.length; + for (auto i = -from; i < 0; i += randomInterval(_lifeLength)) { + createStar(i); + } + updateCallback(_rectToUpdate); + } else { + _animation.start(); + } +} + +int MiniStars::randomInterval(const Interval &interval) const { + return interval.from + base::RandomIndex(interval.length); +} + +crl::time MiniStars::timeNow() const { + return anim::Disabled() ? 0 : crl::now(); +} + +void MiniStars::paint(Painter &p, const QRectF &rect) { + const auto center = rect.center(); + const auto opacity = p.opacity(); + for (const auto &ministar : _ministars) { + const auto progress = (timeNow() - ministar.birthTime) + / float64(ministar.deathTime - ministar.birthTime); + if (progress > 1.) { + continue; + } + const auto appearProgress = std::clamp( + progress / _appearProgressTill, + 0., + 1.); + const auto rsin = float(std::sin(ministar.angle * M_PI / 180.)); + const auto rcos = float(std::cos(ministar.angle * M_PI / 180.)); + const auto end = QPointF( + rect.width() / kSizeFactor * rcos, + rect.height() / kSizeFactor * rsin); + + const auto alphaProgress = 1. + - (std::clamp(progress - _disappearProgressAfter, 0., 1.) + / (1. - _disappearProgressAfter)); + p.setOpacity(ministar.alpha + * alphaProgress + * appearProgress + * opacity); + + const auto deformResult = progress * 360; + const auto rsinDeform = float( + std::sin(ministar.sinFactor * deformResult * M_PI / 180.)); + const auto deformH = 1. + kDeformationMax * rsinDeform; + const auto deformW = 1. / deformH; + + const auto distanceProgress = _distanceProgressStart + progress; + const auto starSide = ministar.size * appearProgress; + const auto widthFade = (std::abs(rcos) >= std::abs(rsin)); + const auto starWidth = starSide + * (widthFade ? alphaProgress : 1.) + * deformW; + const auto starHeight = starSide + * (!widthFade ? alphaProgress : 1.) + * deformH; + const auto renderRect = QRectF( + center.x() + + anim::interpolateF(0, end.x(), distanceProgress) + - starWidth / 2., + center.y() + + anim::interpolateF(0, end.y(), distanceProgress) + - starHeight / 2., + starWidth, + starHeight); + _sprite.render(&p, renderRect); + _rectToUpdate |= renderRect.toRect(); + } + p.setOpacity(opacity); +} + +void MiniStars::setPaused(bool paused) { + _paused = paused; +} + +int MiniStars::angle() const { + const auto &interval = _availableAngles[ + base::RandomIndex(_availableAngles.size())]; + return base::RandomIndex(interval.length) + interval.from; +} + +void MiniStars::createStar(crl::time now) { + auto ministar = MiniStar{ + .birthTime = now, + .deathTime = now + randomInterval(_deathTime), + .angle = angle(), + .size = float64(randomInterval(_size)), + .alpha = float64(randomInterval(_alpha)) / 100., + .sinFactor = randomInterval(_sinFactor) / 100. + * (base::RandomIndex(2) == 1 ? 1. : -1.), + }; + for (auto i = 0; i < _ministars.size(); i++) { + if (ministar.birthTime > _ministars[i].deathTime) { + _ministars[i] = ministar; + return; + } + } + _ministars.push_back(ministar); +} + + +} // namespace Premium +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/premium_stars.h b/Telegram/SourceFiles/ui/effects/premium_stars.h new file mode 100644 index 000000000..be71db324 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/premium_stars.h @@ -0,0 +1,71 @@ +/* +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 <QSvgRenderer> + +namespace Ui { +namespace Premium { + +class MiniStars final { +public: + MiniStars(Fn<void(const QRect &r)> updateCallback, bool opaque = false); + + void paint(Painter &p, const QRectF &rect); + void setPaused(bool paused); + + static constexpr auto kSizeFactor = 1.5; + +private: + struct MiniStar { + crl::time birthTime = 0; + crl::time deathTime = 0; + int angle = 0; + float64 size = 0.; + float64 alpha = 0.; + float64 sinFactor = 0.; + }; + + struct Interval { + int from = 0; + int length = 0; + }; + + void createStar(crl::time now); + [[nodiscard]] int angle() const; + [[nodiscard]] crl::time timeNow() const; + [[nodiscard]] int randomInterval(const Interval &interval) const; + + const std::vector<Interval> _availableAngles; + const Interval _lifeLength; + const Interval _deathTime; + const Interval _size; + const Interval _alpha; + const Interval _sinFactor; + + const float64 _appearProgressTill; + const float64 _disappearProgressAfter; + const float64 _distanceProgressStart; + + QSvgRenderer _sprite; + + Ui::Animations::Basic _animation; + + std::vector<MiniStar> _ministars; + + crl::time _nextBirthTime = 0; + bool _paused = false; + + QRect _rectToUpdate; + +}; + +} // namespace Premium +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 9724f709a..79ae20773 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -229,6 +229,8 @@ PRIVATE ui/effects/fireworks_animation.h ui/effects/premium_graphics.cpp ui/effects/premium_graphics.h + ui/effects/premium_stars.cpp + ui/effects/premium_stars.h ui/effects/round_checkbox.cpp ui/effects/round_checkbox.h ui/effects/scroll_content_shadow.cpp