/* 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 { void RadialAnimation::start(float64 prg) { _firstStart = _lastStart = _lastTime = crl::now(); const auto iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength); const auto iprgstrict = qRound(prg * AlmostFullArcLength); _arcEnd = anim::value(iprgstrict, iprg); _animation.start(); } bool RadialAnimation::update(float64 prg, bool finished, crl::time ms) { const auto iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength); const auto result = (iprg != qRound(_arcEnd.to())); if (_finished != finished) { _arcEnd.start(iprg); _finished = finished; _lastStart = _lastTime; } else if (result) { _arcEnd.start(iprg); _lastStart = _lastTime; } _lastTime = ms; const auto dt = float64(ms - _lastStart); const auto fulldt = float64(ms - _firstStart); const auto opacitydt = _finished ? (_lastStart - _firstStart) : fulldt; _opacity = qMin(opacitydt / st::radialDuration, 1.); if (anim::Disabled()) { _arcEnd.update(1., anim::linear); if (finished) { stop(); } } else if (!finished) { _arcEnd.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear); } else if (dt >= st::radialDuration) { _arcEnd.update(1., anim::linear); stop(); } else { auto r = dt / st::radialDuration; _arcEnd.update(r, anim::linear); _opacity *= 1 - r; } auto fromstart = fulldt / st::radialPeriod; _arcStart.update(fromstart - std::floor(fromstart), anim::linear); return result; } void RadialAnimation::stop() { _firstStart = _lastStart = _lastTime = 0; _arcEnd = anim::value(); _animation.stop(); } void RadialAnimation::draw( Painter &p, const QRect &inner, int32 thickness, style::color color) const { const auto state = computeState(); auto o = p.opacity(); p.setOpacity(o * state.shown); auto pen = color->p; auto was = p.pen(); pen.setWidth(thickness); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); { PainterHighQualityEnabler hq(p); p.drawArc(inner, state.arcFrom, state.arcLength); } p.setPen(was); p.setOpacity(o); } RadialState RadialAnimation::computeState() const { auto length = MinArcLength + qRound(_arcEnd.current()); auto from = QuarterArcLength - length - (anim::Disabled() ? 0 : qRound(_arcStart.current())); if (rtl()) { from = QuarterArcLength - (from - QuarterArcLength) - length; if (from < 0) from += FullArcLength; } return { _opacity, from, length }; } void InfiniteRadialAnimation::start(crl::time skip) { const auto now = crl::now(); if (_workFinished <= now && (_workFinished || !_workStarted)) { _workStarted = std::max(now + _st.sineDuration - skip, crl::time(1)); _workFinished = 0; } if (!_animation.animating()) { _animation.start(); } } void InfiniteRadialAnimation::stop(anim::type animated) { const auto now = crl::now(); if (anim::Disabled() || animated == anim::type::instant) { _workFinished = now; } 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::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); const auto rect = rtlrect( position.x(), position.y(), size.width(), size.height(), outerWidth); const auto was = p.pen(); const auto brush = p.brush(); if (anim::Disabled()) { anim::DrawStaticLoading(p, rect, _st.thickness, _st.color); } else { auto pen = _st.color->p; pen.setWidth(_st.thickness); pen.setCapStyle(Qt::RoundCap); p.setPen(pen); { PainterHighQualityEnabler hq(p); p.drawArc( rect, state.arcFrom, state.arcLength); } } p.setPen(was); p.setBrush(brush); p.setOpacity(o); } RadialState InfiniteRadialAnimation::computeState() { const auto now = crl::now(); const auto linear = FullArcLength - int(((now * FullArcLength) / _st.linearPeriod) % FullArcLength); if (!_workStarted || (_workFinished && _workFinished <= now)) { const auto shown = 0.; _animation.stop(); return { shown, linear, FullArcLength }; } if (anim::Disabled()) { const auto shown = 1.; return { 1., 0, 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, 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 + min + (cycles * (FullArcLength + min - max))) % FullArcLength); if (relative <= smallDuration) { // localZero .. growStart return { shown, basic - min, min }; } else if (relative <= smallDuration + _st.sineDuration) { // growStart .. growEnd const auto growLinear = (relative - smallDuration) / float64(_st.sineDuration); const auto growProgress = anim::sineInOut(1., growLinear); const auto length = anim::interpolate(min, max, growProgress); return { shown, basic - length, length }; } else if (relative <= _st.sinePeriod - _st.sineDuration) { // growEnd .. shrinkStart return { shown, basic - max, 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 - max, 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 + min + cycles * (FullArcLength + min - max)) % FullArcLength); const auto length = anim::interpolate( min, FullArcLength, anim::sineInOut(1., snap(hidden, 0., 1.))); return { 1. - hidden, basic - length, 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, crl::time(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, crl::time(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