Nice reactions panel expanding.

This commit is contained in:
John Preston 2022-08-23 17:47:26 +03:00
parent 20d4d00634
commit 0277d765bb
17 changed files with 253 additions and 175 deletions

View file

@ -1268,13 +1268,12 @@ void StickerSetBox::Inner::paintSticker(
(_singleSize.height() - size.height()) / 2); (_singleSize.height() - size.height()) / 2);
auto lottieFrame = QImage(); auto lottieFrame = QImage();
if (element.emoji) { if (element.emoji) {
element.emoji->paint( element.emoji->paint(p, {
p, .preview = st::windowBgOver->c,
ppos.x(), .now = now,
ppos.y(), .position = ppos,
now, .paused = paused,
st::windowBgOver->c, });
paused);
} else if (element.lottie && element.lottie->ready()) { } else if (element.lottie && element.lottie->ready()) {
lottieFrame = element.lottie->frame(); lottieFrame = element.lottie->frame();
p.drawImage( p.drawImage(

View file

@ -40,6 +40,7 @@ namespace ChatHelpers {
namespace { namespace {
constexpr auto kCollapsedRows = 3; constexpr auto kCollapsedRows = 3;
constexpr auto kAppearDuration = 0.3;
using Core::RecentEmojiId; using Core::RecentEmojiId;
using Core::RecentEmojiDocument; using Core::RecentEmojiDocument;
@ -486,15 +487,19 @@ auto EmojiListWidget::premiumChosen() const
void EmojiListWidget::paintExpanding( void EmojiListWidget::paintExpanding(
QPainter &p, QPainter &p,
QRect clip, QRect clip,
int finalBottom,
float64 progress,
RectPart origin) { RectPart origin) {
const auto shift = clip.topLeft(); const auto shift = clip.topLeft();
const auto adjusted = clip.translated(-shift); const auto adjusted = clip.translated(-shift);
const auto finalHeight = (finalBottom - clip.y());
p.translate(shift); p.translate(shift);
p.setClipRect(adjusted); p.setClipRect(adjusted);
const auto context = ExpandingContext{ paint(p, ExpandingContext{
.progress = progress,
.finalHeight = finalHeight,
.expanding = true, .expanding = true,
}; }, adjusted);
paint(p, context, adjusted);
p.translate(-shift); p.translate(-shift);
} }
@ -778,7 +783,7 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
void EmojiListWidget::paint( void EmojiListWidget::paint(
QPainter &p, QPainter &p,
const ExpandingContext &context, ExpandingContext context,
QRect clip) { QRect clip) {
auto fromColumn = floorclamp( auto fromColumn = floorclamp(
clip.x() - _rowsLeft, clip.x() - _rowsLeft,
@ -796,6 +801,7 @@ void EmojiListWidget::paint(
toColumn = _columnCount - toColumn; toColumn = _columnCount - toColumn;
} }
const auto expandProgress = context.progress;
const auto paused = this->paused(); const auto paused = this->paused();
const auto now = crl::now(); const auto now = crl::now();
auto selectedButton = std::get_if<OverButton>(!v::is_null(_pressed) auto selectedButton = std::get_if<OverButton>(!v::is_null(_pressed)
@ -866,10 +872,26 @@ void EmojiListWidget::paint(
const auto selected = (state == _selected) const auto selected = (state == _selected)
|| (!_picker->isHidden() || (!_picker->isHidden()
&& state == _pickerSelected); && state == _pickerSelected);
auto w = QPoint( const auto position = QPoint(
_rowsLeft + j * _singleSize.width(), _rowsLeft + j * _singleSize.width(),
info.rowsTop + i * _singleSize.height() info.rowsTop + i * _singleSize.height()
) + _areaPosition; );
const auto w = position + _areaPosition;
if (context.expanding) {
const auto y = (position.y() - st().padding.top());
const auto x = (position.x() - _rowsLeft);
const auto sum = y
+ std::max(std::min(y, width()) - x, 0);
const auto maxSum = context.finalHeight
+ std::min(context.finalHeight, width());
const auto started = (sum / float64(maxSum))
- kAppearDuration;
context.progress = (expandProgress <= started)
? 0.
: (expandProgress >= started + kAppearDuration)
? 1.
: ((expandProgress - started) / kAppearDuration);
}
if (info.collapsed if (info.collapsed
&& index + 1 == _columnCount * kCollapsedRows) { && index + 1 == _columnCount * kCollapsedRows) {
drawCollapsedBadge(p, w - _areaPosition, info.count); drawCollapsedBadge(p, w - _areaPosition, info.count);
@ -883,12 +905,12 @@ void EmojiListWidget::paint(
_overBg.paint(p, QRect(tl, st::emojiPanArea)); _overBg.paint(p, QRect(tl, st::emojiPanArea));
} }
if (info.section == int(Section::Recent)) { if (info.section == int(Section::Recent)) {
drawRecent(p, w, now, paused, index); drawRecent(p, context, w, now, paused, index);
} else if (info.section < _staticCount) { } else if (info.section < _staticCount) {
drawEmoji(p, w, _emoji[info.section][index]); drawEmoji(p, context, w, _emoji[info.section][index]);
} else { } else {
const auto set = info.section - _staticCount; const auto set = info.section - _staticCount;
drawCustom(p, w, now, paused, set, index); drawCustom(p, context, w, now, paused, set, index);
} }
} }
} }
@ -919,6 +941,7 @@ void EmojiListWidget::drawCollapsedBadge(
void EmojiListWidget::drawRecent( void EmojiListWidget::drawRecent(
QPainter &p, QPainter &p,
const ExpandingContext &context,
QPoint position, QPoint position,
crl::time now, crl::time now,
bool paused, bool paused,
@ -932,23 +955,25 @@ void EmojiListWidget::drawRecent(
) - _areaPosition; ) - _areaPosition;
p.drawImage(position, _premiumIcon->image()); p.drawImage(position, _premiumIcon->image());
} else { } else {
drawEmoji(p, position, *emoji); drawEmoji(p, context, position, *emoji);
} }
} else { } else {
Assert(_recent[index].custom != nullptr); Assert(_recent[index].custom != nullptr);
position += _innerPosition + _customPosition; position += _innerPosition + _customPosition;
_recent[index].custom->paint( _recent[index].custom->paint(p, {
p, .preview = st::windowBgRipple->c,
position.x(), .now = now,
position.y(), .scale = context.progress,
now, .position = position,
st::windowBgRipple->c, .paused = paused,
paused); .scaled = context.expanding,
});
} }
} }
void EmojiListWidget::drawEmoji( void EmojiListWidget::drawEmoji(
QPainter &p, QPainter &p,
const ExpandingContext &context,
QPoint position, QPoint position,
EmojiPtr emoji) { EmojiPtr emoji) {
position += _innerPosition; position += _innerPosition;
@ -962,6 +987,7 @@ void EmojiListWidget::drawEmoji(
void EmojiListWidget::drawCustom( void EmojiListWidget::drawCustom(
QPainter &p, QPainter &p,
const ExpandingContext &context,
QPoint position, QPoint position,
crl::time now, crl::time now,
bool paused, bool paused,
@ -969,13 +995,14 @@ void EmojiListWidget::drawCustom(
int index) { int index) {
position += _innerPosition + _customPosition; position += _innerPosition + _customPosition;
_custom[set].painted = true; _custom[set].painted = true;
_custom[set].list[index].custom->paint( _custom[set].list[index].custom->paint(p, {
p, .preview = st::windowBgRipple->c,
position.x(), .now = now,
position.y(), .scale = context.progress,
now, .position = position,
st::windowBgRipple->c, .paused = paused,
paused); .scaled = context.expanding,
});
} }
bool EmojiListWidget::checkPickerHide() { bool EmojiListWidget::checkPickerHide() {

View file

@ -112,7 +112,12 @@ public:
[[nodiscard]] auto premiumChosen() const [[nodiscard]] auto premiumChosen() const
-> rpl::producer<not_null<DocumentData*>>; -> rpl::producer<not_null<DocumentData*>>;
void paintExpanding(QPainter &p, QRect clip, RectPart origin); void paintExpanding(
QPainter &p,
QRect clip,
int finalBottom,
float64 progress,
RectPart origin);
protected: protected:
void visibleTopBottomUpdated( void visibleTopBottomUpdated(
@ -208,6 +213,8 @@ private:
OverSet, OverSet,
OverButton>; OverButton>;
struct ExpandingContext { struct ExpandingContext {
float64 progress = 0.;
int finalHeight = 0;
bool expanding = false; bool expanding = false;
}; };
@ -235,20 +242,23 @@ private:
[[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const; [[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const;
void selectEmoji(EmojiPtr emoji); void selectEmoji(EmojiPtr emoji);
void selectCustom(not_null<DocumentData*> document); void selectCustom(not_null<DocumentData*> document);
void paint(QPainter &p, const ExpandingContext &context, QRect clip); void paint(QPainter &p, ExpandingContext context, QRect clip);
void drawCollapsedBadge(QPainter &p, QPoint position, int count); void drawCollapsedBadge(QPainter &p, QPoint position, int count);
void drawRecent( void drawRecent(
QPainter &p, QPainter &p,
const ExpandingContext &context,
QPoint position, QPoint position,
crl::time now, crl::time now,
bool paused, bool paused,
int index); int index);
void drawEmoji( void drawEmoji(
QPainter &p, QPainter &p,
const ExpandingContext &context,
QPoint position, QPoint position,
EmojiPtr emoji); EmojiPtr emoji);
void drawCustom( void drawCustom(
QPainter &p, QPainter &p,
const ExpandingContext &context,
QPoint position, QPoint position,
crl::time now, crl::time now,
bool paused, bool paused,

View file

@ -351,7 +351,11 @@ void SuggestionsWidget::paintEvent(QPaintEvent *e) {
const auto x = i * _oneWidth + (_oneWidth - size) / 2; const auto x = i * _oneWidth + (_oneWidth - size) / 2;
const auto y = (_oneWidth - size) / 2; const auto y = (_oneWidth - size) / 2;
if (row.custom) { if (row.custom) {
row.custom->paint(p, x, y, now, preview, false); row.custom->paint(p, {
.preview = preview,
.now = now,
.position = { x, y },
});
} else { } else {
Ui::Emoji::Draw(p, emoji, esize, x, y); Ui::Emoji::Draw(p, emoji, esize, x, y);
} }

View file

@ -1217,19 +1217,29 @@ void StickersListFooter::paintSetIcon(
crl::time now, crl::time now,
bool paused) const { bool paused) const {
const auto &icon = _icons[info.index]; const auto &icon = _icons[info.index];
if (context.expanding) { const auto expandingShift = context.expanding
p.save(); ? QPoint(
const auto center = QPoint( 0,
info.adjustedLeft + _singleWidth / 2, anim::interpolate(height() / 2, 0, context.progress))
_iconsTop + st().footer / 2); : QPoint();
const auto shift = QPoint(0, anim::interpolate(height() / 2, 0, context.progress));
p.translate(shift + center);
p.scale(context.progress, context.progress);
p.translate(-center);
}
if (icon.sticker) { if (icon.sticker) {
icon.ensureMediaCreated(); icon.ensureMediaCreated();
const_cast<StickersListFooter*>(this)->validateIconAnimation(icon); const_cast<StickersListFooter*>(this)->validateIconAnimation(icon);
}
if (context.expanding) {
if (icon.custom) {
p.translate(expandingShift);
} else {
p.save();
const auto center = QPoint(
info.adjustedLeft + _singleWidth / 2,
_iconsTop + st().footer / 2);
p.translate(expandingShift + center);
p.scale(context.progress, context.progress);
p.translate(-center);
}
}
if (icon.sticker) {
const auto origin = icon.sticker->stickerSetOrigin(); const auto origin = icon.sticker->stickerSetOrigin();
const auto thumb = icon.thumbnailMedia const auto thumb = icon.thumbnailMedia
? icon.thumbnailMedia->image() ? icon.thumbnailMedia->image()
@ -1239,7 +1249,15 @@ void StickersListFooter::paintSetIcon(
const auto x = info.adjustedLeft + (_singleWidth - icon.pixw) / 2; const auto x = info.adjustedLeft + (_singleWidth - icon.pixw) / 2;
const auto y = _iconsTop + (st().footer - icon.pixh) / 2; const auto y = _iconsTop + (st().footer - icon.pixh) / 2;
if (icon.custom) { if (icon.custom) {
icon.custom->paint(p, x, y, now, st::emojiIconFg->c, paused); icon.custom->paint(p, Ui::Text::CustomEmoji::Context{
.preview = st::emojiIconFg->c,
.size = QSize(icon.pixw, icon.pixh),
.now = now,
.scale = context.progress,
.position = { x, y },
.paused = paused,
.scaled = context.expanding,
});
} else if (icon.lottie && icon.lottie->ready()) { } else if (icon.lottie && icon.lottie->ready()) {
const auto frame = icon.lottie->frame(); const auto frame = icon.lottie->frame();
const auto size = frame.size() / cIntRetinaFactor(); const auto size = frame.size() / cIntRetinaFactor();
@ -1368,7 +1386,11 @@ void StickersListFooter::paintSetIcon(
} }
} }
if (context.expanding) { if (context.expanding) {
p.restore(); if (icon.custom) {
p.translate(-expandingShift);
} else {
p.restore();
}
} }
} }

View file

@ -356,12 +356,7 @@ void BottomInfo::paintReactions(
y += st::msgDateFont->height; y += st::msgDateFont->height;
widthLeft = availableWidth; widthLeft = availableWidth;
} }
if (!reaction.custom && reaction.image.isNull()) { if (reaction.image.isNull()) {
reaction.custom = _reactionsOwner->resolveCustomFor(
reaction.id,
::Data::Reactions::ImageSize::BottomInfo);
}
if (!reaction.custom && reaction.image.isNull()) {
reaction.image = _reactionsOwner->resolveImageFor( reaction.image = _reactionsOwner->resolveImageFor(
reaction.id, reaction.id,
::Data::Reactions::ImageSize::BottomInfo); ::Data::Reactions::ImageSize::BottomInfo);
@ -375,9 +370,6 @@ void BottomInfo::paintReactions(
&& (reaction.count < 2 || !reaction.animation->flying()); && (reaction.count < 2 || !reaction.animation->flying());
if (!reaction.image.isNull() && !skipImage) { if (!reaction.image.isNull() && !skipImage) {
p.drawImage(image.topLeft(), reaction.image); p.drawImage(image.topLeft(), reaction.image);
} else if (reaction.custom && !skipImage) {
const auto size = Ui::Text::AdjustCustomEmojiSize(st::emojiSize);
reaction.custom->paint(p, x + (st::reactionInfoSize - size) / 2, y + (st::msgDateFont->height - size) / 2, crl::now(), Qt::white, false);
} }
if (animating) { if (animating) {
animations.push_back({ animations.push_back({

View file

@ -16,10 +16,6 @@ namespace Ui {
struct ChatPaintContext; struct ChatPaintContext;
} // namespace Ui } // namespace Ui
namespace Ui::Text {
class CustomEmoji;
} // namespace Ui::Text
namespace Data { namespace Data {
class Reactions; class Reactions;
} // namespace Data } // namespace Data
@ -105,7 +101,6 @@ private:
struct Reaction { struct Reaction {
mutable std::unique_ptr<Reactions::Animation> animation; mutable std::unique_ptr<Reactions::Animation> animation;
mutable QImage image; mutable QImage image;
mutable std::unique_ptr<Ui::Text::CustomEmoji> custom;
ReactionId id; ReactionId id;
QString countText; QString countText;
int count = 0; int count = 0;

View file

@ -298,16 +298,15 @@ void StickerToast::setupEmojiPreview(
widget->paintRequest( widget->paintRequest(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
auto p = QPainter(widget); auto p = QPainter(widget);
const auto paused = false;
const auto size = Ui::Emoji::GetSizeLarge() const auto size = Ui::Emoji::GetSizeLarge()
/ style::DevicePixelRatio(); / style::DevicePixelRatio();
instance->object.paint( instance->object.paint(p, Ui::Text::CustomEmoji::Context{
p, .preview = st::toastBg->c,
(widget->width() - size) / 2, .now = crl::now(),
(widget->height() - size) / 2, .position = QPoint(
crl::now(), (widget->width() - size) / 2,
st::toastBg->c, (widget->height() - size) / 2),
paused); });
}, widget->lifetime()); }, widget->lifetime());
} }

View file

@ -277,7 +277,11 @@ void CustomEmoji::paintCustom(
} }
_selectedFrame.fill(Qt::transparent); _selectedFrame.fill(Qt::transparent);
auto q = QPainter(&_selectedFrame); auto q = QPainter(&_selectedFrame);
emoji->paint(q, 0, 0, context.now, preview, paused); emoji->paint(q, {
.preview = preview,
.now = context.now,
.paused = paused,
});
q.end(); q.end();
_selectedFrame = Images::Colored( _selectedFrame = Images::Colored(
@ -285,7 +289,12 @@ void CustomEmoji::paintCustom(
context.st->msgStickerOverlay()->c); context.st->msgStickerOverlay()->c);
p.drawImage(x, y, _selectedFrame); p.drawImage(x, y, _selectedFrame);
} else { } else {
emoji->paint(p, x, y, context.now, preview, paused); emoji->paint(p, {
.preview = preview,
.now = context.now,
.position = { x, y },
.paused = paused,
});
} }
} }

View file

@ -148,7 +148,11 @@ void LargeEmoji::paintCustom(
} }
_selectedFrame.fill(Qt::transparent); _selectedFrame.fill(Qt::transparent);
auto q = QPainter(&_selectedFrame); auto q = QPainter(&_selectedFrame);
emoji->paint(q, 0, 0, context.now, preview, paused); emoji->paint(q, {
.preview = preview,
.now = context.now,
.paused = paused,
});
q.end(); q.end();
_selectedFrame = Images::Colored( _selectedFrame = Images::Colored(
@ -156,7 +160,12 @@ void LargeEmoji::paintCustom(
context.st->msgStickerOverlay()->c); context.st->msgStickerOverlay()->c);
p.drawImage(x + skip, y + skip, _selectedFrame); p.drawImage(x + skip, y + skip, _selectedFrame);
} else { } else {
emoji->paint(p, x + skip, y + skip, context.now, preview, paused); emoji->paint(p, {
.preview = preview,
.now = context.now,
.position = { x + skip, y + skip },
.paused = paused,
});
} }
} }

View file

@ -18,12 +18,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/emoji_list_widget.h"
#include "chat_helpers/stickers_list_footer.h" #include "chat_helpers/stickers_list_footer.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "base/call_delayed.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
namespace HistoryView::Reactions { namespace HistoryView::Reactions {
namespace { namespace {
constexpr auto kExpandDuration = crl::time(300);
constexpr auto kScaleDuration = crl::time(120);
constexpr auto kFullDuration = kExpandDuration + kScaleDuration;
constexpr auto kExpandDelay = crl::time(40);
class ShiftedEmoji final : public Ui::Text::CustomEmoji { class ShiftedEmoji final : public Ui::Text::CustomEmoji {
public: public:
ShiftedEmoji( ShiftedEmoji(
@ -33,13 +39,7 @@ public:
QPoint shift); QPoint shift);
QString entityData() override; QString entityData() override;
void paint( void paint(QPainter &p, const Context &context) override;
QPainter &p,
int x,
int y,
crl::time now,
const QColor &preview,
bool paused) override;
void unload() override; void unload() override;
private: private:
@ -64,14 +64,10 @@ QString ShiftedEmoji::entityData() {
return _real->entityData(); return _real->entityData();
} }
void ShiftedEmoji::paint( void ShiftedEmoji::paint(QPainter &p, const Context &context) {
QPainter &p, auto copy = context;
int x, copy.position += _shift;
int y, _real->paint(p, copy);
crl::time now,
const QColor &preview,
bool paused) {
_real->paint(p, x + _shift.x(), y + _shift.y(), now, preview, paused);
} }
void ShiftedEmoji::unload() { void ShiftedEmoji::unload() {
@ -270,6 +266,7 @@ void Selector::paintCollapsed(QPainter &p) {
void Selector::paintExpanding(Painter &p, float64 progress) { void Selector::paintExpanding(Painter &p, float64 progress) {
const auto rects = paintExpandingBg(p, progress); const auto rects = paintExpandingBg(p, progress);
//paintStripWithoutExpand(p); //paintStripWithoutExpand(p);
progress /= kFullDuration;
paintFadingExpandIcon(p, progress); paintFadingExpandIcon(p, progress);
if (_footer) { if (_footer) {
_footer->paintExpanding( _footer->paintExpanding(
@ -281,11 +278,16 @@ void Selector::paintExpanding(Painter &p, float64 progress) {
_list->paintExpanding( _list->paintExpanding(
p, p,
rects.list.marginsRemoved(st::reactPanelEmojiPan.margin), rects.list.marginsRemoved(st::reactPanelEmojiPan.margin),
rects.finalBottom,
progress,
RectPart::TopRight); RectPart::TopRight);
} }
auto Selector::paintExpandingBg(QPainter &p, float64 progress) auto Selector::paintExpandingBg(QPainter &p, float64 progress)
-> ExpandingRects { -> ExpandingRects {
progress = (progress >= kExpandDuration)
? 1.
: (progress / kExpandDuration);
constexpr auto kFramesCount = Ui::RoundAreaWithShadow::kFramesCount; constexpr auto kFramesCount = Ui::RoundAreaWithShadow::kFramesCount;
const auto frame = int(base::SafeRound(progress * (kFramesCount - 1))); const auto frame = int(base::SafeRound(progress * (kFramesCount - 1)));
const auto radiusStart = st::reactStripHeight / 2.; const auto radiusStart = st::reactStripHeight / 2.;
@ -318,6 +320,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
.categories = QRect(inner.x(), inner.y(), inner.width(), categories), .categories = QRect(inner.x(), inner.y(), inner.width(), categories),
.list = inner.marginsRemoved({ 0, categories, 0, 0 }), .list = inner.marginsRemoved({ 0, categories, 0, 0 }),
.radius = radius, .radius = radius,
.finalBottom = height() - extents.bottom(),
}; };
} }
@ -393,7 +396,8 @@ void Selector::paintEvent(QPaintEvent *e) {
paintAppearing(p); paintAppearing(p);
} else if (!_expanded) { } else if (!_expanded) {
paintCollapsed(p); paintCollapsed(p);
} else if (const auto progress = _expanding.value(1.); progress < 1.) { } else if (const auto progress = _expanding.value(kFullDuration)
; progress < kFullDuration) {
paintExpanding(p, progress); paintExpanding(p, progress);
} else { } else {
paintExpanded(p); paintExpanded(p);
@ -468,9 +472,14 @@ void Selector::expand() {
createList(strong); createList(strong);
cacheExpandIcon(); cacheExpandIcon();
_paintBuffer = _cachedRound.PrepareImage(size()); [[maybe_unused]] const auto grabbed = Ui::GrabWidget(_scroll);
_expanded = true;
_expanding.start([=] { update(); }, 0., 1., st::slideDuration); base::call_delayed(kExpandDelay, this, [=] {
_paintBuffer = _cachedRound.PrepareImage(size());
_expanded = true;
const auto full = kExpandDuration + kScaleDuration;
_expanding.start([=] { update(); }, 0., full, full);
});
} }
void Selector::cacheExpandIcon() { void Selector::cacheExpandIcon() {

View file

@ -70,6 +70,7 @@ private:
QRect categories; QRect categories;
QRect list; QRect list;
float64 radius = 0.; float64 radius = 0.;
int finalBottom = 0;
}; };
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;

View file

@ -148,13 +148,11 @@ void BadgeView::setBadge(Badge badge, DocumentId emojiStatusId) {
_view->paintRequest( _view->paintRequest(
) | rpl::start_with_next([=, check = _view.data()]{ ) | rpl::start_with_next([=, check = _view.data()]{
Painter p(check); Painter p(check);
_emojiStatus->paint( _emojiStatus->paint(p, {
p, .preview = st::windowBgOver->c,
0, .now = crl::now(),
0, .paused = _animationPaused && _animationPaused(),
crl::now(), });
st::windowBgOver->c,
_animationPaused && _animationPaused());
}, _view->lifetime()); }, _view->lifetime());
} else { } else {
const auto icon = (_badge == Badge::Verified) const auto icon = (_badge == Badge::Verified)

View file

@ -30,6 +30,32 @@ struct CacheHeader {
int length = 0; int length = 0;
}; };
void PaintScaledImage(
QPainter &p,
const QRect &target,
const Cache::Frame &frame,
const Context &context) {
if (context.scaled) {
const auto sx = anim::interpolate(
target.width() / 2,
0,
context.scale);
const auto sy = (target.height() == target.width())
? sx
: anim::interpolate(target.height() / 2, 0, context.scale);
const auto scaled = target.marginsRemoved({ sx, sy, sx, sy });
if (frame.source.isNull()) {
p.drawImage(scaled, *frame.image);
} else {
p.drawImage(scaled, *frame.image, frame.source);
}
} else if (frame.source.isNull()) {
p.drawImage(target, *frame.image);
} else {
p.drawImage(target, *frame.image, frame.source);
}
}
} // namespace } // namespace
Preview::Preview(QPainterPath path, float64 scale) Preview::Preview(QPainterPath path, float64 scale)
@ -40,15 +66,14 @@ Preview::Preview(QImage image, bool exact)
: _data(Image{ .data = std::move(image), .exact = exact }) { : _data(Image{ .data = std::move(image), .exact = exact }) {
} }
void Preview::paint(QPainter &p, int x, int y, const QColor &preview) { void Preview::paint(QPainter &p, const Context &context) {
if (const auto path = std::get_if<ScaledPath>(&_data)) { if (const auto path = std::get_if<ScaledPath>(&_data)) {
paintPath(p, x, y, preview, *path); paintPath(p, context, *path);
} else if (const auto image = std::get_if<Image>(&_data)) { } else if (const auto image = std::get_if<Image>(&_data)) {
const auto &data = image->data; const auto &data = image->data;
const auto factor = style::DevicePixelRatio(); const auto factor = style::DevicePixelRatio();
const auto width = data.width() / factor; const auto rect = QRect(context.position, data.size() / factor);
const auto height = data.height() / factor; PaintScaledImage(p, rect, { .image = &data }, context);
p.drawImage(QRect(x, y, width, height), data);
} }
} }
@ -72,27 +97,33 @@ QImage Preview::image() const {
void Preview::paintPath( void Preview::paintPath(
QPainter &p, QPainter &p,
int x, const Context &context,
int y,
const QColor &preview,
const ScaledPath &path) { const ScaledPath &path) {
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
p.setBrush(preview); p.setBrush(context.preview);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
const auto scale = path.scale; const auto scale = path.scale;
const auto required = (scale != 1.); const auto required = (scale != 1.) || context.scaled;
if (required) { if (required) {
p.save(); p.save();
} }
p.translate(x, y); p.translate(context.position);
if (required) { if (required) {
p.scale(scale, scale); p.scale(scale, scale);
const auto center = QPoint(
context.size.width() / 2,
context.size.height() / 2);
if (context.scaled) {
p.translate(center);
p.scale(context.scale, context.scale);
p.translate(-center);
}
} }
p.drawPath(path.path); p.drawPath(path.path);
if (required) { if (required) {
p.restore(); p.restore();
} else { } else {
p.translate(-x, -y); p.translate(-context.position);
} }
} }
@ -306,12 +337,11 @@ void Cache::finish() {
PaintFrameResult Cache::paintCurrentFrame( PaintFrameResult Cache::paintCurrentFrame(
QPainter &p, QPainter &p,
int x, const Context &context) {
int y,
crl::time now) {
if (!_frames) { if (!_frames) {
return {}; return {};
} }
const auto now = context.paused ? 0 : context.now;
const auto finishes = now ? currentFrameFinishes() : 0; const auto finishes = now ? currentFrameFinishes() : 0;
if (finishes && now >= finishes) { if (finishes && now >= finishes) {
++_frame; ++_frame;
@ -324,7 +354,8 @@ PaintFrameResult Cache::paintCurrentFrame(
} }
const auto info = frame(std::min(_frame, _frames - 1)); const auto info = frame(std::min(_frame, _frames - 1));
const auto size = _size / style::DevicePixelRatio(); const auto size = _size / style::DevicePixelRatio();
p.drawImage(QRect(x, y, size, size), *info.image, info.source); const auto rect = QRect(context.position, QSize(size, size));
PaintScaledImage(p, rect, info, context);
const auto next = currentFrameFinishes(); const auto next = currentFrameFinishes();
const auto duration = next ? (next - _shown) : 0; const auto duration = next ? (next - _shown) : 0;
return { return {
@ -360,8 +391,8 @@ QString Cached::entityData() const {
return _entityData; return _entityData;
} }
PaintFrameResult Cached::paint(QPainter &p, int x, int y, crl::time now) { PaintFrameResult Cached::paint(QPainter &p, const Context &context) {
return _cache.paintCurrentFrame(p, x, y, now); return _cache.paintCurrentFrame(p, context);
} }
Preview Cached::makePreview() const { Preview Cached::makePreview() const {
@ -469,8 +500,8 @@ void Renderer::finish() {
} }
} }
PaintFrameResult Renderer::paint(QPainter &p, int x, int y, crl::time now) { PaintFrameResult Renderer::paint(QPainter &p, const Context &context) {
const auto result = _cache.paintCurrentFrame(p, x, y, now); const auto result = _cache.paintCurrentFrame(p, context);
if (_generator if (_generator
&& (!result.painted && (!result.painted
|| _cache.currentFrame() + kPreloadFrames >= _cache.frames())) { || _cache.currentFrame() + kPreloadFrames >= _cache.frames())) {
@ -526,13 +557,13 @@ bool Loading::loading() const {
return _loader->loading(); return _loader->loading();
} }
void Loading::paint(QPainter &p, int x, int y, const QColor &preview) { void Loading::paint(QPainter &p, const Context &context) {
if (!_preview) { if (!_preview) {
if (auto preview = _loader->preview()) { if (auto preview = _loader->preview()) {
_preview = std::move(preview); _preview = std::move(preview);
} }
} }
_preview.paint(p, x, y, preview); _preview.paint(p, context);
} }
bool Loading::hasImagePreview() const { bool Loading::hasImagePreview() const {
@ -578,15 +609,9 @@ QString Instance::entityData() const {
Unexpected("State in Instance::entityData."); Unexpected("State in Instance::entityData.");
} }
void Instance::paint( void Instance::paint(QPainter &p, const Context &context) {
QPainter &p,
int x,
int y,
crl::time now,
const QColor &preview,
bool paused) {
if (const auto loading = std::get_if<Loading>(&_state)) { if (const auto loading = std::get_if<Loading>(&_state)) {
loading->paint(p, x, y, preview); loading->paint(p, context);
loading->load([=](Loader::LoadResult result) { loading->load([=](Loader::LoadResult result) {
if (auto caching = std::get_if<Caching>(&result)) { if (auto caching = std::get_if<Caching>(&result)) {
caching->renderer->setRepaintCallback([=] { repaint(); }); caching->renderer->setRepaintCallback([=] { repaint(); });
@ -599,14 +624,14 @@ void Instance::paint(
} }
}); });
} else if (const auto caching = std::get_if<Caching>(&_state)) { } else if (const auto caching = std::get_if<Caching>(&_state)) {
auto result = caching->renderer->paint(p, x, y, paused ? 0 : now); auto result = caching->renderer->paint(p, context);
if (!result.painted) { if (!result.painted) {
caching->preview.paint(p, x, y, preview); caching->preview.paint(p, context);
} else { } else {
if (!caching->preview.isExactImage()) { if (!caching->preview.isExactImage()) {
caching->preview = caching->renderer->makePreview(); caching->preview = caching->renderer->makePreview();
} }
if (result.next > now) { if (result.next > context.now) {
_repaintLater(this, { result.next, result.duration }); _repaintLater(this, { result.next, result.duration });
} }
} }
@ -614,8 +639,8 @@ void Instance::paint(
_state = std::move(*cached); _state = std::move(*cached);
} }
} else if (const auto cached = std::get_if<Cached>(&_state)) { } else if (const auto cached = std::get_if<Cached>(&_state)) {
const auto result = cached->paint(p, x, y, paused ? 0 : now); const auto result = cached->paint(p, context);
if (result.next > now) { if (result.next > context.now) {
_repaintLater(this, { result.next, result.duration }); _repaintLater(this, { result.next, result.duration });
} }
} }
@ -695,18 +720,12 @@ QString Object::entityData() {
return _instance->entityData(); return _instance->entityData();
} }
void Object::paint( void Object::paint(QPainter &p, const Context &context) {
QPainter &p,
int x,
int y,
crl::time now,
const QColor &preview,
bool paused) {
if (!_using) { if (!_using) {
_using = true; _using = true;
_instance->incrementUsage(this); _instance->incrementUsage(this);
} }
_instance->paint(p, x, y, now, preview, paused); _instance->paint(p, context);
} }
void Object::unload() { void Object::unload() {

View file

@ -21,13 +21,15 @@ class FrameGenerator;
namespace Ui::CustomEmoji { namespace Ui::CustomEmoji {
using Context = Ui::Text::CustomEmoji::Context;
class Preview final { class Preview final {
public: public:
Preview() = default; Preview() = default;
Preview(QImage image, bool exact); Preview(QImage image, bool exact);
Preview(QPainterPath path, float64 scale); Preview(QPainterPath path, float64 scale);
void paint(QPainter &p, int x, int y, const QColor &preview); void paint(QPainter &p, const Context &context);
[[nodiscard]] bool isImage() const; [[nodiscard]] bool isImage() const;
[[nodiscard]] bool isExactImage() const; [[nodiscard]] bool isExactImage() const;
[[nodiscard]] QImage image() const; [[nodiscard]] QImage image() const;
@ -48,9 +50,7 @@ private:
void paintPath( void paintPath(
QPainter &p, QPainter &p,
int x, const Context &context,
int y,
const QColor &preview,
const ScaledPath &path); const ScaledPath &path);
std::variant<v::null_t, ScaledPath, Image> _data; std::variant<v::null_t, ScaledPath, Image> _data;
@ -86,11 +86,7 @@ public:
[[nodiscard]] Preview makePreview() const; [[nodiscard]] Preview makePreview() const;
PaintFrameResult paintCurrentFrame( PaintFrameResult paintCurrentFrame(QPainter &p, const Context &context);
QPainter &p,
int x,
int y,
crl::time now);
[[nodiscard]] int currentFrame() const; [[nodiscard]] int currentFrame() const;
private: private:
@ -123,7 +119,7 @@ public:
[[nodiscard]] QString entityData() const; [[nodiscard]] QString entityData() const;
[[nodiscard]] Preview makePreview() const; [[nodiscard]] Preview makePreview() const;
PaintFrameResult paint(QPainter &p, int x, int y, crl::time now); PaintFrameResult paint(QPainter &p, const Context &context);
[[nodiscard]] Loading unload(); [[nodiscard]] Loading unload();
private: private:
@ -145,7 +141,7 @@ public:
explicit Renderer(RendererDescriptor &&descriptor); explicit Renderer(RendererDescriptor &&descriptor);
virtual ~Renderer(); virtual ~Renderer();
PaintFrameResult paint(QPainter &p, int x, int y, crl::time now); PaintFrameResult paint(QPainter &p, const Context &context);
[[nodiscard]] std::optional<Cached> ready(const QString &entityData); [[nodiscard]] std::optional<Cached> ready(const QString &entityData);
[[nodiscard]] std::unique_ptr<Loader> cancel(); [[nodiscard]] std::unique_ptr<Loader> cancel();
@ -199,7 +195,7 @@ public:
void load(Fn<void(Loader::LoadResult)> done); void load(Fn<void(Loader::LoadResult)> done);
[[nodiscard]] bool loading() const; [[nodiscard]] bool loading() const;
void paint(QPainter &p, int x, int y, const QColor &preview); void paint(QPainter &p, const Context &context);
[[nodiscard]] bool hasImagePreview() const; [[nodiscard]] bool hasImagePreview() const;
[[nodiscard]] Preview imagePreview() const; [[nodiscard]] Preview imagePreview() const;
void updatePreview(Preview preview); void updatePreview(Preview preview);
@ -226,13 +222,7 @@ public:
Instance &operator=(const Instance&) = delete; Instance &operator=(const Instance&) = delete;
[[nodiscard]] QString entityData() const; [[nodiscard]] QString entityData() const;
void paint( void paint(QPainter &p, const Context &context);
QPainter &p,
int x,
int y,
crl::time now,
const QColor &preview,
bool paused);
[[nodiscard]] bool hasImagePreview() const; [[nodiscard]] bool hasImagePreview() const;
[[nodiscard]] Preview imagePreview() const; [[nodiscard]] Preview imagePreview() const;
void updatePreview(Preview preview); void updatePreview(Preview preview);
@ -261,13 +251,7 @@ public:
~Object(); ~Object();
QString entityData() override; QString entityData() override;
void paint( void paint(QPainter &p, const Context &context) override;
QPainter &p,
int x,
int y,
crl::time now,
const QColor &preview,
bool paused) override;
void unload() override; void unload() override;
void repaint(); void repaint();

View file

@ -180,13 +180,14 @@ int PeerBadge::drawGetWidth(
id, id,
descriptor.customEmojiRepaint); descriptor.customEmojiRepaint);
} }
_emojiStatus->emoji->paint( _emojiStatus->emoji->paint(p, {
p, .preview = descriptor.preview,
iconx - 2 * _emojiStatus->skip, .now = descriptor.now,
icony + _emojiStatus->skip, .position = QPoint(
descriptor.now, iconx - 2 * _emojiStatus->skip,
descriptor.preview, icony + _emojiStatus->skip),
descriptor.paused); .paused = descriptor.paused,
});
return iconw - 4 * _emojiStatus->skip; return iconw - 4 * _emojiStatus->skip;
} }
return 0; return 0;

@ -1 +1 @@
Subproject commit 8162619cb17456f31d1be378a7ed72dc928e0831 Subproject commit 01c4ba869a07eabc9eea2b633542a53e9ff6ff4c