mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Fade in/out effect preview.
This commit is contained in:
parent
8a58ded582
commit
487fa9728a
6 changed files with 155 additions and 64 deletions
|
@ -575,7 +575,9 @@ void Reactions::preloadReactionImageFor(const ReactionId &emoji) {
|
|||
}
|
||||
|
||||
void Reactions::preloadEffectImageFor(EffectId id) {
|
||||
preloadImageFor({ DocumentId(id) });
|
||||
if (id != kFakeEffectId) {
|
||||
preloadImageFor({ DocumentId(id) });
|
||||
}
|
||||
}
|
||||
|
||||
void Reactions::preloadImageFor(const ReactionId &id) {
|
||||
|
@ -651,7 +653,9 @@ QImage Reactions::resolveReactionImageFor(const ReactionId &emoji) {
|
|||
}
|
||||
|
||||
QImage Reactions::resolveEffectImageFor(EffectId id) {
|
||||
return resolveImageFor({ DocumentId(id) });
|
||||
return (id == kFakeEffectId)
|
||||
? QImage()
|
||||
: resolveImageFor({ DocumentId(id) });
|
||||
}
|
||||
|
||||
QImage Reactions::resolveImageFor(const ReactionId &id) {
|
||||
|
|
|
@ -119,6 +119,10 @@ public:
|
|||
void preloadReactionImageFor(const ReactionId &emoji);
|
||||
[[nodiscard]] QImage resolveReactionImageFor(const ReactionId &emoji);
|
||||
|
||||
// This is used to reserve space for the effect in BottomInfo but not
|
||||
// actually paint anything, used in case we want to paint icon ourselves.
|
||||
static constexpr auto kFakeEffectId = EffectId(1);
|
||||
|
||||
void preloadEffectImageFor(EffectId id);
|
||||
[[nodiscard]] QImage resolveEffectImageFor(EffectId id);
|
||||
|
||||
|
|
|
@ -203,6 +203,7 @@ Selector::Selector(
|
|||
TextWithEntities about,
|
||||
Fn<void(bool fast)> close,
|
||||
IconFactory iconFactory,
|
||||
Fn<bool()> paused,
|
||||
bool child)
|
||||
: Selector(
|
||||
parent,
|
||||
|
@ -216,8 +217,9 @@ Selector::Selector(
|
|||
: ChatHelpers::EmojiListMode::MessageEffects),
|
||||
{},
|
||||
std::move(about),
|
||||
iconFactory,
|
||||
close,
|
||||
std::move(iconFactory),
|
||||
std::move(paused),
|
||||
std::move(close),
|
||||
child) {
|
||||
}
|
||||
|
||||
|
@ -253,6 +255,7 @@ Selector::Selector(
|
|||
std::vector<DocumentId> recent,
|
||||
TextWithEntities about,
|
||||
IconFactory iconFactory,
|
||||
Fn<bool()> paused,
|
||||
Fn<void(bool fast)> close,
|
||||
bool child)
|
||||
: RpWidget(parent)
|
||||
|
@ -261,6 +264,7 @@ Selector::Selector(
|
|||
, _reactions(reactions)
|
||||
, _recent(std::move(recent))
|
||||
, _listMode(mode)
|
||||
, _paused(std::move(paused))
|
||||
, _jumpedToPremium([=] { close(false); })
|
||||
, _cachedRound(
|
||||
QSize(2 * st::reactStripSkip + st::reactStripSize, st::reactStripHeight),
|
||||
|
@ -1005,7 +1009,7 @@ void Selector::createList() {
|
|||
object_ptr<EmojiListWidget>(lists, EmojiListDescriptor{
|
||||
.show = _show,
|
||||
.mode = _listMode,
|
||||
.paused = [] { return false; },
|
||||
.paused = _paused ? _paused : [] { return false; },
|
||||
.customRecentList = std::move(recentList),
|
||||
.customRecentFactory = _unifiedFactoryOwner->factory(),
|
||||
.freeEffects = std::move(freeEffects),
|
||||
|
@ -1026,7 +1030,7 @@ void Selector::createList() {
|
|||
StickersListDescriptor{
|
||||
.show = _show,
|
||||
.mode = StickersListMode::MessageEffects,
|
||||
.paused = [] { return false; },
|
||||
.paused = _paused ? _paused : [] { return false; },
|
||||
.customRecentList = std::move(descriptors),
|
||||
.st = st,
|
||||
}));
|
||||
|
@ -1352,7 +1356,8 @@ auto AttachSelectorToMenu(
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const Data::PossibleItemReactionsRef &reactions,
|
||||
TextWithEntities about,
|
||||
IconFactory iconFactory)
|
||||
IconFactory iconFactory,
|
||||
Fn<bool()> paused)
|
||||
-> base::expected<not_null<Selector*>, AttachSelectorResult> {
|
||||
if (reactions.recent.empty()) {
|
||||
return base::make_unexpected(AttachSelectorResult::Skipped);
|
||||
|
@ -1366,6 +1371,7 @@ auto AttachSelectorToMenu(
|
|||
std::move(about),
|
||||
[=](bool fast) { menu->hideMenu(fast); },
|
||||
std::move(iconFactory),
|
||||
std::move(paused),
|
||||
false); // child
|
||||
if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) {
|
||||
return base::make_unexpected(AttachSelectorResult::Failed);
|
||||
|
|
|
@ -85,6 +85,7 @@ public:
|
|||
TextWithEntities about,
|
||||
Fn<void(bool fast)> close,
|
||||
IconFactory iconFactory = nullptr,
|
||||
Fn<bool()> paused = nullptr,
|
||||
bool child = false);
|
||||
#if 0 // not ready
|
||||
Selector(
|
||||
|
@ -149,6 +150,7 @@ private:
|
|||
std::vector<DocumentId> recent,
|
||||
TextWithEntities about,
|
||||
IconFactory iconFactory,
|
||||
Fn<bool()> paused,
|
||||
Fn<void(bool fast)> close,
|
||||
bool child);
|
||||
|
||||
|
@ -187,6 +189,7 @@ private:
|
|||
const Data::PossibleItemReactions _reactions;
|
||||
const std::vector<DocumentId> _recent;
|
||||
const ChatHelpers::EmojiListMode _listMode;
|
||||
const Fn<bool()> _paused;
|
||||
Fn<void()> _jumpedToPremium;
|
||||
Ui::RoundAreaWithShadow _cachedRound;
|
||||
std::unique_ptr<Strip> _strip;
|
||||
|
@ -274,7 +277,8 @@ AttachSelectorResult AttachSelectorToMenu(
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const Data::PossibleItemReactionsRef &reactions,
|
||||
TextWithEntities about,
|
||||
IconFactory iconFactory = nullptr
|
||||
IconFactory iconFactory = nullptr,
|
||||
Fn<bool()> paused = nullptr
|
||||
) -> base::expected<not_null<Selector*>, AttachSelectorResult>;
|
||||
|
||||
[[nodiscard]] TextWithEntities ItemReactionsAbout(
|
||||
|
|
|
@ -668,7 +668,8 @@ void Reactions::Panel::create() {
|
|||
? tr::lng_stories_reaction_as_message(tr::now)
|
||||
: QString()) },
|
||||
[=](bool fast) { hide(mode); },
|
||||
nullptr,
|
||||
nullptr, // iconFactory
|
||||
nullptr, // paused
|
||||
true);
|
||||
|
||||
_selector->chosen(
|
||||
|
|
|
@ -59,6 +59,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace SendMenu {
|
||||
namespace {
|
||||
|
||||
constexpr auto kToggleDuration = crl::time(400);
|
||||
|
||||
class Delegate final : public HistoryView::DefaultElementDelegate {
|
||||
public:
|
||||
Delegate(not_null<Ui::PathShiftGradient*> pathGradient)
|
||||
|
@ -90,6 +92,8 @@ public:
|
|||
Fn<void(Action, Details)> action,
|
||||
Fn<void()> done);
|
||||
|
||||
void hideAnimated();
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
|
@ -104,8 +108,12 @@ private:
|
|||
void setupSend(Details details);
|
||||
void createLottie();
|
||||
|
||||
[[nodiscard]] bool ready() const;
|
||||
void paintLoading(QPainter &p);
|
||||
void paintLottie(QPainter &p);
|
||||
bool checkIconBecameLoaded();
|
||||
[[nodiscard]] bool checkReady();
|
||||
[[nodiscard]] bool checkLoaded();
|
||||
void toggle(bool shown);
|
||||
|
||||
const EffectId _effectId = 0;
|
||||
const Data::Reaction _effect;
|
||||
|
@ -135,6 +143,10 @@ private:
|
|||
QRect _iconRect;
|
||||
std::unique_ptr<Ui::InfiniteRadialAnimation> _loading;
|
||||
|
||||
Ui::Animations::Simple _shownAnimation;
|
||||
QPixmap _bottomCache;
|
||||
bool _hiding = false;
|
||||
|
||||
rpl::lifetime _readyCheckLifetime;
|
||||
|
||||
};
|
||||
|
@ -248,7 +260,7 @@ EffectPreview::EffectPreview(
|
|||
_history->peer->id,
|
||||
_replyTo->data()->fullId(),
|
||||
tr::lng_settings_chat_message_reply(tr::now),
|
||||
_effectId))
|
||||
Data::Reactions::kFakeEffectId))
|
||||
, _send(canSend()
|
||||
? std::make_unique<BottomRounded>(
|
||||
this,
|
||||
|
@ -271,68 +283,87 @@ EffectPreview::EffectPreview(
|
|||
, _close(done)
|
||||
, _actionWithEffect(ComposeActionWithEffect(action, _effectId, done)) {
|
||||
setupGeometry(position);
|
||||
setupBackground();
|
||||
setupItem();
|
||||
setupBackground();
|
||||
setupLottie();
|
||||
setupSend(details);
|
||||
|
||||
toggle(true);
|
||||
}
|
||||
|
||||
void EffectPreview::paintEvent(QPaintEvent *e) {
|
||||
auto p = Painter(this);
|
||||
checkIconBecameLoaded();
|
||||
|
||||
const auto progress = _shownAnimation.value(_hiding ? 0. : 1.);
|
||||
if (!progress) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto p = QPainter(this);
|
||||
p.setOpacity(progress);
|
||||
p.drawImage(0, 0, _bg);
|
||||
|
||||
p.setClipRect(_inner);
|
||||
p.translate(_itemShift);
|
||||
auto rect = QRect(0, 0, st::windowMinWidth, _inner.height());
|
||||
auto context = _theme->preparePaintContext(
|
||||
_chatStyle.get(),
|
||||
rect,
|
||||
rect,
|
||||
false);
|
||||
context.outbg = _item->hasOutLayout();
|
||||
_item->draw(p, context);
|
||||
p.translate(-_itemShift);
|
||||
if (!_bottomCache.isNull()) {
|
||||
p.drawPixmap(_bottom->pos(), _bottomCache);
|
||||
}
|
||||
|
||||
checkIconBecameLoaded();
|
||||
if (_icon.isNull()) {
|
||||
if (!_loading) {
|
||||
_loading = std::make_unique<Ui::InfiniteRadialAnimation>([=] {
|
||||
update();
|
||||
}, st::effectPreviewLoading);
|
||||
_loading->start(st::defaultInfiniteRadialAnimation.linearPeriod);
|
||||
}
|
||||
const auto loading = _iconRect.marginsRemoved(
|
||||
{ st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth });
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
Ui::InfiniteRadialAnimation::Draw(
|
||||
p,
|
||||
_loading->computeState(),
|
||||
loading.topLeft(),
|
||||
loading.size(),
|
||||
width(),
|
||||
_chatStyle->msgInDateFg(),
|
||||
st::effectPreviewLoading.thickness);
|
||||
if (!ready()) {
|
||||
paintLoading(p);
|
||||
} else {
|
||||
_loading = nullptr;
|
||||
}
|
||||
if (_lottie && _lottie->ready()) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = _inner.size() * factor;
|
||||
const auto rightAligned = _item->hasRightLayout();
|
||||
if (!rightAligned) {
|
||||
request.mirrorHorizontal = true;
|
||||
p.drawImage(_iconRect, _icon);
|
||||
if (!_hiding) {
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
const auto frame = _lottie->frameInfo(request);
|
||||
p.drawImage(
|
||||
QRect(_inner.topLeft(), frame.image.size() / factor),
|
||||
frame.image);
|
||||
_lottie->markFrameShown();
|
||||
paintLottie(p);
|
||||
}
|
||||
}
|
||||
|
||||
bool EffectPreview::ready() const {
|
||||
return !_icon.isNull() && _lottie && _lottie->ready();
|
||||
}
|
||||
|
||||
void EffectPreview::paintLoading(QPainter &p) {
|
||||
if (!_loading) {
|
||||
_loading = std::make_unique<Ui::InfiniteRadialAnimation>([=] {
|
||||
update();
|
||||
}, st::effectPreviewLoading);
|
||||
_loading->start(st::defaultInfiniteRadialAnimation.linearPeriod);
|
||||
}
|
||||
const auto loading = _iconRect.marginsRemoved(
|
||||
{ st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth });
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
Ui::InfiniteRadialAnimation::Draw(
|
||||
p,
|
||||
_loading->computeState(),
|
||||
loading.topLeft(),
|
||||
loading.size(),
|
||||
width(),
|
||||
_chatStyle->msgInDateFg(),
|
||||
st::effectPreviewLoading.thickness);
|
||||
}
|
||||
|
||||
void EffectPreview::paintLottie(QPainter &p) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = _inner.size() * factor;
|
||||
const auto rightAligned = _item->hasRightLayout();
|
||||
if (!rightAligned) {
|
||||
request.mirrorHorizontal = true;
|
||||
}
|
||||
const auto frame = _lottie->frameInfo(request);
|
||||
p.drawImage(
|
||||
QRect(_inner.topLeft(), frame.image.size() / factor),
|
||||
frame.image);
|
||||
_lottie->markFrameShown();
|
||||
}
|
||||
|
||||
void EffectPreview::hideAnimated() {
|
||||
toggle(false);
|
||||
}
|
||||
|
||||
void EffectPreview::mousePressEvent(QMouseEvent *e) {
|
||||
delete this;
|
||||
hideAnimated();
|
||||
}
|
||||
|
||||
void EffectPreview::setupGeometry(QPoint position) {
|
||||
|
@ -402,7 +433,7 @@ void EffectPreview::repaintBackground() {
|
|||
bg.setDevicePixelRatio(ratio);
|
||||
|
||||
{
|
||||
auto p = QPainter(&bg);
|
||||
auto p = Painter(&bg);
|
||||
Window::SectionWidget::PaintBackground(
|
||||
p,
|
||||
_theme.get(),
|
||||
|
@ -411,6 +442,18 @@ void EffectPreview::repaintBackground() {
|
|||
p.fillRect(
|
||||
QRect(0, _inner.height(), _inner.width(), _bottom->height()),
|
||||
st::previewMarkRead.bgColor);
|
||||
|
||||
p.translate(_itemShift - _inner.topLeft());
|
||||
auto rect = QRect(0, 0, st::windowMinWidth, _inner.height());
|
||||
auto context = _theme->preparePaintContext(
|
||||
_chatStyle.get(),
|
||||
rect,
|
||||
rect,
|
||||
false);
|
||||
context.outbg = _item->hasOutLayout();
|
||||
_item->draw(p, context);
|
||||
p.translate(_inner.topLeft() - _itemShift);
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||
auto roundRect = Ui::RoundRect(st::previewMenu.radius, st::menuBg);
|
||||
|
@ -438,7 +481,7 @@ void EffectPreview::setupLottie() {
|
|||
rpl::single(rpl::empty) | rpl::then(
|
||||
_show->session().downloaderTaskFinished()
|
||||
) | rpl::start_with_next([=] {
|
||||
if (checkReady()) {
|
||||
if (checkLoaded()) {
|
||||
_readyCheckLifetime.destroy();
|
||||
createLottie();
|
||||
}
|
||||
|
@ -495,10 +538,14 @@ bool EffectPreview::checkIconBecameLoaded() {
|
|||
}
|
||||
const auto reactions = &_show->session().data().reactions();
|
||||
_icon = reactions->resolveEffectImageFor(_effect.id.custom());
|
||||
return !_icon.isNull();
|
||||
if (_icon.isNull()) {
|
||||
return false;
|
||||
}
|
||||
repaintBackground();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EffectPreview::checkReady() {
|
||||
bool EffectPreview::checkLoaded() {
|
||||
if (checkIconBecameLoaded()) {
|
||||
update();
|
||||
}
|
||||
|
@ -511,6 +558,29 @@ bool EffectPreview::checkReady() {
|
|||
return !_icon.isNull() && (!_bytes.isEmpty() || !_filepath.isEmpty());
|
||||
}
|
||||
|
||||
void EffectPreview::toggle(bool shown) {
|
||||
if (!shown && _hiding) {
|
||||
return;
|
||||
}
|
||||
_hiding = !shown;
|
||||
if (_bottomCache.isNull()) {
|
||||
_bottomCache = Ui::GrabWidget(_bottom);
|
||||
_bottom->hide();
|
||||
}
|
||||
_shownAnimation.start([=] {
|
||||
update();
|
||||
if (!_shownAnimation.animating()) {
|
||||
if (_hiding) {
|
||||
delete this;
|
||||
} else {
|
||||
_bottomCache = QPixmap();
|
||||
_bottom->show();
|
||||
}
|
||||
}
|
||||
}, shown ? 0. : 1., shown ? 1. : 0., kToggleDuration, anim::easeOutCirc);
|
||||
show();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Fn<void(Action, Details)> DefaultCallback(
|
||||
|
@ -571,6 +641,7 @@ FillMenuResult FillSendMenu(
|
|||
}
|
||||
|
||||
using namespace HistoryView::Reactions;
|
||||
const auto effect = std::make_shared<QPointer<EffectPreview>>();
|
||||
const auto position = desiredPositionOverride.value_or(QCursor::pos());
|
||||
const auto selector = (showForEffect && details.effectAllowed)
|
||||
? AttachSelectorToMenu(
|
||||
|
@ -579,7 +650,9 @@ FillMenuResult FillSendMenu(
|
|||
st::reactPanelEmojiPan,
|
||||
showForEffect,
|
||||
LookupPossibleEffects(&showForEffect->session()),
|
||||
{ tr::lng_effect_add_title(tr::now) })
|
||||
{ tr::lng_effect_add_title(tr::now) },
|
||||
nullptr, // iconFactory
|
||||
[=] { return (*effect) != nullptr; }) // paused
|
||||
: base::make_unexpected(AttachSelectorResult::Skipped);
|
||||
if (!selector) {
|
||||
if (selector.error() == AttachSelectorResult::Failed) {
|
||||
|
@ -589,7 +662,6 @@ FillMenuResult FillSendMenu(
|
|||
return FillMenuResult::Prepared;
|
||||
}
|
||||
|
||||
const auto effect = std::make_shared<QPointer<EffectPreview>>();
|
||||
(*selector)->chosen(
|
||||
) | rpl::start_with_next([=](ChosenReaction chosen) {
|
||||
const auto &reactions = showForEffect->session().data().reactions();
|
||||
|
@ -597,7 +669,7 @@ FillMenuResult FillSendMenu(
|
|||
const auto i = ranges::find(effects, chosen.id, &Data::Reaction::id);
|
||||
if (i != end(effects)) {
|
||||
if (const auto strong = effect->data()) {
|
||||
delete strong;
|
||||
strong->hideAnimated();
|
||||
}
|
||||
const auto weak = Ui::MakeWeak(menu);
|
||||
const auto done = [=] {
|
||||
|
|
Loading…
Add table
Reference in a new issue