mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Use lottie instead of webp in reactions dropdown.
This commit is contained in:
parent
409a3357da
commit
c0b19000d6
5 changed files with 195 additions and 99 deletions
|
@ -35,7 +35,6 @@ void AddReactionIcon(
|
||||||
std::shared_ptr<Data::DocumentMedia> media;
|
std::shared_ptr<Data::DocumentMedia> media;
|
||||||
std::unique_ptr<Lottie::Icon> icon;
|
std::unique_ptr<Lottie::Icon> icon;
|
||||||
QImage image;
|
QImage image;
|
||||||
rpl::lifetime downloadLifetime;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto size = st::editPeerReactionsPreview;
|
const auto size = st::editPeerReactionsPreview;
|
||||||
|
@ -60,7 +59,6 @@ void AddReactionIcon(
|
||||||
.sizeOverride = QSize(size, size),
|
.sizeOverride = QSize(size, size),
|
||||||
.frame = -1,
|
.frame = -1,
|
||||||
});
|
});
|
||||||
state->downloadLifetime.destroy();
|
|
||||||
state->media = nullptr;
|
state->media = nullptr;
|
||||||
};
|
};
|
||||||
state->media->checkStickerLarge();
|
state->media->checkStickerLarge();
|
||||||
|
@ -70,10 +68,10 @@ void AddReactionIcon(
|
||||||
document->session().downloaderTaskFinished(
|
document->session().downloaderTaskFinished(
|
||||||
) | rpl::filter([=] {
|
) | rpl::filter([=] {
|
||||||
return state->media->loaded();
|
return state->media->loaded();
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::take(1) | rpl::start_with_next([=] {
|
||||||
initLottie();
|
initLottie();
|
||||||
icon->update();
|
icon->update();
|
||||||
}, state->downloadLifetime);
|
}, icon->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
icon->paintRequest(
|
icon->paintRequest(
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000);
|
constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000);
|
||||||
constexpr auto kPollEach = 20 * crl::time(1000);
|
constexpr auto kPollEach = 20 * crl::time(1000);
|
||||||
constexpr auto kSizeForDownscale = 128;
|
constexpr auto kSizeForDownscale = 64;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ void Reactions::loadImage(
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reactions::setLottie(ImageSet &set) {
|
void Reactions::setLottie(ImageSet &set) {
|
||||||
const auto size = kSizeForDownscale / style::DevicePixelRatio();
|
const auto size = style::ConvertScale(kSizeForDownscale);
|
||||||
set.icon = std::make_unique<Lottie::Icon>(Lottie::IconDescriptor{
|
set.icon = std::make_unique<Lottie::Icon>(Lottie::IconDescriptor{
|
||||||
.path = set.media->owner()->filepath(true),
|
.path = set.media->owner()->filepath(true),
|
||||||
.json = set.media->bytes(),
|
.json = set.media->bytes(),
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_message_reactions.h"
|
#include "data/data_message_reactions.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
|
#include "lottie/lottie_icon.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "base/event_filter.h"
|
#include "base/event_filter.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
@ -20,17 +21,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kToggleDuration = crl::time(80);
|
constexpr auto kToggleDuration = crl::time(120);
|
||||||
constexpr auto kActivateDuration = crl::time(150);
|
constexpr auto kActivateDuration = crl::time(150);
|
||||||
constexpr auto kExpandDuration = crl::time(150);
|
constexpr auto kExpandDuration = crl::time(300);
|
||||||
|
constexpr auto kCollapseDuration = crl::time(250);
|
||||||
constexpr auto kBgCacheIndex = 0;
|
constexpr auto kBgCacheIndex = 0;
|
||||||
constexpr auto kShadowCacheIndex = 0;
|
constexpr auto kShadowCacheIndex = 0;
|
||||||
constexpr auto kEmojiCacheIndex = 1;
|
constexpr auto kEmojiCacheIndex = 1;
|
||||||
constexpr auto kMaskCacheIndex = 2;
|
constexpr auto kMaskCacheIndex = 2;
|
||||||
constexpr auto kCacheColumsCount = 3;
|
constexpr auto kCacheColumsCount = 3;
|
||||||
constexpr auto kButtonShowDelay = crl::time(300);
|
constexpr auto kButtonShowDelay = crl::time(300);
|
||||||
constexpr auto kButtonExpandDelay = crl::time(300);
|
constexpr auto kButtonExpandDelay = crl::time(25);
|
||||||
constexpr auto kButtonHideDelay = crl::time(200);
|
constexpr auto kButtonHideDelay = crl::time(300);
|
||||||
|
constexpr auto kButtonExpandedHideDelay = crl::time(0);
|
||||||
|
constexpr auto kSizeForDownscale = 96;
|
||||||
|
|
||||||
[[nodiscard]] QPoint LocalPosition(not_null<QWheelEvent*> e) {
|
[[nodiscard]] QPoint LocalPosition(not_null<QWheelEvent*> e) {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
@ -55,15 +59,22 @@ constexpr auto kButtonHideDelay = crl::time(200);
|
||||||
return int(base::SafeRound(st::reactionCornerImage * scale));
|
return int(base::SafeRound(st::reactionCornerImage * scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QImage PrepareMaxOtherReaction(QImage image) {
|
[[nodiscard]] int MainReactionSize() {
|
||||||
const auto size = CornerImageSize(1.);
|
return style::ConvertScale(kSizeForDownscale);
|
||||||
const auto factor = style::DevicePixelRatio();
|
}
|
||||||
auto result = image.scaled(
|
|
||||||
QSize(size, size) * factor,
|
[[nodiscard]] std::shared_ptr<Lottie::Icon> CreateIcon(
|
||||||
Qt::IgnoreAspectRatio,
|
not_null<Data::DocumentMedia*> media,
|
||||||
Qt::SmoothTransformation);
|
int startFrame,
|
||||||
result.setDevicePixelRatio(factor);
|
int size) {
|
||||||
return result;
|
Expects(media->loaded());
|
||||||
|
|
||||||
|
return std::make_shared<Lottie::Icon>(Lottie::IconDescriptor{
|
||||||
|
.path = media->owner()->filepath(true),
|
||||||
|
.json = media->bytes(),
|
||||||
|
.sizeOverride = QSize(size, size),
|
||||||
|
.frame = startFrame,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -73,6 +84,7 @@ Button::Button(
|
||||||
ButtonParameters parameters,
|
ButtonParameters parameters,
|
||||||
Fn<void()> hideMe)
|
Fn<void()> hideMe)
|
||||||
: _update(std::move(update))
|
: _update(std::move(update))
|
||||||
|
, _finalScale(ScaleForState(_state))
|
||||||
, _collapsed(QPoint(), CountOuterSize())
|
, _collapsed(QPoint(), CountOuterSize())
|
||||||
, _finalHeight(_collapsed.height())
|
, _finalHeight(_collapsed.height())
|
||||||
, _expandTimer([=] { applyState(State::Inside, _update); })
|
, _expandTimer([=] { applyState(State::Inside, _update); })
|
||||||
|
@ -83,7 +95,7 @@ Button::Button(
|
||||||
Button::~Button() = default;
|
Button::~Button() = default;
|
||||||
|
|
||||||
bool Button::isHidden() const {
|
bool Button::isHidden() const {
|
||||||
return (_state == State::Hidden) && !_scaleAnimation.animating();
|
return (_state == State::Hidden) && !_opacityAnimation.animating();
|
||||||
}
|
}
|
||||||
|
|
||||||
QRect Button::geometry() const {
|
QRect Button::geometry() const {
|
||||||
|
@ -148,6 +160,7 @@ void Button::applyParameters(
|
||||||
}
|
}
|
||||||
_lastGlobalPosition = parameters.globalPointer;
|
_lastGlobalPosition = parameters.globalPointer;
|
||||||
}
|
}
|
||||||
|
const auto wasInside = (_state == State::Inside);
|
||||||
const auto state = (inside && !delayInside)
|
const auto state = (inside && !delayInside)
|
||||||
? State::Inside
|
? State::Inside
|
||||||
: active
|
: active
|
||||||
|
@ -155,7 +168,9 @@ void Button::applyParameters(
|
||||||
: State::Shown;
|
: State::Shown;
|
||||||
applyState(state, update);
|
applyState(state, update);
|
||||||
if (parameters.outside && _state == State::Shown) {
|
if (parameters.outside && _state == State::Shown) {
|
||||||
_hideTimer.callOnce(kButtonHideDelay);
|
_hideTimer.callOnce(wasInside
|
||||||
|
? kButtonExpandedHideDelay
|
||||||
|
: kButtonHideDelay);
|
||||||
} else {
|
} else {
|
||||||
_hideTimer.cancel();
|
_hideTimer.cancel();
|
||||||
}
|
}
|
||||||
|
@ -211,30 +226,49 @@ void Button::applyState(State state, Fn<void(QRect)> update) {
|
||||||
_expandTimer.cancel();
|
_expandTimer.cancel();
|
||||||
_hideTimer.cancel();
|
_hideTimer.cancel();
|
||||||
}
|
}
|
||||||
const auto finalHeight = (state == State::Inside)
|
const auto finalHeight = (state == State::Hidden)
|
||||||
|
? _heightAnimation.value(_finalHeight)
|
||||||
|
: (state == State::Inside)
|
||||||
? _expandedHeight
|
? _expandedHeight
|
||||||
: _collapsed.height();
|
: _collapsed.height();
|
||||||
if (_finalHeight != finalHeight) {
|
if (_finalHeight != finalHeight) {
|
||||||
_heightAnimation.start(
|
if (state == State::Hidden) {
|
||||||
[=] { updateGeometry(_update); },
|
_heightAnimation.stop();
|
||||||
_finalHeight,
|
} else {
|
||||||
finalHeight,
|
_heightAnimation.start(
|
||||||
kExpandDuration);
|
[=] { updateGeometry(_update); },
|
||||||
|
_finalHeight,
|
||||||
|
finalHeight,
|
||||||
|
(state == State::Inside
|
||||||
|
? kExpandDuration
|
||||||
|
: kCollapseDuration),
|
||||||
|
anim::easeOutCirc);
|
||||||
|
}
|
||||||
_finalHeight = finalHeight;
|
_finalHeight = finalHeight;
|
||||||
}
|
}
|
||||||
updateGeometry(update);
|
updateGeometry(update);
|
||||||
if (_state == state) {
|
if (_state == state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto duration = (state == State::Hidden
|
const auto duration = (state == State::Hidden || _state == State::Hidden)
|
||||||
|| _state == State::Hidden)
|
|
||||||
? kToggleDuration
|
? kToggleDuration
|
||||||
: kActivateDuration;
|
: kActivateDuration;
|
||||||
_scaleAnimation.start(
|
const auto finalScale = ScaleForState(state);
|
||||||
|
_opacityAnimation.start(
|
||||||
[=] { _update(_geometry); },
|
[=] { _update(_geometry); },
|
||||||
ScaleForState(_state),
|
OpacityForScale(ScaleForState(_state)),
|
||||||
ScaleForState(state),
|
OpacityForScale(ScaleForState(state)),
|
||||||
duration);
|
duration,
|
||||||
|
anim::sineInOut);
|
||||||
|
if (state != State::Hidden && _finalScale != finalScale) {
|
||||||
|
_scaleAnimation.start(
|
||||||
|
[=] { _update(_geometry); },
|
||||||
|
_finalScale,
|
||||||
|
finalScale,
|
||||||
|
duration,
|
||||||
|
anim::sineInOut);
|
||||||
|
_finalScale = finalScale;
|
||||||
|
}
|
||||||
_state = state;
|
_state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +290,11 @@ float64 Button::OpacityForScale(float64 scale) {
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 Button::currentScale() const {
|
float64 Button::currentScale() const {
|
||||||
return _scaleAnimation.value(ScaleForState(_state));
|
return _scaleAnimation.value(_finalScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
float64 Button::currentOpacity() const {
|
||||||
|
return _opacityAnimation.value(OpacityForScale(ScaleForState(_state)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Manager::Manager(
|
Manager::Manager(
|
||||||
|
@ -355,40 +393,65 @@ void Manager::showButtonDelayed() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::applyList(std::vector<Data::Reaction> list) {
|
void Manager::applyList(std::vector<Data::Reaction> list) {
|
||||||
constexpr auto proj = &Data::Reaction::emoji;
|
constexpr auto predicate = [](
|
||||||
if (ranges::equal(_list, list, ranges::equal_to{}, proj, proj)) {
|
const Data::Reaction &a,
|
||||||
|
const Data::Reaction &b) {
|
||||||
|
return (a.emoji == b.emoji)
|
||||||
|
&& (a.appearAnimation == b.appearAnimation)
|
||||||
|
&& (a.selectAnimation == b.selectAnimation);
|
||||||
|
};
|
||||||
|
if (ranges::equal(_list, list, predicate)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_list = std::move(list);
|
_list = std::move(list);
|
||||||
_links = std::vector<ClickHandlerPtr>(_list.size());
|
_links = std::vector<ClickHandlerPtr>(_list.size());
|
||||||
if (_list.empty()) {
|
if (_list.empty()) {
|
||||||
_mainReactionMedia = nullptr;
|
_mainReactionMedia = nullptr;
|
||||||
|
_mainReactionLifetime.destroy();
|
||||||
|
_icons.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto main = _list.front().staticIcon;
|
const auto main = _list.front().selectAnimation;
|
||||||
if (_mainReactionMedia && _mainReactionMedia->owner() == main) {
|
if (_mainReactionMedia
|
||||||
|
&& _mainReactionMedia->owner() == main) {
|
||||||
|
if (!_mainReactionLifetime) {
|
||||||
|
loadIcons();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_mainReactionLifetime.destroy();
|
||||||
_mainReactionMedia = main->createMediaView();
|
_mainReactionMedia = main->createMediaView();
|
||||||
if (const auto image = _mainReactionMedia->getStickerLarge()) {
|
_mainReactionMedia->checkStickerLarge();
|
||||||
setMainReactionImage(image->original());
|
if (_mainReactionMedia->loaded()) {
|
||||||
|
setMainReactionIcon();
|
||||||
} else {
|
} else {
|
||||||
main->session().downloaderTaskFinished(
|
main->session().downloaderTaskFinished(
|
||||||
) | rpl::map([=] {
|
) | rpl::filter([=] {
|
||||||
return _mainReactionMedia->getStickerLarge();
|
return _mainReactionMedia->loaded();
|
||||||
}) | rpl::filter_nullptr() | rpl::take(
|
}) | rpl::take(1) | rpl::start_with_next([=] {
|
||||||
1
|
setMainReactionIcon();
|
||||||
) | rpl::start_with_next([=](not_null<Image*> image) {
|
|
||||||
setMainReactionImage(image->original());
|
|
||||||
}, _mainReactionLifetime);
|
}, _mainReactionLifetime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::setMainReactionImage(QImage image) {
|
void Manager::setMainReactionIcon() {
|
||||||
_mainReactionImage = std::move(image);
|
_mainReactionLifetime.destroy();
|
||||||
ranges::fill(_validBg, false);
|
ranges::fill(_validBg, false);
|
||||||
ranges::fill(_validEmoji, false);
|
ranges::fill(_validEmoji, false);
|
||||||
loadOtherReactions();
|
loadIcons();
|
||||||
|
const auto i = _loadCache.find(_mainReactionMedia->owner());
|
||||||
|
if (i != end(_loadCache) && i->second.icon) {
|
||||||
|
const auto &icon = i->second.icon;
|
||||||
|
if (icon->frameIndex() == icon->framesCount() - 1
|
||||||
|
&& icon->width() == MainReactionSize()) {
|
||||||
|
_mainReactionImage = i->second.icon->frame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_mainReactionImage = CreateIcon(
|
||||||
|
_mainReactionMedia.get(),
|
||||||
|
-1,
|
||||||
|
MainReactionSize())->frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
QMargins Manager::innerMargins() const {
|
QMargins Manager::innerMargins() const {
|
||||||
|
@ -408,41 +471,56 @@ QRect Manager::buttonInner(not_null<Button*> button) const {
|
||||||
return button->geometry().marginsRemoved(innerMargins());
|
return button->geometry().marginsRemoved(innerMargins());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::loadOtherReactions() {
|
bool Manager::checkIconLoaded(ReactionDocument &entry) const {
|
||||||
for (const auto &reaction : _list) {
|
if (!entry.media) {
|
||||||
const auto icon = reaction.staticIcon;
|
return true;
|
||||||
if (_otherReactions.contains(icon)) {
|
} else if (!entry.media->loaded()) {
|
||||||
continue;
|
return false;
|
||||||
|
}
|
||||||
|
const auto size = (entry.media == _mainReactionMedia)
|
||||||
|
? MainReactionSize()
|
||||||
|
: CornerImageSize(1.);
|
||||||
|
entry.icon = CreateIcon(entry.media.get(), entry.startFrame, size);
|
||||||
|
entry.media = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Manager::loadIcons() {
|
||||||
|
const auto load = [&](not_null<DocumentData*> document, int frame) {
|
||||||
|
if (const auto i = _loadCache.find(document); i != end(_loadCache)) {
|
||||||
|
return i->second.icon;
|
||||||
}
|
}
|
||||||
auto &entry = _otherReactions.emplace(icon, OtherReactionImage{
|
auto &entry = _loadCache.emplace(document).first->second;
|
||||||
.media = icon->createMediaView(),
|
entry.media = document->createMediaView();
|
||||||
}).first->second;
|
entry.media->checkStickerLarge();
|
||||||
if (const auto image = entry.media->getStickerLarge()) {
|
entry.startFrame = frame;
|
||||||
entry.image = PrepareMaxOtherReaction(image->original());
|
if (!checkIconLoaded(entry) && !_loadCacheLifetime) {
|
||||||
entry.media = nullptr;
|
document->session().downloaderTaskFinished(
|
||||||
} else if (!_otherReactionsLifetime) {
|
|
||||||
icon->session().downloaderTaskFinished(
|
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
checkOtherReactions();
|
checkIcons();
|
||||||
}, _otherReactionsLifetime);
|
}, _loadCacheLifetime);
|
||||||
}
|
}
|
||||||
|
return entry.icon;
|
||||||
|
};
|
||||||
|
_icons.clear();
|
||||||
|
for (const auto &reaction : _list) {
|
||||||
|
_icons.push_back({
|
||||||
|
.appear = load(reaction.appearAnimation, 1),
|
||||||
|
.select = load(reaction.selectAnimation, -1),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::checkOtherReactions() {
|
void Manager::checkIcons() {
|
||||||
auto all = true;
|
auto all = true;
|
||||||
for (auto &[icon, entry] : _otherReactions) {
|
for (auto &[document, entry] : _loadCache) {
|
||||||
if (entry.media) {
|
if (!checkIconLoaded(entry)) {
|
||||||
if (const auto image = entry.media->getStickerLarge()) {
|
all = false;
|
||||||
entry.image = PrepareMaxOtherReaction(image->original());
|
|
||||||
entry.media = nullptr;
|
|
||||||
} else {
|
|
||||||
all = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (all) {
|
if (all) {
|
||||||
_otherReactionsLifetime.destroy();
|
_loadCacheLifetime.destroy();
|
||||||
|
loadIcons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,7 +627,7 @@ void Manager::paintButton(
|
||||||
not_null<Button*> button,
|
not_null<Button*> button,
|
||||||
int frameIndex,
|
int frameIndex,
|
||||||
float64 scale) {
|
float64 scale) {
|
||||||
const auto opacity = Button::OpacityForScale(scale);
|
const auto opacity = button->currentOpacity();
|
||||||
if (opacity == 0.) {
|
if (opacity == 0.) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -630,25 +708,31 @@ void Manager::paintAllEmoji(
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
const auto between = st::reactionCornerSkip;
|
const auto between = st::reactionCornerSkip;
|
||||||
const auto oneHeight = st::reactionCornerSize.height() + between;
|
const auto oneHeight = st::reactionCornerSize.height() + between;
|
||||||
const auto oneSize = CornerImageSize(scale);
|
const auto finalSize = CornerImageSize(1.);
|
||||||
|
const auto remove = finalSize * (1. - scale) / 2.;
|
||||||
|
const auto basicTarget = QRectF(QRect(
|
||||||
|
_inner.x() + (_inner.width() - finalSize) / 2,
|
||||||
|
_inner.y() + (_inner.height() - finalSize) / 2,
|
||||||
|
finalSize,
|
||||||
|
finalSize
|
||||||
|
)).marginsRemoved({ remove, remove, remove, remove });
|
||||||
const auto expandUp = button->expandUp();
|
const auto expandUp = button->expandUp();
|
||||||
const auto shift = QPoint(0, oneHeight * (expandUp ? -1 : 1));
|
const auto shift = QPoint(0, oneHeight * (expandUp ? -1 : 1));
|
||||||
auto emojiPosition = mainEmojiPosition
|
auto emojiPosition = mainEmojiPosition
|
||||||
+ QPoint(0, button->scroll() * (expandUp ? 1 : -1));
|
+ QPoint(0, button->scroll() * (expandUp ? 1 : -1));
|
||||||
for (const auto &reaction : _list) {
|
for (const auto &icon : _icons) {
|
||||||
const auto inner = _inner.translated(emojiPosition);
|
const auto target = basicTarget.translated(emojiPosition);
|
||||||
const auto target = QRect(
|
|
||||||
inner.x() + (inner.width() - oneSize) / 2,
|
|
||||||
inner.y() + (inner.height() - oneSize) / 2,
|
|
||||||
oneSize,
|
|
||||||
oneSize);
|
|
||||||
if (target.intersects(clip)) {
|
|
||||||
const auto i = _otherReactions.find(reaction.staticIcon);
|
|
||||||
if (i != end(_otherReactions) && !i->second.image.isNull()) {
|
|
||||||
p.drawImage(target, i->second.image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emojiPosition += shift;
|
emojiPosition += shift;
|
||||||
|
|
||||||
|
if (!target.intersects(clip)) {
|
||||||
|
continue;
|
||||||
|
} else if (icon.appear) {
|
||||||
|
const auto size = int(base::SafeRound(target.width()));
|
||||||
|
const auto frame = icon.appear->frame(
|
||||||
|
{ size, size },
|
||||||
|
[=] { if (_button) _buttonUpdate(_button->geometry()); });
|
||||||
|
p.drawImage(target, frame.image);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,10 @@ using PaintContext = Ui::ChatPaintContext;
|
||||||
struct TextState;
|
struct TextState;
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
||||||
|
namespace Lottie {
|
||||||
|
class Icon;
|
||||||
|
} // namespace Lottie
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
|
|
||||||
enum class ExpandDirection {
|
enum class ExpandDirection {
|
||||||
|
@ -74,6 +78,7 @@ public:
|
||||||
[[nodiscard]] QRect geometry() const;
|
[[nodiscard]] QRect geometry() const;
|
||||||
[[nodiscard]] int scroll() const;
|
[[nodiscard]] int scroll() const;
|
||||||
[[nodiscard]] float64 currentScale() const;
|
[[nodiscard]] float64 currentScale() const;
|
||||||
|
[[nodiscard]] float64 currentOpacity() const;
|
||||||
[[nodiscard]] bool consumeWheelEvent(not_null<QWheelEvent*> e);
|
[[nodiscard]] bool consumeWheelEvent(not_null<QWheelEvent*> e);
|
||||||
|
|
||||||
[[nodiscard]] static float64 ScaleForState(State state);
|
[[nodiscard]] static float64 ScaleForState(State state);
|
||||||
|
@ -89,7 +94,9 @@ private:
|
||||||
|
|
||||||
const Fn<void(QRect)> _update;
|
const Fn<void(QRect)> _update;
|
||||||
State _state = State::Hidden;
|
State _state = State::Hidden;
|
||||||
|
float64 _finalScale = 0.;
|
||||||
Ui::Animations::Simple _scaleAnimation;
|
Ui::Animations::Simple _scaleAnimation;
|
||||||
|
Ui::Animations::Simple _opacityAnimation;
|
||||||
Ui::Animations::Simple _heightAnimation;
|
Ui::Animations::Simple _heightAnimation;
|
||||||
|
|
||||||
QRect _collapsed;
|
QRect _collapsed;
|
||||||
|
@ -131,9 +138,14 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct OtherReactionImage {
|
struct ReactionDocument {
|
||||||
QImage image;
|
|
||||||
std::shared_ptr<Data::DocumentMedia> media;
|
std::shared_ptr<Data::DocumentMedia> media;
|
||||||
|
std::shared_ptr<Lottie::Icon> icon;
|
||||||
|
int startFrame = 0;
|
||||||
|
};
|
||||||
|
struct ReactionIcons {
|
||||||
|
std::shared_ptr<Lottie::Icon> appear;
|
||||||
|
std::shared_ptr<Lottie::Icon> select;
|
||||||
};
|
};
|
||||||
static constexpr auto kFramesCount = 30;
|
static constexpr auto kFramesCount = 30;
|
||||||
|
|
||||||
|
@ -164,7 +176,7 @@ private:
|
||||||
const QImage &image,
|
const QImage &image,
|
||||||
QRect source);
|
QRect source);
|
||||||
|
|
||||||
void setMainReactionImage(QImage image);
|
void setMainReactionIcon();
|
||||||
void applyPatternedShadow(const QColor &shadow);
|
void applyPatternedShadow(const QColor &shadow);
|
||||||
[[nodiscard]] QRect cacheRect(int frameIndex, int columnIndex) const;
|
[[nodiscard]] QRect cacheRect(int frameIndex, int columnIndex) const;
|
||||||
QRect validateShadow(
|
QRect validateShadow(
|
||||||
|
@ -181,12 +193,15 @@ private:
|
||||||
[[nodiscard]] QMargins innerMargins() const;
|
[[nodiscard]] QMargins innerMargins() const;
|
||||||
[[nodiscard]] QRect buttonInner() const;
|
[[nodiscard]] QRect buttonInner() const;
|
||||||
[[nodiscard]] QRect buttonInner(not_null<Button*> button) const;
|
[[nodiscard]] QRect buttonInner(not_null<Button*> button) const;
|
||||||
void loadOtherReactions();
|
|
||||||
void checkOtherReactions();
|
|
||||||
[[nodiscard]] ClickHandlerPtr computeButtonLink(QPoint position) const;
|
[[nodiscard]] ClickHandlerPtr computeButtonLink(QPoint position) const;
|
||||||
[[nodiscard]] ClickHandlerPtr resolveButtonLink(
|
[[nodiscard]] ClickHandlerPtr resolveButtonLink(
|
||||||
const Data::Reaction &reaction) const;
|
const Data::Reaction &reaction) const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool checkIconLoaded(ReactionDocument &entry) const;
|
||||||
|
void loadIcons();
|
||||||
|
void checkIcons();
|
||||||
|
|
||||||
rpl::event_stream<Chosen> _chosen;
|
rpl::event_stream<Chosen> _chosen;
|
||||||
std::vector<Data::Reaction> _list;
|
std::vector<Data::Reaction> _list;
|
||||||
mutable std::vector<ClickHandlerPtr> _links;
|
mutable std::vector<ClickHandlerPtr> _links;
|
||||||
|
@ -205,10 +220,9 @@ private:
|
||||||
QImage _mainReactionImage;
|
QImage _mainReactionImage;
|
||||||
rpl::lifetime _mainReactionLifetime;
|
rpl::lifetime _mainReactionLifetime;
|
||||||
|
|
||||||
base::flat_map<
|
base::flat_map<not_null<DocumentData*>, ReactionDocument> _loadCache;
|
||||||
not_null<DocumentData*>,
|
std::vector<ReactionIcons> _icons;
|
||||||
OtherReactionImage> _otherReactions;
|
rpl::lifetime _loadCacheLifetime;
|
||||||
rpl::lifetime _otherReactionsLifetime;
|
|
||||||
|
|
||||||
std::optional<ButtonParameters> _scheduledParameters;
|
std::optional<ButtonParameters> _scheduledParameters;
|
||||||
base::Timer _buttonShowTimer;
|
base::Timer _buttonShowTimer;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 5b576ba9b90f621b3dd965d44caf8bd71c00f6ae
|
Subproject commit ab022b57a0a970a9a3ba73bc7fff7ea2cffc046b
|
Loading…
Add table
Reference in a new issue