From d15b0cdb084f9c706b9b190879e8b1b6e69fcfc5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 May 2018 19:57:50 +0300 Subject: [PATCH] Improve infinite radial animation. --- Telegram/SourceFiles/boxes/connection_box.cpp | 13 +- .../ui/effects/radial_animation.cpp | 206 +++++++++++++----- .../SourceFiles/ui/effects/radial_animation.h | 26 ++- Telegram/SourceFiles/ui/special_buttons.cpp | 34 +-- Telegram/SourceFiles/ui/special_buttons.h | 8 +- 5 files changed, 191 insertions(+), 96 deletions(-) diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 81549d53ce..3fef984772 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -198,7 +198,8 @@ void ProxyRow::updateFields(View &&view) { if (state == State::Connecting || state == State::Checking) { if (!_progress) { _progress = std::make_unique( - animation(this, &ProxyRow::step_radial)); + animation(this, &ProxyRow::step_radial), + st::proxyCheckingAnimation); _progress->start(); } } else { @@ -213,11 +214,8 @@ void ProxyRow::updateFields(View &&view) { void ProxyRow::step_radial(TimeMs ms, bool timer) { if (timer) { update(); - } else if (_progress) { - _progress->update(false, ms); - if (!_progress->animating()) { - _progress = nullptr; - } + } else if (_progress && !_progress->animating()) { + _progress = nullptr; } } @@ -317,8 +315,7 @@ void ProxyRow::paintEvent(QPaintEvent *e) { { st::proxyCheckingPosition.x() + statusLeft, st::proxyCheckingPosition.y() + top }, - width(), - st::proxyCheckingAnimation); + width()); statusLeft += st::proxyCheckingPosition.x() + st::proxyCheckingAnimation.size.width() + st::proxyCheckingSkip; diff --git a/Telegram/SourceFiles/ui/effects/radial_animation.cpp b/Telegram/SourceFiles/ui/effects/radial_animation.cpp index fbf4cb5eec..c4ef6d82f4 100644 --- a/Telegram/SourceFiles/ui/effects/radial_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/radial_animation.cpp @@ -84,37 +84,37 @@ void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, styl p.setOpacity(o); } -InfiniteRadialAnimation::InfiniteRadialAnimation(AnimationCallbacks &&callbacks) -: _animation(std::move(callbacks)) { +InfiniteRadialAnimation::InfiniteRadialAnimation( + AnimationCallbacks &&callbacks, + const style::InfiniteRadialAnimation &st) +: _st(st) +, _animation(std::move(callbacks)) { } void InfiniteRadialAnimation::start() { - _start = _changed = getms(); - _finished = false; - _animation.start(); -} - -void InfiniteRadialAnimation::update(bool finished, TimeMs ms) { - if (_finished != finished) { - _finished = finished; - _changed = ms; + const auto now = getms(); + if (_workFinished <= now) { + _workStarted = now + _st.sineDuration; + _workFinished = 0; } - - auto dt = float64(ms - _changed); - auto fulldt = float64(ms - _start); - _opacity = qMin(fulldt / st::radialDuration, 1.); - if (!finished) { - } else if (dt >= st::radialDuration) { - stop(); - } else { - auto r = dt / st::radialDuration; - _opacity *= 1 - r; + if (!_animation.animating()) { + _animation.start(); } } void InfiniteRadialAnimation::stop() { - _start = _changed = 0; - _animation.stop(); + const auto now = getms(); + if (!_workFinished) { + const auto zero = _workStarted - _st.sineDuration; + const auto index = (now - zero + _st.sinePeriod - _st.sineShift) + / _st.sinePeriod; + _workFinished = zero + + _st.sineShift + + (index * _st.sinePeriod) + + _st.sineDuration; + } else if (_workFinished <= now) { + _animation.stop(); + } } void InfiniteRadialAnimation::step(TimeMs ms) { @@ -124,56 +124,148 @@ void InfiniteRadialAnimation::step(TimeMs ms) { void InfiniteRadialAnimation::draw( Painter &p, QPoint position, - int outerWidth, - const style::InfiniteRadialAnimation &st) { - auto o = p.opacity(); - p.setOpacity(o * _opacity); + int outerWidth) { + const auto state = computeState(); - auto pen = st.color->p; + auto o = p.opacity(); + p.setOpacity(o * state.shown); + + auto pen = _st.color->p; auto was = p.pen(); - pen.setWidth(st.thickness); + pen.setWidth(_st.thickness); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); - const auto time = (getms() - _start); - const auto linear = (time * FullArcLength) / st.linearPeriod; - const auto frontPeriods = time / st.sinePeriod; - const auto frontCurrent = time % st.sinePeriod; - const auto frontProgress = anim::sineInOut( - st.arcMax - st.arcMin, - std::min(frontCurrent, TimeMs(st.sineDuration)) - / float64(st.sineDuration)); - const auto backTime = std::max(time - st.sineShift, 0LL); - const auto backPeriods = backTime / st.sinePeriod; - const auto backCurrent = backTime % st.sinePeriod; - const auto backProgress = anim::sineInOut( - st.arcMax - st.arcMin, - std::min(backCurrent, TimeMs(st.sineDuration)) - / float64(st.sineDuration)); - const auto front = linear + std::round((st.arcMin + frontProgress + frontPeriods * (st.arcMax - st.arcMin)) * FullArcLength); - const auto from = linear + std::round((backProgress + backPeriods * (st.arcMax - st.arcMin)) * FullArcLength); - const auto len = (front - from); - - //if (rtl()) { - // from = QuarterArcLength - (from - QuarterArcLength) - len; - // if (from < 0) from += FullArcLength; - //} - { PainterHighQualityEnabler hq(p); p.drawArc( rtlrect( position.x(), position.y(), - st.size.width(), - st.size.height(), + _st.size.width(), + _st.size.height(), outerWidth), - from, - len); + state.arcFrom, + state.arcLength); } p.setPen(was); p.setOpacity(o); } +auto InfiniteRadialAnimation::computeState() -> State { + const auto now = getms(); + const auto linear = int(((now * FullArcLength) / _st.linearPeriod) + % FullArcLength); + if (!_workStarted || (_workFinished && _workFinished <= now)) { + const auto shown = 0.; + _animation.stop(); + return { + shown, + linear, + FullArcLength }; + } + const auto min = int(std::round(FullArcLength * _st.arcMin)); + const auto max = int(std::round(FullArcLength * _st.arcMax)); + if (now <= _workStarted) { + // zero .. _workStarted + const auto zero = _workStarted - _st.sineDuration; + const auto shown = (now - zero) / float64(_st.sineDuration); + const auto length = anim::interpolate( + FullArcLength, + min, + anim::sineInOut(1., snap(shown, 0., 1.))); + return { + shown, + linear + (FullArcLength - length), + length }; + } else if (!_workFinished || now <= _workFinished - _st.sineDuration) { + // _workStared .. _workFinished - _st.sineDuration + const auto shown = 1.; + const auto cycles = (now - _workStarted) / _st.sinePeriod; + const auto relative = (now - _workStarted) % _st.sinePeriod; + const auto smallDuration = _st.sineShift - _st.sineDuration; + const auto largeDuration = _st.sinePeriod + - _st.sineShift + - _st.sineDuration; + const auto basic = int((linear + + (FullArcLength - min) + + cycles * (max - min)) % FullArcLength); + if (relative <= smallDuration) { + // localZero .. growStart + return { + shown, + basic, + min }; + } else if (relative <= smallDuration + _st.sineDuration) { + // growStart .. growEnd + const auto growLinear = (relative - smallDuration) / + float64(_st.sineDuration); + const auto growProgress = anim::sineInOut(1., growLinear); + return { + shown, + basic, + anim::interpolate(min, max, growProgress) }; + } else if (relative <= _st.sinePeriod - _st.sineDuration) { + // growEnd .. shrinkStart + return { + shown, + basic, + max }; + } else { + // shrinkStart .. shrinkEnd + const auto shrinkLinear = (relative + - (_st.sinePeriod - _st.sineDuration)) + / float64(_st.sineDuration); + const auto shrinkProgress = anim::sineInOut(1., shrinkLinear); + const auto shrink = anim::interpolate( + 0, + max - min, + shrinkProgress); + return { + shown, + basic + shrink, + max - shrink }; // interpolate(max, min, shrinkProgress) + } + } else { + // _workFinished - _st.sineDuration .. _workFinished + const auto hidden = (now - (_workFinished - _st.sineDuration)) + / float64(_st.sineDuration); + const auto cycles = (_workFinished - _workStarted) / _st.sinePeriod; + const auto basic = int((linear + + (FullArcLength - min) + + cycles * (max - min)) % FullArcLength); + const auto length = anim::interpolate( + min, + FullArcLength, + anim::sineInOut(1., snap(hidden, 0., 1.))); + return { + 1. - hidden, + basic, + length }; + } + //const auto frontPeriods = time / st.sinePeriod; + //const auto frontCurrent = time % st.sinePeriod; + //const auto frontProgress = anim::sineInOut( + // st.arcMax - st.arcMin, + // std::min(frontCurrent, TimeMs(st.sineDuration)) + // / float64(st.sineDuration)); + //const auto backTime = std::max(time - st.sineShift, 0LL); + //const auto backPeriods = backTime / st.sinePeriod; + //const auto backCurrent = backTime % st.sinePeriod; + //const auto backProgress = anim::sineInOut( + // st.arcMax - st.arcMin, + // std::min(backCurrent, TimeMs(st.sineDuration)) + // / float64(st.sineDuration)); + //const auto front = linear + std::round((st.arcMin + frontProgress + frontPeriods * (st.arcMax - st.arcMin)) * FullArcLength); + //const auto from = linear + std::round((backProgress + backPeriods * (st.arcMax - st.arcMin)) * FullArcLength); + //const auto length = (front - from); + + //return { + // _opacity, + // from, + // length + //}; +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/radial_animation.h b/Telegram/SourceFiles/ui/effects/radial_animation.h index b3c0f28bd8..093cac8b8c 100644 --- a/Telegram/SourceFiles/ui/effects/radial_animation.h +++ b/Telegram/SourceFiles/ui/effects/radial_animation.h @@ -48,17 +48,20 @@ private: class InfiniteRadialAnimation { public: - InfiniteRadialAnimation(AnimationCallbacks &&callbacks); + struct State { + float64 shown = 0.; + int arcFrom = 0; + int arcLength = FullArcLength; + }; + InfiniteRadialAnimation( + AnimationCallbacks &&callbacks, + const style::InfiniteRadialAnimation &st); - float64 opacity() const { - return _opacity; - } bool animating() const { return _animation.animating(); } void start(); - void update(bool finished, TimeMs ms); void stop(); void step(TimeMs ms); @@ -69,14 +72,15 @@ public: void draw( Painter &p, QPoint position, - int outerWidth, - const style::InfiniteRadialAnimation &st); + int outerWidth); + + State computeState(); private: - float64 _opacity = 0.; - TimeMs _start = 0; - TimeMs _changed = 0; - bool _finished = false; + const style::InfiniteRadialAnimation &_st; + float64 _shown = 0.; + TimeMs _workStarted = 0; + TimeMs _workFinished = 0; BasicAnimation _animation; }; diff --git a/Telegram/SourceFiles/ui/special_buttons.cpp b/Telegram/SourceFiles/ui/special_buttons.cpp index 85ac563587..e442b3a998 100644 --- a/Telegram/SourceFiles/ui/special_buttons.cpp +++ b/Telegram/SourceFiles/ui/special_buttons.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_history.h" #include "dialogs/dialogs_layout.h" #include "ui/effects/ripple_animation.h" +#include "ui/effects/radial_animation.h" #include "ui/empty_userpic.h" #include "data/data_photo.h" #include "data/data_session.h" @@ -163,8 +164,7 @@ void HistoryDownButton::setUnreadCount(int unreadCount) { EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st) : RippleButton(parent, st.ripple) -, _st(st) -, _a_loading(animation(this, &EmojiButton::step_loading)) { +, _st(st) { resize(_st.width, _st.height); setCursor(style::cur_pointer); } @@ -177,8 +177,10 @@ void EmojiButton::paintEvent(QPaintEvent *e) { p.fillRect(e->rect(), st::historyComposeAreaBg); paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms, _rippleOverride ? &(*_rippleOverride)->c : nullptr); - auto loading = a_loading.current(ms, _loading ? 1 : 0); - p.setOpacity(1 - loading); + const auto loadingState = _loading + ? _loading->computeState() + : Ui::InfiniteRadialAnimation::State{ 0., 0, FullArcLength }; + p.setOpacity(1. - loadingState.shown); auto over = isOver(); auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon); @@ -193,25 +195,23 @@ void EmojiButton::paintEvent(QPaintEvent *e) { PainterHighQualityEnabler hq(p); QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, st::historyEmojiCircleTop), st::historyEmojiCircle); - if (loading > 0) { - int32 full = FullArcLength; - int32 start = qRound(full * float64(ms % st::historyEmojiCirclePeriod) / st::historyEmojiCirclePeriod), part = qRound(loading * full / st::historyEmojiCirclePart); - p.drawArc(inner, start, full - part); + if (loadingState.arcLength < FullArcLength) { + p.drawArc(inner, loadingState.arcFrom, loadingState.arcLength); } else { p.drawEllipse(inner); } } void EmojiButton::setLoading(bool loading) { - if (_loading != loading) { - _loading = loading; - auto from = loading ? 0. : 1., to = loading ? 1. : 0.; - a_loading.start([this] { update(); }, from, to, st::historyEmojiCircleDuration); - if (loading) { - _a_loading.start(); - } else { - _a_loading.stop(); - } + if (loading && !_loading) { + _loading = std::make_unique( + animation(this, &EmojiButton::step_loading), + st::defaultInfiniteRadialAnimation); + } + if (loading) { + _loading->start(); + } else { + _loading->stop(); } } diff --git a/Telegram/SourceFiles/ui/special_buttons.h b/Telegram/SourceFiles/ui/special_buttons.h index 8c06eef5f4..c6badf860c 100644 --- a/Telegram/SourceFiles/ui/special_buttons.h +++ b/Telegram/SourceFiles/ui/special_buttons.h @@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class PeerData; +namespace Ui { +class InfiniteRadialAnimation; +} // namespace Ui + namespace Data { class Feed; } // namespace Data @@ -69,9 +73,7 @@ private: const style::IconButton &_st; - bool _loading = false; - Animation a_loading; - BasicAnimation _a_loading; + std::unique_ptr _loading; const style::icon *_iconOverride = nullptr; const style::color *_colorOverride = nullptr;