Suggested reaction effect around the widget.

This commit is contained in:
John Preston 2023-09-14 18:47:22 +04:00
parent 5d5cae7860
commit d4ba01bad0
7 changed files with 159 additions and 12 deletions

View File

@ -567,7 +567,8 @@ void Story::applyFields(
}
}
auto total = 0;
if (const auto list = data.vreactions()) {
if (const auto list = data.vreactions()
; list && _peer->isChannel()) {
reactionsCounts.reserve(list->v.size());
for (const auto &reaction : list->v) {
const auto &data = reaction.data();

View File

@ -1143,14 +1143,23 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
}
for (const auto &suggestedReaction : _suggestedReactions) {
const auto id = suggestedReaction.reaction;
auto widget = _reactions->makeSuggestedReactionWidget(
suggestedReaction);
const auto raw = widget.get();
_areas.push_back({
.original = suggestedReaction.area.geometry,
.rotation = suggestedReaction.area.rotation,
.handler = std::make_shared<LambdaClickHandler>([=] {
_reactions->applyLike(id);
raw->playEffect();
if (const auto now = story()) {
if (now->sentReactionId() != id) {
now->owner().stories().sendReaction(
now->fullId(),
id);
}
}
}),
.reaction = _reactions->makeSuggestedReactionWidget(
suggestedReaction),
.reaction = std::move(widget),
});
}
rebuildActiveAreas(*layout);

View File

@ -57,6 +57,7 @@ constexpr auto kSuggestedTailBigRotation = -42.29;
constexpr auto kSuggestedTailSmallRotation = -40.87;
constexpr auto kSuggestedReactionSize = 0.7;
constexpr auto kSuggestedWithCountSize = 0.55;
constexpr auto kStoppingFadeDuration = crl::time(150);
class ReactionView final
: public Ui::RpWidget
@ -70,9 +71,16 @@ public:
void setAreaGeometry(QRect geometry) override;
void updateCount(int count) override;
void playEffect() override;
private:
using Element = HistoryView::Element;
struct Stopping {
std::unique_ptr<Ui::ReactionFlyAnimation> effect;
Ui::Animations::Simple animation;
};
not_null<HistoryView::ElementDelegate*> delegate();
HistoryView::Context elementContext() override;
bool elementAnimationsPaused() override;
@ -81,7 +89,15 @@ private:
void paintEvent(QPaintEvent *e) override;
void setupCustomChatStylePalette();
void cacheBackground();
void paintEffectFrame(
QPainter &p,
not_null<Ui::ReactionFlyAnimation*> effect,
crl::time now);
void updateEffectGeometry();
void createEffectCanvas();
void stopEffect();
Data::SuggestedReaction _data;
std::unique_ptr<Ui::ChatStyle> _chatStyle;
@ -102,6 +118,11 @@ private:
float64 _smallOffset = 0;
float64 _smallSize = 0;
std::unique_ptr<Ui::RpWidget> _effectCanvas;
std::unique_ptr<Ui::ReactionFlyAnimation> _effect;
std::vector<Stopping> _effectStopping;
QRect _effectTarget;
};
[[nodiscard]] AdminLog::OwnedItem GenerateFakeItem(
@ -190,11 +211,17 @@ ReactionView::ReactionView(
_data.count = 0;
updateCount(reaction.count);
setupCustomChatStylePalette();
setAttribute(Qt::WA_TransparentForMouseEvents);
show();
}
void ReactionView::setupCustomChatStylePalette() {
const auto color = uchar(_data.dark ? 255 : 0);
_chatStyle->historyTextInFg().set(color, color, color, 255);
_chatStyle->applyCustomPalette(_chatStyle.get());
}
void ReactionView::setAreaGeometry(QRect geometry) {
_size = std::min(geometry.width(), geometry.height());
const auto scaled = [&](float64 scale) {
@ -208,6 +235,10 @@ void ReactionView::setAreaGeometry(QRect geometry) {
const auto add = int(base::SafeRound(_smallOffset + _smallSize))
- (_size / 2);
setGeometry(geometry.marginsAdded({ add, add, add, add }));
const auto sub = int(base::SafeRound(
(1. - kSuggestedReactionSize) * _size / 2));
_effectTarget = geometry.marginsRemoved({ sub, sub, sub, sub });
updateEffectGeometry();
}
void ReactionView::updateCount(int count) {
@ -228,6 +259,97 @@ void ReactionView::updateCount(int count) {
update();
}
void ReactionView::playEffect() {
const auto exists = (_effectCanvas != nullptr);
if (exists) {
stopEffect();
} else {
createEffectCanvas();
}
const auto reactions = &_fake->history()->owner().reactions();
const auto scaleDown = _bubbleGeometry.width() / float64(_mediaWidth);
auto args = Ui::ReactionFlyAnimationArgs{
.id = _data.reaction,
.miniCopyMultiplier = std::min(1., scaleDown),
.effectOnly = true,
};
_effect = std::make_unique<Ui::ReactionFlyAnimation>(
reactions,
std::move(args),
[=] { _effectCanvas->update(); },
_size / 2,
Data::CustomEmojiSizeTag::Isolated);
if (exists) {
_effectStopping.back().animation.start([=] {
_effectCanvas->update();
}, 1., 0., kStoppingFadeDuration);
}
}
void ReactionView::paintEffectFrame(
QPainter &p,
not_null<Ui::ReactionFlyAnimation*> effect,
crl::time now) {
effect->paintGetArea(
p,
QPoint(),
_effectTarget.translated(-_effectCanvas->pos()),
_data.dark ? Qt::white : Qt::black,
QRect(),
now);
}
void ReactionView::createEffectCanvas() {
_effectCanvas = std::make_unique<Ui::RpWidget>(parentWidget());
const auto raw = _effectCanvas.get();
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
raw->show();
raw->paintRequest() | rpl::start_with_next([=] {
if (!_effect || _effect->finished()) {
crl::on_main(_effectCanvas.get(), [=] {
_effect = nullptr;
_effectStopping.clear();
_effectCanvas = nullptr;
});
return;
}
const auto now = crl::now();
auto p = QPainter(raw);
auto hq = PainterHighQualityEnabler(p);
_effectStopping.erase(ranges::remove_if(_effectStopping, [&](
const Stopping &stopping) {
if (!stopping.animation.animating()
|| stopping.effect->finished()) {
return true;
}
p.setOpacity(stopping.animation.value(0.));
paintEffectFrame(p, stopping.effect.get(), now);
return false;
}), end(_effectStopping));
paintEffectFrame(p, _effect.get(), now);
}, raw->lifetime());
updateEffectGeometry();
}
void ReactionView::stopEffect() {
_effectStopping.push_back({ .effect = std::move(_effect) });
_effectStopping.back().animation.start([=] {
_effectCanvas->update();
}, 1., 0., kStoppingFadeDuration);
}
void ReactionView::updateEffectGeometry() {
if (!_effectCanvas) {
return;
}
const auto center = geometry().center();
_effectCanvas->setGeometry(
center.x() - _size,
center.y() - _size,
_size * 2,
_size * 3);
}
not_null<HistoryView::ElementDelegate*> ReactionView::delegate() {
return static_cast<HistoryView::ElementDelegate*>(this);
}

View File

@ -47,6 +47,7 @@ public:
virtual void setAreaGeometry(QRect geometry) = 0;
virtual void updateCount(int count) = 0;
virtual void playEffect() = 0;
};
class Reactions final {

View File

@ -5537,10 +5537,15 @@ bool OverlayWidget::handleDoubleClick(
Qt::MouseButton button) {
updateOver(position);
if (_over != Over::Video || !_streamed || button != Qt::LeftButton) {
if (_over != Over::Video || button != Qt::LeftButton) {
return false;
} else if (_stories) {
if (ClickHandler::getActive()) {
return false;
}
toggleFullScreen(_windowed);
} else if (!_streamed) {
return false;
} else {
playbackToggleFullScreen();
playbackPauseResume();

View File

@ -114,9 +114,9 @@ ReactionFlyAnimation::ReactionFlyAnimation(
});
return true;
};
generateMiniCopies(size + size / 2);
generateMiniCopies(size + size / 2, args.miniCopyMultiplier);
if (args.effectOnly) {
_custom = nullptr;
_effectOnly = true;
} else if (!_custom && !resolve(_center, centerIcon, size)) {
return;
}
@ -227,6 +227,9 @@ void ReactionFlyAnimation::paintCenterFrame(
QRect target,
const QColor &colored,
crl::time now) const {
if (_effectOnly) {
return;
}
const auto size = QSize(
int(base::SafeRound(target.width() * _centerSizeMultiplier)),
int(base::SafeRound(target.height() * _centerSizeMultiplier)));
@ -298,7 +301,9 @@ void ReactionFlyAnimation::paintMiniCopies(
}
}
void ReactionFlyAnimation::generateMiniCopies(int size) {
void ReactionFlyAnimation::generateMiniCopies(
int size,
float64 miniCopyMultiplier) {
if (!_custom) {
return;
}
@ -313,17 +318,19 @@ void ReactionFlyAnimation::generateMiniCopies(int size) {
};
_miniCopies.reserve(kMiniCopies);
for (auto i = 0; i != kMiniCopies; ++i) {
const auto maxScale = kMiniCopiesMaxScaleMin
const auto scale = kMiniCopiesMaxScaleMin
+ (kMiniCopiesMaxScaleMax - kMiniCopiesMaxScaleMin) * random();
const auto maxScale = scale * miniCopyMultiplier;
const auto duration = between(
kMiniCopiesDurationMin,
kMiniCopiesDurationMax);
const auto maxSize = int(std::ceil(maxScale * _customSize));
const auto maxHalf = (maxSize + 1) / 2;
const auto flyUpTill = std::max(size - maxHalf, size / 4 + 1);
_miniCopies.push_back({
.maxScale = maxScale,
.duration = duration / float64(kMiniCopiesDurationMax),
.flyUp = between(size / 4, size - maxHalf),
.flyUp = between(size / 4, flyUpTill),
.finalX = between(-size, size),
.finalY = between(size - (size / 4), size),
});

View File

@ -29,6 +29,7 @@ struct ReactionFlyAnimationArgs {
QRect flyFrom;
crl::time scaleOutDuration = 0;
float64 scaleOutTarget = 0.;
float64 miniCopyMultiplier = 1.;
bool effectOnly = false;
[[nodiscard]] ReactionFlyAnimationArgs translated(QPoint point) const;
@ -102,7 +103,7 @@ private:
QPoint center,
const QColor &colored,
crl::time now) const;
void generateMiniCopies(int size);
void generateMiniCopies(int size, float64 miniCopyMultiplier);
const not_null<::Data::Reactions*> _owner;
Fn<void()> _repaint;
@ -120,6 +121,7 @@ private:
crl::time _scaleOutDuration = 0;
float64 _scaleOutTarget = 0.;
bool _noEffectScaleStarted = false;
bool _effectOnly = false;
bool _valid = false;
mutable Parabolic _cached;