mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-07-31 18:03:04 +02:00
Initial reaction effects implementation.
This commit is contained in:
parent
db453ab7ae
commit
0ab26f0c82
33 changed files with 733 additions and 107 deletions
|
@ -638,6 +638,8 @@ PRIVATE
|
||||||
history/view/history_view_pinned_section.h
|
history/view/history_view_pinned_section.h
|
||||||
history/view/history_view_pinned_tracker.cpp
|
history/view/history_view_pinned_tracker.cpp
|
||||||
history/view/history_view_pinned_tracker.h
|
history/view/history_view_pinned_tracker.h
|
||||||
|
history/view/history_view_react_animation.cpp
|
||||||
|
history/view/history_view_react_animation.h
|
||||||
history/view/history_view_react_button.cpp
|
history/view/history_view_react_button.cpp
|
||||||
history/view/history_view_react_button.h
|
history/view/history_view_react_button.h
|
||||||
history/view/history_view_reactions.cpp
|
history/view/history_view_reactions.cpp
|
||||||
|
|
|
@ -224,19 +224,21 @@ void Reactions::request() {
|
||||||
MTP_int(_hash)
|
MTP_int(_hash)
|
||||||
)).done([=](const MTPmessages_AvailableReactions &result) {
|
)).done([=](const MTPmessages_AvailableReactions &result) {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
|
const auto oldCache = base::take(_iconsCache);
|
||||||
|
const auto toCache = [&](DocumentData *document) {
|
||||||
|
if (document) {
|
||||||
|
_iconsCache.emplace(document, document->createMediaView());
|
||||||
|
}
|
||||||
|
};
|
||||||
result.match([&](const MTPDmessages_availableReactions &data) {
|
result.match([&](const MTPDmessages_availableReactions &data) {
|
||||||
_hash = data.vhash().v;
|
_hash = data.vhash().v;
|
||||||
|
|
||||||
const auto &list = data.vreactions().v;
|
const auto &list = data.vreactions().v;
|
||||||
const auto oldCache = base::take(_iconsCache);
|
|
||||||
_active.clear();
|
_active.clear();
|
||||||
_available.clear();
|
_available.clear();
|
||||||
_active.reserve(list.size());
|
_active.reserve(list.size());
|
||||||
_available.reserve(list.size());
|
_available.reserve(list.size());
|
||||||
_iconsCache.reserve(list.size() * 2);
|
_iconsCache.reserve(list.size() * 2);
|
||||||
const auto toCache = [&](not_null<DocumentData*> document) {
|
|
||||||
_iconsCache.emplace(document, document->createMediaView());
|
|
||||||
};
|
|
||||||
for (const auto &reaction : list) {
|
for (const auto &reaction : list) {
|
||||||
if (const auto parsed = parse(reaction)) {
|
if (const auto parsed = parse(reaction)) {
|
||||||
_available.push_back(*parsed);
|
_available.push_back(*parsed);
|
||||||
|
@ -244,6 +246,8 @@ void Reactions::request() {
|
||||||
_active.push_back(*parsed);
|
_active.push_back(*parsed);
|
||||||
toCache(parsed->appearAnimation);
|
toCache(parsed->appearAnimation);
|
||||||
toCache(parsed->selectAnimation);
|
toCache(parsed->selectAnimation);
|
||||||
|
toCache(parsed->centerIcon);
|
||||||
|
toCache(parsed->aroundAnimation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,10 +281,10 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
||||||
.appearAnimation = _owner->processDocument(
|
.appearAnimation = _owner->processDocument(
|
||||||
data.vappear_animation()),
|
data.vappear_animation()),
|
||||||
.selectAnimation = selectAnimation,
|
.selectAnimation = selectAnimation,
|
||||||
.activateAnimation = _owner->processDocument(
|
//.activateAnimation = _owner->processDocument(
|
||||||
data.vactivate_animation()),
|
// data.vactivate_animation()),
|
||||||
.activateEffects = _owner->processDocument(
|
//.activateEffects = _owner->processDocument(
|
||||||
data.veffect_animation()),
|
// data.veffect_animation()),
|
||||||
.centerIcon = (data.vcenter_icon()
|
.centerIcon = (data.vcenter_icon()
|
||||||
? _owner->processDocument(*data.vcenter_icon()).get()
|
? _owner->processDocument(*data.vcenter_icon()).get()
|
||||||
: nullptr),
|
: nullptr),
|
||||||
|
|
|
@ -24,8 +24,8 @@ struct Reaction {
|
||||||
not_null<DocumentData*> staticIcon;
|
not_null<DocumentData*> staticIcon;
|
||||||
not_null<DocumentData*> appearAnimation;
|
not_null<DocumentData*> appearAnimation;
|
||||||
not_null<DocumentData*> selectAnimation;
|
not_null<DocumentData*> selectAnimation;
|
||||||
not_null<DocumentData*> activateAnimation;
|
//not_null<DocumentData*> activateAnimation;
|
||||||
not_null<DocumentData*> activateEffects;
|
//not_null<DocumentData*> activateEffects;
|
||||||
DocumentData *centerIcon = nullptr;
|
DocumentData *centerIcon = nullptr;
|
||||||
DocumentData *aroundAnimation = nullptr;
|
DocumentData *aroundAnimation = nullptr;
|
||||||
bool active = false;
|
bool active = false;
|
||||||
|
|
|
@ -377,8 +377,21 @@ HistoryInner::HistoryInner(
|
||||||
using ChosenReaction = HistoryView::Reactions::Manager::Chosen;
|
using ChosenReaction = HistoryView::Reactions::Manager::Chosen;
|
||||||
_reactionsManager->chosen(
|
_reactionsManager->chosen(
|
||||||
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
||||||
if (const auto item = session().data().message(reaction.context)) {
|
const auto item = session().data().message(reaction.context);
|
||||||
item->toggleReaction(reaction.emoji);
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item->toggleReaction(reaction.emoji);
|
||||||
|
if (item->chosenReaction() != reaction.emoji) {
|
||||||
|
return;
|
||||||
|
} else if (const auto view = item->mainView()) {
|
||||||
|
if (const auto top = itemTop(view); top >= 0) {
|
||||||
|
view->animateSendReaction({
|
||||||
|
.emoji = reaction.emoji,
|
||||||
|
.flyIcon = reaction.icon,
|
||||||
|
.flyFrom = reaction.geometry.translated(0, -top),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/view/history_view_message.h"
|
#include "history/view/history_view_message.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
|
#include "history/view/history_view_react_animation.h"
|
||||||
|
#include "lottie/lottie_icon.h"
|
||||||
#include "data/data_message_reactions.h"
|
#include "data/data_message_reactions.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
@ -31,6 +33,8 @@ BottomInfo::BottomInfo(
|
||||||
layout();
|
layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BottomInfo::~BottomInfo() = default;
|
||||||
|
|
||||||
void BottomInfo::update(Data &&data, int availableWidth) {
|
void BottomInfo::update(Data &&data, int availableWidth) {
|
||||||
_data = std::move(data);
|
_data = std::move(data);
|
||||||
layout();
|
layout();
|
||||||
|
@ -221,19 +225,27 @@ void BottomInfo::paint(
|
||||||
left += width() - available;
|
left += width() - available;
|
||||||
top += st::msgDateFont->height;
|
top += st::msgDateFont->height;
|
||||||
}
|
}
|
||||||
paintReactions(p, left, top, available);
|
paintReactions(p, position, left, top, available);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BottomInfo::paintReactions(
|
void BottomInfo::paintReactions(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
|
QPoint origin,
|
||||||
int left,
|
int left,
|
||||||
int top,
|
int top,
|
||||||
int availableWidth) const {
|
int availableWidth) const {
|
||||||
auto x = left;
|
auto x = left;
|
||||||
auto y = top;
|
auto y = top;
|
||||||
auto widthLeft = availableWidth;
|
auto widthLeft = availableWidth;
|
||||||
|
const auto animated = _reactionAnimation
|
||||||
|
? _reactionAnimation->playingAroundEmoji()
|
||||||
|
: QString();
|
||||||
|
if (_reactionAnimation && animated.isEmpty()) {
|
||||||
|
_reactionAnimation = nullptr;
|
||||||
|
}
|
||||||
for (const auto &reaction : _reactions) {
|
for (const auto &reaction : _reactions) {
|
||||||
|
const auto animating = (reaction.emoji == animated);
|
||||||
const auto add = (reaction.countTextWidth > 0)
|
const auto add = (reaction.countTextWidth > 0)
|
||||||
? st::reactionInfoDigitSkip
|
? st::reactionInfoDigitSkip
|
||||||
: st::reactionInfoBetween;
|
: st::reactionInfoBetween;
|
||||||
|
@ -251,11 +263,18 @@ void BottomInfo::paintReactions(
|
||||||
reaction.emoji,
|
reaction.emoji,
|
||||||
::Data::Reactions::ImageSize::BottomInfo);
|
::Data::Reactions::ImageSize::BottomInfo);
|
||||||
}
|
}
|
||||||
if (!reaction.image.isNull()) {
|
const auto image = QRect(
|
||||||
p.drawImage(
|
x + (st::reactionInfoSize - st::reactionInfoImage) / 2,
|
||||||
x + (st::reactionInfoSize - st::reactionInfoImage) / 2,
|
y + (st::msgDateFont->height - st::reactionInfoImage) / 2,
|
||||||
y + (st::msgDateFont->height - st::reactionInfoImage) / 2,
|
st::reactionInfoImage,
|
||||||
reaction.image);
|
st::reactionInfoImage);
|
||||||
|
const auto skipImage = animating
|
||||||
|
&& (reaction.count < 2 || !_reactionAnimation->flying());
|
||||||
|
if (!reaction.image.isNull() && !skipImage) {
|
||||||
|
p.drawImage(image.topLeft(), reaction.image);
|
||||||
|
}
|
||||||
|
if (animating) {
|
||||||
|
_reactionAnimation->paint(p, origin, image);
|
||||||
}
|
}
|
||||||
if (reaction.countTextWidth > 0) {
|
if (reaction.countTextWidth > 0) {
|
||||||
p.drawText(
|
p.drawText(
|
||||||
|
@ -406,6 +425,26 @@ void BottomInfo::setReactionCount(Reaction &reaction, int count) {
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BottomInfo::animateReactionSend(
|
||||||
|
SendReactionAnimationArgs &&args,
|
||||||
|
Fn<void()> repaint) {
|
||||||
|
_reactionAnimation = std::make_unique<Reactions::SendAnimation>(
|
||||||
|
_reactionsOwner,
|
||||||
|
args.translated(QPoint(width(), height())),
|
||||||
|
std::move(repaint),
|
||||||
|
st::reactionInfoImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto BottomInfo::takeSendReactionAnimation()
|
||||||
|
-> std::unique_ptr<Reactions::SendAnimation> {
|
||||||
|
return std::move(_reactionAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BottomInfo::continueSendReactionAnimation(
|
||||||
|
std::unique_ptr<Reactions::SendAnimation> animation) {
|
||||||
|
_reactionAnimation = std::move(animation);
|
||||||
|
}
|
||||||
|
|
||||||
BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
|
BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
|
||||||
using Flag = BottomInfo::Data::Flag;
|
using Flag = BottomInfo::Data::Flag;
|
||||||
const auto item = message->message();
|
const auto item = message->message();
|
||||||
|
|
|
@ -20,11 +20,15 @@ class Reactions;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
namespace Reactions {
|
||||||
|
class SendAnimation;
|
||||||
|
} // namespace Reactions
|
||||||
|
|
||||||
using PaintContext = Ui::ChatPaintContext;
|
using PaintContext = Ui::ChatPaintContext;
|
||||||
|
|
||||||
class Message;
|
class Message;
|
||||||
struct TextState;
|
struct TextState;
|
||||||
|
struct SendReactionAnimationArgs;
|
||||||
|
|
||||||
class BottomInfo final : public Object {
|
class BottomInfo final : public Object {
|
||||||
public:
|
public:
|
||||||
|
@ -49,6 +53,7 @@ public:
|
||||||
Flags flags;
|
Flags flags;
|
||||||
};
|
};
|
||||||
BottomInfo(not_null<::Data::Reactions*> reactionsOwner, Data &&data);
|
BottomInfo(not_null<::Data::Reactions*> reactionsOwner, Data &&data);
|
||||||
|
~BottomInfo();
|
||||||
|
|
||||||
void update(Data &&data, int availableWidth);
|
void update(Data &&data, int availableWidth);
|
||||||
|
|
||||||
|
@ -67,6 +72,14 @@ public:
|
||||||
bool inverted,
|
bool inverted,
|
||||||
const PaintContext &context) const;
|
const PaintContext &context) const;
|
||||||
|
|
||||||
|
void animateReactionSend(
|
||||||
|
SendReactionAnimationArgs &&args,
|
||||||
|
Fn<void()> repaint);
|
||||||
|
[[nodiscard]] auto takeSendReactionAnimation()
|
||||||
|
-> std::unique_ptr<Reactions::SendAnimation>;
|
||||||
|
void continueSendReactionAnimation(
|
||||||
|
std::unique_ptr<Reactions::SendAnimation> animation);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Reaction {
|
struct Reaction {
|
||||||
mutable QImage image;
|
mutable QImage image;
|
||||||
|
@ -86,6 +99,7 @@ private:
|
||||||
[[nodiscard]] int countReactionsHeight(int newWidth) const;
|
[[nodiscard]] int countReactionsHeight(int newWidth) const;
|
||||||
void paintReactions(
|
void paintReactions(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
|
QPoint origin,
|
||||||
int left,
|
int left,
|
||||||
int top,
|
int top,
|
||||||
int availableWidth) const;
|
int availableWidth) const;
|
||||||
|
@ -102,6 +116,7 @@ private:
|
||||||
Ui::Text::String _views;
|
Ui::Text::String _views;
|
||||||
Ui::Text::String _replies;
|
Ui::Text::String _replies;
|
||||||
std::vector<Reaction> _reactions;
|
std::vector<Reaction> _reactions;
|
||||||
|
mutable std::unique_ptr<Reactions::SendAnimation> _reactionAnimation;
|
||||||
int _reactionsMaxWidth = 0;
|
int _reactionsMaxWidth = 0;
|
||||||
int _dateWidth = 0;
|
int _dateWidth = 0;
|
||||||
bool _authorElided = false;
|
bool _authorElided = false;
|
||||||
|
|
|
@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/media/history_view_media_grouped.h"
|
#include "history/view/media/history_view_media_grouped.h"
|
||||||
#include "history/view/media/history_view_sticker.h"
|
#include "history/view/media/history_view_sticker.h"
|
||||||
#include "history/view/media/history_view_large_emoji.h"
|
#include "history/view/media/history_view_large_emoji.h"
|
||||||
|
#include "history/view/history_view_react_animation.h"
|
||||||
#include "history/view/history_view_react_button.h"
|
#include "history/view/history_view_react_button.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
@ -341,6 +342,15 @@ void DateBadge::paint(
|
||||||
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
|
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SendReactionAnimationArgs SendReactionAnimationArgs::translated(
|
||||||
|
QPoint point) const {
|
||||||
|
return {
|
||||||
|
.emoji = emoji,
|
||||||
|
.flyIcon = flyIcon,
|
||||||
|
.flyFrom = flyFrom.translated(point),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Element::Element(
|
Element::Element(
|
||||||
not_null<ElementDelegate*> delegate,
|
not_null<ElementDelegate*> delegate,
|
||||||
not_null<HistoryItem*> data,
|
not_null<HistoryItem*> data,
|
||||||
|
@ -392,6 +402,10 @@ void Element::setY(int y) {
|
||||||
void Element::refreshDataIdHook() {
|
void Element::refreshDataIdHook() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Element::repaint() const {
|
||||||
|
history()->owner().requestViewRepaint(this);
|
||||||
|
}
|
||||||
|
|
||||||
void Element::paintHighlight(
|
void Element::paintHighlight(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
|
@ -1020,7 +1034,7 @@ void Element::clickHandlerActiveChanged(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
App::hoveredLinkItem(active ? this : nullptr);
|
App::hoveredLinkItem(active ? this : nullptr);
|
||||||
history()->owner().requestViewRepaint(this);
|
repaint();
|
||||||
if (const auto media = this->media()) {
|
if (const auto media = this->media()) {
|
||||||
media->clickHandlerActiveChanged(handler, active);
|
media->clickHandlerActiveChanged(handler, active);
|
||||||
}
|
}
|
||||||
|
@ -1035,12 +1049,20 @@ void Element::clickHandlerPressedChanged(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
App::pressedLinkItem(pressed ? this : nullptr);
|
App::pressedLinkItem(pressed ? this : nullptr);
|
||||||
history()->owner().requestViewRepaint(this);
|
repaint();
|
||||||
if (const auto media = this->media()) {
|
if (const auto media = this->media()) {
|
||||||
media->clickHandlerPressedChanged(handler, pressed);
|
media->clickHandlerPressedChanged(handler, pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Element::animateSendReaction(SendReactionAnimationArgs &&args) {
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Element::takeSendReactionAnimation()
|
||||||
|
-> std::unique_ptr<Reactions::SendAnimation> {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
Element::~Element() {
|
Element::~Element() {
|
||||||
// Delete media while owner still exists.
|
// Delete media while owner still exists.
|
||||||
base::take(_media);
|
base::take(_media);
|
||||||
|
|
|
@ -33,6 +33,10 @@ struct ChatPaintContext;
|
||||||
class ChatStyle;
|
class ChatStyle;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Lottie {
|
||||||
|
class Icon;
|
||||||
|
} // namespace Lottie
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
enum class PointState : char;
|
enum class PointState : char;
|
||||||
|
@ -45,6 +49,7 @@ using PaintContext = Ui::ChatPaintContext;
|
||||||
|
|
||||||
namespace Reactions {
|
namespace Reactions {
|
||||||
struct ButtonParameters;
|
struct ButtonParameters;
|
||||||
|
class SendAnimation;
|
||||||
} // namespace Reactions
|
} // namespace Reactions
|
||||||
|
|
||||||
enum class Context : char {
|
enum class Context : char {
|
||||||
|
@ -224,6 +229,14 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SendReactionAnimationArgs {
|
||||||
|
QString emoji;
|
||||||
|
std::shared_ptr<Lottie::Icon> flyIcon;
|
||||||
|
QRect flyFrom;
|
||||||
|
|
||||||
|
[[nodiscard]] SendReactionAnimationArgs translated(QPoint point) const;
|
||||||
|
};
|
||||||
|
|
||||||
class Element
|
class Element
|
||||||
: public Object
|
: public Object
|
||||||
, public RuntimeComposer<Element>
|
, public RuntimeComposer<Element>
|
||||||
|
@ -407,9 +420,15 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] bool markSponsoredViewed(int shownFromTop) const;
|
[[nodiscard]] bool markSponsoredViewed(int shownFromTop) const;
|
||||||
|
|
||||||
|
virtual void animateSendReaction(SendReactionAnimationArgs &&args);
|
||||||
|
[[nodiscard]] virtual auto takeSendReactionAnimation()
|
||||||
|
-> std::unique_ptr<Reactions::SendAnimation>;
|
||||||
|
|
||||||
virtual ~Element();
|
virtual ~Element();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void repaint() const;
|
||||||
|
|
||||||
void paintHighlight(
|
void paintHighlight(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
|
|
|
@ -342,8 +342,21 @@ ListWidget::ListWidget(
|
||||||
using ChosenReaction = Reactions::Manager::Chosen;
|
using ChosenReaction = Reactions::Manager::Chosen;
|
||||||
_reactionsManager->chosen(
|
_reactionsManager->chosen(
|
||||||
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
||||||
if (const auto item = session().data().message(reaction.context)) {
|
const auto item = session().data().message(reaction.context);
|
||||||
item->toggleReaction(reaction.emoji);
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item->toggleReaction(reaction.emoji);
|
||||||
|
if (item->chosenReaction() != reaction.emoji) {
|
||||||
|
return;
|
||||||
|
} else if (const auto view = viewForItem(item)) {
|
||||||
|
if (const auto top = itemTop(view); top >= 0) {
|
||||||
|
view->animateSendReaction({
|
||||||
|
.emoji = reaction.emoji,
|
||||||
|
.flyIcon = reaction.icon,
|
||||||
|
.flyFrom = reaction.geometry.translated(0, -top),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
#include "history/view/media/history_view_media.h"
|
#include "history/view/media/history_view_media.h"
|
||||||
#include "history/view/media/history_view_web_page.h"
|
#include "history/view/media/history_view_web_page.h"
|
||||||
|
#include "history/view/history_view_react_animation.h"
|
||||||
#include "history/view/history_view_react_button.h"
|
#include "history/view/history_view_react_button.h"
|
||||||
#include "history/view/history_view_reactions.h"
|
#include "history/view/history_view_reactions.h"
|
||||||
#include "history/view/history_view_group_call_bar.h" // UserpicInRow.
|
#include "history/view/history_view_group_call_bar.h" // UserpicInRow.
|
||||||
|
@ -252,6 +253,17 @@ Message::Message(
|
||||||
initLogEntryOriginal();
|
initLogEntryOriginal();
|
||||||
initPsa();
|
initPsa();
|
||||||
refreshReactions();
|
refreshReactions();
|
||||||
|
auto animation = replacing
|
||||||
|
? replacing->takeSendReactionAnimation()
|
||||||
|
: nullptr;
|
||||||
|
if (animation) {
|
||||||
|
animation->setRepaintCallback([=] { repaint(); });
|
||||||
|
if (_reactions) {
|
||||||
|
_reactions->continueSendAnimation(std::move(animation));
|
||||||
|
} else {
|
||||||
|
_bottomInfo.continueSendReactionAnimation(std::move(animation));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::~Message() {
|
Message::~Message() {
|
||||||
|
@ -314,6 +326,111 @@ void Message::applyGroupAdminChanges(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Message::animateSendReaction(SendReactionAnimationArgs &&args) {
|
||||||
|
const auto item = message();
|
||||||
|
const auto media = this->media();
|
||||||
|
|
||||||
|
auto g = countGeometry();
|
||||||
|
if (g.width() < 1 || isHidden()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto repainter = [=] { repaint(); };
|
||||||
|
|
||||||
|
const auto bubble = drawBubble();
|
||||||
|
const auto reactionsInBubble = _reactions && embedReactionsInBubble();
|
||||||
|
const auto mediaDisplayed = media && media->isDisplayed();
|
||||||
|
auto keyboard = item->inlineReplyKeyboard();
|
||||||
|
auto keyboardHeight = 0;
|
||||||
|
if (keyboard) {
|
||||||
|
keyboardHeight = keyboard->naturalHeight();
|
||||||
|
g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_reactions && !reactionsInBubble) {
|
||||||
|
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
|
||||||
|
const auto reactionsLeft = (!bubble && mediaDisplayed)
|
||||||
|
? media->contentRectForReactions().x()
|
||||||
|
: 0;
|
||||||
|
g.setHeight(g.height() - reactionsHeight);
|
||||||
|
const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
|
||||||
|
_reactions->animateSend(args.translated(-reactionsPosition), repainter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto animateInBottomInfo = [&](QPoint bottomRight) {
|
||||||
|
_bottomInfo.animateReactionSend(args.translated(-bottomRight), repainter);
|
||||||
|
};
|
||||||
|
if (bubble) {
|
||||||
|
auto entry = logEntryOriginal();
|
||||||
|
|
||||||
|
// Entry page is always a bubble bottom.
|
||||||
|
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
|
||||||
|
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
|
||||||
|
|
||||||
|
auto inner = g;
|
||||||
|
if (_comments) {
|
||||||
|
inner.setHeight(inner.height() - st::historyCommentsButtonHeight);
|
||||||
|
}
|
||||||
|
auto trect = inner.marginsRemoved(st::msgPadding);
|
||||||
|
const auto reactionsTop = (reactionsInBubble && !_viewButton)
|
||||||
|
? st::mediaInBubbleSkip
|
||||||
|
: 0;
|
||||||
|
const auto reactionsHeight = reactionsInBubble
|
||||||
|
? (reactionsTop + _reactions->height())
|
||||||
|
: 0;
|
||||||
|
if (reactionsInBubble) {
|
||||||
|
trect.setHeight(trect.height() - reactionsHeight);
|
||||||
|
const auto reactionsPosition = QPoint(trect.left(), trect.top() + trect.height() + reactionsTop);
|
||||||
|
_reactions->animateSend(args.translated(-reactionsPosition), repainter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_viewButton) {
|
||||||
|
const auto belowInfo = _viewButton->belowMessageInfo();
|
||||||
|
const auto infoHeight = reactionsInBubble
|
||||||
|
? (reactionsHeight + st::msgPadding.bottom())
|
||||||
|
: _bottomInfo.height();
|
||||||
|
const auto heightMargins = QMargins(0, 0, 0, infoHeight);
|
||||||
|
if (belowInfo) {
|
||||||
|
inner -= heightMargins;
|
||||||
|
}
|
||||||
|
trect.setHeight(trect.height() - _viewButton->height());
|
||||||
|
if (reactionsInBubble) {
|
||||||
|
trect.setHeight(trect.height() + st::msgPadding.bottom());
|
||||||
|
} else if (mediaDisplayed) {
|
||||||
|
trect.setHeight(trect.height() - st::mediaInBubbleSkip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mediaOnBottom) {
|
||||||
|
trect.setHeight(trect.height()
|
||||||
|
+ st::msgPadding.bottom()
|
||||||
|
- viewButtonHeight());
|
||||||
|
}
|
||||||
|
if (mediaOnTop) {
|
||||||
|
trect.setY(trect.y() - st::msgPadding.top());
|
||||||
|
}
|
||||||
|
if (mediaDisplayed && mediaOnBottom && media->customInfoLayout()) {
|
||||||
|
auto mediaHeight = media->height();
|
||||||
|
auto mediaLeft = trect.x() - st::msgPadding.left();
|
||||||
|
auto mediaTop = (trect.y() + trect.height() - mediaHeight);
|
||||||
|
animateInBottomInfo(QPoint(mediaLeft, mediaTop) + media->resolveCustomInfoRightBottom());
|
||||||
|
} else {
|
||||||
|
animateInBottomInfo({
|
||||||
|
inner.left() + inner.width() - (st::msgPadding.right() - st::msgDateDelta.x()),
|
||||||
|
inner.top() + inner.height() - (st::msgPadding.bottom() - st::msgDateDelta.y()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (mediaDisplayed) {
|
||||||
|
animateInBottomInfo(g.topLeft() + media->resolveCustomInfoRightBottom());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Message::takeSendReactionAnimation()
|
||||||
|
-> std::unique_ptr<Reactions::SendAnimation> {
|
||||||
|
return _reactions
|
||||||
|
? _reactions->takeSendAnimation()
|
||||||
|
: _bottomInfo.takeSendReactionAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
QSize Message::performCountOptimalSize() {
|
QSize Message::performCountOptimalSize() {
|
||||||
const auto item = message();
|
const auto item = message();
|
||||||
const auto media = this->media();
|
const auto media = this->media();
|
||||||
|
@ -516,12 +633,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
const auto stm = context.messageStyle();
|
const auto stm = context.messageStyle();
|
||||||
const auto bubble = drawBubble();
|
const auto bubble = drawBubble();
|
||||||
|
|
||||||
auto dateh = 0;
|
|
||||||
if (const auto date = Get<DateBadge>()) {
|
|
||||||
dateh = date->height();
|
|
||||||
}
|
|
||||||
if (const auto bar = Get<UnreadBar>()) {
|
if (const auto bar = Get<UnreadBar>()) {
|
||||||
auto unreadbarh = bar->height();
|
auto unreadbarh = bar->height();
|
||||||
|
auto dateh = 0;
|
||||||
|
if (const auto date = Get<DateBadge>()) {
|
||||||
|
dateh = date->height();
|
||||||
|
}
|
||||||
if (context.clip.intersects(QRect(0, dateh, width(), unreadbarh))) {
|
if (context.clip.intersects(QRect(0, dateh, width(), unreadbarh))) {
|
||||||
p.translate(0, dateh);
|
p.translate(0, dateh);
|
||||||
bar->paint(
|
bar->paint(
|
||||||
|
@ -1207,7 +1324,7 @@ void Message::toggleCommentsButtonRipple(bool pressed) {
|
||||||
_comments->ripple = std::make_unique<Ui::RippleAnimation>(
|
_comments->ripple = std::make_unique<Ui::RippleAnimation>(
|
||||||
st::defaultRippleAnimation,
|
st::defaultRippleAnimation,
|
||||||
std::move(mask),
|
std::move(mask),
|
||||||
[=] { history()->owner().requestViewRepaint(this); });
|
[=] { repaint(); });
|
||||||
}
|
}
|
||||||
_comments->ripple->add(_comments->lastPoint);
|
_comments->ripple->add(_comments->lastPoint);
|
||||||
} else if (_comments->ripple) {
|
} else if (_comments->ripple) {
|
||||||
|
@ -1653,7 +1770,7 @@ void Message::psaTooltipToggled(bool tooltipShown) const {
|
||||||
state->buttonVisible = visible;
|
state->buttonVisible = visible;
|
||||||
history()->owner().notifyViewLayoutChange(this);
|
history()->owner().notifyViewLayoutChange(this);
|
||||||
state->buttonVisibleAnimation.start(
|
state->buttonVisibleAnimation.start(
|
||||||
[=] { history()->owner().requestViewRepaint(this); },
|
[=] { repaint(); },
|
||||||
visible ? 0. : 1.,
|
visible ? 0. : 1.,
|
||||||
visible ? 1. : 0.,
|
visible ? 1. : 0.,
|
||||||
st::fadeWrapDuration);
|
st::fadeWrapDuration);
|
||||||
|
@ -2009,14 +2126,17 @@ void Message::refreshReactions() {
|
||||||
auto reactionsData = InlineListDataFromMessage(this);
|
auto reactionsData = InlineListDataFromMessage(this);
|
||||||
if (!_reactions) {
|
if (!_reactions) {
|
||||||
const auto handlerFactory = [=](QString emoji) {
|
const auto handlerFactory = [=](QString emoji) {
|
||||||
|
const auto weak = base::make_weak(this);
|
||||||
const auto fullId = data()->fullId();
|
const auto fullId = data()->fullId();
|
||||||
return std::make_shared<LambdaClickHandler>([=](
|
return std::make_shared<LambdaClickHandler>([=] {
|
||||||
ClickContext context) {
|
if (const auto strong = weak.get()) {
|
||||||
const auto my = context.other.value<ClickHandlerContext>();
|
strong->data()->toggleReaction(emoji);
|
||||||
if (const auto controller = my.sessionWindow.get()) {
|
if (const auto now = weak.get()) {
|
||||||
const auto &data = controller->session().data();
|
if (now->data()->chosenReaction() == emoji) {
|
||||||
if (const auto item = data.message(fullId)) {
|
now->animateSendReaction({
|
||||||
item->toggleReaction(emoji);
|
.emoji = emoji,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2044,7 +2164,7 @@ void Message::itemDataChanged() {
|
||||||
if (wasInfo != nowInfo || wasReactions != nowReactions) {
|
if (wasInfo != nowInfo || wasReactions != nowReactions) {
|
||||||
history()->owner().requestViewResize(this);
|
history()->owner().requestViewResize(this);
|
||||||
} else {
|
} else {
|
||||||
history()->owner().requestViewRepaint(this);
|
repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2097,10 +2217,10 @@ void Message::updateViewButtonExistence() {
|
||||||
} else if (_viewButton) {
|
} else if (_viewButton) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto callback = [=] { history()->owner().requestViewRepaint(this); };
|
auto repainter = [=] { repaint(); };
|
||||||
_viewButton = sponsored
|
_viewButton = sponsored
|
||||||
? std::make_unique<ViewButton>(sponsored, std::move(callback))
|
? std::make_unique<ViewButton>(sponsored, std::move(repainter))
|
||||||
: std::make_unique<ViewButton>(media, std::move(callback));
|
: std::make_unique<ViewButton>(media, std::move(repainter));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Message::initLogEntryOriginal() {
|
void Message::initLogEntryOriginal() {
|
||||||
|
|
|
@ -134,6 +134,10 @@ public:
|
||||||
void applyGroupAdminChanges(
|
void applyGroupAdminChanges(
|
||||||
const base::flat_set<UserId> &changes) override;
|
const base::flat_set<UserId> &changes) override;
|
||||||
|
|
||||||
|
void animateSendReaction(SendReactionAnimationArgs &&args) override;
|
||||||
|
auto takeSendReactionAnimation()
|
||||||
|
-> std::unique_ptr<Reactions::SendAnimation> override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void refreshDataIdHook() override;
|
void refreshDataIdHook() override;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "history/view/history_view_react_animation.h"
|
||||||
|
|
||||||
|
#include "history/view/history_view_element.h"
|
||||||
|
#include "lottie/lottie_icon.h"
|
||||||
|
#include "data/data_message_reactions.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_document_media.h"
|
||||||
|
|
||||||
|
namespace HistoryView::Reactions {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kFlyDuration = crl::time(200);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SendAnimation::SendAnimation(
|
||||||
|
not_null<::Data::Reactions*> owner,
|
||||||
|
SendReactionAnimationArgs &&args,
|
||||||
|
Fn<void()> repaint,
|
||||||
|
int size)
|
||||||
|
: _owner(owner)
|
||||||
|
, _emoji(args.emoji)
|
||||||
|
, _repaint(std::move(repaint))
|
||||||
|
, _flyFrom(args.flyFrom) {
|
||||||
|
const auto &list = owner->list(::Data::Reactions::Type::All);
|
||||||
|
const auto i = ranges::find(list, _emoji, &::Data::Reaction::emoji);
|
||||||
|
if (i == end(list) || !i->centerIcon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto resolve = [&](
|
||||||
|
std::unique_ptr<Lottie::Icon> &icon,
|
||||||
|
DocumentData *document,
|
||||||
|
int size) {
|
||||||
|
if (!document) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto media = document->activeMediaView();
|
||||||
|
if (!media || !media->loaded()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
icon = std::make_unique<Lottie::Icon>(Lottie::IconDescriptor{
|
||||||
|
.path = document->filepath(true),
|
||||||
|
.json = media->bytes(),
|
||||||
|
.sizeOverride = QSize(size, size),
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
_flyIcon = std::move(args.flyIcon);
|
||||||
|
if (!resolve(_center, i->centerIcon, size)
|
||||||
|
|| !resolve(_effect, i->aroundAnimation, size * 2)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_flyIcon) {
|
||||||
|
_fly.start([=] { flyCallback(); }, 0., 1., kFlyDuration);
|
||||||
|
} else {
|
||||||
|
startAnimations();
|
||||||
|
}
|
||||||
|
_valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendAnimation::~SendAnimation() = default;
|
||||||
|
|
||||||
|
void SendAnimation::paint(QPainter &p, QPoint origin, QRect target) const {
|
||||||
|
if (_flyIcon) {
|
||||||
|
const auto from = _flyFrom.translated(origin);
|
||||||
|
const auto lshift = target.width() / 4;
|
||||||
|
const auto rshift = target.width() / 2 - lshift;
|
||||||
|
const auto margins = QMargins{ lshift, lshift, rshift, rshift };
|
||||||
|
target = target.marginsRemoved(margins);
|
||||||
|
const auto progress = _fly.value(1.);
|
||||||
|
const auto rect = QRect(
|
||||||
|
anim::interpolate(from.x(), target.x(), progress),
|
||||||
|
anim::interpolate(from.y(), target.y(), progress),
|
||||||
|
anim::interpolate(from.width(), target.width(), progress),
|
||||||
|
anim::interpolate(from.height(), target.height(), progress));
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
if (progress < 1.) {
|
||||||
|
p.setOpacity(1. - progress);
|
||||||
|
p.drawImage(rect, _flyIcon->frame());
|
||||||
|
}
|
||||||
|
if (progress > 0.) {
|
||||||
|
p.setOpacity(progress);
|
||||||
|
p.drawImage(rect.marginsAdded(margins), _center->frame());
|
||||||
|
}
|
||||||
|
p.setOpacity(1.);
|
||||||
|
} else {
|
||||||
|
p.drawImage(target, _center->frame());
|
||||||
|
p.drawImage(QRect(
|
||||||
|
target.topLeft() - QPoint(target.width(), target.height()) / 2,
|
||||||
|
target.size() * 2
|
||||||
|
), _effect->frame());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendAnimation::startAnimations() {
|
||||||
|
_center->animate([=] { callback(); }, 0, _center->framesCount() - 1);
|
||||||
|
_effect->animate([=] { callback(); }, 0, _effect->framesCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendAnimation::flyCallback() {
|
||||||
|
if (!_fly.animating()) {
|
||||||
|
_flyIcon = nullptr;
|
||||||
|
startAnimations();
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendAnimation::callback() {
|
||||||
|
if (_repaint) {
|
||||||
|
_repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendAnimation::setRepaintCallback(Fn<void()> repaint) {
|
||||||
|
_repaint = std::move(repaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SendAnimation::flying() const {
|
||||||
|
return (_flyIcon != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SendAnimation::playingAroundEmoji() const {
|
||||||
|
const auto finished = !_valid
|
||||||
|
|| (!_flyIcon && !_center->animating() && !_effect->animating());
|
||||||
|
return finished ? QString() : _emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HistoryView::Reactions
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/effects/animations.h"
|
||||||
|
|
||||||
|
namespace Lottie {
|
||||||
|
class Icon;
|
||||||
|
} // namespace Lottie
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
class Reactions;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
|
namespace HistoryView {
|
||||||
|
struct SendReactionAnimationArgs;
|
||||||
|
} // namespace HistoryView
|
||||||
|
|
||||||
|
namespace HistoryView::Reactions {
|
||||||
|
|
||||||
|
class SendAnimation final {
|
||||||
|
public:
|
||||||
|
SendAnimation(
|
||||||
|
not_null<::Data::Reactions*> owner,
|
||||||
|
SendReactionAnimationArgs &&args,
|
||||||
|
Fn<void()> repaint,
|
||||||
|
int size);
|
||||||
|
~SendAnimation();
|
||||||
|
|
||||||
|
void setRepaintCallback(Fn<void()> repaint);
|
||||||
|
void paint(QPainter &p, QPoint origin, QRect target) const;
|
||||||
|
|
||||||
|
[[nodiscard]] QString playingAroundEmoji() const;
|
||||||
|
[[nodiscard]] bool flying() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void flyCallback();
|
||||||
|
void startAnimations();
|
||||||
|
void callback();
|
||||||
|
|
||||||
|
const not_null<::Data::Reactions*> _owner;
|
||||||
|
const QString _emoji;
|
||||||
|
Fn<void()> _repaint;
|
||||||
|
std::shared_ptr<Lottie::Icon> _flyIcon;
|
||||||
|
std::unique_ptr<Lottie::Icon> _center;
|
||||||
|
std::unique_ptr<Lottie::Icon> _effect;
|
||||||
|
Ui::Animations::Simple _fly;
|
||||||
|
QRect _flyFrom;
|
||||||
|
bool _valid = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace HistoryView::Reactions
|
|
@ -70,6 +70,20 @@ constexpr auto kHoverScale = 1.24;
|
||||||
return style::ConvertScale(kSizeForDownscale);
|
return style::ConvertScale(kSizeForDownscale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::shared_ptr<Lottie::Icon> CreateIcon(
|
||||||
|
not_null<Data::DocumentMedia*> media,
|
||||||
|
int size,
|
||||||
|
int frame) {
|
||||||
|
Expects(media->loaded());
|
||||||
|
|
||||||
|
return std::make_shared<Lottie::Icon>(Lottie::IconDescriptor{
|
||||||
|
.path = media->owner()->filepath(true),
|
||||||
|
.json = media->bytes(),
|
||||||
|
.sizeOverride = QSize(size, size),
|
||||||
|
.frame = frame,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Button::Button(
|
Button::Button(
|
||||||
|
@ -383,17 +397,58 @@ Manager::Manager(
|
||||||
|
|
||||||
_createChooseCallback = [=](QString emoji) {
|
_createChooseCallback = [=](QString emoji) {
|
||||||
return [=] {
|
return [=] {
|
||||||
if (const auto context = _buttonContext) {
|
if (auto chosen = lookupChosen(emoji)) {
|
||||||
updateButton({});
|
updateButton({});
|
||||||
_chosen.fire({
|
_chosen.fire(std::move(chosen));
|
||||||
.context = context,
|
|
||||||
.emoji = emoji,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Manager::Chosen Manager::lookupChosen(const QString &emoji) const {
|
||||||
|
auto result = Chosen{
|
||||||
|
.context = _buttonContext,
|
||||||
|
.emoji = emoji,
|
||||||
|
};
|
||||||
|
const auto button = _button.get();
|
||||||
|
const auto i = ranges::find(_icons, emoji, &ReactionIcons::emoji);
|
||||||
|
if (i == end(_icons) || !button) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const auto &icon = *i;
|
||||||
|
if (const auto &appear = icon->appear; appear && appear->animating()) {
|
||||||
|
result.icon = CreateIcon(
|
||||||
|
icon->appearAnimation->activeMediaView().get(),
|
||||||
|
appear->width(),
|
||||||
|
appear->frameIndex());
|
||||||
|
} else if (const auto &select = icon->select) {
|
||||||
|
result.icon = CreateIcon(
|
||||||
|
icon->selectAnimation->activeMediaView().get(),
|
||||||
|
select->width(),
|
||||||
|
select->frameIndex());
|
||||||
|
}
|
||||||
|
const auto index = (i - begin(_icons));
|
||||||
|
const auto between = st::reactionCornerSkip;
|
||||||
|
const auto oneHeight = (st::reactionCornerSize.height() + between);
|
||||||
|
const auto expanded = (_icons.size() > 1);
|
||||||
|
const auto skip = (expanded ? st::reactionExpandedSkip : 0);
|
||||||
|
const auto scroll = button->scroll();
|
||||||
|
const auto local = skip + index * oneHeight - scroll;
|
||||||
|
const auto geometry = button->geometry();
|
||||||
|
const auto top = button->expandUp()
|
||||||
|
? (geometry.height() - local - _outer.height())
|
||||||
|
: local;
|
||||||
|
const auto rect = QRect(geometry.topLeft() + QPoint(0, top), _outer);
|
||||||
|
const auto imageSize = int(base::SafeRound(
|
||||||
|
st::reactionCornerImage * kHoverScale));
|
||||||
|
result.geometry = QRect(
|
||||||
|
rect.x() + (rect.width() - imageSize) / 2,
|
||||||
|
rect.y() + (rect.height() - imageSize) / 2,
|
||||||
|
imageSize,
|
||||||
|
imageSize);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void Manager::applyListFilters() {
|
void Manager::applyListFilters() {
|
||||||
const auto limit = _uniqueLimit.current();
|
const auto limit = _uniqueLimit.current();
|
||||||
const auto applyUniqueLimit = _buttonContext
|
const auto applyUniqueLimit = _buttonContext
|
||||||
|
@ -480,15 +535,13 @@ void Manager::showButtonDelayed() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::applyList(const std::vector<Data::Reaction> &list) {
|
void Manager::applyList(const std::vector<Data::Reaction> &list) {
|
||||||
constexpr auto predicate = [](
|
|
||||||
const Data::Reaction &a,
|
|
||||||
const Data::Reaction &b) {
|
|
||||||
return (a.emoji == b.emoji)
|
|
||||||
&& (a.appearAnimation == b.appearAnimation)
|
|
||||||
&& (a.selectAnimation == b.selectAnimation);
|
|
||||||
};
|
|
||||||
const auto proj = [](const auto &obj) {
|
const auto proj = [](const auto &obj) {
|
||||||
return std::tie(obj.emoji, obj.appearAnimation, obj.selectAnimation);
|
return std::tie(
|
||||||
|
obj.emoji,
|
||||||
|
obj.appearAnimation,
|
||||||
|
obj.selectAnimation,
|
||||||
|
obj.centerIcon,
|
||||||
|
obj.aroundAnimation);
|
||||||
};
|
};
|
||||||
if (ranges::equal(_list, list, ranges::equal_to(), proj, proj)) {
|
if (ranges::equal(_list, list, ranges::equal_to(), proj, proj)) {
|
||||||
return;
|
return;
|
||||||
|
@ -502,6 +555,8 @@ void Manager::applyList(const std::vector<Data::Reaction> &list) {
|
||||||
.emoji = reaction.emoji,
|
.emoji = reaction.emoji,
|
||||||
.appearAnimation = reaction.appearAnimation,
|
.appearAnimation = reaction.appearAnimation,
|
||||||
.selectAnimation = reaction.selectAnimation,
|
.selectAnimation = reaction.selectAnimation,
|
||||||
|
.centerIcon = reaction.centerIcon,
|
||||||
|
.aroundAnimation = reaction.aroundAnimation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
applyListFilters();
|
applyListFilters();
|
||||||
|
@ -649,6 +704,7 @@ void Manager::loadIcons() {
|
||||||
}
|
}
|
||||||
return entry.icon;
|
return entry.icon;
|
||||||
};
|
};
|
||||||
|
auto all = true;
|
||||||
for (const auto &icon : _icons) {
|
for (const auto &icon : _icons) {
|
||||||
if (!icon->appear) {
|
if (!icon->appear) {
|
||||||
icon->appear = load(icon->appearAnimation);
|
icon->appear = load(icon->appearAnimation);
|
||||||
|
@ -656,6 +712,23 @@ void Manager::loadIcons() {
|
||||||
if (!icon->select) {
|
if (!icon->select) {
|
||||||
icon->select = load(icon->selectAnimation);
|
icon->select = load(icon->selectAnimation);
|
||||||
}
|
}
|
||||||
|
if (!icon->appear || !icon->select) {
|
||||||
|
all = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (all) {
|
||||||
|
const auto preload = [&](DocumentData *document) {
|
||||||
|
const auto view = document
|
||||||
|
? document->activeMediaView()
|
||||||
|
: nullptr;
|
||||||
|
if (view) {
|
||||||
|
view->checkStickerLarge();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const auto &icon : _icons) {
|
||||||
|
preload(icon->centerIcon);
|
||||||
|
preload(icon->aroundAnimation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,6 +894,7 @@ void Manager::paintButton(
|
||||||
if (opacity == 0.) {
|
if (opacity == 0.) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto geometry = button->geometry();
|
const auto geometry = button->geometry();
|
||||||
const auto position = geometry.topLeft();
|
const auto position = geometry.topLeft();
|
||||||
const auto size = geometry.size();
|
const auto size = geometry.size();
|
||||||
|
@ -1469,13 +1543,7 @@ IconFactory CachedIconFactory::createMethod() {
|
||||||
std::shared_ptr<Lottie::Icon> DefaultIconFactory(
|
std::shared_ptr<Lottie::Icon> DefaultIconFactory(
|
||||||
not_null<Data::DocumentMedia*> media,
|
not_null<Data::DocumentMedia*> media,
|
||||||
int size) {
|
int size) {
|
||||||
Expects(media->loaded());
|
return CreateIcon(media, size, 0);
|
||||||
|
|
||||||
return std::make_shared<Lottie::Icon>(Lottie::IconDescriptor{
|
|
||||||
.path = media->owner()->filepath(true),
|
|
||||||
.json = media->bytes(),
|
|
||||||
.sizeOverride = QSize(size, size),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -154,6 +154,12 @@ public:
|
||||||
struct Chosen {
|
struct Chosen {
|
||||||
FullMsgId context;
|
FullMsgId context;
|
||||||
QString emoji;
|
QString emoji;
|
||||||
|
std::shared_ptr<Lottie::Icon> icon;
|
||||||
|
QRect geometry;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return context && !emoji.isNull();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
[[nodiscard]] rpl::producer<Chosen> chosen() const {
|
[[nodiscard]] rpl::producer<Chosen> chosen() const {
|
||||||
return _chosen.events();
|
return _chosen.events();
|
||||||
|
@ -172,6 +178,8 @@ private:
|
||||||
QString emoji;
|
QString emoji;
|
||||||
not_null<DocumentData*> appearAnimation;
|
not_null<DocumentData*> appearAnimation;
|
||||||
not_null<DocumentData*> selectAnimation;
|
not_null<DocumentData*> selectAnimation;
|
||||||
|
DocumentData *centerIcon = nullptr;
|
||||||
|
DocumentData *aroundAnimation = nullptr;
|
||||||
std::shared_ptr<Lottie::Icon> appear;
|
std::shared_ptr<Lottie::Icon> appear;
|
||||||
std::shared_ptr<Lottie::Icon> select;
|
std::shared_ptr<Lottie::Icon> select;
|
||||||
mutable ClickHandlerPtr link;
|
mutable ClickHandlerPtr link;
|
||||||
|
@ -190,6 +198,7 @@ private:
|
||||||
void showButtonDelayed();
|
void showButtonDelayed();
|
||||||
void stealWheelEvents(not_null<QWidget*> target);
|
void stealWheelEvents(not_null<QWidget*> target);
|
||||||
|
|
||||||
|
[[nodiscard]] Chosen lookupChosen(const QString &emoji) const;
|
||||||
[[nodiscard]] bool overCurrentButton(QPoint position) const;
|
[[nodiscard]] bool overCurrentButton(QPoint position) const;
|
||||||
|
|
||||||
void removeStaleButtons();
|
void removeStaleButtons();
|
||||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/view/history_view_message.h"
|
#include "history/view/history_view_message.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
|
#include "history/view/history_view_react_animation.h"
|
||||||
#include "data/data_message_reactions.h"
|
#include "data/data_message_reactions.h"
|
||||||
#include "lang/lang_tag.h"
|
#include "lang/lang_tag.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
|
@ -39,6 +40,8 @@ InlineList::InlineList(
|
||||||
layout();
|
layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InlineList::~InlineList() = default;
|
||||||
|
|
||||||
void InlineList::update(Data &&data, int availableWidth) {
|
void InlineList::update(Data &&data, int availableWidth) {
|
||||||
_data = std::move(data);
|
_data = std::move(data);
|
||||||
layout();
|
layout();
|
||||||
|
@ -187,11 +190,19 @@ void InlineList::paint(
|
||||||
const auto size = st::reactionBottomSize;
|
const auto size = st::reactionBottomSize;
|
||||||
const auto skip = (size - st::reactionBottomImage) / 2;
|
const auto skip = (size - st::reactionBottomImage) / 2;
|
||||||
const auto inbubble = (_data.flags & InlineListData::Flag::InBubble);
|
const auto inbubble = (_data.flags & InlineListData::Flag::InBubble);
|
||||||
|
const auto animated = _animation
|
||||||
|
? _animation->playingAroundEmoji()
|
||||||
|
: QString();
|
||||||
|
if (_animation && animated.isEmpty()) {
|
||||||
|
_animation = nullptr;
|
||||||
|
}
|
||||||
p.setFont(st::semiboldFont);
|
p.setFont(st::semiboldFont);
|
||||||
for (const auto &button : _buttons) {
|
for (const auto &button : _buttons) {
|
||||||
|
const auto animating = (animated == button.emoji);
|
||||||
const auto &geometry = button.geometry;
|
const auto &geometry = button.geometry;
|
||||||
const auto inner = geometry.marginsRemoved(padding);
|
const auto inner = geometry.marginsRemoved(padding);
|
||||||
const auto chosen = (_data.chosenReaction == button.emoji);
|
const auto chosen = (_data.chosenReaction == button.emoji)
|
||||||
|
&& (!animating || !_animation->flying());
|
||||||
{
|
{
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
p.setPen(Qt::NoPen);
|
p.setPen(Qt::NoPen);
|
||||||
|
@ -216,8 +227,16 @@ void InlineList::paint(
|
||||||
button.emoji,
|
button.emoji,
|
||||||
::Data::Reactions::ImageSize::InlineList);
|
::Data::Reactions::ImageSize::InlineList);
|
||||||
}
|
}
|
||||||
if (!button.image.isNull()) {
|
const auto image = QRect(
|
||||||
p.drawImage(inner.topLeft() + QPoint(skip, skip), button.image);
|
inner.topLeft() + QPoint(skip, skip),
|
||||||
|
QSize(st::reactionBottomImage, st::reactionBottomImage));
|
||||||
|
const auto skipImage = animating
|
||||||
|
&& (button.count < 2 || !_animation->flying());
|
||||||
|
if (!button.image.isNull() && !skipImage) {
|
||||||
|
p.drawImage(image.topLeft(), button.image);
|
||||||
|
}
|
||||||
|
if (animating) {
|
||||||
|
_animation->paint(p, QPoint(), image);
|
||||||
}
|
}
|
||||||
p.setPen(!inbubble
|
p.setPen(!inbubble
|
||||||
? (chosen
|
? (chosen
|
||||||
|
@ -262,6 +281,25 @@ bool InlineList::getState(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InlineList::animateSend(
|
||||||
|
SendReactionAnimationArgs &&args,
|
||||||
|
Fn<void()> repaint) {
|
||||||
|
_animation = std::make_unique<Reactions::SendAnimation>(
|
||||||
|
_owner,
|
||||||
|
std::move(args),
|
||||||
|
std::move(repaint),
|
||||||
|
st::reactionBottomImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<SendAnimation> InlineList::takeSendAnimation() {
|
||||||
|
return std::move(_animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineList::continueSendAnimation(
|
||||||
|
std::unique_ptr<SendAnimation> animation) {
|
||||||
|
_animation = std::move(animation);
|
||||||
|
}
|
||||||
|
|
||||||
InlineListData InlineListDataFromMessage(not_null<Message*> message) {
|
InlineListData InlineListDataFromMessage(not_null<Message*> message) {
|
||||||
using Flag = InlineListData::Flag;
|
using Flag = InlineListData::Flag;
|
||||||
const auto item = message->message();
|
const auto item = message->message();
|
||||||
|
|
|
@ -21,10 +21,13 @@ namespace HistoryView {
|
||||||
using PaintContext = Ui::ChatPaintContext;
|
using PaintContext = Ui::ChatPaintContext;
|
||||||
class Message;
|
class Message;
|
||||||
struct TextState;
|
struct TextState;
|
||||||
|
struct SendReactionAnimationArgs;
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
|
|
||||||
|
class SendAnimation;
|
||||||
|
|
||||||
struct InlineListData {
|
struct InlineListData {
|
||||||
enum class Flag : uchar {
|
enum class Flag : uchar {
|
||||||
InBubble = 0x01,
|
InBubble = 0x01,
|
||||||
|
@ -45,6 +48,7 @@ public:
|
||||||
not_null<::Data::Reactions*> owner,
|
not_null<::Data::Reactions*> owner,
|
||||||
Fn<ClickHandlerPtr(QString)> handlerFactory,
|
Fn<ClickHandlerPtr(QString)> handlerFactory,
|
||||||
Data &&data);
|
Data &&data);
|
||||||
|
~InlineList();
|
||||||
|
|
||||||
void update(Data &&data, int availableWidth);
|
void update(Data &&data, int availableWidth);
|
||||||
QSize countCurrentSize(int newWidth) override;
|
QSize countCurrentSize(int newWidth) override;
|
||||||
|
@ -63,6 +67,12 @@ public:
|
||||||
QPoint point,
|
QPoint point,
|
||||||
not_null<TextState*> outResult) const;
|
not_null<TextState*> outResult) const;
|
||||||
|
|
||||||
|
void animateSend(
|
||||||
|
SendReactionAnimationArgs &&args,
|
||||||
|
Fn<void()> repaint);
|
||||||
|
[[nodiscard]] std::unique_ptr<SendAnimation> takeSendAnimation();
|
||||||
|
void continueSendAnimation(std::unique_ptr<SendAnimation> animation);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Button {
|
struct Button {
|
||||||
QRect geometry;
|
QRect geometry;
|
||||||
|
@ -88,6 +98,8 @@ private:
|
||||||
std::vector<Button> _buttons;
|
std::vector<Button> _buttons;
|
||||||
QSize _skipBlock;
|
QSize _skipBlock;
|
||||||
|
|
||||||
|
mutable std::unique_ptr<SendAnimation> _animation;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] InlineListData InlineListDataFromMessage(
|
[[nodiscard]] InlineListData InlineListDataFromMessage(
|
||||||
|
|
|
@ -798,7 +798,7 @@ void Document::updatePressed(QPoint point) {
|
||||||
/ float64(width() - nameleft - nameright),
|
/ float64(width() - nameleft - nameright),
|
||||||
0.,
|
0.,
|
||||||
1.));
|
1.));
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1008,7 +1008,7 @@ bool Document::voiceProgressAnimationCallback(crl::time now) {
|
||||||
} else {
|
} else {
|
||||||
voice->_playback->progress.update(qMin(dt, 1.), anim::linear);
|
voice->_playback->progress.update(qMin(dt, 1.), anim::linear);
|
||||||
}
|
}
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
return (dt < 1.);
|
return (dt < 1.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,21 +30,17 @@ void File::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||||
if (p == _savel || p == _cancell) {
|
if (p == _savel || p == _cancell) {
|
||||||
if (active && !dataLoaded()) {
|
if (active && !dataLoaded()) {
|
||||||
ensureAnimation();
|
ensureAnimation();
|
||||||
_animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 0., 1., st::msgFileOverDuration);
|
_animation->a_thumbOver.start([=] { repaint(); }, 0., 1., st::msgFileOverDuration);
|
||||||
} else if (!active && _animation && !dataLoaded()) {
|
} else if (!active && _animation && !dataLoaded()) {
|
||||||
_animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 1., 0., st::msgFileOverDuration);
|
_animation->a_thumbOver.start([=] { repaint(); }, 1., 0., st::msgFileOverDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::thumbAnimationCallback() {
|
|
||||||
history()->owner().requestViewRepaint(_parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
void File::clickHandlerPressedChanged(
|
void File::clickHandlerPressedChanged(
|
||||||
const ClickHandlerPtr &handler,
|
const ClickHandlerPtr &handler,
|
||||||
bool pressed) {
|
bool pressed) {
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::setLinks(
|
void File::setLinks(
|
||||||
|
@ -92,7 +88,7 @@ void File::radialAnimationCallback(crl::time now) const {
|
||||||
now);
|
now);
|
||||||
}();
|
}();
|
||||||
if (!anim::Disabled() || updated) {
|
if (!anim::Disabled() || updated) {
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
}
|
}
|
||||||
if (!_animation->radial.animating()) {
|
if (!_animation->radial.animating()) {
|
||||||
checkAnimationFinished();
|
checkAnimationFinished();
|
||||||
|
|
|
@ -66,7 +66,6 @@ protected:
|
||||||
void setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const;
|
void setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const;
|
||||||
|
|
||||||
void radialAnimationCallback(crl::time now) const;
|
void radialAnimationCallback(crl::time now) const;
|
||||||
void thumbAnimationCallback();
|
|
||||||
|
|
||||||
void ensureAnimation() const;
|
void ensureAnimation() const;
|
||||||
void checkAnimationFinished() const;
|
void checkAnimationFinished() const;
|
||||||
|
|
|
@ -1203,28 +1203,44 @@ std::optional<int> Gif::reactionButtonCenterOverride() const {
|
||||||
if (!isSeparateRoundVideo()) {
|
if (!isSeparateRoundVideo()) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
const auto right = resolveCustomInfoRightBottom().x()
|
||||||
|
- _parent->infoWidth()
|
||||||
|
- 3 * st::msgDateImgPadding.x();
|
||||||
|
return right - st::reactionCornerSize.width() / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint Gif::resolveCustomInfoRightBottom() const {
|
||||||
const auto inner = contentRectForReactions();
|
const auto inner = contentRectForReactions();
|
||||||
|
auto fullBottom = inner.y() + inner.height();
|
||||||
auto fullRight = inner.x() + inner.width();
|
auto fullRight = inner.x() + inner.width();
|
||||||
auto maxRight = _parent->width() - st::msgMargin.left();
|
const auto isRound = isSeparateRoundVideo();
|
||||||
if (_parent->hasFromPhoto()) {
|
if (isRound) {
|
||||||
maxRight -= st::msgMargin.right();
|
auto maxRight = _parent->width() - st::msgMargin.left();
|
||||||
} else {
|
if (_parent->hasFromPhoto()) {
|
||||||
maxRight -= st::msgMargin.left();
|
maxRight -= st::msgMargin.right();
|
||||||
}
|
} else {
|
||||||
const auto infoWidth = _parent->infoWidth();
|
maxRight -= st::msgMargin.left();
|
||||||
const auto outbg = _parent->hasOutLayout();
|
}
|
||||||
const auto rightAligned = outbg
|
const auto infoWidth = _parent->infoWidth();
|
||||||
&& !_parent->delegate()->elementIsChatWide();
|
const auto outbg = _parent->hasOutLayout();
|
||||||
if (!rightAligned) {
|
const auto rightAligned = outbg
|
||||||
// This is just some arbitrary point,
|
&& !_parent->delegate()->elementIsChatWide();
|
||||||
// the main idea is to make info left aligned here.
|
if (!rightAligned) {
|
||||||
fullRight += infoWidth - st::normalFont->height;
|
// This is just some arbitrary point,
|
||||||
if (fullRight > maxRight) {
|
// the main idea is to make info left aligned here.
|
||||||
fullRight = maxRight;
|
fullRight += infoWidth - st::normalFont->height;
|
||||||
|
if (fullRight > maxRight) {
|
||||||
|
fullRight = maxRight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto right = fullRight - infoWidth - 3 * st::msgDateImgPadding.x();
|
const auto skipx = isRound
|
||||||
return right - st::reactionCornerSize.width() / 2;
|
? st::msgDateImgPadding.x()
|
||||||
|
: (st::msgDateImgDelta + st::msgDateImgPadding.x());
|
||||||
|
const auto skipy = isRound
|
||||||
|
? st::msgDateImgPadding.y()
|
||||||
|
: (st::msgDateImgDelta + st::msgDateImgPadding.y());
|
||||||
|
return QPoint(fullRight - skipx, fullBottom - skipy);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Gif::additionalWidth() const {
|
int Gif::additionalWidth() const {
|
||||||
|
@ -1544,7 +1560,7 @@ void Gif::repaintStreamedContent() {
|
||||||
&& !activeRoundStreamed()) {
|
&& !activeRoundStreamed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gif::streamingReady(::Media::Streaming::Information &&info) {
|
void Gif::streamingReady(::Media::Streaming::Information &&info) {
|
||||||
|
|
|
@ -98,6 +98,7 @@ public:
|
||||||
}
|
}
|
||||||
QRect contentRectForReactions() const override;
|
QRect contentRectForReactions() const override;
|
||||||
std::optional<int> reactionButtonCenterOverride() const override;
|
std::optional<int> reactionButtonCenterOverride() const override;
|
||||||
|
QPoint resolveCustomInfoRightBottom() const override;
|
||||||
QString additionalInfoString() const override;
|
QString additionalInfoString() const override;
|
||||||
|
|
||||||
bool skipBubbleTail() const override {
|
bool skipBubbleTail() const override {
|
||||||
|
|
|
@ -345,6 +345,12 @@ bool Location::needsBubble() const {
|
||||||
|| _parent->displayFromName();
|
|| _parent->displayFromName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPoint Location::resolveCustomInfoRightBottom() const {
|
||||||
|
const auto skipx = (st::msgDateImgDelta + st::msgDateImgPadding.x());
|
||||||
|
const auto skipy = (st::msgDateImgDelta + st::msgDateImgPadding.y());
|
||||||
|
return QPoint(width() - skipx, height() - skipy);
|
||||||
|
}
|
||||||
|
|
||||||
int Location::fullWidth() const {
|
int Location::fullWidth() const {
|
||||||
return st::locationSize.width();
|
return st::locationSize.width();
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ public:
|
||||||
bool customInfoLayout() const override {
|
bool customInfoLayout() const override {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
QPoint resolveCustomInfoRightBottom() const override;
|
||||||
|
|
||||||
bool skipBubbleTail() const override {
|
bool skipBubbleTail() const override {
|
||||||
return isRoundedInBubbleBottom();
|
return isRoundedInBubbleBottom();
|
||||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lottie/lottie_single_player.h"
|
#include "lottie/lottie_single_player.h"
|
||||||
#include "storage/storage_shared_media.h"
|
#include "storage/storage_shared_media.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
#include "data/data_web_page.h"
|
#include "data/data_web_page.h"
|
||||||
#include "ui/item_text_options.h"
|
#include "ui/item_text_options.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
|
@ -186,6 +187,10 @@ QSize Media::countCurrentSize(int newWidth) {
|
||||||
return QSize(qMin(newWidth, maxWidth()), minHeight());
|
return QSize(qMin(newWidth, maxWidth()), minHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Media::repaint() const {
|
||||||
|
history()->owner().requestViewRepaint(_parent);
|
||||||
|
}
|
||||||
|
|
||||||
Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
|
Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
|
||||||
if (item->emptyText()) {
|
if (item->emptyText()) {
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -212,6 +212,9 @@ public:
|
||||||
-> std::optional<int> {
|
-> std::optional<int> {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] virtual QPoint resolveCustomInfoRightBottom() const {
|
||||||
|
return QPoint();
|
||||||
|
}
|
||||||
[[nodiscard]] virtual QMargins bubbleMargins() const {
|
[[nodiscard]] virtual QMargins bubbleMargins() const {
|
||||||
return QMargins();
|
return QMargins();
|
||||||
}
|
}
|
||||||
|
@ -311,6 +314,8 @@ protected:
|
||||||
|
|
||||||
[[nodiscard]] bool usesBubblePattern(const PaintContext &context) const;
|
[[nodiscard]] bool usesBubblePattern(const PaintContext &context) const;
|
||||||
|
|
||||||
|
void repaint() const;
|
||||||
|
|
||||||
const not_null<Element*> _parent;
|
const not_null<Element*> _parent;
|
||||||
MediaInBubbleState _inBubbleState = MediaInBubbleState::None;
|
MediaInBubbleState _inBubbleState = MediaInBubbleState::None;
|
||||||
|
|
||||||
|
|
|
@ -730,6 +730,12 @@ bool GroupedMedia::needsBubble() const {
|
||||||
return _needBubble;
|
return _needBubble;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPoint GroupedMedia::resolveCustomInfoRightBottom() const {
|
||||||
|
const auto skipx = (st::msgDateImgDelta + st::msgDateImgPadding.x());
|
||||||
|
const auto skipy = (st::msgDateImgDelta + st::msgDateImgPadding.y());
|
||||||
|
return QPoint(width() - skipx, height() - skipy);
|
||||||
|
}
|
||||||
|
|
||||||
bool GroupedMedia::computeNeedBubble() const {
|
bool GroupedMedia::computeNeedBubble() const {
|
||||||
if (!_caption.isEmpty() || _mode == Mode::Column) {
|
if (!_caption.isEmpty() || _mode == Mode::Column) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -84,6 +84,8 @@ public:
|
||||||
bool customInfoLayout() const override {
|
bool customInfoLayout() const override {
|
||||||
return _caption.isEmpty() && (_mode != Mode::Column);
|
return _caption.isEmpty() && (_mode != Mode::Column);
|
||||||
}
|
}
|
||||||
|
QPoint resolveCustomInfoRightBottom() const override;
|
||||||
|
|
||||||
bool allowsFastShare() const override {
|
bool allowsFastShare() const override {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -441,6 +441,15 @@ std::optional<int> UnwrappedMedia::reactionButtonCenterOverride() const {
|
||||||
return right - st::reactionCornerSize.width() / 2;
|
return right - st::reactionCornerSize.width() / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPoint UnwrappedMedia::resolveCustomInfoRightBottom() const {
|
||||||
|
const auto inner = contentRectForReactions();
|
||||||
|
const auto fullBottom = inner.y() + inner.height();
|
||||||
|
const auto fullRight = calculateFullRight(inner);
|
||||||
|
const auto skipx = st::msgDateImgPadding.x();
|
||||||
|
const auto skipy = st::msgDateImgPadding.y();
|
||||||
|
return QPoint(fullRight - skipx, fullBottom - skipy);
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<Lottie::SinglePlayer> UnwrappedMedia::stickerTakeLottie(
|
std::unique_ptr<Lottie::SinglePlayer> UnwrappedMedia::stickerTakeLottie(
|
||||||
not_null<DocumentData*> data,
|
not_null<DocumentData*> data,
|
||||||
const Lottie::ColorReplacements *replacements) {
|
const Lottie::ColorReplacements *replacements) {
|
||||||
|
|
|
@ -83,6 +83,8 @@ public:
|
||||||
}
|
}
|
||||||
QRect contentRectForReactions() const override;
|
QRect contentRectForReactions() const override;
|
||||||
std::optional<int> reactionButtonCenterOverride() const override;
|
std::optional<int> reactionButtonCenterOverride() const override;
|
||||||
|
QPoint resolveCustomInfoRightBottom() const override;
|
||||||
|
|
||||||
void stickerClearLoopPlayed() override {
|
void stickerClearLoopPlayed() override {
|
||||||
_content->stickerClearLoopPlayed();
|
_content->stickerClearLoopPlayed();
|
||||||
}
|
}
|
||||||
|
|
|
@ -750,11 +750,11 @@ void Photo::repaintStreamedContent() {
|
||||||
} else if (_parent->delegate()->elementIsGifPaused()) {
|
} else if (_parent->delegate()->elementIsGifPaused()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Photo::streamingReady(::Media::Streaming::Information &&info) {
|
void Photo::streamingReady(::Media::Streaming::Information &&info) {
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Photo::checkAnimation() {
|
void Photo::checkAnimation() {
|
||||||
|
@ -831,6 +831,12 @@ bool Photo::needsBubble() const {
|
||||||
|| _parent->displayFromName());
|
|| _parent->displayFromName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPoint Photo::resolveCustomInfoRightBottom() const {
|
||||||
|
const auto skipx = (st::msgDateImgDelta + st::msgDateImgPadding.x());
|
||||||
|
const auto skipy = (st::msgDateImgDelta + st::msgDateImgPadding.y());
|
||||||
|
return QPoint(width() - skipx, height() - skipy);
|
||||||
|
}
|
||||||
|
|
||||||
bool Photo::isReadyForOpen() const {
|
bool Photo::isReadyForOpen() const {
|
||||||
ensureDataMediaCreated();
|
ensureDataMediaCreated();
|
||||||
return _dataMedia->loaded();
|
return _dataMedia->loaded();
|
||||||
|
|
|
@ -82,6 +82,7 @@ public:
|
||||||
bool customInfoLayout() const override {
|
bool customInfoLayout() const override {
|
||||||
return _caption.isEmpty();
|
return _caption.isEmpty();
|
||||||
}
|
}
|
||||||
|
QPoint resolveCustomInfoRightBottom() const override;
|
||||||
bool skipBubbleTail() const override {
|
bool skipBubbleTail() const override {
|
||||||
return isRoundedInBubbleBottom() && _caption.isEmpty();
|
return isRoundedInBubbleBottom() && _caption.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -425,10 +425,10 @@ void Poll::checkQuizAnswered() {
|
||||||
}
|
}
|
||||||
if (i->correct) {
|
if (i->correct) {
|
||||||
_fireworksAnimation = std::make_unique<Ui::FireworksAnimation>(
|
_fireworksAnimation = std::make_unique<Ui::FireworksAnimation>(
|
||||||
[=] { history()->owner().requestViewRepaint(_parent); });
|
[=] { repaint(); });
|
||||||
} else {
|
} else {
|
||||||
_wrongAnswerAnimation.start(
|
_wrongAnswerAnimation.start(
|
||||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
[=] { repaint(); },
|
||||||
0.,
|
0.,
|
||||||
1.,
|
1.,
|
||||||
kRollDuration,
|
kRollDuration,
|
||||||
|
@ -455,7 +455,7 @@ void Poll::solutionToggled(
|
||||||
if (animated == anim::type::instant
|
if (animated == anim::type::instant
|
||||||
&& _solutionButtonAnimation.animating()) {
|
&& _solutionButtonAnimation.animating()) {
|
||||||
_solutionButtonAnimation.stop();
|
_solutionButtonAnimation.stop();
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -463,10 +463,10 @@ void Poll::solutionToggled(
|
||||||
history()->owner().notifyViewLayoutChange(_parent);
|
history()->owner().notifyViewLayoutChange(_parent);
|
||||||
if (animated == anim::type::instant) {
|
if (animated == anim::type::instant) {
|
||||||
_solutionButtonAnimation.stop();
|
_solutionButtonAnimation.stop();
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
} else {
|
} else {
|
||||||
_solutionButtonAnimation.start(
|
_solutionButtonAnimation.start(
|
||||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
[=] { repaint(); },
|
||||||
visible ? 0. : 1.,
|
visible ? 0. : 1.,
|
||||||
visible ? 1. : 0.,
|
visible ? 1. : 0.,
|
||||||
st::fadeWrapDuration);
|
st::fadeWrapDuration);
|
||||||
|
@ -562,7 +562,7 @@ void Poll::toggleMultiOption(const QByteArray &option) {
|
||||||
const auto selected = i->selected;
|
const auto selected = i->selected;
|
||||||
i->selected = !selected;
|
i->selected = !selected;
|
||||||
i->selectedAnimation.start(
|
i->selectedAnimation.start(
|
||||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
[=] { repaint(); },
|
||||||
selected ? 1. : 0.,
|
selected ? 1. : 0.,
|
||||||
selected ? 0. : 1.,
|
selected ? 0. : 1.,
|
||||||
st::defaultCheck.duration);
|
st::defaultCheck.duration);
|
||||||
|
@ -575,7 +575,7 @@ void Poll::toggleMultiOption(const QByteArray &option) {
|
||||||
} else {
|
} else {
|
||||||
_hasSelected = true;
|
_hasSelected = true;
|
||||||
}
|
}
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,7 +861,7 @@ void Poll::resetAnswersAnimation() const {
|
||||||
|
|
||||||
void Poll::radialAnimationCallback() const {
|
void Poll::radialAnimationCallback() const {
|
||||||
if (!anim::Disabled()) {
|
if (!anim::Disabled()) {
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -930,7 +930,7 @@ void Poll::paintCloseByTimer(
|
||||||
_close = std::make_unique<CloseInformation>(
|
_close = std::make_unique<CloseInformation>(
|
||||||
_poll->closeDate,
|
_poll->closeDate,
|
||||||
_poll->closePeriod,
|
_poll->closePeriod,
|
||||||
[=] { history()->owner().requestViewRepaint(_parent); });
|
[=] { repaint(); });
|
||||||
}
|
}
|
||||||
const auto now = crl::now();
|
const auto now = crl::now();
|
||||||
const auto left = std::max(_close->finish - now, crl::time(0));
|
const auto left = std::max(_close->finish - now, crl::time(0));
|
||||||
|
@ -940,7 +940,7 @@ void Poll::paintCloseByTimer(
|
||||||
} else if (left < radial && !anim::Disabled()) {
|
} else if (left < radial && !anim::Disabled()) {
|
||||||
if (!_close->radial.animating()) {
|
if (!_close->radial.animating()) {
|
||||||
_close->radial.init([=] {
|
_close->radial.init([=] {
|
||||||
history()->owner().requestViewRepaint(_parent);
|
repaint();
|
||||||
});
|
});
|
||||||
_close->radial.start();
|
_close->radial.start();
|
||||||
}
|
}
|
||||||
|
@ -1342,7 +1342,7 @@ void Poll::startAnswersAnimation() const {
|
||||||
data.correct = data.correct || answer.correct;
|
data.correct = data.correct || answer.correct;
|
||||||
}
|
}
|
||||||
_answersAnimation->progress.start(
|
_answersAnimation->progress.start(
|
||||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
[=] { repaint(); },
|
||||||
0.,
|
0.,
|
||||||
1.,
|
1.,
|
||||||
st::historyPollDuration);
|
st::historyPollDuration);
|
||||||
|
@ -1524,7 +1524,7 @@ void Poll::toggleRipple(Answer &answer, bool pressed) {
|
||||||
answer.ripple = std::make_unique<Ui::RippleAnimation>(
|
answer.ripple = std::make_unique<Ui::RippleAnimation>(
|
||||||
st::defaultRippleAnimation,
|
st::defaultRippleAnimation,
|
||||||
std::move(mask),
|
std::move(mask),
|
||||||
[=] { history()->owner().requestViewRepaint(_parent); });
|
[=] { repaint(); });
|
||||||
}
|
}
|
||||||
const auto top = countAnswerTop(answer, innerWidth);
|
const auto top = countAnswerTop(answer, innerWidth);
|
||||||
answer.ripple->add(_lastLinkPoint - QPoint(0, top));
|
answer.ripple->add(_lastLinkPoint - QPoint(0, top));
|
||||||
|
@ -1587,7 +1587,7 @@ void Poll::toggleLinkRipple(bool pressed) {
|
||||||
_linkRipple = std::make_unique<Ui::RippleAnimation>(
|
_linkRipple = std::make_unique<Ui::RippleAnimation>(
|
||||||
st::defaultRippleAnimation,
|
st::defaultRippleAnimation,
|
||||||
std::move(mask),
|
std::move(mask),
|
||||||
[=] { history()->owner().requestViewRepaint(_parent); });
|
[=] { repaint(); });
|
||||||
}
|
}
|
||||||
_linkRipple->add(_lastLinkPoint - QPoint(0, height() - linkHeight));
|
_linkRipple->add(_lastLinkPoint - QPoint(0, height() - linkHeight));
|
||||||
} else if (_linkRipple) {
|
} else if (_linkRipple) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue