220 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// This file is part of Desktop App Toolkit,
 | 
						|
// a set of libraries for developing nice desktop applications.
 | 
						|
//
 | 
						|
// For license and copyright information please follow this link:
 | 
						|
// https://github.com/desktop-app/legal/blob/master/LEGAL
 | 
						|
//
 | 
						|
#include "ui/paint/arcs.h"
 | 
						|
 | 
						|
#include "ui/effects/animation_value.h"
 | 
						|
#include "ui/effects/animation_value_f.h"
 | 
						|
#include "ui/painter.h"
 | 
						|
 | 
						|
namespace Ui::Paint {
 | 
						|
 | 
						|
ArcsAnimation::ArcsAnimation(
 | 
						|
	const style::ArcsAnimation &st,
 | 
						|
	std::vector<float> thresholds,
 | 
						|
	float64 startValue,
 | 
						|
	Direction direction)
 | 
						|
: _st(st)
 | 
						|
, _direction(direction)
 | 
						|
, _startAngle(16
 | 
						|
	* (st.deltaAngle
 | 
						|
		+ ((direction == Direction::Up)
 | 
						|
			? 90
 | 
						|
			: (direction == Direction::Down)
 | 
						|
			? 270
 | 
						|
			: (direction == Direction::Left)
 | 
						|
			? 180
 | 
						|
			: 0)))
 | 
						|
, _spanAngle(-st.deltaAngle * 2 * 16)
 | 
						|
, _emptyRect(computeArcRect(0))
 | 
						|
, _currentValue(startValue) {
 | 
						|
	initArcs(std::move(thresholds));
 | 
						|
}
 | 
						|
 | 
						|
void ArcsAnimation::initArcs(std::vector<float> thresholds) {
 | 
						|
	const auto count = thresholds.size();
 | 
						|
	_arcs.reserve(count);
 | 
						|
 | 
						|
	for (auto i = 0; i < count; i++) {
 | 
						|
		const auto threshold = thresholds[i];
 | 
						|
		const auto progress = (threshold > _currentValue) ? 1. : 0.;
 | 
						|
		auto arc = Arc{
 | 
						|
			.rect = computeArcRect(i + 1),
 | 
						|
			.threshold = threshold,
 | 
						|
			.progress = progress,
 | 
						|
		};
 | 
						|
		_arcs.push_back(std::move(arc));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool ArcsAnimation::isHorizontal() const {
 | 
						|
	return _direction == Direction::Left || _direction == Direction::Right;
 | 
						|
}
 | 
						|
 | 
						|
QRectF ArcsAnimation::computeArcRect(int index) const {
 | 
						|
	const auto w = _st.startWidth + _st.deltaWidth * index;
 | 
						|
	const auto h = _st.startHeight + _st.deltaHeight * index;
 | 
						|
	if (isHorizontal()) {
 | 
						|
		auto rect = QRectF(0, -h / 2.0, w, h);
 | 
						|
		if (_direction == Direction::Right) {
 | 
						|
			rect.moveRight(index * _st.space);
 | 
						|
		} else {
 | 
						|
			rect.moveLeft(-index * _st.space);
 | 
						|
		}
 | 
						|
		return rect;
 | 
						|
	} else {
 | 
						|
		auto rect = QRectF(-w / 2.0, 0, w, h);
 | 
						|
		if (_direction == Direction::Up) {
 | 
						|
			rect.moveTop(-index * _st.space);
 | 
						|
		} else {
 | 
						|
			rect.moveBottom(index * _st.space);
 | 
						|
		}
 | 
						|
		return rect;
 | 
						|
	}
 | 
						|
	return QRectF();
 | 
						|
}
 | 
						|
 | 
						|
void ArcsAnimation::update(crl::time now) {
 | 
						|
	for (auto &arc : _arcs) {
 | 
						|
		if (!isArcFinished(arc)) {
 | 
						|
			const auto progress = std::clamp(
 | 
						|
				(now - arc.startTime) / float64(_st.duration),
 | 
						|
				0.,
 | 
						|
				1.);
 | 
						|
			arc.progress = (arc.threshold > _currentValue)
 | 
						|
				? progress
 | 
						|
				: (1. - progress);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (isFinished()) {
 | 
						|
		_stopUpdateRequests.fire({});
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void ArcsAnimation::setValue(float64 value) {
 | 
						|
	if (_currentValue == value) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto previousValue = _currentValue;
 | 
						|
	_currentValue = value;
 | 
						|
	if (!isFinished()) {
 | 
						|
		const auto now = crl::now();
 | 
						|
		_startUpdateRequests.fire({});
 | 
						|
		for (auto &arc : _arcs) {
 | 
						|
			updateArcStartTime(arc, previousValue, now);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void ArcsAnimation::updateArcStartTime(
 | 
						|
		Arc &arc,
 | 
						|
		float64 previousValue,
 | 
						|
		crl::time now) {
 | 
						|
	if ((arc.progress == 0.) || (arc.progress == 1.)) {
 | 
						|
		arc.startTime = isArcFinished(arc) ? 0 : now;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto isPreviousToHide = (arc.threshold <= previousValue); // 0 -> 1
 | 
						|
	const auto isCurrentToHide = (arc.threshold <= _currentValue);
 | 
						|
	if (isPreviousToHide != isCurrentToHide) {
 | 
						|
		const auto passedTime = _st.duration * arc.progress;
 | 
						|
		const auto newDelta = isCurrentToHide
 | 
						|
			? (_st.duration - passedTime)
 | 
						|
			: passedTime;
 | 
						|
		arc.startTime = now - newDelta;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
float ArcsAnimation::width() const {
 | 
						|
	if (_arcs.empty()) {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	for (const auto &arc : ranges::views::reverse(_arcs)) {
 | 
						|
		if ((arc.progress != 1.)) {
 | 
						|
			return arc.rect.x() + arc.rect.width();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
float ArcsAnimation::finishedWidth() const {
 | 
						|
	if (_arcs.empty()) {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	for (const auto &arc : ranges::views::reverse(_arcs)) {
 | 
						|
		if (arc.threshold <= _currentValue) {
 | 
						|
			return arc.rect.x() + arc.rect.width();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
float ArcsAnimation::maxWidth() const {
 | 
						|
	if (_arcs.empty()) {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	const auto &r = _arcs.back().rect;
 | 
						|
	return r.x() + r.width();
 | 
						|
}
 | 
						|
 | 
						|
float ArcsAnimation::height() const {
 | 
						|
	return _arcs.empty()
 | 
						|
		? 0
 | 
						|
		: _arcs.back().rect.height();
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<> ArcsAnimation::startUpdateRequests() {
 | 
						|
	return _startUpdateRequests.events();
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<> ArcsAnimation::stopUpdateRequests() {
 | 
						|
	return _stopUpdateRequests.events();
 | 
						|
}
 | 
						|
 | 
						|
bool ArcsAnimation::isFinished() const {
 | 
						|
	return ranges::all_of(
 | 
						|
		_arcs,
 | 
						|
		[=](const Arc &arc) { return isArcFinished(arc); });
 | 
						|
}
 | 
						|
 | 
						|
bool ArcsAnimation::isArcFinished(const Arc &arc) const {
 | 
						|
	return ((arc.threshold > _currentValue) && (arc.progress == 1.))
 | 
						|
		|| ((arc.threshold <= _currentValue) && (arc.progress == 0.));
 | 
						|
}
 | 
						|
 | 
						|
void ArcsAnimation::paint(Painter &p, std::optional<QColor> colorOverride) {
 | 
						|
	PainterHighQualityEnabler hq(p);
 | 
						|
	QPen pen;
 | 
						|
	if (_strokeRatio) {
 | 
						|
		pen.setWidthF(_st.stroke * _strokeRatio);
 | 
						|
	} else {
 | 
						|
		pen.setWidth(_st.stroke);
 | 
						|
	}
 | 
						|
	pen.setCapStyle(Qt::RoundCap);
 | 
						|
	pen.setColor(colorOverride ? (*colorOverride) : _st.fg->c);
 | 
						|
	p.setPen(pen);
 | 
						|
	for (auto i = 0; i < _arcs.size(); i++) {
 | 
						|
		const auto &arc = _arcs[i];
 | 
						|
		const auto previousRect = (!i) ? _emptyRect : _arcs[i - 1].rect;
 | 
						|
		const auto progress = arc.progress;
 | 
						|
		const auto opactity = (1. - progress);
 | 
						|
		p.setOpacity(opactity * opactity);
 | 
						|
		const auto rect = (progress == 0.)
 | 
						|
			? arc.rect
 | 
						|
			: (progress == 1.)
 | 
						|
			? previousRect
 | 
						|
			: anim::interpolatedRectF(arc.rect, previousRect, progress);
 | 
						|
		p.drawArc(rect, _startAngle, _spanAngle);
 | 
						|
	}
 | 
						|
	p.setOpacity(1.);
 | 
						|
}
 | 
						|
 | 
						|
void ArcsAnimation::setStrokeRatio(float ratio) {
 | 
						|
	_strokeRatio = ratio;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace Ui::Paint
 |