1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #include "ui/effects/radial_animation.h"
8
9 #include "ui/painter.h"
10 #include "styles/style_widgets.h"
11
12 namespace Ui {
13 namespace {
14
15 constexpr auto kFullArcLength = 360 * 16;
16 constexpr auto kQuarterArcLength = (kFullArcLength / 4);
17 constexpr auto kMinArcLength = (kFullArcLength / 360);
18 constexpr auto kAlmostFullArcLength = (kFullArcLength - kMinArcLength);
19
20 } // namespace
21
22 const int RadialState::kFull = kFullArcLength;
23
start(float64 prg)24 void RadialAnimation::start(float64 prg) {
25 _firstStart = _lastStart = _lastTime = crl::now();
26 const auto iprg = qRound(qMax(prg, 0.0001) * kAlmostFullArcLength);
27 const auto iprgstrict = qRound(prg * kAlmostFullArcLength);
28 _arcEnd = anim::value(iprgstrict, iprg);
29 _animation.start();
30 }
31
update(float64 prg,bool finished,crl::time ms)32 bool RadialAnimation::update(float64 prg, bool finished, crl::time ms) {
33 const auto iprg = qRound(qMax(prg, 0.0001) * kAlmostFullArcLength);
34 const auto result = (iprg != qRound(_arcEnd.to()))
35 || (_finished != finished);
36 if (_finished != finished) {
37 _arcEnd.start(iprg);
38 _finished = finished;
39 _lastStart = _lastTime;
40 } else if (result) {
41 _arcEnd.start(iprg);
42 _lastStart = _lastTime;
43 }
44 _lastTime = ms;
45
46 const auto dt = float64(ms - _lastStart);
47 const auto fulldt = float64(ms - _firstStart);
48 const auto opacitydt = _finished
49 ? (_lastStart - _firstStart)
50 : fulldt;
51 _opacity = qMin(opacitydt / st::radialDuration, 1.);
52 if (anim::Disabled()) {
53 _arcEnd.update(1., anim::linear);
54 if (finished) {
55 stop();
56 }
57 } else if (!finished) {
58 _arcEnd.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear);
59 } else if (dt >= st::radialDuration) {
60 _arcEnd.update(1., anim::linear);
61 stop();
62 } else {
63 auto r = dt / st::radialDuration;
64 _arcEnd.update(r, anim::linear);
65 _opacity *= 1 - r;
66 }
67 auto fromstart = fulldt / st::radialPeriod;
68 _arcStart.update(fromstart - std::floor(fromstart), anim::linear);
69 return result;
70 }
71
stop()72 void RadialAnimation::stop() {
73 _firstStart = _lastStart = _lastTime = 0;
74 _arcEnd = anim::value();
75 _animation.stop();
76 }
77
draw(QPainter & p,const QRect & inner,int32 thickness,style::color color) const78 void RadialAnimation::draw(
79 QPainter &p,
80 const QRect &inner,
81 int32 thickness,
82 style::color color) const {
83 const auto state = computeState();
84
85 auto o = p.opacity();
86 p.setOpacity(o * state.shown);
87
88 auto pen = color->p;
89 auto was = p.pen();
90 pen.setWidth(thickness);
91 pen.setCapStyle(Qt::RoundCap);
92 p.setPen(pen);
93
94 {
95 PainterHighQualityEnabler hq(p);
96 p.drawArc(inner, state.arcFrom, state.arcLength);
97 }
98
99 p.setPen(was);
100 p.setOpacity(o);
101 }
102
computeState() const103 RadialState RadialAnimation::computeState() const {
104 auto length = kMinArcLength + qRound(_arcEnd.current());
105 auto from = kQuarterArcLength
106 - length
107 - (anim::Disabled() ? 0 : qRound(_arcStart.current()));
108 if (style::RightToLeft()) {
109 from = kQuarterArcLength - (from - kQuarterArcLength) - length;
110 if (from < 0) from += kFullArcLength;
111 }
112 return { _opacity, from, length };
113 }
114
start(crl::time skip)115 void InfiniteRadialAnimation::start(crl::time skip) {
116 const auto now = crl::now();
117 if (_workFinished <= now && (_workFinished || !_workStarted)) {
118 _workStarted = std::max(now + _st.sineDuration - skip, crl::time(1));
119 _workFinished = 0;
120 }
121 if (!_animation.animating()) {
122 _animation.start();
123 }
124 }
125
stop(anim::type animated)126 void InfiniteRadialAnimation::stop(anim::type animated) {
127 const auto now = crl::now();
128 if (anim::Disabled() || animated == anim::type::instant) {
129 _workFinished = now;
130 }
131 if (!_workFinished) {
132 const auto zero = _workStarted - _st.sineDuration;
133 const auto index = (now - zero + _st.sinePeriod - _st.sineShift)
134 / _st.sinePeriod;
135 _workFinished = zero
136 + _st.sineShift
137 + (index * _st.sinePeriod)
138 + _st.sineDuration;
139 } else if (_workFinished <= now) {
140 _animation.stop();
141 }
142 }
143
draw(QPainter & p,QPoint position,int outerWidth)144 void InfiniteRadialAnimation::draw(
145 QPainter &p,
146 QPoint position,
147 int outerWidth) {
148 Draw(
149 p,
150 computeState(),
151 position,
152 _st.size,
153 outerWidth,
154 _st.color,
155 _st.thickness);
156 }
157
draw(QPainter & p,QPoint position,QSize size,int outerWidth)158 void InfiniteRadialAnimation::draw(
159 QPainter &p,
160 QPoint position,
161 QSize size,
162 int outerWidth) {
163 Draw(
164 p,
165 computeState(),
166 position,
167 size,
168 outerWidth,
169 _st.color,
170 _st.thickness);
171 }
172
Draw(QPainter & p,const RadialState & state,QPoint position,QSize size,int outerWidth,QPen pen,int thickness)173 void InfiniteRadialAnimation::Draw(
174 QPainter &p,
175 const RadialState &state,
176 QPoint position,
177 QSize size,
178 int outerWidth,
179 QPen pen,
180 int thickness) {
181 auto o = p.opacity();
182 p.setOpacity(o * state.shown);
183
184 const auto rect = style::rtlrect(
185 position.x(),
186 position.y(),
187 size.width(),
188 size.height(),
189 outerWidth);
190 const auto was = p.pen();
191 const auto brush = p.brush();
192 if (anim::Disabled()) {
193 anim::DrawStaticLoading(p, rect, thickness, pen);
194 } else {
195 pen.setWidth(thickness);
196 pen.setCapStyle(Qt::RoundCap);
197 p.setPen(pen);
198
199 {
200 PainterHighQualityEnabler hq(p);
201 p.drawArc(
202 rect,
203 state.arcFrom,
204 state.arcLength);
205 }
206 }
207 p.setPen(was);
208 p.setBrush(brush);
209 p.setOpacity(o);
210 }
211
computeState()212 RadialState InfiniteRadialAnimation::computeState() {
213 const auto now = crl::now();
214 const auto linear = kFullArcLength
215 - int(((now * kFullArcLength) / _st.linearPeriod) % kFullArcLength);
216 if (!_workStarted || (_workFinished && _workFinished <= now)) {
217 const auto shown = 0.;
218 _animation.stop();
219 return {
220 shown,
221 linear,
222 kFullArcLength };
223 }
224 if (anim::Disabled()) {
225 return { 1., 0, kFullArcLength };
226 }
227 const auto min = int(base::SafeRound(kFullArcLength * _st.arcMin));
228 const auto max = int(base::SafeRound(kFullArcLength * _st.arcMax));
229 if (now <= _workStarted) {
230 // zero .. _workStarted
231 const auto zero = _workStarted - _st.sineDuration;
232 const auto shown = (now - zero) / float64(_st.sineDuration);
233 const auto length = anim::interpolate(
234 kFullArcLength,
235 min,
236 anim::sineInOut(1., std::clamp(shown, 0., 1.)));
237 return {
238 shown,
239 linear,
240 length };
241 } else if (!_workFinished || now <= _workFinished - _st.sineDuration) {
242 // _workStared .. _workFinished - _st.sineDuration
243 const auto shown = 1.;
244 const auto cycles = (now - _workStarted) / _st.sinePeriod;
245 const auto relative = (now - _workStarted) % _st.sinePeriod;
246 const auto smallDuration = _st.sineShift - _st.sineDuration;
247 const auto basic = int((linear
248 + min
249 + (cycles * (kFullArcLength + min - max))) % kFullArcLength);
250 if (relative <= smallDuration) {
251 // localZero .. growStart
252 return {
253 shown,
254 basic - min,
255 min };
256 } else if (relative <= smallDuration + _st.sineDuration) {
257 // growStart .. growEnd
258 const auto growLinear = (relative - smallDuration) /
259 float64(_st.sineDuration);
260 const auto growProgress = anim::sineInOut(1., growLinear);
261 const auto length = anim::interpolate(min, max, growProgress);
262 return {
263 shown,
264 basic - length,
265 length };
266 } else if (relative <= _st.sinePeriod - _st.sineDuration) {
267 // growEnd .. shrinkStart
268 return {
269 shown,
270 basic - max,
271 max };
272 } else {
273 // shrinkStart .. shrinkEnd
274 const auto shrinkLinear = (relative
275 - (_st.sinePeriod - _st.sineDuration))
276 / float64(_st.sineDuration);
277 const auto shrinkProgress = anim::sineInOut(1., shrinkLinear);
278 const auto shrink = anim::interpolate(
279 0,
280 max - min,
281 shrinkProgress);
282 return {
283 shown,
284 basic - max,
285 max - shrink }; // interpolate(max, min, shrinkProgress)
286 }
287 } else {
288 // _workFinished - _st.sineDuration .. _workFinished
289 const auto hidden = (now - (_workFinished - _st.sineDuration))
290 / float64(_st.sineDuration);
291 const auto cycles = (_workFinished - _workStarted) / _st.sinePeriod;
292 const auto basic = int((linear
293 + min
294 + cycles * (kFullArcLength + min - max)) % kFullArcLength);
295 const auto length = anim::interpolate(
296 min,
297 kFullArcLength,
298 anim::sineInOut(1., std::clamp(hidden, 0., 1.)));
299 return {
300 1. - hidden,
301 basic - length,
302 length };
303 }
304 //const auto frontPeriods = time / st.sinePeriod;
305 //const auto frontCurrent = time % st.sinePeriod;
306 //const auto frontProgress = anim::sineInOut(
307 // st.arcMax - st.arcMin,
308 // std::min(frontCurrent, crl::time(st.sineDuration))
309 // / float64(st.sineDuration));
310 //const auto backTime = std::max(time - st.sineShift, 0LL);
311 //const auto backPeriods = backTime / st.sinePeriod;
312 //const auto backCurrent = backTime % st.sinePeriod;
313 //const auto backProgress = anim::sineInOut(
314 // st.arcMax - st.arcMin,
315 // std::min(backCurrent, crl::time(st.sineDuration))
316 // / float64(st.sineDuration));
317 //const auto front = linear + base::SafeRound((st.arcMin + frontProgress + frontPeriods * (st.arcMax - st.arcMin)) * kFullArcLength);
318 //const auto from = linear + base::SafeRound((backProgress + backPeriods * (st.arcMax - st.arcMin)) * kFullArcLength);
319 //const auto length = (front - from);
320
321 //return {
322 // _opacity,
323 // from,
324 // length
325 //};
326 }
327
328 } // namespace Ui
329