314 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			314 lines
		
	
	
	
		
			8.8 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/controls/download_bar.h"
 | |
| 
 | |
| #include "ui/widgets/buttons.h"
 | |
| #include "ui/text/format_values.h"
 | |
| #include "ui/text/text_utilities.h"
 | |
| #include "ui/image/image_prepare.h"
 | |
| #include "ui/painter.h"
 | |
| #include "lang/lang_keys.h"
 | |
| #include "styles/style_dialogs.h"
 | |
| 
 | |
| namespace Ui {
 | |
| namespace {
 | |
| 
 | |
| constexpr auto kFullArcLength = 360 * 16;
 | |
| 
 | |
| [[nodiscard]] QImage Make(const QImage &image, int size) {
 | |
| 	if (image.isNull()) {
 | |
| 		return QImage();
 | |
| 	}
 | |
| 	auto result = image.scaledToWidth(
 | |
| 		size * style::DevicePixelRatio(),
 | |
| 		Qt::SmoothTransformation);
 | |
| 	result.setDevicePixelRatio(style::DevicePixelRatio());
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| DownloadBar::DownloadBar(
 | |
| 	not_null<QWidget*> parent,
 | |
| 	rpl::producer<DownloadBarProgress> progress)
 | |
| : _button(
 | |
| 	parent,
 | |
| 	object_ptr<RippleButton>(parent, st::dialogsMenuToggle.ripple))
 | |
| , _shadow(parent)
 | |
| , _progress(std::move(progress))
 | |
| , _radial([=](crl::time now) { radialAnimationCallback(now); }) {
 | |
| 	_button.hide(anim::type::instant);
 | |
| 	_shadow.showOn(_button.shownValue());
 | |
| 	_button.setDirectionUp(false);
 | |
| 	_button.entity()->resize(0, st::downloadBarHeight);
 | |
| 	_button.entity()->paintRequest(
 | |
| 	) | rpl::start_with_next([=](QRect clip) {
 | |
| 		auto p = Painter(_button.entity());
 | |
| 		paint(p, clip);
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	style::PaletteChanged(
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		refreshIcon();
 | |
| 	}, lifetime());
 | |
| 	refreshIcon();
 | |
| 
 | |
| 	_progress.value(
 | |
| 	) | rpl::start_with_next([=](const DownloadBarProgress &progress) {
 | |
| 		refreshInfo(progress);
 | |
| 	}, lifetime());
 | |
| }
 | |
| 
 | |
| DownloadBar::~DownloadBar() = default;
 | |
| 
 | |
| void DownloadBar::show(DownloadBarContent &&content) {
 | |
| 	_button.toggle(content.count > 0, anim::type::normal);
 | |
| 	if (!content.count) {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (!_radial.animating()) {
 | |
| 		_radial.start(computeProgress());
 | |
| 	}
 | |
| 	_content = content;
 | |
| 	const auto finished = (_content.done == _content.count);
 | |
| 	if (_finished != finished) {
 | |
| 		_finished = finished;
 | |
| 		_finishedAnimation.start(
 | |
| 			[=] { _button.update(); },
 | |
| 			_finished ? 0. : 1.,
 | |
| 			_finished ? 1. : 0.,
 | |
| 			st::widgetFadeDuration);
 | |
| 	}
 | |
| 	refreshThumbnail();
 | |
| 	_title.setMarkedText(
 | |
| 		st::defaultTextStyle,
 | |
| 		(content.count > 1
 | |
| 			? Ui::Text::Bold(
 | |
| 				tr::lng_profile_files(tr::now, lt_count, content.count))
 | |
| 			: content.singleName));
 | |
| 	refreshInfo(_progress.current());
 | |
| }
 | |
| 
 | |
| void DownloadBar::refreshThumbnail() {
 | |
| 	if (_content.singleThumbnail.isNull()) {
 | |
| 		_thumbnail = _thumbnailDone = QImage();
 | |
| 		_thumbnailCacheKey = 0;
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto cacheKey = _content.singleThumbnail.cacheKey();
 | |
| 	if (_thumbnailCacheKey == cacheKey) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_thumbnailCacheKey = cacheKey;
 | |
| 	_thumbnailLarge = _content.singleThumbnail;
 | |
| 	_thumbnailLarge.detach();
 | |
| 	const auto width = _thumbnailLarge.width();
 | |
| 	const auto height = _thumbnailLarge.height();
 | |
| 	if (width != height) {
 | |
| 		const auto size = std::min(width, height);
 | |
| 		_thumbnailLarge = _thumbnailLarge.copy(
 | |
| 			(width - size) / 2,
 | |
| 			(height - size) / 2,
 | |
| 			size,
 | |
| 			size);
 | |
| 	}
 | |
| 	const auto size = st::downloadLoadingSize;
 | |
| 	const auto added = 3 * st::downloadLoadingLine;
 | |
| 	const auto loadingsize = size;
 | |
| 	const auto donesize = size + (added - st::downloadLoadingLine) * 2;
 | |
| 	const auto make = [&](int size) {
 | |
| 		return Images::Circle(Make(_thumbnailLarge, size));
 | |
| 	};
 | |
| 	_thumbnail = make(loadingsize);
 | |
| 	_thumbnailDone = make(donesize);
 | |
| 	_thumbnailLarge = Images::Circle(std::move(_thumbnailLarge));
 | |
| }
 | |
| 
 | |
| void DownloadBar::refreshIcon() {
 | |
| 	_documentIconLarge = st::downloadIconDocument.instance(
 | |
| 		st::windowFgActive->c,
 | |
| 		style::kScaleMax / style::DevicePixelRatio());
 | |
| 	_documentIcon = Make(_documentIconLarge, st::downloadIconSize);
 | |
| 	_documentIconDone = Make(_documentIconLarge, st::downloadIconSizeDone);
 | |
| }
 | |
| 
 | |
| void DownloadBar::refreshInfo(const DownloadBarProgress &progress) {
 | |
| 	_info.setMarkedText(
 | |
| 		st::downloadInfoStyle,
 | |
| 		(progress.ready < progress.total
 | |
| 			? Text::WithEntities(
 | |
| 				FormatDownloadText(progress.ready, progress.total))
 | |
| 			: Text::Link((_content.count > 1)
 | |
| 				? tr::lng_downloads_view_in_section(tr::now)
 | |
| 				: tr::lng_downloads_view_in_chat(tr::now))));
 | |
| 	_button.entity()->update();
 | |
| }
 | |
| 
 | |
| bool DownloadBar::isHidden() const {
 | |
| 	return _button.isHidden();
 | |
| }
 | |
| 
 | |
| int DownloadBar::height() const {
 | |
| 	return _button.height();
 | |
| }
 | |
| 
 | |
| rpl::producer<int> DownloadBar::heightValue() const {
 | |
| 	return _button.heightValue();
 | |
| }
 | |
| 
 | |
| rpl::producer<bool> DownloadBar::shownValue() const {
 | |
| 	return _button.shownValue();
 | |
| }
 | |
| 
 | |
| void DownloadBar::setGeometry(int left, int top, int width, int height) {
 | |
| 	_button.resizeToWidth(width);
 | |
| 	_button.moveToLeft(left, top);
 | |
| 	_shadow.setGeometry(left, top - st::lineWidth, width, st::lineWidth);
 | |
| }
 | |
| 
 | |
| rpl::producer<> DownloadBar::clicks() const {
 | |
| 	return _button.entity()->clicks() | rpl::to_empty;
 | |
| }
 | |
| 
 | |
| rpl::lifetime &DownloadBar::lifetime() {
 | |
| 	return _button.lifetime();
 | |
| }
 | |
| 
 | |
| void DownloadBar::paint(Painter &p, QRect clip) {
 | |
| 	const auto button = _button.entity();
 | |
| 	const auto outerw = button->width();
 | |
| 	const auto over = button->isOver() || button->isDown();
 | |
| 	const auto &icon = over ? st::downloadArrowOver : st::downloadArrow;
 | |
| 	p.fillRect(clip, st::windowBg);
 | |
| 	button->paintRipple(p, 0, 0);
 | |
| 
 | |
| 	const auto finished = _finishedAnimation.value(_finished ? 1. : 0.);
 | |
| 	const auto size = st::downloadLoadingSize;
 | |
| 	const auto added = 3 * st::downloadLoadingLine;
 | |
| 	const auto skipx = st::downloadLoadingLeft;
 | |
| 	const auto skipy = (button->height() - size) / 2;
 | |
| 	const auto full = QRect(
 | |
| 		skipx - added,
 | |
| 		skipy - added,
 | |
| 		size + added * 2,
 | |
| 		size + added * 2);
 | |
| 	if (full.intersects(clip)) {
 | |
| 		const auto done = (finished == 1.);
 | |
| 		const auto loading = _radial.computeState();
 | |
| 		if (loading.shown > 0) {
 | |
| 			auto hq = PainterHighQualityEnabler(p);
 | |
| 			p.setOpacity(loading.shown);
 | |
| 			auto pen = st::windowBgActive->p;
 | |
| 			pen.setWidth(st::downloadLoadingLine);
 | |
| 			p.setPen(pen);
 | |
| 			p.setBrush(Qt::NoBrush);
 | |
| 			const auto m = added / 2.;
 | |
| 			auto rect = QRectF(full).marginsRemoved({ m, m, m, m });
 | |
| 			if (loading.arcLength < kFullArcLength) {
 | |
| 				p.drawArc(rect, loading.arcFrom, loading.arcLength);
 | |
| 			} else {
 | |
| 				p.drawEllipse(rect);
 | |
| 			}
 | |
| 			p.setOpacity(1.);
 | |
| 		}
 | |
| 		const auto shift = st::downloadLoadingLine
 | |
| 			+ (1. - finished) * (added - st::downloadLoadingLine);
 | |
| 		const auto ellipse = QRectF(full).marginsRemoved(
 | |
| 			{ shift, shift, shift, shift });
 | |
| 		if (_thumbnail.isNull()) {
 | |
| 			auto hq = PainterHighQualityEnabler(p);
 | |
| 			p.setPen(Qt::NoPen);
 | |
| 			p.setBrush(st::windowBgActive);
 | |
| 			p.drawEllipse(ellipse);
 | |
| 			const auto sizeLoading = st::downloadIconSize;
 | |
| 			if (finished == 0. || done) {
 | |
| 				const auto size = done
 | |
| 					? st::downloadIconSizeDone
 | |
| 					: sizeLoading;
 | |
| 				const auto image = done ? _documentIconDone : _documentIcon;
 | |
| 				p.drawImage(
 | |
| 					full.x() + (full.width() - size) / 2,
 | |
| 					full.y() + (full.height() - size) / 2,
 | |
| 					image);
 | |
| 			} else {
 | |
| 				auto hq = PainterHighQualityEnabler(p);
 | |
| 				const auto size = sizeLoading
 | |
| 					+ (st::downloadIconSizeDone - sizeLoading) * finished;
 | |
| 				p.drawImage(
 | |
| 					QRectF(
 | |
| 						full.x() + (full.width() - size) / 2.,
 | |
| 						full.y() + (full.height() - size) / 2.,
 | |
| 						size,
 | |
| 						size),
 | |
| 					_documentIconLarge);
 | |
| 			}
 | |
| 		} else if (finished == 0. || done) {
 | |
| 			p.drawImage(
 | |
| 				base::SafeRound(ellipse.x()),
 | |
| 				base::SafeRound(ellipse.y()),
 | |
| 				done ? _thumbnailDone : _thumbnail);
 | |
| 		} else {
 | |
| 			auto hq = PainterHighQualityEnabler(p);
 | |
| 			p.drawImage(ellipse, _thumbnailLarge);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	const auto minleft = std::min(
 | |
| 		st::downloadTitleLeft,
 | |
| 		st::downloadInfoLeft);
 | |
| 	const auto maxwidth = outerw - minleft;
 | |
| 	if (!clip.intersects({ minleft, 0, maxwidth, st::downloadBarHeight })) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto right = st::downloadArrowRight + icon.width();
 | |
| 	const auto available = button->width() - st::downloadTitleLeft - right;
 | |
| 	p.setPen(st::windowBoldFg);
 | |
| 	_title.drawLeftElided(
 | |
| 		p,
 | |
| 		st::downloadTitleLeft,
 | |
| 		st::downloadTitleTop,
 | |
| 		available,
 | |
| 		outerw);
 | |
| 
 | |
| 	p.setPen(st::windowSubTextFg);
 | |
| 	p.setTextPalette(st::defaultTextPalette);
 | |
| 	_info.drawLeftElided(
 | |
| 		p,
 | |
| 		st::downloadInfoLeft,
 | |
| 		st::downloadInfoTop,
 | |
| 		available,
 | |
| 		outerw);
 | |
| 
 | |
| 	const auto iconTop = (st::downloadBarHeight - icon.height()) / 2;
 | |
| 	icon.paint(p, outerw - right, iconTop, outerw);
 | |
| }
 | |
| 
 | |
| float64 DownloadBar::computeProgress() const {
 | |
| 	const auto now  = _progress.current();
 | |
| 	return now.total ? (now.ready / float64(now.total)) : 0.;
 | |
| }
 | |
| 
 | |
| void DownloadBar::radialAnimationCallback(crl::time now) {
 | |
| 	const auto finished = (_content.done == _content.count);
 | |
| 	const auto updated = _radial.update(computeProgress(), finished, now);
 | |
| 	if (!anim::Disabled() || updated) {
 | |
| 		const auto button = _button.entity();
 | |
| 		const auto size = st::downloadLoadingSize;
 | |
| 		const auto added = 3 * st::downloadLoadingLine;
 | |
| 		const auto skipx = st::downloadLoadingLeft;
 | |
| 		const auto skipy = (button->height() - size) / 2;
 | |
| 		const auto full = QRect(
 | |
| 			skipx - added,
 | |
| 			skipy - added,
 | |
| 			size + added * 2,
 | |
| 			size + added * 2);
 | |
| 		button->update(full);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| } // namespace Ui
 | 
