| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726 |
- /*
- 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 "ui/effects/send_action_animations.h"
- #include "api/api_send_progress.h"
- #include "base/never_freed_pointer.h"
- #include "ui/effects/animation_value.h"
- #include "ui/painter.h"
- #include "styles/style_widgets.h"
- #include "styles/style_dialogs.h"
- namespace Ui {
- namespace {
- constexpr int kTypingDotsCount = 3;
- constexpr int kRecordArcsCount = 4;
- constexpr int kUploadArrowsCount = 3;
- constexpr auto kSpeakingDuration = 3200;
- constexpr auto kSpeakingFadeDuration = 400;
- } // namespace
- class SendActionAnimation::Impl {
- public:
- using Type = Api::SendProgressType;
- Impl(int period) : _period(period), _started(crl::now()) {
- }
- struct MetaData {
- int index;
- std::unique_ptr<Impl>(*creator)();
- };
- virtual const MetaData *metaData() const = 0;
- bool supports(Type type) const;
- virtual int width() const = 0;
- virtual int widthNoMargins() const {
- return width();
- }
- virtual void paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) = 0;
- virtual void restartedAt(crl::time now) {
- }
- virtual bool finishNow() {
- return true;
- }
- virtual ~Impl() = default;
- protected:
- [[nodiscard]] int period() const {
- return _period;
- }
- [[nodiscard]] crl::time started() const {
- return _started;
- }
- [[nodiscard]] int frameTime(crl::time now) const {
- return anim::Disabled()
- ? 0
- : (std::max(now - _started, crl::time(0)) % _period);
- }
- private:
- int _period = 1;
- crl::time _started = 0;
- };
- namespace {
- using ImplementationsMap = QMap<
- Api::SendProgressType,
- const SendActionAnimation::Impl::MetaData*>;
- base::NeverFreedPointer<ImplementationsMap> Implementations;
- class TypingAnimation : public SendActionAnimation::Impl {
- public:
- TypingAnimation() : Impl(st::historySendActionTypingDuration) {
- }
- static const MetaData kMeta;
- static std::unique_ptr<Impl> create() {
- return std::make_unique<TypingAnimation>();
- }
- const MetaData *metaData() const override {
- return &kMeta;
- }
- int width() const override {
- return st::historySendActionTypingPosition.x()
- + kTypingDotsCount * st::historySendActionTypingDelta;
- }
- void paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) override;
- };
- const TypingAnimation::MetaData TypingAnimation::kMeta = {
- 0,
- &TypingAnimation::create,
- };
- void TypingAnimation::paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) {
- PainterHighQualityEnabler hq(p);
- p.setPen(Qt::NoPen);
- p.setBrush(color);
- auto frameMs = frameTime(now);
- auto position = QPointF(x + 0.5, y - 0.5)
- + st::historySendActionTypingPosition;
- for (auto i = 0; i != kTypingDotsCount; ++i) {
- auto r = st::historySendActionTypingSmallNumerator
- / st::historySendActionTypingDenominator;
- if (frameMs < 2 * st::historySendActionTypingHalfPeriod) {
- const auto delta = (st::historySendActionTypingLargeNumerator
- - st::historySendActionTypingSmallNumerator)
- / st::historySendActionTypingDenominator;
- if (frameMs < st::historySendActionTypingHalfPeriod) {
- r += delta
- * anim::easeOutCirc(
- 1.,
- float64(frameMs)
- / st::historySendActionTypingHalfPeriod);
- } else {
- r += delta
- * (1. - anim::easeOutCirc(
- 1.,
- float64(frameMs
- - st::historySendActionTypingHalfPeriod)
- / st::historySendActionTypingHalfPeriod));
- }
- }
- p.drawEllipse(position, r, r);
- position.setX(position.x() + st::historySendActionTypingDelta);
- frameMs = (frameMs + period() - st::historySendActionTypingDeltaTime)
- % period();
- }
- }
- class RecordAnimation : public SendActionAnimation::Impl {
- public:
- RecordAnimation() : Impl(st::historySendActionRecordDuration) {
- }
- static const MetaData kMeta;
- static std::unique_ptr<Impl> create() {
- return std::make_unique<RecordAnimation>();
- }
- const MetaData *metaData() const override {
- return &kMeta;
- }
- int width() const override {
- return st::historySendActionRecordPosition.x()
- + (kRecordArcsCount + 1) * st::historySendActionRecordDelta;
- }
- void paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) override;
- };
- const RecordAnimation::MetaData RecordAnimation::kMeta = {
- 0,
- &RecordAnimation::create,
- };
- void RecordAnimation::paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) {
- PainterHighQualityEnabler hq(p);
- const auto frameMs = frameTime(now);
- auto pen = color->p;
- pen.setWidth(st::historySendActionRecordStrokeNumerator
- / st::historySendActionRecordDenominator);
- pen.setJoinStyle(Qt::RoundJoin);
- pen.setCapStyle(Qt::RoundCap);
- p.setPen(pen);
- p.setBrush(Qt::NoBrush);
- auto progress = frameMs / float64(period());
- auto size = st::historySendActionRecordPosition.x()
- + st::historySendActionRecordDelta * progress;
- y += st::historySendActionRecordPosition.y();
- constexpr auto kAngleStart = -arc::kFullLength / 24;
- constexpr auto kAngleSpan = arc::kFullLength / 12;
- for (auto i = 0; i != kRecordArcsCount; ++i) {
- p.setOpacity((i == 0)
- ? progress
- : (i == kRecordArcsCount - 1)
- ? (1. - progress)
- : 1.);
- auto rect = QRectF(x - size, y - size, 2 * size, 2 * size);
- p.drawArc(rect, kAngleStart, kAngleSpan);
- size += st::historySendActionRecordDelta;
- }
- p.setOpacity(1.);
- }
- class UploadAnimation : public SendActionAnimation::Impl {
- public:
- UploadAnimation() : Impl(st::historySendActionUploadDuration) {
- }
- static const MetaData kMeta;
- static std::unique_ptr<Impl> create() {
- return std::make_unique<UploadAnimation>();
- }
- const MetaData *metaData() const override {
- return &kMeta;
- }
- int width() const override {
- return st::historySendActionUploadPosition.x()
- + (kUploadArrowsCount + 1) * st::historySendActionUploadDelta;
- }
- void paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) override;
- };
- const UploadAnimation::MetaData UploadAnimation::kMeta = {
- 0,
- &UploadAnimation::create,
- };
- void UploadAnimation::paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) {
- PainterHighQualityEnabler hq(p);
- const auto frameMs = frameTime(now);
- auto pen = color->p;
- pen.setWidth(st::historySendActionUploadStrokeNumerator
- / st::historySendActionUploadDenominator);
- pen.setJoinStyle(Qt::RoundJoin);
- pen.setCapStyle(Qt::RoundCap);
- p.setPen(pen);
- p.setBrush(Qt::NoBrush);
- auto progress = frameMs / float64(period());
- auto position = st::historySendActionUploadPosition
- + QPointF(x + st::historySendActionUploadDelta * progress, y);
- auto path = QPainterPath();
- path.moveTo(
- 0.,
- -st::historySendActionUploadSizeNumerator
- / st::historySendActionUploadDenominator);
- path.lineTo(
- st::historySendActionUploadSizeNumerator
- / st::historySendActionUploadDenominator,
- 0.);
- path.lineTo(
- 0.,
- st::historySendActionUploadSizeNumerator
- / st::historySendActionUploadDenominator);
- p.translate(position);
- for (auto i = 0; i != kUploadArrowsCount; ++i) {
- p.setOpacity((i == 0)
- ? progress
- : (i == kUploadArrowsCount - 1)
- ? (1. - progress)
- : 1.);
- p.drawPath(path);
- position.setX(position.x() + st::historySendActionUploadDelta);
- p.translate(st::historySendActionUploadDelta, 0);
- }
- p.setOpacity(1.);
- p.translate(-position);
- }
- class SpeakingAnimation : public SendActionAnimation::Impl {
- public:
- SpeakingAnimation();
- static const MetaData kMeta;
- static std::unique_ptr<Impl> create() {
- return std::make_unique<SpeakingAnimation>();
- }
- const MetaData *metaData() const override {
- return &kMeta;
- }
- int width() const override {
- const auto &numerator = st::dialogsSpeakingStrokeNumerator;
- const auto &denominator = st::dialogsSpeakingDenominator;
- return 4 * (numerator / denominator);
- }
- void restartedAt(crl::time now) override;
- bool finishNow() override;
- static void PaintIdle(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth);
- void paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) override;
- private:
- static void PaintFrame(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- int frameMs,
- float64 started);
- crl::time _startStarted = 0;
- crl::time _finishStarted = 0;
- };
- const SpeakingAnimation::MetaData SpeakingAnimation::kMeta = {
- 0,
- &SpeakingAnimation::create };
- SpeakingAnimation::SpeakingAnimation()
- : Impl(kSpeakingDuration)
- , _startStarted(crl::now()) {
- }
- void SpeakingAnimation::restartedAt(crl::time now) {
- if (!_finishStarted) {
- return;
- }
- const auto finishFinishes = _finishStarted + kSpeakingFadeDuration;
- const auto leftToFinish = (finishFinishes - now);
- if (leftToFinish > 0) {
- _startStarted = now - leftToFinish;
- } else {
- _startStarted = now;
- }
- _finishStarted = 0;
- }
- bool SpeakingAnimation::finishNow() {
- const auto now = crl::now();
- if (_finishStarted) {
- return (_finishStarted + kSpeakingFadeDuration <= now);
- } else if (_startStarted >= now) {
- return true;
- }
- const auto startFinishes = _startStarted + kSpeakingFadeDuration;
- const auto leftToStart = (startFinishes - now);
- if (leftToStart > 0) {
- _finishStarted = now - leftToStart;
- } else {
- _finishStarted = now;
- }
- return false;
- }
- void SpeakingAnimation::PaintIdle(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth) {
- PaintFrame(p, color, x, y, outerWidth, 0, 0.);
- }
- void SpeakingAnimation::paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) {
- const auto started = _finishStarted
- ? (1. - ((now - _finishStarted) / float64(kSpeakingFadeDuration)))
- : (now - _startStarted) / float64(kSpeakingFadeDuration);
- const auto progress = std::clamp(started, 0., 1.);
- PaintFrame(p, color, x, y, outerWidth, frameTime(now), progress);
- }
- void SpeakingAnimation::PaintFrame(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- int frameMs,
- float64 started) {
- PainterHighQualityEnabler hq(p);
- const auto line = st::dialogsSpeakingStrokeNumerator
- / (2 * st::dialogsSpeakingDenominator);
- p.setPen(Qt::NoPen);
- p.setBrush(color);
- const auto duration = kSpeakingDuration;
- const auto stageDuration = duration / 8;
- const auto fullprogress = frameMs;
- const auto stage = fullprogress / stageDuration;
- const auto progress = (fullprogress - stage * stageDuration)
- / float64(stageDuration);
- const auto half = st::dialogsCallBadgeSize / 2.;
- const auto center = QPointF(x + half, y + half);
- const auto middleSize = [&] {
- if (!started) {
- return 2 * line;
- }
- auto result = line;
- switch (stage) {
- case 0: result += 4 * line * progress; break;
- case 1: result += 4 * line * (1. - progress); break;
- case 2: result += 2 * line * progress; break;
- case 3: result += 2 * line * (1. - progress); break;
- case 4: result += 4 * line * progress; break;
- case 5: result += 4 * line * (1. - progress); break;
- case 6: result += 4 * line * progress; break;
- case 7: result += 4 * line * (1. - progress); break;
- }
- return (started == 1.)
- ? result
- : (started * result) + ((1. - started) * 2 * line);
- }();
- const auto sideSize = [&] {
- if (!started) {
- return 2 * line;
- }
- auto result = line;
- switch (stage) {
- case 0: result += 2 * line * (1. - progress); break;
- case 1: result += 4 * line * progress; break;
- case 2: result += 4 * line * (1. - progress); break;
- case 3: result += 2 * line * progress; break;
- case 4: result += 2 * line * (1. - progress); break;
- case 5: result += 4 * line * progress; break;
- case 6: result += 4 * line * (1. - progress); break;
- case 7: result += 2 * line * progress; break;
- }
- return (started == 1.)
- ? result
- : (started * result) + ((1. - started) * 2 * line);
- }();
- const auto drawRoundedRect = [&](float left, float size) {
- const auto top = center.y() - size;
- p.drawRoundedRect(QRectF(left, top, 2 * line, 2 * size), line, line);
- };
- auto left = center.x() - 4 * line;
- drawRoundedRect(left, sideSize);
- left += 3 * line;
- drawRoundedRect(left, middleSize);
- left += 3 * line;
- drawRoundedRect(left, sideSize);
- }
- class ChooseStickerAnimation : public SendActionAnimation::Impl {
- public:
- ChooseStickerAnimation()
- : Impl(st::historySendActionChooseStickerDuration)
- , _eye({
- .outWidth = float64(st::historySendActionChooseStickerEyeWidth),
- .outHeight = float64(st::historySendActionChooseStickerEyeHeight),
- .step = float64(st::historySendActionChooseStickerEyeStep),
- .inLeftOffset = style::ConvertScale(1.5),
- .inRightOffset = -style::ConvertScale(2.5)
- + st::historySendActionChooseStickerEyeWidth,
- .outXOffset = style::ConvertScale(1.5),
- .outStrokeWidth = style::ConvertScale(0.8 * 1.3),
- .inStrokeWidth = style::ConvertScale(1.2 * 1.3),
- .inSize = style::ConvertScale(2.),
- .minProgress = 0.3,
- .outHeightOffset = 1.5,
- }) {
- }
- static const MetaData kMeta;
- static std::unique_ptr<Impl> create() {
- return std::make_unique<ChooseStickerAnimation>();
- }
- const MetaData *metaData() const override {
- return &kMeta;
- }
- int width() const override {
- return widthNoMargins() + _eye.step * 2;
- }
- int widthNoMargins() const override {
- return st::historySendActionChooseStickerPosition.x()
- + 2 * (_eye.outWidth + _eye.step)
- + _eye.step;
- }
- void paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) override;
- private:
- const struct {
- const float64 outWidth;
- const float64 outHeight;
- const float64 step;
- const float64 inLeftOffset;
- const float64 inRightOffset;
- const float64 outXOffset;
- const float64 outStrokeWidth;
- const float64 inStrokeWidth;
- const float64 inSize;
- const float64 minProgress;
- const float64 outHeightOffset;
- } _eye;
- };
- const ChooseStickerAnimation::MetaData ChooseStickerAnimation::kMeta = {
- 0,
- &ChooseStickerAnimation::create,
- };
- void ChooseStickerAnimation::paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time now) {
- PainterHighQualityEnabler hq(p);
- const auto frameMs = frameTime(now);
- auto pen = color->p;
- pen.setJoinStyle(Qt::RoundJoin);
- pen.setCapStyle(Qt::RoundCap);
- const auto half = float64(period() / 2);
- const auto increment = (frameMs < half) ? true : false;
- // A double-progress within a period half.
- const auto progress = (frameMs / (half / 2)) - (increment ? 0 : 2);
- const auto animationProgress = std::min(progress, 1.);
- const auto k = _eye.minProgress;
- const auto pIn = anim::easeInCirc(1, std::min(animationProgress / k, 1.));
- const auto pInRev = 1. - pIn;
- const auto pOut = anim::easeOutCirc(1., (animationProgress < k)
- ? 0.
- : (animationProgress - k) / (1. - k));
- const auto inX = _eye.inLeftOffset * (increment ? pIn : pInRev)
- + _eye.inRightOffset * (increment ? pInRev : pIn);
- const auto inY = (_eye.outHeight - _eye.inSize) / 2.;
- const auto outLeft = _eye.outXOffset
- * (increment
- ? (1. - anim::easeOutCirc(1., progress / 2.))
- : anim::easeOutQuint(1., progress / 2.));
- const auto outScaleOffset = (pIn - pOut) * _eye.outHeightOffset;
- const auto top = st::historySendActionChooseStickerPosition.y() + y;
- const auto left = st::historySendActionChooseStickerPosition.x()
- + x
- + outLeft;
- for (auto i = 0; i < 2; i++) {
- const auto currentLeft = left + (_eye.outWidth + _eye.step) * i;
- pen.setWidthF(_eye.outStrokeWidth);
- p.setPen(pen);
- p.setBrush(Qt::NoBrush);
- p.drawEllipse(QRectF(
- currentLeft,
- top + outScaleOffset,
- _eye.outWidth,
- _eye.outHeight - outScaleOffset));
- pen.setWidthF(_eye.inStrokeWidth);
- p.setPen(pen);
- p.setBrush(color->b);
- p.drawEllipse(QRectF(
- currentLeft + inX,
- top + inY,
- _eye.inSize,
- _eye.inSize));
- }
- }
- void CreateImplementationsMap() {
- if (Implementations) {
- return;
- }
- using Type = Api::SendProgressType;
- Implementations.createIfNull();
- static constexpr auto kRecordTypes = {
- Type::RecordVideo,
- Type::RecordVoice,
- Type::RecordRound,
- };
- for (const auto type : kRecordTypes) {
- Implementations->insert(type, &RecordAnimation::kMeta);
- }
- static constexpr auto kUploadTypes = {
- Type::UploadFile,
- Type::UploadPhoto,
- Type::UploadVideo,
- Type::UploadVoice,
- Type::UploadRound,
- };
- for (const auto type : kUploadTypes) {
- Implementations->insert(type, &UploadAnimation::kMeta);
- }
- Implementations->insert(Type::Speaking, &SpeakingAnimation::kMeta);
- Implementations->insert(
- Type::ChooseSticker,
- &ChooseStickerAnimation::kMeta);
- }
- } // namespace
- SendActionAnimation::SendActionAnimation() = default;
- SendActionAnimation::~SendActionAnimation() = default;
- bool SendActionAnimation::Impl::supports(Type type) const {
- CreateImplementationsMap();
- return Implementations->value(type, &TypingAnimation::kMeta)
- == metaData();
- }
- void SendActionAnimation::start(Type type) {
- if (!_impl || !_impl->supports(type)) {
- _impl = CreateByType(type);
- } else {
- _impl->restartedAt(crl::now());
- }
- }
- void SendActionAnimation::tryToFinish() {
- if (!_impl) {
- return;
- } else if (_impl->finishNow()) {
- _impl.reset();
- }
- }
- int SendActionAnimation::width() const {
- return _impl ? _impl->width() : 0;
- }
- int SendActionAnimation::widthNoMargins() const {
- return _impl ? _impl->widthNoMargins() : 0;
- }
- void SendActionAnimation::paint(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth,
- crl::time ms) const {
- if (_impl) {
- _impl->paint(p, color, x, y, outerWidth, ms);
- }
- }
- void SendActionAnimation::PaintSpeakingIdle(
- QPainter &p,
- style::color color,
- int x,
- int y,
- int outerWidth) {
- SpeakingAnimation::PaintIdle(p, color, x, y, outerWidth);
- }
- auto SendActionAnimation::CreateByType(Type type) -> std::unique_ptr<Impl> {
- CreateImplementationsMap();
- return Implementations->value(type, &TypingAnimation::kMeta)->creator();
- }
- } // namespace Ui
|