Initial reaction effects implementation.
This commit is contained in:
parent
db453ab7ae
commit
0ab26f0c82
|
@ -638,6 +638,8 @@ PRIVATE
|
|||
history/view/history_view_pinned_section.h
|
||||
history/view/history_view_pinned_tracker.cpp
|
||||
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.h
|
||||
history/view/history_view_reactions.cpp
|
||||
|
|
|
@ -224,19 +224,21 @@ void Reactions::request() {
|
|||
MTP_int(_hash)
|
||||
)).done([=](const MTPmessages_AvailableReactions &result) {
|
||||
_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) {
|
||||
_hash = data.vhash().v;
|
||||
|
||||
const auto &list = data.vreactions().v;
|
||||
const auto oldCache = base::take(_iconsCache);
|
||||
_active.clear();
|
||||
_available.clear();
|
||||
_active.reserve(list.size());
|
||||
_available.reserve(list.size());
|
||||
_iconsCache.reserve(list.size() * 2);
|
||||
const auto toCache = [&](not_null<DocumentData*> document) {
|
||||
_iconsCache.emplace(document, document->createMediaView());
|
||||
};
|
||||
for (const auto &reaction : list) {
|
||||
if (const auto parsed = parse(reaction)) {
|
||||
_available.push_back(*parsed);
|
||||
|
@ -244,6 +246,8 @@ void Reactions::request() {
|
|||
_active.push_back(*parsed);
|
||||
toCache(parsed->appearAnimation);
|
||||
toCache(parsed->selectAnimation);
|
||||
toCache(parsed->centerIcon);
|
||||
toCache(parsed->aroundAnimation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -277,10 +281,10 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
|||
.appearAnimation = _owner->processDocument(
|
||||
data.vappear_animation()),
|
||||
.selectAnimation = selectAnimation,
|
||||
.activateAnimation = _owner->processDocument(
|
||||
data.vactivate_animation()),
|
||||
.activateEffects = _owner->processDocument(
|
||||
data.veffect_animation()),
|
||||
//.activateAnimation = _owner->processDocument(
|
||||
// data.vactivate_animation()),
|
||||
//.activateEffects = _owner->processDocument(
|
||||
// data.veffect_animation()),
|
||||
.centerIcon = (data.vcenter_icon()
|
||||
? _owner->processDocument(*data.vcenter_icon()).get()
|
||||
: nullptr),
|
||||
|
|
|
@ -24,8 +24,8 @@ struct Reaction {
|
|||
not_null<DocumentData*> staticIcon;
|
||||
not_null<DocumentData*> appearAnimation;
|
||||
not_null<DocumentData*> selectAnimation;
|
||||
not_null<DocumentData*> activateAnimation;
|
||||
not_null<DocumentData*> activateEffects;
|
||||
//not_null<DocumentData*> activateAnimation;
|
||||
//not_null<DocumentData*> activateEffects;
|
||||
DocumentData *centerIcon = nullptr;
|
||||
DocumentData *aroundAnimation = nullptr;
|
||||
bool active = false;
|
||||
|
|
|
@ -377,8 +377,21 @@ HistoryInner::HistoryInner(
|
|||
using ChosenReaction = HistoryView::Reactions::Manager::Chosen;
|
||||
_reactionsManager->chosen(
|
||||
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
||||
if (const auto item = session().data().message(reaction.context)) {
|
||||
item->toggleReaction(reaction.emoji);
|
||||
const auto item = session().data().message(reaction.context);
|
||||
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());
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history.h"
|
||||
#include "history/view/history_view_message.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 "styles/style_chat.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
@ -31,6 +33,8 @@ BottomInfo::BottomInfo(
|
|||
layout();
|
||||
}
|
||||
|
||||
BottomInfo::~BottomInfo() = default;
|
||||
|
||||
void BottomInfo::update(Data &&data, int availableWidth) {
|
||||
_data = std::move(data);
|
||||
layout();
|
||||
|
@ -221,19 +225,27 @@ void BottomInfo::paint(
|
|||
left += width() - available;
|
||||
top += st::msgDateFont->height;
|
||||
}
|
||||
paintReactions(p, left, top, available);
|
||||
paintReactions(p, position, left, top, available);
|
||||
}
|
||||
}
|
||||
|
||||
void BottomInfo::paintReactions(
|
||||
Painter &p,
|
||||
QPoint origin,
|
||||
int left,
|
||||
int top,
|
||||
int availableWidth) const {
|
||||
auto x = left;
|
||||
auto y = top;
|
||||
auto widthLeft = availableWidth;
|
||||
const auto animated = _reactionAnimation
|
||||
? _reactionAnimation->playingAroundEmoji()
|
||||
: QString();
|
||||
if (_reactionAnimation && animated.isEmpty()) {
|
||||
_reactionAnimation = nullptr;
|
||||
}
|
||||
for (const auto &reaction : _reactions) {
|
||||
const auto animating = (reaction.emoji == animated);
|
||||
const auto add = (reaction.countTextWidth > 0)
|
||||
? st::reactionInfoDigitSkip
|
||||
: st::reactionInfoBetween;
|
||||
|
@ -251,11 +263,18 @@ void BottomInfo::paintReactions(
|
|||
reaction.emoji,
|
||||
::Data::Reactions::ImageSize::BottomInfo);
|
||||
}
|
||||
if (!reaction.image.isNull()) {
|
||||
p.drawImage(
|
||||
x + (st::reactionInfoSize - st::reactionInfoImage) / 2,
|
||||
y + (st::msgDateFont->height - st::reactionInfoImage) / 2,
|
||||
reaction.image);
|
||||
const auto image = QRect(
|
||||
x + (st::reactionInfoSize - st::reactionInfoImage) / 2,
|
||||
y + (st::msgDateFont->height - st::reactionInfoImage) / 2,
|
||||
st::reactionInfoImage,
|
||||
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) {
|
||||
p.drawText(
|
||||
|
@ -406,6 +425,26 @@ void BottomInfo::setReactionCount(Reaction &reaction, int count) {
|
|||
: 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) {
|
||||
using Flag = BottomInfo::Data::Flag;
|
||||
const auto item = message->message();
|
||||
|
|
|
@ -20,11 +20,15 @@ class Reactions;
|
|||
} // namespace Data
|
||||
|
||||
namespace HistoryView {
|
||||
namespace Reactions {
|
||||
class SendAnimation;
|
||||
} // namespace Reactions
|
||||
|
||||
using PaintContext = Ui::ChatPaintContext;
|
||||
|
||||
class Message;
|
||||
struct TextState;
|
||||
struct SendReactionAnimationArgs;
|
||||
|
||||
class BottomInfo final : public Object {
|
||||
public:
|
||||
|
@ -49,6 +53,7 @@ public:
|
|||
Flags flags;
|
||||
};
|
||||
BottomInfo(not_null<::Data::Reactions*> reactionsOwner, Data &&data);
|
||||
~BottomInfo();
|
||||
|
||||
void update(Data &&data, int availableWidth);
|
||||
|
||||
|
@ -67,6 +72,14 @@ public:
|
|||
bool inverted,
|
||||
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:
|
||||
struct Reaction {
|
||||
mutable QImage image;
|
||||
|
@ -86,6 +99,7 @@ private:
|
|||
[[nodiscard]] int countReactionsHeight(int newWidth) const;
|
||||
void paintReactions(
|
||||
Painter &p,
|
||||
QPoint origin,
|
||||
int left,
|
||||
int top,
|
||||
int availableWidth) const;
|
||||
|
@ -102,6 +116,7 @@ private:
|
|||
Ui::Text::String _views;
|
||||
Ui::Text::String _replies;
|
||||
std::vector<Reaction> _reactions;
|
||||
mutable std::unique_ptr<Reactions::SendAnimation> _reactionAnimation;
|
||||
int _reactionsMaxWidth = 0;
|
||||
int _dateWidth = 0;
|
||||
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_sticker.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_cursor_state.h"
|
||||
#include "history/history.h"
|
||||
|
@ -341,6 +342,15 @@ void DateBadge::paint(
|
|||
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(
|
||||
not_null<ElementDelegate*> delegate,
|
||||
not_null<HistoryItem*> data,
|
||||
|
@ -392,6 +402,10 @@ void Element::setY(int y) {
|
|||
void Element::refreshDataIdHook() {
|
||||
}
|
||||
|
||||
void Element::repaint() const {
|
||||
history()->owner().requestViewRepaint(this);
|
||||
}
|
||||
|
||||
void Element::paintHighlight(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
|
@ -1020,7 +1034,7 @@ void Element::clickHandlerActiveChanged(
|
|||
}
|
||||
}
|
||||
App::hoveredLinkItem(active ? this : nullptr);
|
||||
history()->owner().requestViewRepaint(this);
|
||||
repaint();
|
||||
if (const auto media = this->media()) {
|
||||
media->clickHandlerActiveChanged(handler, active);
|
||||
}
|
||||
|
@ -1035,12 +1049,20 @@ void Element::clickHandlerPressedChanged(
|
|||
}
|
||||
}
|
||||
App::pressedLinkItem(pressed ? this : nullptr);
|
||||
history()->owner().requestViewRepaint(this);
|
||||
repaint();
|
||||
if (const auto media = this->media()) {
|
||||
media->clickHandlerPressedChanged(handler, pressed);
|
||||
}
|
||||
}
|
||||
|
||||
void Element::animateSendReaction(SendReactionAnimationArgs &&args) {
|
||||
}
|
||||
|
||||
auto Element::takeSendReactionAnimation()
|
||||
-> std::unique_ptr<Reactions::SendAnimation> {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Element::~Element() {
|
||||
// Delete media while owner still exists.
|
||||
base::take(_media);
|
||||
|
|
|
@ -33,6 +33,10 @@ struct ChatPaintContext;
|
|||
class ChatStyle;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Lottie {
|
||||
class Icon;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
enum class PointState : char;
|
||||
|
@ -45,6 +49,7 @@ using PaintContext = Ui::ChatPaintContext;
|
|||
|
||||
namespace Reactions {
|
||||
struct ButtonParameters;
|
||||
class SendAnimation;
|
||||
} // namespace Reactions
|
||||
|
||||
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
|
||||
: public Object
|
||||
, public RuntimeComposer<Element>
|
||||
|
@ -407,9 +420,15 @@ public:
|
|||
|
||||
[[nodiscard]] bool markSponsoredViewed(int shownFromTop) const;
|
||||
|
||||
virtual void animateSendReaction(SendReactionAnimationArgs &&args);
|
||||
[[nodiscard]] virtual auto takeSendReactionAnimation()
|
||||
-> std::unique_ptr<Reactions::SendAnimation>;
|
||||
|
||||
virtual ~Element();
|
||||
|
||||
protected:
|
||||
void repaint() const;
|
||||
|
||||
void paintHighlight(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
|
|
|
@ -342,8 +342,21 @@ ListWidget::ListWidget(
|
|||
using ChosenReaction = Reactions::Manager::Chosen;
|
||||
_reactionsManager->chosen(
|
||||
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
||||
if (const auto item = session().data().message(reaction.context)) {
|
||||
item->toggleReaction(reaction.emoji);
|
||||
const auto item = session().data().message(reaction.context);
|
||||
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());
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history_message.h"
|
||||
#include "history/view/media/history_view_media.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_reactions.h"
|
||||
#include "history/view/history_view_group_call_bar.h" // UserpicInRow.
|
||||
|
@ -252,6 +253,17 @@ Message::Message(
|
|||
initLogEntryOriginal();
|
||||
initPsa();
|
||||
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() {
|
||||
|
@ -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() {
|
||||
const auto item = message();
|
||||
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 bubble = drawBubble();
|
||||
|
||||
auto dateh = 0;
|
||||
if (const auto date = Get<DateBadge>()) {
|
||||
dateh = date->height();
|
||||
}
|
||||
if (const auto bar = Get<UnreadBar>()) {
|
||||
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))) {
|
||||
p.translate(0, dateh);
|
||||
bar->paint(
|
||||
|
@ -1207,7 +1324,7 @@ void Message::toggleCommentsButtonRipple(bool pressed) {
|
|||
_comments->ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
std::move(mask),
|
||||
[=] { history()->owner().requestViewRepaint(this); });
|
||||
[=] { repaint(); });
|
||||
}
|
||||
_comments->ripple->add(_comments->lastPoint);
|
||||
} else if (_comments->ripple) {
|
||||
|
@ -1653,7 +1770,7 @@ void Message::psaTooltipToggled(bool tooltipShown) const {
|
|||
state->buttonVisible = visible;
|
||||
history()->owner().notifyViewLayoutChange(this);
|
||||
state->buttonVisibleAnimation.start(
|
||||
[=] { history()->owner().requestViewRepaint(this); },
|
||||
[=] { repaint(); },
|
||||
visible ? 0. : 1.,
|
||||
visible ? 1. : 0.,
|
||||
st::fadeWrapDuration);
|
||||
|
@ -2009,14 +2126,17 @@ void Message::refreshReactions() {
|
|||
auto reactionsData = InlineListDataFromMessage(this);
|
||||
if (!_reactions) {
|
||||
const auto handlerFactory = [=](QString emoji) {
|
||||
const auto weak = base::make_weak(this);
|
||||
const auto fullId = data()->fullId();
|
||||
return std::make_shared<LambdaClickHandler>([=](
|
||||
ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
const auto &data = controller->session().data();
|
||||
if (const auto item = data.message(fullId)) {
|
||||
item->toggleReaction(emoji);
|
||||
return std::make_shared<LambdaClickHandler>([=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->data()->toggleReaction(emoji);
|
||||
if (const auto now = weak.get()) {
|
||||
if (now->data()->chosenReaction() == emoji) {
|
||||
now->animateSendReaction({
|
||||
.emoji = emoji,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -2044,7 +2164,7 @@ void Message::itemDataChanged() {
|
|||
if (wasInfo != nowInfo || wasReactions != nowReactions) {
|
||||
history()->owner().requestViewResize(this);
|
||||
} else {
|
||||
history()->owner().requestViewRepaint(this);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2097,10 +2217,10 @@ void Message::updateViewButtonExistence() {
|
|||
} else if (_viewButton) {
|
||||
return;
|
||||
}
|
||||
auto callback = [=] { history()->owner().requestViewRepaint(this); };
|
||||
auto repainter = [=] { repaint(); };
|
||||
_viewButton = sponsored
|
||||
? std::make_unique<ViewButton>(sponsored, std::move(callback))
|
||||
: std::make_unique<ViewButton>(media, std::move(callback));
|
||||
? std::make_unique<ViewButton>(sponsored, std::move(repainter))
|
||||
: std::make_unique<ViewButton>(media, std::move(repainter));
|
||||
}
|
||||
|
||||
void Message::initLogEntryOriginal() {
|
||||
|
|
|
@ -134,6 +134,10 @@ public:
|
|||
void applyGroupAdminChanges(
|
||||
const base::flat_set<UserId> &changes) override;
|
||||
|
||||
void animateSendReaction(SendReactionAnimationArgs &&args) override;
|
||||
auto takeSendReactionAnimation()
|
||||
-> std::unique_ptr<Reactions::SendAnimation> override;
|
||||
|
||||
protected:
|
||||
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);
|
||||
}
|
||||
|
||||
[[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
|
||||
|
||||
Button::Button(
|
||||
|
@ -383,17 +397,58 @@ Manager::Manager(
|
|||
|
||||
_createChooseCallback = [=](QString emoji) {
|
||||
return [=] {
|
||||
if (const auto context = _buttonContext) {
|
||||
if (auto chosen = lookupChosen(emoji)) {
|
||||
updateButton({});
|
||||
_chosen.fire({
|
||||
.context = context,
|
||||
.emoji = emoji,
|
||||
});
|
||||
_chosen.fire(std::move(chosen));
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
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() {
|
||||
const auto limit = _uniqueLimit.current();
|
||||
const auto applyUniqueLimit = _buttonContext
|
||||
|
@ -480,15 +535,13 @@ void Manager::showButtonDelayed() {
|
|||
}
|
||||
|
||||
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) {
|
||||
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)) {
|
||||
return;
|
||||
|
@ -502,6 +555,8 @@ void Manager::applyList(const std::vector<Data::Reaction> &list) {
|
|||
.emoji = reaction.emoji,
|
||||
.appearAnimation = reaction.appearAnimation,
|
||||
.selectAnimation = reaction.selectAnimation,
|
||||
.centerIcon = reaction.centerIcon,
|
||||
.aroundAnimation = reaction.aroundAnimation,
|
||||
});
|
||||
}
|
||||
applyListFilters();
|
||||
|
@ -649,6 +704,7 @@ void Manager::loadIcons() {
|
|||
}
|
||||
return entry.icon;
|
||||
};
|
||||
auto all = true;
|
||||
for (const auto &icon : _icons) {
|
||||
if (!icon->appear) {
|
||||
icon->appear = load(icon->appearAnimation);
|
||||
|
@ -656,6 +712,23 @@ void Manager::loadIcons() {
|
|||
if (!icon->select) {
|
||||
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.) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto geometry = button->geometry();
|
||||
const auto position = geometry.topLeft();
|
||||
const auto size = geometry.size();
|
||||
|
@ -1469,13 +1543,7 @@ IconFactory CachedIconFactory::createMethod() {
|
|||
std::shared_ptr<Lottie::Icon> DefaultIconFactory(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
int size) {
|
||||
Expects(media->loaded());
|
||||
|
||||
return std::make_shared<Lottie::Icon>(Lottie::IconDescriptor{
|
||||
.path = media->owner()->filepath(true),
|
||||
.json = media->bytes(),
|
||||
.sizeOverride = QSize(size, size),
|
||||
});
|
||||
return CreateIcon(media, size, 0);
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -154,6 +154,12 @@ public:
|
|||
struct Chosen {
|
||||
FullMsgId context;
|
||||
QString emoji;
|
||||
std::shared_ptr<Lottie::Icon> icon;
|
||||
QRect geometry;
|
||||
|
||||
explicit operator bool() const {
|
||||
return context && !emoji.isNull();
|
||||
}
|
||||
};
|
||||
[[nodiscard]] rpl::producer<Chosen> chosen() const {
|
||||
return _chosen.events();
|
||||
|
@ -172,6 +178,8 @@ private:
|
|||
QString emoji;
|
||||
not_null<DocumentData*> appearAnimation;
|
||||
not_null<DocumentData*> selectAnimation;
|
||||
DocumentData *centerIcon = nullptr;
|
||||
DocumentData *aroundAnimation = nullptr;
|
||||
std::shared_ptr<Lottie::Icon> appear;
|
||||
std::shared_ptr<Lottie::Icon> select;
|
||||
mutable ClickHandlerPtr link;
|
||||
|
@ -190,6 +198,7 @@ private:
|
|||
void showButtonDelayed();
|
||||
void stealWheelEvents(not_null<QWidget*> target);
|
||||
|
||||
[[nodiscard]] Chosen lookupChosen(const QString &emoji) const;
|
||||
[[nodiscard]] bool overCurrentButton(QPoint position) const;
|
||||
|
||||
void removeStaleButtons();
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history.h"
|
||||
#include "history/view/history_view_message.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/history_view_react_animation.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "lang/lang_tag.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
|
@ -39,6 +40,8 @@ InlineList::InlineList(
|
|||
layout();
|
||||
}
|
||||
|
||||
InlineList::~InlineList() = default;
|
||||
|
||||
void InlineList::update(Data &&data, int availableWidth) {
|
||||
_data = std::move(data);
|
||||
layout();
|
||||
|
@ -187,11 +190,19 @@ void InlineList::paint(
|
|||
const auto size = st::reactionBottomSize;
|
||||
const auto skip = (size - st::reactionBottomImage) / 2;
|
||||
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);
|
||||
for (const auto &button : _buttons) {
|
||||
const auto animating = (animated == button.emoji);
|
||||
const auto &geometry = button.geometry;
|
||||
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);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
@ -216,8 +227,16 @@ void InlineList::paint(
|
|||
button.emoji,
|
||||
::Data::Reactions::ImageSize::InlineList);
|
||||
}
|
||||
if (!button.image.isNull()) {
|
||||
p.drawImage(inner.topLeft() + QPoint(skip, skip), button.image);
|
||||
const auto image = QRect(
|
||||
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
|
||||
? (chosen
|
||||
|
@ -262,6 +281,25 @@ bool InlineList::getState(
|
|||
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) {
|
||||
using Flag = InlineListData::Flag;
|
||||
const auto item = message->message();
|
||||
|
|
|
@ -21,10 +21,13 @@ namespace HistoryView {
|
|||
using PaintContext = Ui::ChatPaintContext;
|
||||
class Message;
|
||||
struct TextState;
|
||||
struct SendReactionAnimationArgs;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
||||
class SendAnimation;
|
||||
|
||||
struct InlineListData {
|
||||
enum class Flag : uchar {
|
||||
InBubble = 0x01,
|
||||
|
@ -45,6 +48,7 @@ public:
|
|||
not_null<::Data::Reactions*> owner,
|
||||
Fn<ClickHandlerPtr(QString)> handlerFactory,
|
||||
Data &&data);
|
||||
~InlineList();
|
||||
|
||||
void update(Data &&data, int availableWidth);
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
@ -63,6 +67,12 @@ public:
|
|||
QPoint point,
|
||||
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:
|
||||
struct Button {
|
||||
QRect geometry;
|
||||
|
@ -88,6 +98,8 @@ private:
|
|||
std::vector<Button> _buttons;
|
||||
QSize _skipBlock;
|
||||
|
||||
mutable std::unique_ptr<SendAnimation> _animation;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] InlineListData InlineListDataFromMessage(
|
||||
|
|
|
@ -798,7 +798,7 @@ void Document::updatePressed(QPoint point) {
|
|||
/ float64(width() - nameleft - nameright),
|
||||
0.,
|
||||
1.));
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1008,7 +1008,7 @@ bool Document::voiceProgressAnimationCallback(crl::time now) {
|
|||
} else {
|
||||
voice->_playback->progress.update(qMin(dt, 1.), anim::linear);
|
||||
}
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
return (dt < 1.);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,21 +30,17 @@ void File::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
|||
if (p == _savel || p == _cancell) {
|
||||
if (active && !dataLoaded()) {
|
||||
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()) {
|
||||
_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(
|
||||
const ClickHandlerPtr &handler,
|
||||
bool pressed) {
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
}
|
||||
|
||||
void File::setLinks(
|
||||
|
@ -92,7 +88,7 @@ void File::radialAnimationCallback(crl::time now) const {
|
|||
now);
|
||||
}();
|
||||
if (!anim::Disabled() || updated) {
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
}
|
||||
if (!_animation->radial.animating()) {
|
||||
checkAnimationFinished();
|
||||
|
|
|
@ -66,7 +66,6 @@ protected:
|
|||
void setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const;
|
||||
|
||||
void radialAnimationCallback(crl::time now) const;
|
||||
void thumbAnimationCallback();
|
||||
|
||||
void ensureAnimation() const;
|
||||
void checkAnimationFinished() const;
|
||||
|
|
|
@ -1203,28 +1203,44 @@ std::optional<int> Gif::reactionButtonCenterOverride() const {
|
|||
if (!isSeparateRoundVideo()) {
|
||||
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();
|
||||
auto fullBottom = inner.y() + inner.height();
|
||||
auto fullRight = inner.x() + inner.width();
|
||||
auto maxRight = _parent->width() - st::msgMargin.left();
|
||||
if (_parent->hasFromPhoto()) {
|
||||
maxRight -= st::msgMargin.right();
|
||||
} else {
|
||||
maxRight -= st::msgMargin.left();
|
||||
}
|
||||
const auto infoWidth = _parent->infoWidth();
|
||||
const auto outbg = _parent->hasOutLayout();
|
||||
const auto rightAligned = outbg
|
||||
&& !_parent->delegate()->elementIsChatWide();
|
||||
if (!rightAligned) {
|
||||
// This is just some arbitrary point,
|
||||
// the main idea is to make info left aligned here.
|
||||
fullRight += infoWidth - st::normalFont->height;
|
||||
if (fullRight > maxRight) {
|
||||
fullRight = maxRight;
|
||||
const auto isRound = isSeparateRoundVideo();
|
||||
if (isRound) {
|
||||
auto maxRight = _parent->width() - st::msgMargin.left();
|
||||
if (_parent->hasFromPhoto()) {
|
||||
maxRight -= st::msgMargin.right();
|
||||
} else {
|
||||
maxRight -= st::msgMargin.left();
|
||||
}
|
||||
const auto infoWidth = _parent->infoWidth();
|
||||
const auto outbg = _parent->hasOutLayout();
|
||||
const auto rightAligned = outbg
|
||||
&& !_parent->delegate()->elementIsChatWide();
|
||||
if (!rightAligned) {
|
||||
// This is just some arbitrary point,
|
||||
// the main idea is to make info left aligned here.
|
||||
fullRight += infoWidth - st::normalFont->height;
|
||||
if (fullRight > maxRight) {
|
||||
fullRight = maxRight;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto right = fullRight - infoWidth - 3 * st::msgDateImgPadding.x();
|
||||
return right - st::reactionCornerSize.width() / 2;
|
||||
const auto skipx = isRound
|
||||
? 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 {
|
||||
|
@ -1544,7 +1560,7 @@ void Gif::repaintStreamedContent() {
|
|||
&& !activeRoundStreamed()) {
|
||||
return;
|
||||
}
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Gif::streamingReady(::Media::Streaming::Information &&info) {
|
||||
|
|
|
@ -98,6 +98,7 @@ public:
|
|||
}
|
||||
QRect contentRectForReactions() const override;
|
||||
std::optional<int> reactionButtonCenterOverride() const override;
|
||||
QPoint resolveCustomInfoRightBottom() const override;
|
||||
QString additionalInfoString() const override;
|
||||
|
||||
bool skipBubbleTail() const override {
|
||||
|
|
|
@ -345,6 +345,12 @@ bool Location::needsBubble() const {
|
|||
|| _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 {
|
||||
return st::locationSize.width();
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ public:
|
|||
bool customInfoLayout() const override {
|
||||
return true;
|
||||
}
|
||||
QPoint resolveCustomInfoRightBottom() const override;
|
||||
|
||||
bool skipBubbleTail() const override {
|
||||
return isRoundedInBubbleBottom();
|
||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lottie/lottie_single_player.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "ui/item_text_options.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
|
@ -186,6 +187,10 @@ QSize Media::countCurrentSize(int newWidth) {
|
|||
return QSize(qMin(newWidth, maxWidth()), minHeight());
|
||||
}
|
||||
|
||||
void Media::repaint() const {
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
}
|
||||
|
||||
Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
|
||||
if (item->emptyText()) {
|
||||
return {};
|
||||
|
|
|
@ -212,6 +212,9 @@ public:
|
|||
-> std::optional<int> {
|
||||
return std::nullopt;
|
||||
}
|
||||
[[nodiscard]] virtual QPoint resolveCustomInfoRightBottom() const {
|
||||
return QPoint();
|
||||
}
|
||||
[[nodiscard]] virtual QMargins bubbleMargins() const {
|
||||
return QMargins();
|
||||
}
|
||||
|
@ -311,6 +314,8 @@ protected:
|
|||
|
||||
[[nodiscard]] bool usesBubblePattern(const PaintContext &context) const;
|
||||
|
||||
void repaint() const;
|
||||
|
||||
const not_null<Element*> _parent;
|
||||
MediaInBubbleState _inBubbleState = MediaInBubbleState::None;
|
||||
|
||||
|
|
|
@ -730,6 +730,12 @@ bool GroupedMedia::needsBubble() const {
|
|||
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 {
|
||||
if (!_caption.isEmpty() || _mode == Mode::Column) {
|
||||
return true;
|
||||
|
|
|
@ -84,6 +84,8 @@ public:
|
|||
bool customInfoLayout() const override {
|
||||
return _caption.isEmpty() && (_mode != Mode::Column);
|
||||
}
|
||||
QPoint resolveCustomInfoRightBottom() const override;
|
||||
|
||||
bool allowsFastShare() const override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -441,6 +441,15 @@ std::optional<int> UnwrappedMedia::reactionButtonCenterOverride() const {
|
|||
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(
|
||||
not_null<DocumentData*> data,
|
||||
const Lottie::ColorReplacements *replacements) {
|
||||
|
|
|
@ -83,6 +83,8 @@ public:
|
|||
}
|
||||
QRect contentRectForReactions() const override;
|
||||
std::optional<int> reactionButtonCenterOverride() const override;
|
||||
QPoint resolveCustomInfoRightBottom() const override;
|
||||
|
||||
void stickerClearLoopPlayed() override {
|
||||
_content->stickerClearLoopPlayed();
|
||||
}
|
||||
|
|
|
@ -750,11 +750,11 @@ void Photo::repaintStreamedContent() {
|
|||
} else if (_parent->delegate()->elementIsGifPaused()) {
|
||||
return;
|
||||
}
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Photo::streamingReady(::Media::Streaming::Information &&info) {
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Photo::checkAnimation() {
|
||||
|
@ -831,6 +831,12 @@ bool Photo::needsBubble() const {
|
|||
|| _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 {
|
||||
ensureDataMediaCreated();
|
||||
return _dataMedia->loaded();
|
||||
|
|
|
@ -82,6 +82,7 @@ public:
|
|||
bool customInfoLayout() const override {
|
||||
return _caption.isEmpty();
|
||||
}
|
||||
QPoint resolveCustomInfoRightBottom() const override;
|
||||
bool skipBubbleTail() const override {
|
||||
return isRoundedInBubbleBottom() && _caption.isEmpty();
|
||||
}
|
||||
|
|
|
@ -425,10 +425,10 @@ void Poll::checkQuizAnswered() {
|
|||
}
|
||||
if (i->correct) {
|
||||
_fireworksAnimation = std::make_unique<Ui::FireworksAnimation>(
|
||||
[=] { history()->owner().requestViewRepaint(_parent); });
|
||||
[=] { repaint(); });
|
||||
} else {
|
||||
_wrongAnswerAnimation.start(
|
||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
||||
[=] { repaint(); },
|
||||
0.,
|
||||
1.,
|
||||
kRollDuration,
|
||||
|
@ -455,7 +455,7 @@ void Poll::solutionToggled(
|
|||
if (animated == anim::type::instant
|
||||
&& _solutionButtonAnimation.animating()) {
|
||||
_solutionButtonAnimation.stop();
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -463,10 +463,10 @@ void Poll::solutionToggled(
|
|||
history()->owner().notifyViewLayoutChange(_parent);
|
||||
if (animated == anim::type::instant) {
|
||||
_solutionButtonAnimation.stop();
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
} else {
|
||||
_solutionButtonAnimation.start(
|
||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
||||
[=] { repaint(); },
|
||||
visible ? 0. : 1.,
|
||||
visible ? 1. : 0.,
|
||||
st::fadeWrapDuration);
|
||||
|
@ -562,7 +562,7 @@ void Poll::toggleMultiOption(const QByteArray &option) {
|
|||
const auto selected = i->selected;
|
||||
i->selected = !selected;
|
||||
i->selectedAnimation.start(
|
||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
||||
[=] { repaint(); },
|
||||
selected ? 1. : 0.,
|
||||
selected ? 0. : 1.,
|
||||
st::defaultCheck.duration);
|
||||
|
@ -575,7 +575,7 @@ void Poll::toggleMultiOption(const QByteArray &option) {
|
|||
} else {
|
||||
_hasSelected = true;
|
||||
}
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -861,7 +861,7 @@ void Poll::resetAnswersAnimation() const {
|
|||
|
||||
void Poll::radialAnimationCallback() const {
|
||||
if (!anim::Disabled()) {
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -930,7 +930,7 @@ void Poll::paintCloseByTimer(
|
|||
_close = std::make_unique<CloseInformation>(
|
||||
_poll->closeDate,
|
||||
_poll->closePeriod,
|
||||
[=] { history()->owner().requestViewRepaint(_parent); });
|
||||
[=] { repaint(); });
|
||||
}
|
||||
const auto now = crl::now();
|
||||
const auto left = std::max(_close->finish - now, crl::time(0));
|
||||
|
@ -940,7 +940,7 @@ void Poll::paintCloseByTimer(
|
|||
} else if (left < radial && !anim::Disabled()) {
|
||||
if (!_close->radial.animating()) {
|
||||
_close->radial.init([=] {
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
repaint();
|
||||
});
|
||||
_close->radial.start();
|
||||
}
|
||||
|
@ -1342,7 +1342,7 @@ void Poll::startAnswersAnimation() const {
|
|||
data.correct = data.correct || answer.correct;
|
||||
}
|
||||
_answersAnimation->progress.start(
|
||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
||||
[=] { repaint(); },
|
||||
0.,
|
||||
1.,
|
||||
st::historyPollDuration);
|
||||
|
@ -1524,7 +1524,7 @@ void Poll::toggleRipple(Answer &answer, bool pressed) {
|
|||
answer.ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
std::move(mask),
|
||||
[=] { history()->owner().requestViewRepaint(_parent); });
|
||||
[=] { repaint(); });
|
||||
}
|
||||
const auto top = countAnswerTop(answer, innerWidth);
|
||||
answer.ripple->add(_lastLinkPoint - QPoint(0, top));
|
||||
|
@ -1587,7 +1587,7 @@ void Poll::toggleLinkRipple(bool pressed) {
|
|||
_linkRipple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
std::move(mask),
|
||||
[=] { history()->owner().requestViewRepaint(_parent); });
|
||||
[=] { repaint(); });
|
||||
}
|
||||
_linkRipple->add(_lastLinkPoint - QPoint(0, height() - linkHeight));
|
||||
} else if (_linkRipple) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user