mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +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;
|
||||
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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ public:
|
|||
|
||||
virtual void setAreaGeometry(QRect geometry) = 0;
|
||||
virtual void updateCount(int count) = 0;
|
||||
virtual void playEffect() = 0;
|
||||
};
|
||||
|
||||
class Reactions final {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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),
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue