mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Initial implementation of download progress bar.
This commit is contained in:
parent
e8a7e86f41
commit
1a664edd39
9 changed files with 539 additions and 56 deletions
|
@ -17,10 +17,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_account.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "core/application.h"
|
||||
#include "ui/controls/download_bar.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kClearLoadingTimeout = 5 * crl::time(1000);
|
||||
|
||||
constexpr auto ByItem = [](const auto &entry) {
|
||||
if constexpr (std::is_same_v<decltype(entry), const DownloadingId&>) {
|
||||
return entry.object.item;
|
||||
|
@ -41,7 +45,9 @@ constexpr auto ByItem = [](const auto &entry) {
|
|||
|
||||
} // namespace
|
||||
|
||||
DownloadManager::DownloadManager() = default;
|
||||
DownloadManager::DownloadManager()
|
||||
: _clearLoadingTimer([=] { clearLoading(); }) {
|
||||
}
|
||||
|
||||
DownloadManager::~DownloadManager() = default;
|
||||
|
||||
|
@ -50,14 +56,14 @@ void DownloadManager::trackSession(not_null<Main::Session*> session) {
|
|||
|
||||
session->data().itemRepaintRequest(
|
||||
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return _downloading.contains(item);
|
||||
return _loading.contains(item);
|
||||
}) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
||||
check(item);
|
||||
}, data.lifetime);
|
||||
|
||||
session->data().itemLayoutChanged(
|
||||
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return _downloading.contains(item);
|
||||
return _loading.contains(item);
|
||||
}) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
||||
check(item);
|
||||
}, data.lifetime);
|
||||
|
@ -104,8 +110,13 @@ void DownloadManager::addLoading(DownloadObject object) {
|
|||
: object.photo->imageByteSize(PhotoSize::Large);
|
||||
|
||||
data.downloading.push_back({ .object = object, .total = size });
|
||||
_downloading.emplace(item);
|
||||
_loadingTotal += size;
|
||||
_loading.emplace(item);
|
||||
_loadingProgress = DownloadProgress{
|
||||
.ready = _loadingProgress.current().ready,
|
||||
.total = _loadingProgress.current().total + size,
|
||||
};
|
||||
_loadingListChanges.fire({});
|
||||
_clearLoadingTimer.cancel();
|
||||
|
||||
check(item);
|
||||
}
|
||||
|
@ -120,12 +131,7 @@ void DownloadManager::check(not_null<const HistoryItem*> item) {
|
|||
const auto photo = media ? media->photo() : nullptr;
|
||||
const auto document = media ? media->document() : nullptr;
|
||||
if (entry.object.photo != photo || entry.object.document != document) {
|
||||
if (const auto document = entry.object.document) {
|
||||
document->cancel();
|
||||
} else if (const auto photo = entry.object.photo) {
|
||||
photo->cancel();
|
||||
}
|
||||
remove(data, i);
|
||||
cancel(data, i);
|
||||
return;
|
||||
}
|
||||
// Load with progress only documents for now.
|
||||
|
@ -133,19 +139,23 @@ void DownloadManager::check(not_null<const HistoryItem*> item) {
|
|||
|
||||
const auto path = document->filepath(true);
|
||||
if (!path.isEmpty()) {
|
||||
addLoaded(entry.object, path, entry.started);
|
||||
if (_loading.contains(item)) {
|
||||
addLoaded(entry.object, path, entry.started);
|
||||
}
|
||||
} else if (!document->loading()) {
|
||||
remove(data, i);
|
||||
} else {
|
||||
const auto total = document->size;
|
||||
const auto ready = document->loadOffset();
|
||||
if (total == entry.total && ready == entry.ready) {
|
||||
const auto totalChange = document->size - entry.total;
|
||||
const auto readyChange = document->loadOffset() - entry.ready;
|
||||
if (!readyChange && !totalChange) {
|
||||
return;
|
||||
}
|
||||
_loadingTotal += (total - entry.total);
|
||||
_loadingReady += (ready - entry.ready);
|
||||
entry.total = total;
|
||||
entry.ready = ready;
|
||||
entry.ready += readyChange;
|
||||
entry.total += totalChange;
|
||||
_loadingProgress = DownloadProgress{
|
||||
.ready = _loadingProgress.current().ready + readyChange,
|
||||
.total = _loadingProgress.current().total + totalChange,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,18 +180,61 @@ void DownloadManager::addLoaded(
|
|||
.peerAccessHash = PeerAccessHash(item->history()->peer),
|
||||
.object = std::make_unique<DownloadObject>(object),
|
||||
});
|
||||
_downloaded.emplace(item);
|
||||
_loaded.emplace(item);
|
||||
|
||||
const auto i = ranges::find(data.downloading, item, ByItem);
|
||||
if (i != end(data.downloading)) {
|
||||
auto &entry = *i;
|
||||
const auto size = entry.total;
|
||||
remove(data, i);
|
||||
if (_downloading.empty()) {
|
||||
Assert(_loadingTotal == 0 && _loadingReady == 0);
|
||||
_loadedTotal = 0;
|
||||
} else {
|
||||
_loadedTotal += size;
|
||||
const auto j = _loading.find(entry.object.item);
|
||||
if (j == end(_loading)) {
|
||||
return;
|
||||
}
|
||||
const auto document = entry.object.document;
|
||||
const auto totalChange = document->size - entry.total;
|
||||
const auto readyChange = document->size - entry.ready;
|
||||
entry.ready += readyChange;
|
||||
entry.total += totalChange;
|
||||
entry.done = true;
|
||||
_loading.erase(j);
|
||||
_loadingDone.emplace(entry.object.item);
|
||||
_loadingProgress = DownloadProgress{
|
||||
.ready = _loadingProgress.current().ready + readyChange,
|
||||
.total = _loadingProgress.current().total + totalChange,
|
||||
};
|
||||
if (_loading.empty()) {
|
||||
_clearLoadingTimer.callOnce(kClearLoadingTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto DownloadManager::loadingList() const
|
||||
-> ranges::any_view<DownloadingId, ranges::category::input> {
|
||||
return ranges::views::all(
|
||||
_sessions
|
||||
) | ranges::views::transform([=](const auto &pair) {
|
||||
return ranges::views::all(pair.second.downloading);
|
||||
}) | ranges::views::join;
|
||||
}
|
||||
|
||||
DownloadProgress DownloadManager::loadingProgress() const {
|
||||
return _loadingProgress.current();
|
||||
}
|
||||
|
||||
rpl::producer<> DownloadManager::loadingListChanges() const {
|
||||
return _loadingListChanges.events();
|
||||
}
|
||||
|
||||
auto DownloadManager::loadingProgressValue() const
|
||||
-> rpl::producer<DownloadProgress> {
|
||||
return _loadingProgress.value();
|
||||
}
|
||||
|
||||
void DownloadManager::clearLoading() {
|
||||
Expects(_loading.empty());
|
||||
|
||||
for (auto &[session, data] : _sessions) {
|
||||
while (!data.downloading.empty()) {
|
||||
remove(data, data.downloading.end() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,16 +242,34 @@ void DownloadManager::addLoaded(
|
|||
void DownloadManager::remove(
|
||||
SessionData &data,
|
||||
std::vector<DownloadingId>::iterator i) {
|
||||
_loadingTotal -= i->total;
|
||||
_loadingReady -= i->ready;
|
||||
_downloading.remove(i->object.item);
|
||||
const auto now = DownloadProgress{
|
||||
.ready = _loadingProgress.current().ready - i->ready,
|
||||
.total = _loadingProgress.current().total - i->total,
|
||||
};
|
||||
_loading.remove(i->object.item);
|
||||
_loadingDone.remove(i->object.item);
|
||||
data.downloading.erase(i);
|
||||
_loadingListChanges.fire({});
|
||||
_loadingProgress = now;
|
||||
if (_loading.empty() && !_loadingDone.empty()) {
|
||||
_clearLoadingTimer.callOnce(kClearLoadingTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadManager::cancel(
|
||||
SessionData &data,
|
||||
std::vector<DownloadingId>::iterator i) {
|
||||
const auto object = i->object;
|
||||
remove(data, i);
|
||||
if (const auto document = object.document) {
|
||||
document->cancel();
|
||||
} else if (const auto photo = object.photo) {
|
||||
photo->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadManager::changed(not_null<const HistoryItem*> item) {
|
||||
if (_downloading.contains(item)) {
|
||||
check(item);
|
||||
} else if (_downloaded.contains(item)) {
|
||||
if (_loaded.contains(item)) {
|
||||
auto &data = sessionData(item);
|
||||
const auto i = ranges::find(data.downloaded, item, ByItem);
|
||||
Assert(i != end(data.downloaded));
|
||||
|
@ -209,13 +280,24 @@ void DownloadManager::changed(not_null<const HistoryItem*> item) {
|
|||
if (i->object->photo != photo || i->object->document != document) {
|
||||
*i->object = DownloadObject();
|
||||
|
||||
_downloaded.remove(item);
|
||||
_loaded.remove(item);
|
||||
}
|
||||
}
|
||||
if (_loading.contains(item) || _loadingDone.contains(item)) {
|
||||
check(item);
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadManager::removed(not_null<const HistoryItem*> item) {
|
||||
if (_downloading.contains(item)) {
|
||||
if (_loaded.contains(item)) {
|
||||
auto &data = sessionData(item);
|
||||
const auto i = ranges::find(data.downloaded, item, ByItem);
|
||||
Assert(i != end(data.downloaded));
|
||||
*i->object = DownloadObject();
|
||||
|
||||
_loaded.remove(item);
|
||||
}
|
||||
if (_loading.contains(item) || _loadingDone.contains(item)) {
|
||||
auto &data = sessionData(item);
|
||||
const auto i = ranges::find(data.downloading, item, ByItem);
|
||||
Assert(i != end(data.downloading));
|
||||
|
@ -224,19 +306,7 @@ void DownloadManager::removed(not_null<const HistoryItem*> item) {
|
|||
// We don't want to download files without messages.
|
||||
// For example, there is no way to refresh a file reference for them.
|
||||
//entry.object.item = nullptr;
|
||||
if (const auto document = entry.object.document) {
|
||||
document->cancel();
|
||||
} else if (const auto photo = entry.object.photo) {
|
||||
photo->cancel();
|
||||
}
|
||||
remove(data, i);
|
||||
} else if (_downloaded.contains(item)) {
|
||||
auto &data = sessionData(item);
|
||||
const auto i = ranges::find(data.downloaded, item, ByItem);
|
||||
Assert(i != end(data.downloaded));
|
||||
*i->object = DownloadObject();
|
||||
|
||||
_downloaded.remove(item);
|
||||
cancel(data, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,7 +329,7 @@ void DownloadManager::untrack(not_null<Main::Session*> session) {
|
|||
for (const auto &entry : i->second.downloaded) {
|
||||
if (const auto resolved = entry.object.get()) {
|
||||
if (const auto item = resolved->item) {
|
||||
_downloaded.remove(item);
|
||||
_loaded.remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -269,4 +339,36 @@ void DownloadManager::untrack(not_null<Main::Session*> session) {
|
|||
_sessions.erase(i);
|
||||
}
|
||||
|
||||
rpl::producer<Ui::DownloadBarProgress> MakeDownloadBarProgress() {
|
||||
return Core::App().downloadManager().loadingProgressValue(
|
||||
) | rpl::map([=](const DownloadProgress &progress) {
|
||||
return Ui::DownloadBarProgress{
|
||||
.ready = progress.ready,
|
||||
.total = progress.total,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<Ui::DownloadBarContent> 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.isEmpty()) {
|
||||
result.singleName = id.object.document->filename();
|
||||
result.singleThumbnail = QImage();
|
||||
}
|
||||
++result.count;
|
||||
if (id.done) {
|
||||
++result.done;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -7,6 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace Ui {
|
||||
struct DownloadBarProgress;
|
||||
struct DownloadBarContent;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
@ -26,6 +33,16 @@ struct DownloadId {
|
|||
DownloadType type = DownloadType::Document;
|
||||
};
|
||||
|
||||
struct DownloadProgress {
|
||||
int64 ready = 0;
|
||||
int64 total = 0;
|
||||
};
|
||||
inline constexpr bool operator==(
|
||||
const DownloadProgress &a,
|
||||
const DownloadProgress &b) {
|
||||
return (a.ready == b.ready) && (a.total == b.total);
|
||||
}
|
||||
|
||||
struct DownloadObject {
|
||||
HistoryItem *item = nullptr;
|
||||
DocumentData *document = nullptr;
|
||||
|
@ -48,6 +65,7 @@ struct DownloadingId {
|
|||
DownloadDate started = 0;
|
||||
int ready = 0;
|
||||
int total = 0;
|
||||
bool done = false;
|
||||
};
|
||||
|
||||
class DownloadManager final {
|
||||
|
@ -63,6 +81,13 @@ public:
|
|||
const QString &path,
|
||||
DownloadDate started);
|
||||
|
||||
[[nodiscard]] auto loadingList() const
|
||||
-> ranges::any_view<DownloadingId, ranges::category::input>;
|
||||
[[nodiscard]] DownloadProgress loadingProgress() const;
|
||||
[[nodiscard]] rpl::producer<> loadingListChanges() const;
|
||||
[[nodiscard]] auto loadingProgressValue() const
|
||||
-> rpl::producer<DownloadProgress>;
|
||||
|
||||
private:
|
||||
struct SessionData {
|
||||
std::vector<DownloadedId> downloaded;
|
||||
|
@ -77,19 +102,30 @@ private:
|
|||
void remove(
|
||||
SessionData &data,
|
||||
std::vector<DownloadingId>::iterator i);
|
||||
void cancel(
|
||||
SessionData &data,
|
||||
std::vector<DownloadingId>::iterator i);
|
||||
void clearLoading();
|
||||
|
||||
[[nodiscard]] SessionData &sessionData(not_null<Main::Session*> session);
|
||||
[[nodiscard]] SessionData &sessionData(
|
||||
not_null<const HistoryItem*> item);
|
||||
|
||||
base::flat_map<not_null<Main::Session*>, SessionData> _sessions;
|
||||
base::flat_set<not_null<const HistoryItem*>> _downloading;
|
||||
base::flat_set<not_null<const HistoryItem*>> _downloaded;
|
||||
base::flat_set<not_null<const HistoryItem*>> _loading;
|
||||
base::flat_set<not_null<const HistoryItem*>> _loadingDone;
|
||||
base::flat_set<not_null<const HistoryItem*>> _loaded;
|
||||
|
||||
int64 _loadedTotal = 0;
|
||||
int64 _loadingReady = 0;
|
||||
int64 _loadingTotal = 0;
|
||||
rpl::event_stream<> _loadingListChanges;
|
||||
rpl::variable<DownloadProgress> _loadingProgress;
|
||||
|
||||
base::Timer _clearLoadingTimer;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] auto MakeDownloadBarProgress()
|
||||
-> rpl::producer<Ui::DownloadBarProgress>;
|
||||
|
||||
[[nodiscard]] rpl::producer<Ui::DownloadBarContent> MakeDownloadBarContent();
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -309,3 +309,22 @@ dialogsUnreadMentionActive: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgAct
|
|||
dialogsUnreadReaction: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFg }};
|
||||
dialogsUnreadReactionOver: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgOver }};
|
||||
dialogsUnreadReactionActive: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgActive }};
|
||||
|
||||
downloadBarHeight: 46px;
|
||||
downloadArrow: icon{{ "fast_to_original", menuIconFg }};
|
||||
downloadArrowOver: icon{{ "fast_to_original", menuIconFgOver }};
|
||||
downloadArrowRight: 10px;
|
||||
downloadTitleLeft: 57px;
|
||||
downloadTitleTop: 4px;
|
||||
downloadInfoStyle: TextStyle(defaultTextStyle) {
|
||||
font: font(12px);
|
||||
linkFont: font(12px);
|
||||
linkFontOver: font(12px underline);
|
||||
}
|
||||
downloadInfoLeft: 57px;
|
||||
downloadInfoTop: 23px;
|
||||
downloadLoadingLeft: 15px;
|
||||
downloadLoadingSize: 24px;
|
||||
downloadLoadingLine: 2px;
|
||||
downloadLoadedSize: 30px;
|
||||
downloadIconDocument: icon {{ "history_file_document", windowFgActive }};
|
||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/controls/download_bar.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
|
@ -46,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_folder.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_download_manager.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
@ -361,6 +363,8 @@ Widget::Widget(
|
|||
) | rpl::start_with_next([=](Data::Folder *folder) {
|
||||
changeOpenedFolder(folder, anim::type::normal);
|
||||
}, lifetime());
|
||||
|
||||
setupDownloadBar();
|
||||
}
|
||||
|
||||
void Widget::setGeometryWithTopMoved(
|
||||
|
@ -399,6 +403,47 @@ void Widget::setupScrollUpButton() {
|
|||
updateScrollUpVisibility();
|
||||
}
|
||||
|
||||
void Widget::setupDownloadBar() {
|
||||
Data::MakeDownloadBarContent(
|
||||
) | rpl::start_with_next([=](Ui::DownloadBarContent &&content) {
|
||||
const auto create = (content.count && !_downloadBar);
|
||||
if (create) {
|
||||
_downloadBar = std::make_unique<Ui::DownloadBar>(
|
||||
this,
|
||||
Data::MakeDownloadBarProgress());
|
||||
}
|
||||
if (_downloadBar) {
|
||||
_downloadBar->show(std::move(content));
|
||||
}
|
||||
if (create) {
|
||||
_downloadBar->heightValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateControlsGeometry();
|
||||
}, _downloadBar->lifetime());
|
||||
|
||||
_downloadBar->shownValue(
|
||||
) | rpl::filter(
|
||||
!rpl::mappers::_1
|
||||
) | rpl::start_with_next([=] {
|
||||
_downloadBar = nullptr;
|
||||
}, _downloadBar->lifetime());
|
||||
|
||||
_downloadBar->clicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto &&list = Core::App().downloadManager().loadingList();
|
||||
for (const auto &id : list) {
|
||||
controller()->showPeerHistoryAtItem(id.object.item);
|
||||
break;
|
||||
}
|
||||
}, _downloadBar->lifetime());
|
||||
|
||||
if (_connecting) {
|
||||
_connecting->raise();
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void Widget::updateScrollUpVisibility() {
|
||||
if (_scrollToAnimation.animating()) {
|
||||
return;
|
||||
|
@ -1632,7 +1677,7 @@ void Widget::updateControlsGeometry() {
|
|||
auto scrollTop = filterAreaTop + filterAreaHeight;
|
||||
auto newScrollTop = _scroll->scrollTop() + _topDelta;
|
||||
auto scrollHeight = height() - scrollTop;
|
||||
const auto putBottomButton = [&](object_ptr<BottomButton> &button) {
|
||||
const auto putBottomButton = [&](auto &button) {
|
||||
if (button && !button->isHidden()) {
|
||||
const auto buttonHeight = button->height();
|
||||
scrollHeight -= buttonHeight;
|
||||
|
@ -1644,6 +1689,7 @@ void Widget::updateControlsGeometry() {
|
|||
}
|
||||
};
|
||||
putBottomButton(_updateTelegram);
|
||||
putBottomButton(_downloadBar);
|
||||
putBottomButton(_loadMoreChats);
|
||||
auto wasScrollHeight = _scroll->height();
|
||||
_scroll->setGeometry(0, scrollTop, width(), scrollHeight);
|
||||
|
|
|
@ -36,6 +36,7 @@ class DropdownMenu;
|
|||
class FlatButton;
|
||||
class FlatInput;
|
||||
class CrossButton;
|
||||
class DownloadBar;
|
||||
template <typename Widget>
|
||||
class FadeWrapScaled;
|
||||
} // namespace Ui
|
||||
|
@ -148,6 +149,7 @@ private:
|
|||
void setupSupportMode();
|
||||
void setupConnectingWidget();
|
||||
void setupMainMenuToggle();
|
||||
void setupDownloadBar();
|
||||
bool searchForPeersRequired(const QString &query) const;
|
||||
void setSearchInChat(Key chat, PeerData *from = nullptr);
|
||||
void showCalendar();
|
||||
|
@ -204,6 +206,7 @@ private:
|
|||
class BottomButton;
|
||||
object_ptr<BottomButton> _updateTelegram = { nullptr };
|
||||
object_ptr<BottomButton> _loadMoreChats = { nullptr };
|
||||
std::unique_ptr<Ui::DownloadBar> _downloadBar;
|
||||
std::unique_ptr<Window::ConnectionState> _connecting;
|
||||
|
||||
Ui::Animations::Simple _scrollToAnimation;
|
||||
|
|
209
Telegram/SourceFiles/ui/controls/download_bar.cpp
Normal file
209
Telegram/SourceFiles/ui/controls/download_bar.cpp
Normal file
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
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 "lang/lang_keys.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kFullArcLength = 360 * 16;
|
||||
|
||||
} // 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());
|
||||
|
||||
_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;
|
||||
_title.setText(st::semiboldTextStyle,
|
||||
(content.count > 1
|
||||
? tr::lng_profile_files(tr::now, lt_count, content.count)
|
||||
: content.singleName));
|
||||
refreshInfo(_progress.current());
|
||||
}
|
||||
|
||||
void DownloadBar::refreshInfo(const DownloadBarProgress &progress) {
|
||||
_info.setMarkedText(st::downloadInfoStyle,
|
||||
(progress.ready < progress.total
|
||||
? Text::WithEntities(
|
||||
FormatDownloadText(progress.ready, progress.total))
|
||||
: (_content.count > 1)
|
||||
? Text::Link(u"View downloads"_q)
|
||||
: Text::Link(u"View in chat"_q)));
|
||||
_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 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 loading = _radial.computeState();
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::windowBgActive);
|
||||
p.drawEllipse(full.marginsRemoved({ added, added, added, added }));
|
||||
|
||||
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.save();
|
||||
p.translate(full.center());
|
||||
p.scale(0.6, 0.6);
|
||||
p.translate(-full.center());
|
||||
st::downloadIconDocument.paintInCenter(p, full);
|
||||
p.restore();
|
||||
}
|
||||
|
||||
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
|
66
Telegram/SourceFiles/ui/controls/download_bar.h
Normal file
66
Telegram/SourceFiles/ui/controls/download_bar.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/text/text.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RippleButton;
|
||||
|
||||
struct DownloadBarProgress {
|
||||
int64 ready = 0;
|
||||
int64 total = 0;
|
||||
};
|
||||
|
||||
struct DownloadBarContent {
|
||||
QString singleName;
|
||||
QImage singleThumbnail;
|
||||
int count = 0;
|
||||
int done = 0;
|
||||
};
|
||||
|
||||
class DownloadBar final {
|
||||
public:
|
||||
DownloadBar(
|
||||
not_null<QWidget*> parent,
|
||||
rpl::producer<DownloadBarProgress> progress);
|
||||
~DownloadBar();
|
||||
|
||||
void show(DownloadBarContent &&content);
|
||||
|
||||
[[nodiscard]] bool isHidden() const;
|
||||
[[nodiscard]] int height() const;
|
||||
[[nodiscard]] rpl::producer<int> heightValue() const;
|
||||
[[nodiscard]] rpl::producer<bool> shownValue() const;
|
||||
void setGeometry(int left, int top, int width, int height);
|
||||
|
||||
[[nodiscard]] rpl::producer<> clicks() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
void paint(Painter &p, QRect clip);
|
||||
void refreshInfo(const DownloadBarProgress &progress);
|
||||
void radialAnimationCallback(crl::time now);
|
||||
[[nodiscard]] float64 computeProgress() const;
|
||||
|
||||
SlideWrap<RippleButton> _button;
|
||||
PlainShadow _shadow;
|
||||
DownloadBarContent _content;
|
||||
rpl::variable<DownloadBarProgress> _progress;
|
||||
Text::String _title;
|
||||
Text::String _info;
|
||||
RadialAnimation _radial;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
|
@ -192,6 +192,8 @@ PRIVATE
|
|||
ui/controls/call_mute_button.h
|
||||
ui/controls/delete_message_context_action.cpp
|
||||
ui/controls/delete_message_context_action.h
|
||||
ui/controls/download_bar.cpp
|
||||
ui/controls/download_bar.h
|
||||
ui/controls/emoji_button.cpp
|
||||
ui/controls/emoji_button.h
|
||||
ui/controls/invite_link_buttons.cpp
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 43c61172d8d2ef08c2c190d6b3425823727ea9c9
|
||||
Subproject commit 6316443b27cffbf0205e07edc0616d370044893c
|
Loading…
Add table
Reference in a new issue