tdesktop/Telegram/SourceFiles/ui/effects/radial_animation.cpp

280 lines
7.5 KiB
C++

/*
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/radial_animation.h"
#include "styles/style_widgets.h"
namespace Ui {
RadialAnimation::RadialAnimation(AnimationCallbacks &&callbacks)
: a_arcStart(0, FullArcLength)
, _animation(std::move(callbacks)) {
}
void RadialAnimation::start(float64 prg) {
_firstStart = _lastStart = _lastTime = getms();
int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength), iprgstrict = qRound(prg * AlmostFullArcLength);
a_arcEnd = anim::value(iprgstrict, iprg);
_animation.start();
}
void RadialAnimation::update(float64 prg, bool finished, TimeMs ms) {
auto iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength);
if (iprg != qRound(a_arcEnd.to())) {
a_arcEnd.start(iprg);
_lastStart = _lastTime;
}
_lastTime = ms;
auto dt = float64(ms - _lastStart);
auto fulldt = float64(ms - _firstStart);
_opacity = qMin(fulldt / st::radialDuration, 1.);
if (!finished) {
a_arcEnd.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear);
} else if (dt >= st::radialDuration) {
a_arcEnd.update(1., anim::linear);
stop();
} else {
auto r = dt / st::radialDuration;
a_arcEnd.update(r, anim::linear);
_opacity *= 1 - r;
}
auto fromstart = fulldt / st::radialPeriod;
a_arcStart.update(fromstart - std::floor(fromstart), anim::linear);
}
void RadialAnimation::stop() {
_firstStart = _lastStart = _lastTime = 0;
a_arcEnd = anim::value();
_animation.stop();
}
void RadialAnimation::step(TimeMs ms) {
_animation.step(ms);
}
void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, style::color color) {
auto o = p.opacity();
p.setOpacity(o * _opacity);
auto pen = color->p;
auto was = p.pen();
pen.setWidth(thickness);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
auto len = MinArcLength + qRound(a_arcEnd.current());
auto from = QuarterArcLength - qRound(a_arcStart.current()) - len;
if (rtl()) {
from = QuarterArcLength - (from - QuarterArcLength) - len;
if (from < 0) from += FullArcLength;
}
{
PainterHighQualityEnabler hq(p);
p.drawArc(inner, from, len);
}
p.setPen(was);
p.setOpacity(o);
}
InfiniteRadialAnimation::InfiniteRadialAnimation(
AnimationCallbacks &&callbacks,
const style::InfiniteRadialAnimation &st)
: _st(st)
, _animation(std::move(callbacks)) {
}
void InfiniteRadialAnimation::start() {
const auto now = getms();
if (_workFinished <= now && (_workFinished || !_workStarted)) {
_workStarted = now + _st.sineDuration;
_workFinished = 0;
}
if (!_animation.animating()) {
_animation.start();
}
}
void InfiniteRadialAnimation::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) {
_animation.step(ms);
}
void InfiniteRadialAnimation::draw(
Painter &p,
QPoint position,
int outerWidth) {
draw(p, position, _st.size, outerWidth);
}
void InfiniteRadialAnimation::draw(
Painter &p,
QPoint position,
QSize size,
int outerWidth) {
const auto state = computeState();
auto o = p.opacity();
p.setOpacity(o * state.shown);
auto pen = _st.color->p;
auto was = p.pen();
pen.setWidth(_st.thickness);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
{
PainterHighQualityEnabler hq(p);
p.drawArc(
rtlrect(
position.x(),
position.y(),
size.width(),
size.height(),
outerWidth),
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