mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Suggested reaction effect around the widget.
This commit is contained in:
parent
5d5cae7860
commit
d4ba01bad0
7 changed files with 159 additions and 12 deletions
|
@ -567,7 +567,8 @@ void Story::applyFields(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto total = 0;
|
auto total = 0;
|
||||||
if (const auto list = data.vreactions()) {
|
if (const auto list = data.vreactions()
|
||||||
|
; list && _peer->isChannel()) {
|
||||||
reactionsCounts.reserve(list->v.size());
|
reactionsCounts.reserve(list->v.size());
|
||||||
for (const auto &reaction : list->v) {
|
for (const auto &reaction : list->v) {
|
||||||
const auto &data = reaction.data();
|
const auto &data = reaction.data();
|
||||||
|
|
|
@ -1143,14 +1143,23 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
|
||||||
}
|
}
|
||||||
for (const auto &suggestedReaction : _suggestedReactions) {
|
for (const auto &suggestedReaction : _suggestedReactions) {
|
||||||
const auto id = suggestedReaction.reaction;
|
const auto id = suggestedReaction.reaction;
|
||||||
|
auto widget = _reactions->makeSuggestedReactionWidget(
|
||||||
|
suggestedReaction);
|
||||||
|
const auto raw = widget.get();
|
||||||
_areas.push_back({
|
_areas.push_back({
|
||||||
.original = suggestedReaction.area.geometry,
|
.original = suggestedReaction.area.geometry,
|
||||||
.rotation = suggestedReaction.area.rotation,
|
.rotation = suggestedReaction.area.rotation,
|
||||||
.handler = std::make_shared<LambdaClickHandler>([=] {
|
.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(
|
.reaction = std::move(widget),
|
||||||
suggestedReaction),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
rebuildActiveAreas(*layout);
|
rebuildActiveAreas(*layout);
|
||||||
|
|
|
@ -57,6 +57,7 @@ constexpr auto kSuggestedTailBigRotation = -42.29;
|
||||||
constexpr auto kSuggestedTailSmallRotation = -40.87;
|
constexpr auto kSuggestedTailSmallRotation = -40.87;
|
||||||
constexpr auto kSuggestedReactionSize = 0.7;
|
constexpr auto kSuggestedReactionSize = 0.7;
|
||||||
constexpr auto kSuggestedWithCountSize = 0.55;
|
constexpr auto kSuggestedWithCountSize = 0.55;
|
||||||
|
constexpr auto kStoppingFadeDuration = crl::time(150);
|
||||||
|
|
||||||
class ReactionView final
|
class ReactionView final
|
||||||
: public Ui::RpWidget
|
: public Ui::RpWidget
|
||||||
|
@ -70,9 +71,16 @@ public:
|
||||||
|
|
||||||
void setAreaGeometry(QRect geometry) override;
|
void setAreaGeometry(QRect geometry) override;
|
||||||
void updateCount(int count) override;
|
void updateCount(int count) override;
|
||||||
|
void playEffect() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Element = HistoryView::Element;
|
using Element = HistoryView::Element;
|
||||||
|
|
||||||
|
struct Stopping {
|
||||||
|
std::unique_ptr<Ui::ReactionFlyAnimation> effect;
|
||||||
|
Ui::Animations::Simple animation;
|
||||||
|
};
|
||||||
|
|
||||||
not_null<HistoryView::ElementDelegate*> delegate();
|
not_null<HistoryView::ElementDelegate*> delegate();
|
||||||
HistoryView::Context elementContext() override;
|
HistoryView::Context elementContext() override;
|
||||||
bool elementAnimationsPaused() override;
|
bool elementAnimationsPaused() override;
|
||||||
|
@ -81,7 +89,15 @@ private:
|
||||||
|
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
void setupCustomChatStylePalette();
|
||||||
void cacheBackground();
|
void cacheBackground();
|
||||||
|
void paintEffectFrame(
|
||||||
|
QPainter &p,
|
||||||
|
not_null<Ui::ReactionFlyAnimation*> effect,
|
||||||
|
crl::time now);
|
||||||
|
void updateEffectGeometry();
|
||||||
|
void createEffectCanvas();
|
||||||
|
void stopEffect();
|
||||||
|
|
||||||
Data::SuggestedReaction _data;
|
Data::SuggestedReaction _data;
|
||||||
std::unique_ptr<Ui::ChatStyle> _chatStyle;
|
std::unique_ptr<Ui::ChatStyle> _chatStyle;
|
||||||
|
@ -102,6 +118,11 @@ private:
|
||||||
float64 _smallOffset = 0;
|
float64 _smallOffset = 0;
|
||||||
float64 _smallSize = 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(
|
[[nodiscard]] AdminLog::OwnedItem GenerateFakeItem(
|
||||||
|
@ -190,11 +211,17 @@ ReactionView::ReactionView(
|
||||||
|
|
||||||
_data.count = 0;
|
_data.count = 0;
|
||||||
updateCount(reaction.count);
|
updateCount(reaction.count);
|
||||||
|
setupCustomChatStylePalette();
|
||||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
show();
|
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) {
|
void ReactionView::setAreaGeometry(QRect geometry) {
|
||||||
_size = std::min(geometry.width(), geometry.height());
|
_size = std::min(geometry.width(), geometry.height());
|
||||||
const auto scaled = [&](float64 scale) {
|
const auto scaled = [&](float64 scale) {
|
||||||
|
@ -208,6 +235,10 @@ void ReactionView::setAreaGeometry(QRect geometry) {
|
||||||
const auto add = int(base::SafeRound(_smallOffset + _smallSize))
|
const auto add = int(base::SafeRound(_smallOffset + _smallSize))
|
||||||
- (_size / 2);
|
- (_size / 2);
|
||||||
setGeometry(geometry.marginsAdded({ add, add, add, add }));
|
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) {
|
void ReactionView::updateCount(int count) {
|
||||||
|
@ -228,6 +259,97 @@ void ReactionView::updateCount(int count) {
|
||||||
update();
|
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() {
|
not_null<HistoryView::ElementDelegate*> ReactionView::delegate() {
|
||||||
return static_cast<HistoryView::ElementDelegate*>(this);
|
return static_cast<HistoryView::ElementDelegate*>(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ public:
|
||||||
|
|
||||||
virtual void setAreaGeometry(QRect geometry) = 0;
|
virtual void setAreaGeometry(QRect geometry) = 0;
|
||||||
virtual void updateCount(int count) = 0;
|
virtual void updateCount(int count) = 0;
|
||||||
|
virtual void playEffect() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Reactions final {
|
class Reactions final {
|
||||||
|
|
|
@ -5537,10 +5537,15 @@ bool OverlayWidget::handleDoubleClick(
|
||||||
Qt::MouseButton button) {
|
Qt::MouseButton button) {
|
||||||
updateOver(position);
|
updateOver(position);
|
||||||
|
|
||||||
if (_over != Over::Video || !_streamed || button != Qt::LeftButton) {
|
if (_over != Over::Video || button != Qt::LeftButton) {
|
||||||
return false;
|
return false;
|
||||||
} else if (_stories) {
|
} else if (_stories) {
|
||||||
|
if (ClickHandler::getActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
toggleFullScreen(_windowed);
|
toggleFullScreen(_windowed);
|
||||||
|
} else if (!_streamed) {
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
playbackToggleFullScreen();
|
playbackToggleFullScreen();
|
||||||
playbackPauseResume();
|
playbackPauseResume();
|
||||||
|
|
|
@ -114,9 +114,9 @@ ReactionFlyAnimation::ReactionFlyAnimation(
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
generateMiniCopies(size + size / 2);
|
generateMiniCopies(size + size / 2, args.miniCopyMultiplier);
|
||||||
if (args.effectOnly) {
|
if (args.effectOnly) {
|
||||||
_custom = nullptr;
|
_effectOnly = true;
|
||||||
} else if (!_custom && !resolve(_center, centerIcon, size)) {
|
} else if (!_custom && !resolve(_center, centerIcon, size)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -227,6 +227,9 @@ void ReactionFlyAnimation::paintCenterFrame(
|
||||||
QRect target,
|
QRect target,
|
||||||
const QColor &colored,
|
const QColor &colored,
|
||||||
crl::time now) const {
|
crl::time now) const {
|
||||||
|
if (_effectOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const auto size = QSize(
|
const auto size = QSize(
|
||||||
int(base::SafeRound(target.width() * _centerSizeMultiplier)),
|
int(base::SafeRound(target.width() * _centerSizeMultiplier)),
|
||||||
int(base::SafeRound(target.height() * _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) {
|
if (!_custom) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -313,17 +318,19 @@ void ReactionFlyAnimation::generateMiniCopies(int size) {
|
||||||
};
|
};
|
||||||
_miniCopies.reserve(kMiniCopies);
|
_miniCopies.reserve(kMiniCopies);
|
||||||
for (auto i = 0; i != kMiniCopies; ++i) {
|
for (auto i = 0; i != kMiniCopies; ++i) {
|
||||||
const auto maxScale = kMiniCopiesMaxScaleMin
|
const auto scale = kMiniCopiesMaxScaleMin
|
||||||
+ (kMiniCopiesMaxScaleMax - kMiniCopiesMaxScaleMin) * random();
|
+ (kMiniCopiesMaxScaleMax - kMiniCopiesMaxScaleMin) * random();
|
||||||
|
const auto maxScale = scale * miniCopyMultiplier;
|
||||||
const auto duration = between(
|
const auto duration = between(
|
||||||
kMiniCopiesDurationMin,
|
kMiniCopiesDurationMin,
|
||||||
kMiniCopiesDurationMax);
|
kMiniCopiesDurationMax);
|
||||||
const auto maxSize = int(std::ceil(maxScale * _customSize));
|
const auto maxSize = int(std::ceil(maxScale * _customSize));
|
||||||
const auto maxHalf = (maxSize + 1) / 2;
|
const auto maxHalf = (maxSize + 1) / 2;
|
||||||
|
const auto flyUpTill = std::max(size - maxHalf, size / 4 + 1);
|
||||||
_miniCopies.push_back({
|
_miniCopies.push_back({
|
||||||
.maxScale = maxScale,
|
.maxScale = maxScale,
|
||||||
.duration = duration / float64(kMiniCopiesDurationMax),
|
.duration = duration / float64(kMiniCopiesDurationMax),
|
||||||
.flyUp = between(size / 4, size - maxHalf),
|
.flyUp = between(size / 4, flyUpTill),
|
||||||
.finalX = between(-size, size),
|
.finalX = between(-size, size),
|
||||||
.finalY = between(size - (size / 4), size),
|
.finalY = between(size - (size / 4), size),
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,6 +29,7 @@ struct ReactionFlyAnimationArgs {
|
||||||
QRect flyFrom;
|
QRect flyFrom;
|
||||||
crl::time scaleOutDuration = 0;
|
crl::time scaleOutDuration = 0;
|
||||||
float64 scaleOutTarget = 0.;
|
float64 scaleOutTarget = 0.;
|
||||||
|
float64 miniCopyMultiplier = 1.;
|
||||||
bool effectOnly = false;
|
bool effectOnly = false;
|
||||||
|
|
||||||
[[nodiscard]] ReactionFlyAnimationArgs translated(QPoint point) const;
|
[[nodiscard]] ReactionFlyAnimationArgs translated(QPoint point) const;
|
||||||
|
@ -102,7 +103,7 @@ private:
|
||||||
QPoint center,
|
QPoint center,
|
||||||
const QColor &colored,
|
const QColor &colored,
|
||||||
crl::time now) const;
|
crl::time now) const;
|
||||||
void generateMiniCopies(int size);
|
void generateMiniCopies(int size, float64 miniCopyMultiplier);
|
||||||
|
|
||||||
const not_null<::Data::Reactions*> _owner;
|
const not_null<::Data::Reactions*> _owner;
|
||||||
Fn<void()> _repaint;
|
Fn<void()> _repaint;
|
||||||
|
@ -120,6 +121,7 @@ private:
|
||||||
crl::time _scaleOutDuration = 0;
|
crl::time _scaleOutDuration = 0;
|
||||||
float64 _scaleOutTarget = 0.;
|
float64 _scaleOutTarget = 0.;
|
||||||
bool _noEffectScaleStarted = false;
|
bool _noEffectScaleStarted = false;
|
||||||
|
bool _effectOnly = false;
|
||||||
bool _valid = false;
|
bool _valid = false;
|
||||||
|
|
||||||
mutable Parabolic _cached;
|
mutable Parabolic _cached;
|
||||||
|
|
Loading…
Add table
Reference in a new issue