From 10829d4a6c2b84e3b23da2296671434accecae92 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 14 Aug 2023 16:08:24 +0300 Subject: [PATCH] Added glare effect to inline bot buttons while waiting response. --- .../SourceFiles/chat_helpers/bot_keyboard.cpp | 8 +- .../history/history_item_components.cpp | 2 +- .../history/history_item_components.h | 4 +- .../history/view/history_view_message.cpp | 143 ++++++++++++++++-- Telegram/SourceFiles/ui/effects/glare.cpp | 34 +++-- Telegram/SourceFiles/ui/effects/glare.h | 1 + Telegram/lib_ui | 2 +- 7 files changed, 162 insertions(+), 32 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp index 93ee5f624..5344d1184 100644 --- a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp +++ b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp @@ -52,7 +52,9 @@ protected: void paintButtonLoading( QPainter &p, const Ui::ChatStyle *st, - const QRect &rect) const override; + const QRect &rect, + int outerWidth, + Ui::BubbleRounding rounding) const override; int minButtonWidth(HistoryMessageMarkupButton::Type type) const override; private: @@ -107,7 +109,9 @@ void Style::paintButtonIcon( void Style::paintButtonLoading( QPainter &p, const Ui::ChatStyle *st, - const QRect &rect) const { + const QRect &rect, + int outerWidth, + Ui::BubbleRounding rounding) const { // Buttons with loading progress should not appear here. } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index dc648b811..f9e1c403f 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -1068,7 +1068,7 @@ void ReplyKeyboard::Style::paintButton( || button.type == HistoryMessageMarkupButton::Type::Game) { if (const auto data = button.link->getButton()) { if (data->requestId) { - paintButtonLoading(p, st, rect); + paintButtonLoading(p, st, rect, outerWidth, rounding); } } } diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index e743932b9..75581f969 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -426,7 +426,9 @@ public: virtual void paintButtonLoading( QPainter &p, const Ui::ChatStyle *st, - const QRect &rect) const = 0; + const QRect &rect, + int outerWidth, + Ui::BubbleRounding rounding) const = 0; virtual int minButtonWidth( HistoryMessageMarkupButton::Type type) const = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index d3ea404c6..e562ce6c6 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -21,10 +21,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_view_button.h" // ViewButton. #include "history/history.h" #include "boxes/share_box.h" +#include "ui/effects/glare.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/reaction_fly_animation.h" #include "ui/chat/message_bubble.h" #include "ui/chat/chat_style.h" +#include "ui/rect.h" #include "ui/text/text_utilities.h" #include "ui/text/text_entity.h" #include "ui/cached_round_corners.h" @@ -65,7 +67,7 @@ std::optional ExtractController( class KeyboardStyle : public ReplyKeyboard::Style { public: - using ReplyKeyboard::Style::Style; + KeyboardStyle(const style::BotKeyboardButton &st); Images::CornersMaskRef buttonRounding( Ui::BubbleRounding outer, @@ -93,11 +95,29 @@ protected: void paintButtonLoading( QPainter &p, const Ui::ChatStyle *st, - const QRect &rect) const override; + const QRect &rect, + int outerWidth, + Ui::BubbleRounding rounding) const override; int minButtonWidth(HistoryMessageMarkupButton::Type type) const override; +private: + using BubbleRoundingKey = uchar; + mutable base::flat_map _cachedBg; + mutable base::flat_map _cachedOutline; + mutable std::unique_ptr _glare; + rpl::lifetime _lifetime; + }; +KeyboardStyle::KeyboardStyle(const style::BotKeyboardButton &st) +: ReplyKeyboard::Style(st) { + style::PaletteChanged( + ) | rpl::start_with_next([=] { + _cachedBg = {}; + _cachedOutline = {}; + }, _lifetime); +} + void KeyboardStyle::startPaint( QPainter &p, const Ui::ChatStyle *st) const { @@ -112,6 +132,15 @@ const style::TextStyle &KeyboardStyle::textStyle() const { void KeyboardStyle::repaint(not_null item) const { item->history()->owner().requestItemRepaint(item); + if (_glare && !_glare->glare.birthTime) { + constexpr auto kTimeout = crl::time(0); + constexpr auto kDuration = crl::time(1100); + _glare->validate( + st::premiumButtonFg->c, + [=] { repaint(item); }, + kTimeout, + kDuration); + } } Images::CornersMaskRef KeyboardStyle::buttonRounding( @@ -143,15 +172,42 @@ void KeyboardStyle::paintButtonBg( float64 howMuchOver) const { Expects(st != nullptr); - const auto sti = &st->imageStyle(false); - const auto &small = sti->msgServiceBgCornersSmall; - const auto &large = sti->msgServiceBgCornersLarge; - auto corners = Ui::CornersPixmaps(); using Corner = Ui::BubbleCornerRounding; - for (auto i = 0; i != 4; ++i) { - corners.p[i] = (rounding[i] == Corner::Large ? large : small).p[i]; + auto &cachedBg = _cachedBg[rounding.key()]; + + if (cachedBg.isNull() + || cachedBg.width() != (rect.width() * style::DevicePixelRatio())) { + cachedBg = QImage( + rect.size() * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + cachedBg.setDevicePixelRatio(style::DevicePixelRatio()); + cachedBg.fill(Qt::transparent); + { + auto painter = QPainter(&cachedBg); + + const auto sti = &st->imageStyle(false); + const auto &small = sti->msgServiceBgCornersSmall; + const auto &large = sti->msgServiceBgCornersLarge; + auto corners = Ui::CornersPixmaps(); + int radiuses[4]; + for (auto i = 0; i != 4; ++i) { + const auto isLarge = (rounding[i] == Corner::Large); + corners.p[i] = (isLarge ? large : small).p[i]; + radiuses[i] = Ui::CachedCornerRadiusValue(isLarge + ? Ui::CachedCornerRadius::BubbleLarge + : Ui::CachedCornerRadius::BubbleSmall); + } + const auto r = Rect(rect.size()); + _cachedOutline[rounding.key()] = Ui::ComplexRoundedRectPath( + r - Margins(st::lineWidth), + radiuses[0], + radiuses[1], + radiuses[2], + radiuses[3]); + Ui::FillRoundRect(painter, r, sti->msgServiceBg, corners); + } } - Ui::FillRoundRect(p, rect, sti->msgServiceBg, corners); + p.drawImage(rect.topLeft(), cachedBg); if (howMuchOver > 0) { auto o = p.opacity(); p.setOpacity(o * howMuchOver); @@ -195,11 +251,74 @@ void KeyboardStyle::paintButtonIcon( void KeyboardStyle::paintButtonLoading( QPainter &p, const Ui::ChatStyle *st, - const QRect &rect) const { + const QRect &rect, + int outerWidth, + Ui::BubbleRounding rounding) const { Expects(st != nullptr); - const auto &icon = st->historySendingInvertedIcon(); - icon.paint(p, rect.x() + rect.width() - icon.width() - st::msgBotKbIconPadding, rect.y() + rect.height() - icon.height() - st::msgBotKbIconPadding, rect.x() * 2 + rect.width()); + if (anim::Disabled()) { + const auto &icon = st->historySendingInvertedIcon(); + icon.paint( + p, + rect::right(rect) - icon.width() - st::msgBotKbIconPadding, + rect::bottom(rect) - icon.height() - st::msgBotKbIconPadding, + rect.x() * 2 + rect.width()); + return; + } + + const auto cacheKey = rounding.key(); + auto &cachedBg = _cachedBg[cacheKey]; + if (!cachedBg.isNull()) { + if (_glare && _glare->glare.birthTime) { + const auto progress = _glare->progress(crl::now()); + const auto w = _glare->width; + const auto h = rect.height(); + const auto x = (-w) + (w * 2) * progress; + + auto frame = cachedBg; + frame.fill(Qt::transparent); + { + auto painter = QPainter(&frame); + auto hq = PainterHighQualityEnabler(painter); + painter.setPen(Qt::NoPen); + painter.drawTiledPixmap(x, 0, w, h, _glare->pixmap, 0, 0); + + auto path = QPainterPath(); + path.addRect(Rect(rect.size())); + path -= _cachedOutline[cacheKey]; + + constexpr auto kBgOutlineAlpha = 0.5; + constexpr auto kFgOutlineAlpha = 0.8; + const auto &c = st::premiumButtonFg->c; + painter.setPen(Qt::NoPen); + painter.setBrush(c); + painter.setOpacity(kBgOutlineAlpha); + painter.drawPath(path); + auto gradient = QLinearGradient(-w, 0, w * 2, 0); + { + constexpr auto kShiftLeft = 0.01; + constexpr auto kShiftRight = 0.99; + auto stops = _glare->computeGradient(c).stops(); + stops[1] = { + std::clamp(progress, kShiftLeft, kShiftRight), + QColor(c.red(), c.green(), c.blue(), kFgOutlineAlpha), + }; + gradient.setStops(std::move(stops)); + } + painter.setBrush(QBrush(gradient)); + painter.setOpacity(1); + painter.drawPath(path); + + painter.setCompositionMode( + QPainter::CompositionMode_DestinationIn); + painter.drawImage(0, 0, cachedBg); + } + p.drawImage(rect.x(), rect.y(), frame); + } else { + _glare = std::make_unique(); + _glare->width = outerWidth; + } + } } int KeyboardStyle::minButtonWidth( diff --git a/Telegram/SourceFiles/ui/effects/glare.cpp b/Telegram/SourceFiles/ui/effects/glare.cpp index e5cda2a29..e326982df 100644 --- a/Telegram/SourceFiles/ui/effects/glare.cpp +++ b/Telegram/SourceFiles/ui/effects/glare.cpp @@ -21,6 +21,24 @@ float64 GlareEffect::progress(crl::time now) const { / float64(glare.deathTime - glare.birthTime); } +QLinearGradient GlareEffect::computeGradient(const QColor &color) const { + auto gradient = QLinearGradient( + QPointF(0, 0), + QPointF(width, 0)); + + auto tempColor = color; + tempColor.setAlphaF(0); + const auto edge = tempColor; + tempColor.setAlphaF(kMaxGlareOpaque); + const auto middle = tempColor; + gradient.setStops({ + { 0., edge }, + { .5, middle }, + { 1., edge }, + }); + return gradient; +} + void GlareEffect::validate( const QColor &color, Fn updateCallback, @@ -53,21 +71,7 @@ void GlareEffect::validate( newPixmap.fill(Qt::transparent); { auto p = QPainter(&newPixmap); - auto gradient = QLinearGradient( - QPointF(0, 0), - QPointF(width, 0)); - - auto tempColor = color; - tempColor.setAlphaF(0); - const auto edge = tempColor; - tempColor.setAlphaF(kMaxGlareOpaque); - const auto middle = tempColor; - gradient.setStops({ - { 0., edge }, - { .5, middle }, - { 1., edge }, - }); - p.fillRect(newPixmap.rect(), QBrush(gradient)); + p.fillRect(newPixmap.rect(), QBrush(computeGradient(color))); } pixmap = std::move(newPixmap); } diff --git a/Telegram/SourceFiles/ui/effects/glare.h b/Telegram/SourceFiles/ui/effects/glare.h index cebf1a9a0..ce416bc2d 100644 --- a/Telegram/SourceFiles/ui/effects/glare.h +++ b/Telegram/SourceFiles/ui/effects/glare.h @@ -16,6 +16,7 @@ struct GlareEffect final { crl::time timeout, crl::time duration); [[nodiscard]] float64 progress(crl::time now) const; + [[nodiscard]] QLinearGradient computeGradient(const QColor &color) const; Ui::Animations::Basic animation; struct { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index fd55e9b71..70867536a 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit fd55e9b71b03282de682e9b8ac01f1b6801d25a9 +Subproject commit 70867536a4f499f64c0efea18b870a24043c7ce0