Improve infinite radial animation.

This commit is contained in:
John Preston 2018-05-04 19:57:50 +03:00
parent 1af2769209
commit d15b0cdb08
5 changed files with 191 additions and 96 deletions

View file

@ -198,7 +198,8 @@ void ProxyRow::updateFields(View &&view) {
if (state == State::Connecting || state == State::Checking) {
if (!_progress) {
_progress = std::make_unique<Ui::InfiniteRadialAnimation>(
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;

View file

@ -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

View file

@ -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;
};

View file

@ -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<Ui::InfiniteRadialAnimation>(
animation(this, &EmojiButton::step_loading),
st::defaultInfiniteRadialAnimation);
}
if (loading) {
_loading->start();
} else {
_loading->stop();
}
}

View file

@ -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<Ui::InfiniteRadialAnimation> _loading;
const style::icon *_iconOverride = nullptr;
const style::color *_colorOverride = nullptr;