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(
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.
}

View File

@ -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);
}
}
}

View File

@ -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;

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/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<Window::SessionController*> 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<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(
QPainter &p,
const Ui::ChatStyle *st) const {
@ -112,6 +132,15 @@ const style::TextStyle &KeyboardStyle::textStyle() const {
void KeyboardStyle::repaint(not_null<const HistoryItem*> 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<Ui::GlareEffect>();
_glare->width = outerWidth;
}
}
}
int KeyboardStyle::minButtonWidth(

View File

@ -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<void()> 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);
}

View File

@ -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 {

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