Added glare effect to inline bot buttons while waiting response.

This commit is contained in:
23rd 2023-08-14 16:08:24 +03:00 committed by John Preston
parent 79e8b1dbca
commit 10829d4a6c
7 changed files with 162 additions and 32 deletions

View file

@ -52,7 +52,9 @@ protected:
void paintButtonLoading( void paintButtonLoading(
QPainter &p, QPainter &p,
const Ui::ChatStyle *st, 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; int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
private: private:
@ -107,7 +109,9 @@ void Style::paintButtonIcon(
void Style::paintButtonLoading( void Style::paintButtonLoading(
QPainter &p, QPainter &p,
const Ui::ChatStyle *st, 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. // Buttons with loading progress should not appear here.
} }

View file

@ -1068,7 +1068,7 @@ void ReplyKeyboard::Style::paintButton(
|| button.type == HistoryMessageMarkupButton::Type::Game) { || button.type == HistoryMessageMarkupButton::Type::Game) {
if (const auto data = button.link->getButton()) { if (const auto data = button.link->getButton()) {
if (data->requestId) { if (data->requestId) {
paintButtonLoading(p, st, rect); paintButtonLoading(p, st, rect, outerWidth, rounding);
} }
} }
} }

View file

@ -426,7 +426,9 @@ public:
virtual void paintButtonLoading( virtual void paintButtonLoading(
QPainter &p, QPainter &p,
const Ui::ChatStyle *st, const Ui::ChatStyle *st,
const QRect &rect) const = 0; const QRect &rect,
int outerWidth,
Ui::BubbleRounding rounding) const = 0;
virtual int minButtonWidth( virtual int minButtonWidth(
HistoryMessageMarkupButton::Type type) const = 0; HistoryMessageMarkupButton::Type type) const = 0;

View file

@ -21,10 +21,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_view_button.h" // ViewButton. #include "history/view/history_view_view_button.h" // ViewButton.
#include "history/history.h" #include "history/history.h"
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "ui/effects/glare.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "ui/effects/reaction_fly_animation.h" #include "ui/effects/reaction_fly_animation.h"
#include "ui/chat/message_bubble.h" #include "ui/chat/message_bubble.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/text/text_entity.h" #include "ui/text/text_entity.h"
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
@ -65,7 +67,7 @@ std::optional<Window::SessionController*> ExtractController(
class KeyboardStyle : public ReplyKeyboard::Style { class KeyboardStyle : public ReplyKeyboard::Style {
public: public:
using ReplyKeyboard::Style::Style; KeyboardStyle(const style::BotKeyboardButton &st);
Images::CornersMaskRef buttonRounding( Images::CornersMaskRef buttonRounding(
Ui::BubbleRounding outer, Ui::BubbleRounding outer,
@ -93,11 +95,29 @@ protected:
void paintButtonLoading( void paintButtonLoading(
QPainter &p, QPainter &p,
const Ui::ChatStyle *st, 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; int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
private:
using BubbleRoundingKey = uchar;
mutable base::flat_map<BubbleRoundingKey, QImage> _cachedBg;
mutable base::flat_map<BubbleRoundingKey, QPainterPath> _cachedOutline;
mutable std::unique_ptr<Ui::GlareEffect> _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( void KeyboardStyle::startPaint(
QPainter &p, QPainter &p,
const Ui::ChatStyle *st) const { const Ui::ChatStyle *st) const {
@ -112,6 +132,15 @@ const style::TextStyle &KeyboardStyle::textStyle() const {
void KeyboardStyle::repaint(not_null<const HistoryItem*> item) const { void KeyboardStyle::repaint(not_null<const HistoryItem*> item) const {
item->history()->owner().requestItemRepaint(item); 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( Images::CornersMaskRef KeyboardStyle::buttonRounding(
@ -143,15 +172,42 @@ void KeyboardStyle::paintButtonBg(
float64 howMuchOver) const { float64 howMuchOver) const {
Expects(st != nullptr); 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; using Corner = Ui::BubbleCornerRounding;
for (auto i = 0; i != 4; ++i) { auto &cachedBg = _cachedBg[rounding.key()];
corners.p[i] = (rounding[i] == Corner::Large ? large : small).p[i];
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) { if (howMuchOver > 0) {
auto o = p.opacity(); auto o = p.opacity();
p.setOpacity(o * howMuchOver); p.setOpacity(o * howMuchOver);
@ -195,11 +251,74 @@ void KeyboardStyle::paintButtonIcon(
void KeyboardStyle::paintButtonLoading( void KeyboardStyle::paintButtonLoading(
QPainter &p, QPainter &p,
const Ui::ChatStyle *st, const Ui::ChatStyle *st,
const QRect &rect) const { const QRect &rect,
int outerWidth,
Ui::BubbleRounding rounding) const {
Expects(st != nullptr); Expects(st != nullptr);
const auto &icon = st->historySendingInvertedIcon(); if (anim::Disabled()) {
icon.paint(p, rect.x() + rect.width() - icon.width() - st::msgBotKbIconPadding, rect.y() + rect.height() - icon.height() - st::msgBotKbIconPadding, rect.x() * 2 + rect.width()); 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<Ui::GlareEffect>();
_glare->width = outerWidth;
}
}
} }
int KeyboardStyle::minButtonWidth( int KeyboardStyle::minButtonWidth(

View file

@ -21,6 +21,24 @@ float64 GlareEffect::progress(crl::time now) const {
/ float64(glare.deathTime - glare.birthTime); / 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( void GlareEffect::validate(
const QColor &color, const QColor &color,
Fn<void()> updateCallback, Fn<void()> updateCallback,
@ -53,21 +71,7 @@ void GlareEffect::validate(
newPixmap.fill(Qt::transparent); newPixmap.fill(Qt::transparent);
{ {
auto p = QPainter(&newPixmap); auto p = QPainter(&newPixmap);
auto gradient = QLinearGradient( p.fillRect(newPixmap.rect(), QBrush(computeGradient(color)));
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));
} }
pixmap = std::move(newPixmap); pixmap = std::move(newPixmap);
} }

View file

@ -16,6 +16,7 @@ struct GlareEffect final {
crl::time timeout, crl::time timeout,
crl::time duration); crl::time duration);
[[nodiscard]] float64 progress(crl::time now) const; [[nodiscard]] float64 progress(crl::time now) const;
[[nodiscard]] QLinearGradient computeGradient(const QColor &color) const;
Ui::Animations::Basic animation; Ui::Animations::Basic animation;
struct { struct {

@ -1 +1 @@
Subproject commit fd55e9b71b03282de682e9b8ac01f1b6801d25a9 Subproject commit 70867536a4f499f64c0efea18b870a24043c7ce0