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