diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp index 18de9627ad..1d17ec81c5 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -10,9 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_photo.h" #include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_changes.h" #include "data/data_user.h" #include "data/data_channel.h" +#include "data/data_file_origin.h" #include "base/unixtime.h" #include "base/random.h" #include "main/main_session.h" @@ -846,26 +848,99 @@ rpl::producer MakeDownloadBarProgress() { } rpl::producer MakeDownloadBarContent() { - auto &manager = Core::App().downloadManager(); - return rpl::single( - rpl::empty_value() - ) | rpl::then( - manager.loadingListChanges() | rpl::to_empty - ) | rpl::map([=, &manager] { - auto result = Ui::DownloadBarContent(); - for (const auto id : manager.loadingList()) { - if (result.singleName.text.isEmpty()) { - const auto document = id->object.document; - result.singleName = Ui::Text::FormatDownloadsName(document); - result.singleThumbnail = QImage(); + return [](auto consumer) { + auto lifetime = rpl::lifetime(); + + struct State { + DocumentData *document = nullptr; + std::shared_ptr media; + rpl::lifetime downloadTaskLifetime; + QImage thumbnail; + Fn push; + }; + + const auto state = lifetime.make_state(); + auto &manager = Core::App().downloadManager(); + + const auto resolveThumbnailRecursive = [=](auto &&self) -> bool { + if (state->document + && (!state->document->hasThumbnail() + || state->document->thumbnailFailed())) { + state->media = nullptr; } - ++result.count; - if (id->done) { - ++result.done; + if (!state->media) { + state->downloadTaskLifetime.destroy(); + if (!state->thumbnail.isNull()) { + return false; + } + state->thumbnail = QImage(); + return true; } - } - return result; - }); + if (const auto image = state->media->thumbnail()) { + state->thumbnail = image->original(); + state->downloadTaskLifetime.destroy(); + state->media = nullptr; + return true; + } else if (const auto embed = state->media->thumbnailInline()) { + if (!state->thumbnail.isNull()) { + return false; + } + state->thumbnail = Images::Prepare(embed->original(), 0, { + .options = Images::Option::Blur, + }); + } + state->document->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return self(self); + }) | rpl::start_with_next( + state->push, + state->downloadTaskLifetime); + return !state->thumbnail.isNull(); + }; + const auto resolveThumbnail = [=] { + return resolveThumbnailRecursive(resolveThumbnailRecursive); + }; + + state->push = [=, &manager] { + auto content = Ui::DownloadBarContent(); + auto single = (const Data::DownloadObject*) nullptr; + for (const auto id : manager.loadingList()) { + if (!single) { + single = &id->object; + } + ++content.count; + if (id->done) { + ++content.done; + } + } + if (content.count == 1) { + const auto document = single->document; + const auto thumbnailed = (single->item + && document->hasThumbnail()) + ? document + : nullptr; + if (state->document != thumbnailed) { + state->document = thumbnailed; + state->media = thumbnailed + ? thumbnailed->createMediaView() + : nullptr; + state->media->thumbnailWanted(single->item->fullId()); + state->thumbnail = QImage(); + resolveThumbnail(); + } + content.singleName = Ui::Text::FormatDownloadsName( + document); + content.singleThumbnail = state->thumbnail; + } + consumer.put_next(std::move(content)); + }; + + manager.loadingListChanges( + ) | rpl::start_with_next(state->push, lifetime); + + state->push(); + return lifetime; + }; } } // namespace Data diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 02f4456489..38ab85cd10 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1910,6 +1910,9 @@ void Widget::onDialogMoved(int movedFrom, int movedTo) { Widget::~Widget() { cancelSearchRequest(); + + // Destructor may hide the bar and attempt to double-destroy it. + base::take(_downloadBar); } } // namespace Dialogs diff --git a/Telegram/SourceFiles/ui/controls/download_bar.cpp b/Telegram/SourceFiles/ui/controls/download_bar.cpp index a4af3c1b7a..8b59665292 100644 --- a/Telegram/SourceFiles/ui/controls/download_bar.cpp +++ b/Telegram/SourceFiles/ui/controls/download_bar.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" +#include "ui/image/image_prepare.h" #include "lang/lang_keys.h" #include "styles/style_dialogs.h" @@ -18,6 +19,17 @@ 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( @@ -71,6 +83,7 @@ void DownloadBar::show(DownloadBarContent &&content) { _finished ? 1. : 0., st::widgetFadeDuration); } + refreshThumbnail(); _title.setMarkedText( st::defaultTextStyle, (content.count > 1 @@ -80,19 +93,47 @@ void DownloadBar::show(DownloadBarContent &&content) { 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() { - _documentIconOriginal = st::downloadIconDocument.instance( + _documentIconLarge = st::downloadIconDocument.instance( st::windowFgActive->c, style::kScaleMax / style::DevicePixelRatio()); - const auto make = [&](int size) { - auto result = _documentIconOriginal.scaledToWidth( - size * style::DevicePixelRatio(), - Qt::SmoothTransformation); - result.setDevicePixelRatio(style::DevicePixelRatio()); - return result; - }; - _documentIcon = make(st::downloadIconSize); - _documentIconDone = make(st::downloadIconSizeDone); + _documentIcon = Make(_documentIconLarge, st::downloadIconSize); + _documentIconDone = Make(_documentIconLarge, st::downloadIconSizeDone); } void DownloadBar::refreshInfo(const DownloadBarProgress &progress) { @@ -156,52 +197,63 @@ void DownloadBar::paint(Painter &p, QRect clip) { 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); - const auto shift = st::downloadLoadingLine - + (1. - finished) * (added - st::downloadLoadingLine); - p.drawEllipse(QRectF(full).marginsRemoved( - { shift, shift, shift, shift })); - - if (loading.shown > 0) { - 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.); + 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); } - } - const auto sizeLoading = st::downloadIconSize; - if (finished == 0. || finished == 1.) { - const auto size = (finished == 1.) - ? st::downloadIconSizeDone - : sizeLoading; + } else if (finished == 0. || done) { p.drawImage( - full.x() + (full.width() - size) / 2, - full.y() + (full.height() - size) / 2, - (finished == 1.) ? _documentIconDone : _documentIcon); + base::SafeRound(ellipse.x()), + base::SafeRound(ellipse.y()), + done ? _thumbnailDone : _thumbnail); } 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), - _documentIconOriginal); + p.drawImage(ellipse, _thumbnailLarge); } } diff --git a/Telegram/SourceFiles/ui/controls/download_bar.h b/Telegram/SourceFiles/ui/controls/download_bar.h index 50952a372a..b0b9f6a4e7 100644 --- a/Telegram/SourceFiles/ui/controls/download_bar.h +++ b/Telegram/SourceFiles/ui/controls/download_bar.h @@ -50,6 +50,7 @@ public: private: void paint(Painter &p, QRect clip); void refreshIcon(); + void refreshThumbnail(); void refreshInfo(const DownloadBarProgress &progress); void radialAnimationCallback(crl::time now); [[nodiscard]] float64 computeProgress() const; @@ -60,9 +61,13 @@ private: rpl::variable _progress; Ui::Animations::Simple _finishedAnimation; bool _finished = false; - QImage _documentIconOriginal; + QImage _documentIconLarge; QImage _documentIcon; QImage _documentIconDone; + qint64 _thumbnailCacheKey = 0; + QImage _thumbnailLarge; + QImage _thumbnail; + QImage _thumbnailDone; Text::String _title; Text::String _info; RadialAnimation _radial;