Added simple radial upload progress overlay to userpic widgets.

This commit is contained in:
23rd
2026-04-02 12:17:58 +03:00
parent 6769944e16
commit 2119dae0b5
12 changed files with 549 additions and 21 deletions
+108 -17
View File
@@ -150,12 +150,37 @@ PeerPhoto::PeerPhoto(not_null<ApiWrap*> api)
: _session(&api->session())
, _api(&api->instance()) {
crl::on_main(_session, [=] {
auto &uploader = _session->uploader();
// You can't use _session->lifetime() in the constructor,
// only queued, because it is not constructed yet.
_session->uploader().photoReady(
uploader.photoReady(
) | rpl::on_next([=](const Storage::UploadedMedia &data) {
ready(data.fullId, data.info.file, std::nullopt);
}, _session->lifetime());
uploader.photoProgress(
) | rpl::on_next([=](const FullMsgId &id) {
const auto i = _uploads.find(id);
if (i == end(_uploads) || !i->second.photoId) {
return;
}
const auto peer = i->second.peer;
const auto photo = _session->data().photo(
i->second.photoId);
_uploadProgress.fire({ peer, photo->progress() });
}, _session->lifetime());
uploader.photoFailed(
) | rpl::on_next([=](const FullMsgId &id) {
const auto i = _uploads.find(id);
if (i == end(_uploads)) {
return;
}
const auto peer = i->second.peer;
_uploads.erase(i);
_uploadFailed.fire_copy(peer);
}, _session->lifetime());
});
}
@@ -222,17 +247,18 @@ void PeerPhoto::upload(
_session->uploader().cancel(already->first);
_uploads.erase(already);
}
_uploads.emplace(
const auto &[it, ok] = _uploads.emplace(
fakeId,
UploadValue{ peer, type, std::move(done) });
UploadValue{ peer, type, std::move(done), PhotoId(0) });
if (mtpMarkup) {
ready(fakeId, std::nullopt, mtpMarkup);
} else {
const auto ready = PreparePeerPhoto(
const auto prepared = PreparePeerPhoto(
_api.instance().mainDcId(),
peer->id,
base::take(photo.image));
_session->uploader().upload(fakeId, ready);
it->second.photoId = prepared->thumbId;
_session->uploader().upload(fakeId, prepared);
}
}
@@ -240,6 +266,68 @@ void PeerPhoto::suggest(not_null<PeerData*> peer, UserPhoto &&photo) {
upload(peer, std::move(photo), UploadType::Suggestion, nullptr);
}
void PeerPhoto::subscribeToUpload(
not_null<PeerData*> peer,
rpl::lifetime &lifetime,
UploadCallbacks callbacks) {
uploadProgress(
) | rpl::filter([=](const UploadProgress &data) {
return (data.peer == peer);
}) | rpl::on_next([cb = callbacks.progress](const UploadProgress &data) {
if (cb) {
cb(data.progress);
}
}, lifetime);
uploadDone(
) | rpl::filter([=](not_null<PeerData*> p) {
return (p == peer);
}) | rpl::on_next([cb = callbacks.done](not_null<PeerData*>) {
if (cb) {
cb();
}
}, lifetime);
uploadFailed(
) | rpl::filter([=](not_null<PeerData*> p) {
return (p == peer);
}) | rpl::on_next([cb = callbacks.failed](not_null<PeerData*>) {
if (cb) {
cb();
}
}, lifetime);
}
auto PeerPhoto::uploadProgress() const
-> rpl::producer<UploadProgress> {
return _uploadProgress.events();
}
auto PeerPhoto::uploadDone() const
-> rpl::producer<not_null<PeerData*>> {
return _uploadDone.events();
}
auto PeerPhoto::uploadFailed() const
-> rpl::producer<not_null<PeerData*>> {
return _uploadFailed.events();
}
void PeerPhoto::cancelUpload(not_null<PeerData*> peer) {
peer = peer->migrateToOrMe();
const auto i = ranges::find(
_uploads,
peer,
[](const auto &pair) { return pair.second.peer; });
if (i == end(_uploads)) {
return;
}
const auto fakeId = i->first;
_uploads.erase(i);
_session->uploader().cancel(fakeId);
_uploadFailed.fire_copy(peer);
}
void PeerPhoto::clear(not_null<PhotoData*> photo) {
const auto self = _session->user();
if (self->userpicPhotoId() == photo->id) {
@@ -362,12 +450,19 @@ void PeerPhoto::ready(
const auto peer = maybeUploadValue->peer;
const auto type = maybeUploadValue->type;
const auto done = maybeUploadValue->done;
const auto applier = [=](const MTPUpdates &result) {
_session->updates().applyUpdates(result);
const auto finish = [=] {
_uploadDone.fire_copy(peer);
if (done) {
done();
}
};
const auto fail = [=](const MTP::Error &error) {
_uploadFailed.fire_copy(peer);
};
const auto applier = [=](const MTPUpdates &result) {
_session->updates().applyUpdates(result);
finish();
};
const auto botUserInput = [&] {
const auto user = peer->asUser();
return (user && user->botInfo && user->botInfo->canEditInformation)
@@ -400,10 +495,8 @@ void PeerPhoto::ready(
peerToUser(peer->id),
photoId));
}
if (done) {
done();
}
}).send();
finish();
}).fail(fail).send();
} else if (const auto chat = peer->asChat()) {
const auto history = _session->data().history(chat);
using Flag = MTPDinputChatUploadedPhoto::Flag;
@@ -417,7 +510,7 @@ void PeerPhoto::ready(
MTPInputFile(), // video
MTPdouble(), // video_start_ts
videoSize ? (*videoSize) : MTPVideoSize()) // video_emoji_markup
)).done(applier).afterRequest(history->sendRequestId).send();
)).done(applier).fail(fail).afterRequest(history->sendRequestId).send();
} else if (const auto channel = peer->asChannel()) {
using Flag = MTPDinputChatUploadedPhoto::Flag;
const auto none = MTPDinputChatUploadedPhoto::Flags(0);
@@ -431,7 +524,7 @@ void PeerPhoto::ready(
MTPInputFile(), // video
MTPdouble(), // video_start_ts
videoSize ? (*videoSize) : MTPVideoSize()) // video_emoji_markup
)).done(applier).afterRequest(history->sendRequestId).send();
)).done(applier).fail(fail).afterRequest(history->sendRequestId).send();
} else if (const auto user = peer->asUser()) {
using Flag = MTPphotos_UploadContactProfilePhoto::Flag;
const auto none = MTPphotos_UploadContactProfilePhoto::Flags(0);
@@ -454,10 +547,8 @@ void PeerPhoto::ready(
if (type != UploadType::Suggestion) {
user->updateFullForced();
}
if (done) {
done();
}
}).send();
finish();
}).fail(fail).send();
}
}
+27
View File
@@ -41,6 +41,11 @@ public:
std::vector<QColor> markupColors;
};
struct UploadProgress {
not_null<PeerData*> peer;
float64 progress = 0.;
};
void upload(
not_null<PeerData*> peer,
UserPhoto &&photo,
@@ -55,6 +60,24 @@ public:
void clearPersonal(not_null<UserData*> user);
void set(not_null<PeerData*> peer, not_null<PhotoData*> photo);
struct UploadCallbacks {
Fn<void(float64)> progress;
Fn<void()> done;
Fn<void()> failed;
};
void subscribeToUpload(
not_null<PeerData*> peer,
rpl::lifetime &lifetime,
UploadCallbacks callbacks);
[[nodiscard]] auto uploadProgress() const
-> rpl::producer<UploadProgress>;
[[nodiscard]] auto uploadDone() const
-> rpl::producer<not_null<PeerData*>>;
[[nodiscard]] auto uploadFailed() const
-> rpl::producer<not_null<PeerData*>>;
void cancelUpload(not_null<PeerData*> peer);
void requestUserPhotos(not_null<UserData*> user, UserPhotoId afterId);
void requestEmojiList(EmojiListType type);
@@ -100,9 +123,13 @@ private:
not_null<PeerData*> peer;
UploadType type = UploadType::Default;
Fn<void()> done;
PhotoId photoId = 0;
};
base::flat_map<FullMsgId, UploadValue> _uploads;
rpl::event_stream<UploadProgress> _uploadProgress;
rpl::event_stream<not_null<PeerData*>> _uploadDone;
rpl::event_stream<not_null<PeerData*>> _uploadFailed;
base::flat_map<not_null<UserData*>, mtpRequestId> _userPhotosRequests;
+5
View File
@@ -23,6 +23,8 @@ UserpicButton {
uploadBg: color;
uploadIcon: icon;
uploadIconPosition: point;
uploadProgressLine: pixels;
uploadProgressMargin: pixels;
}
UserpicsRow {
button: UserpicButton;
@@ -72,6 +74,8 @@ defaultUserpicButton: UserpicButton {
uploadBg: msgDateImgBgOver;
uploadIcon: defaultUploadUserpicIcon;
uploadIconPosition: point(-1px, 1px);
uploadProgressLine: 3px;
uploadProgressMargin: 8px;
}
uploadUserpicSize: 32px;
uploadUserpicButton: UserpicButton(defaultUserpicButton) {
@@ -81,6 +85,7 @@ uploadUserpicButton: UserpicButton(defaultUserpicButton) {
changeIconPosition: point(4px, 4px);
}
uploadUserpicButtonBorder: 2px;
userpicUploadCancel: icon {{ "history_file_cancel", historyFileThumbIconFg }};
restoreUserpicIcon: UserpicButton(defaultUserpicButton) {
size: size(22px, 22px);
photoSize: 22px;
@@ -70,6 +70,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/stars_rating.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/animations.h"
#include "ui/effects/upload_progress_overlay.h"
#include "ui/effects/outline_segments.h"
#include "ui/effects/round_checkbox.h"
#include "ui/empty_userpic.h"
@@ -1120,6 +1121,7 @@ void TopBar::setupUserpicButton(
_peer->session().api().peerPhoto().upload(
_peer,
std::move(result));
startUploadOverlay();
break;
case ChosenType::Suggest:
_peer->session().api().peerPhoto().suggest(
@@ -1280,7 +1282,9 @@ void TopBar::setupUserpicButton(
(*menu)->popup(QCursor::pos());
}
} else if (button == Qt::LeftButton) {
if (_topicIconView && _topic && _topic->iconId()) {
if (_uploadOverlay && _uploadOverlay->uploading()) {
_peer->session().api().peerPhoto().cancelUpload(_peer);
} else if (_topicIconView && _topic && _topic->iconId()) {
const auto document = _peer->owner().document(
_topic->iconId());
if (const auto sticker = document->sticker()) {
@@ -1321,6 +1325,46 @@ void TopBar::setupUserpicButton(
}, _userpicButton->lifetime());
}
void TopBar::startUploadOverlay() {
if (_uploadOverlay && _uploadOverlay->uploading()) {
return;
}
_waitingUserpicCloudLoad = true;
_uploadOverlay = std::make_unique<Ui::UploadProgressOverlay>(
this,
[=] { update(); });
_uploadOverlay->start();
_userpicButton->events(
) | rpl::filter([](not_null<QEvent*> e) {
return e->type() == QEvent::Enter
|| e->type() == QEvent::Leave;
}) | rpl::on_next([=](not_null<QEvent*> e) {
if (_uploadOverlay) {
_uploadOverlay->setOver(e->type() == QEvent::Enter);
}
}, _uploadLifetime);
const auto cleanup = [=] {
_uploadLifetime.destroy();
_uploadOverlay = nullptr;
};
_peer->session().api().peerPhoto().subscribeToUpload(
_peer,
_uploadLifetime,
{
.progress = [=](float64 value) {
_uploadOverlay->setProgress(value);
},
.done = [=] {
_uploadOverlay->stop(cleanup);
},
.failed = [=] {
_uploadOverlay->fail(cleanup);
},
});
}
void TopBar::setupUniqueBadgeTooltip() {
if (!_badge || _source == Source::Preview) {
return;
@@ -1738,7 +1782,11 @@ void TopBar::paintUserpic(QPainter &p, const QRect &geometry) {
}
}
const auto key = _peer->userpicUniqueKey(_userpicView);
if (_userpicUniqueKey != key) {
const auto overlayActive = _uploadOverlay && _uploadOverlay->shown();
const auto awaitingCloud = _waitingUserpicCloudLoad
&& !_peer->userpicCloudImage(_userpicView);
if (!overlayActive && !awaitingCloud && _userpicUniqueKey != key) {
_waitingUserpicCloudLoad = false;
_userpicUniqueKey = key;
const auto fullSize = st::infoProfileTopBarPhotoSize;
const auto scaled = fullSize * style::DevicePixelRatio();
@@ -1773,6 +1821,15 @@ void TopBar::paintUserpic(QPainter &p, const QRect &geometry) {
_cachedUserpic.setDevicePixelRatio(style::DevicePixelRatio());
}
p.drawImage(geometry, _cachedUserpic);
if (_uploadOverlay && _uploadOverlay->shown()) {
_uploadOverlay->paint(p, geometry, {
.lineWidth = st::defaultUserpicButton.uploadProgressLine,
.margin = st::defaultUserpicButton.uploadProgressMargin,
.progressFg = st::historyFileThumbRadialFg,
.overlayFg = st::songCoverOverlayFg,
.cancelIcon = &st::userpicUploadCancel,
});
}
}
void TopBar::paintEvent(QPaintEvent *e) {
@@ -32,6 +32,7 @@ class MultiPlayer;
} // namespace Lottie
namespace Ui {
class UploadProgressOverlay;
class VideoUserpicPlayer;
struct OutlineSegment;
namespace Text {
@@ -151,6 +152,7 @@ private:
not_null<Window::SessionController*> controller,
const Ui::Menu::MenuCallback &addAction);
void setupUserpicButton(not_null<Window::SessionController*> controller);
void startUploadOverlay();
void setupActions(not_null<Window::SessionController*> controller);
void setupButtons(
not_null<Window::SessionController*> controller,
@@ -252,6 +254,9 @@ private:
QImage _monoforumMask;
std::unique_ptr<Ui::VideoUserpicPlayer> _videoUserpicPlayer;
std::unique_ptr<TopicIconView> _topicIconView;
std::unique_ptr<Ui::UploadProgressOverlay> _uploadOverlay;
rpl::lifetime _uploadLifetime;
bool _waitingUserpicCloudLoad = false;
rpl::lifetime _userpicLoadingLifetime;
base::unique_qptr<Ui::IconButton> _close;
@@ -283,6 +283,7 @@ void SetupPhoto(
auto &image = chosen.image;
UpdatePhotoLocally(self, image);
photo->showCustom(base::duplicate(image));
const auto isMarkup = (chosen.markup.documentId != 0);
self->session().api().peerPhoto().upload(
self,
{
@@ -290,6 +291,9 @@ void SetupPhoto(
chosen.markup.documentId,
chosen.markup.colors,
});
if (!isMarkup) {
photo->showUploadProgress();
}
}, upload->lifetime());
const auto name = Ui::CreateChild<Ui::FlatLabel>(
@@ -188,6 +188,7 @@ Cover::Cover(
Ui::UserpicButton::ChosenImage chosen) {
auto &image = chosen.image;
_userpic->showCustom(base::duplicate(image));
const auto isMarkup = (chosen.markup.documentId != 0);
_user->session().api().peerPhoto().upload(
_user,
{
@@ -195,6 +196,9 @@ Cover::Cover(
chosen.markup.documentId,
chosen.markup.colors,
});
if (!isMarkup) {
_userpic->showUploadProgress();
}
});
_badge.setPremiumClickCallback([=] {
@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/userpic_button.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "ui/effects/upload_progress_overlay.h"
#include "api/api_user_privacy.h"
#include "base/call_delayed.h"
#include "boxes/edit_privacy_box.h"
@@ -30,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/ui_utility.h"
#include "editor/photo_editor_common.h"
#include "editor/photo_editor_layer_widget.h"
@@ -294,6 +297,10 @@ void UserpicButton::setClickHandlerByRole() {
}
void UserpicButton::choosePhotoLocally() {
if (_uploadOverlay && _uploadOverlay->uploading()) {
_peer->session().api().peerPhoto().cancelUpload(_peer);
return;
}
if (!_window) {
return;
} else if (const auto controller = _window->sessionController()) {
@@ -499,6 +506,10 @@ void UserpicButton::openPeerPhoto() {
Expects(_peer != nullptr);
Expects(_controller != nullptr);
if (_uploadOverlay && _uploadOverlay->uploading()) {
_peer->session().api().peerPhoto().cancelUpload(_peer);
return;
}
if (_changeOverlayEnabled && _cursorInChangeOverlay) {
choosePhotoLocally();
return;
@@ -612,7 +623,10 @@ void UserpicButton::paintEvent(QPaintEvent *e) {
p.translate(-photoLeft, -photoTop);
};
if (_role == Role::ChangePhoto || _role == Role::ChoosePhoto) {
const auto uploadShown = _uploadOverlay
&& _uploadOverlay->shown();
if (!uploadShown
&& (_role == Role::ChangePhoto || _role == Role::ChoosePhoto)) {
auto over = isOver() || isDown();
if (over) {
fillTranslatedShape(_userpicHasImage
@@ -633,7 +647,7 @@ void UserpicButton::paintEvent(QPaintEvent *e) {
photoTop + iconTop,
width());
}
} else if (_changeOverlayEnabled) {
} else if (!uploadShown && _changeOverlayEnabled) {
auto current = _changeOverlayShown.value(
(isOver() || isDown()) ? 1. : 0.);
auto barHeight = anim::interpolate(
@@ -665,6 +679,18 @@ void UserpicButton::paintEvent(QPaintEvent *e) {
}
}
}
if (uploadShown) {
_uploadOverlay->paint(p, QRect(photoPosition, Size(_st.photoSize)), {
.lineWidth = _st.uploadProgressLine,
.margin = _st.uploadProgressMargin,
.progressFg = st::historyFileThumbRadialFg,
.overlayFg = st::songCoverOverlayFg,
.cancelIcon = &st::userpicUploadCancel,
.roundRadius = useForumShape()
? (_st.photoSize * ForumUserpicRadiusMultiplier())
: 0.,
});
}
}
void UserpicButton::paintUserpicFrame(Painter &p, QPoint photoPosition) {
@@ -1055,6 +1081,14 @@ void UserpicButton::onStateChanged(
startChangeOverlayAnimation();
}
}
if (_uploadOverlay && _uploadOverlay->uploading()) {
const auto over = isOver() || isDown();
const auto wasOver = (was & StateFlag::Over)
|| (was & StateFlag::Down);
if (over != wasOver) {
_uploadOverlay->setOver(over);
}
}
}
void UserpicButton::showCustom(QImage &&image) {
@@ -1122,6 +1156,40 @@ rpl::producer<> UserpicButton::resetPersonalRequests() const {
return _resetPersonalRequests.events();
}
void UserpicButton::showUploadProgress() {
if (_uploadOverlay && _uploadOverlay->uploading()) {
return;
}
Expects(_peer != nullptr);
_uploadOverlay = std::make_unique<UploadProgressOverlay>(
this,
[=] { update(); });
_uploadOverlay->start();
_peer->session().api().peerPhoto().subscribeToUpload(
_peer,
_uploadLifetime,
{
.progress = [=](float64 value) {
_uploadOverlay->setProgress(value);
},
.done = [=] {
_uploadOverlay->stop([=] {
_uploadLifetime.destroy();
_uploadOverlay = nullptr;
});
},
.failed = [=] {
_uploadOverlay->fail([=] {
_uploadLifetime.destroy();
_uploadOverlay = nullptr;
showSource(Source::PeerPhoto);
});
},
});
}
void UserpicButton::fillShape(QPainter &p, QBrush brush) const {
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
@@ -40,6 +40,8 @@ class ItemBase;
namespace Ui {
class UploadProgressOverlay;
class PopupMenu;
class UserpicButton final : public RippleButton {
@@ -111,6 +113,8 @@ public:
void showSource(Source source);
void showCustomOnChosen();
void showUploadProgress();
[[nodiscard]] PopupMenu *showChangePhotoMenu();
void overrideHasPersonalPhoto(bool has);
@@ -202,6 +206,9 @@ private:
rpl::event_stream<> _resetPersonalRequests;
rpl::lifetime _sourceLifetime;
std::unique_ptr<UploadProgressOverlay> _uploadOverlay;
rpl::lifetime _uploadLifetime;
};
[[nodiscard]] not_null<Ui::UserpicButton*> CreateUploadSubButton(
@@ -0,0 +1,198 @@
/*
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/effects/upload_progress_overlay.h"
#include "base/call_delayed.h"
#include "ui/effects/animation_value.h"
#include "ui/painter.h"
#include "styles/style_basic.h"
namespace Ui {
UploadProgressOverlay::UploadProgressOverlay(
not_null<QWidget*> parent,
Fn<void()> update)
: _parent(parent)
, _update(std::move(update))
, _radial([=](crl::time now) { radialAnimationCallback(now); }) {
}
void UploadProgressOverlay::start() {
_uploading = true;
_stopping = false;
_startedAt = crl::now();
_progress = 0.;
_hiding.stop();
_hidingDone = nullptr;
_radial.start(_progress);
_update();
}
void UploadProgressOverlay::stop(Fn<void()> done) {
if (!_uploading) {
if (done) {
done();
}
return;
}
_hidingDone = std::move(done);
_stopping = true;
_progress = 1.;
_update();
const auto catchUp = st::radialDuration * 2;
base::call_delayed(catchUp, _parent, [=] {
if (!_stopping) {
return;
}
proceedWithStop();
});
}
void UploadProgressOverlay::fail(Fn<void()> done) {
if (!_uploading) {
if (done) {
done();
}
return;
}
_hidingDone = std::move(done);
_stopping = true;
_progress = 0.;
_update();
const auto catchUp = st::radialDuration * 2;
base::call_delayed(catchUp, _parent, [=] {
if (!_stopping) {
return;
}
proceedWithStop();
});
}
void UploadProgressOverlay::proceedWithStop() {
_stopping = false;
_uploading = false;
const auto elapsed = crl::now() - _startedAt;
const auto minDuration = st::radialDuration * 3;
const auto delay = std::max(minDuration - elapsed, crl::time(0));
base::call_delayed(delay, _parent, [=] {
if (_uploading) {
return;
}
_hiding.start(
_update,
1.,
0.,
st::radialDuration);
_hiding.setFinishedCallback([=] {
_radial.stop();
const auto callback = base::take(_hidingDone);
_update();
if (callback) {
callback();
}
});
});
}
void UploadProgressOverlay::finish(Fn<void()> done) {
_uploading = false;
_update();
if (done) {
done();
}
}
void UploadProgressOverlay::setProgress(float64 progress) {
_progress = progress;
_update();
}
void UploadProgressOverlay::setOver(bool over) {
_over = over;
if (!_uploading) {
return;
}
_cancelShown.start(
_update,
over ? 0. : 1.,
over ? 1. : 0.,
st::universalDuration);
}
bool UploadProgressOverlay::shown() const {
return _uploading
|| _radial.animating()
|| _hiding.animating()
|| _hidingDone;
}
bool UploadProgressOverlay::uploading() const {
return _uploading && !_stopping;
}
void UploadProgressOverlay::paint(
QPainter &p,
QRect rect,
const PaintArgs &args) {
const auto hideOpacity = _hiding.animating()
? _hiding.value(0.)
: (_uploading || _hidingDone) ? 1. : 0.;
if (hideOpacity <= 0.) {
return;
}
auto o = p.opacity();
if (hideOpacity < 1.) {
p.setOpacity(o * hideOpacity);
}
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(args.overlayFg);
if (args.roundRadius > 0.) {
p.drawRoundedRect(rect, args.roundRadius, args.roundRadius);
} else {
p.drawEllipse(rect);
}
if (_uploading) {
const auto cancelOpacity = _cancelShown.value(
_over ? 1. : 0.);
if (cancelOpacity > 0. && args.cancelIcon) {
p.setOpacity(o * hideOpacity * cancelOpacity);
args.cancelIcon->paintInCenter(p, rect);
}
}
p.setOpacity(o * hideOpacity);
const auto line = float64(args.lineWidth);
const auto margin = float64(args.margin);
const auto arc = QRectF(rect) - QMarginsF(
margin,
margin,
margin,
margin);
_radial.draw(p, arc, line, args.progressFg);
p.setOpacity(o);
}
void UploadProgressOverlay::radialAnimationCallback(crl::time now) {
const auto updated = _radial.update(
_progress,
!_uploading,
now);
if (!anim::Disabled() || updated || _radial.animating()) {
_update();
}
}
} // namespace Ui
@@ -0,0 +1,60 @@
/*
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/effects/radial_animation.h"
#include "ui/effects/animations.h"
namespace Ui {
class UploadProgressOverlay final {
public:
struct PaintArgs {
int lineWidth = 3;
int margin = 8;
style::color progressFg;
style::color overlayFg;
const style::icon *cancelIcon = nullptr;
float64 roundRadius = 0.;
};
UploadProgressOverlay(
not_null<QWidget*> parent,
Fn<void()> update);
void start();
void stop(Fn<void()> done = nullptr);
void fail(Fn<void()> done = nullptr);
void setProgress(float64 progress);
void setOver(bool over);
[[nodiscard]] bool shown() const;
[[nodiscard]] bool uploading() const;
void paint(QPainter &p, QRect rect, const PaintArgs &args);
private:
void radialAnimationCallback(crl::time now);
void finish(Fn<void()> done);
void proceedWithStop();
const not_null<QWidget*> _parent;
const Fn<void()> _update;
RadialAnimation _radial;
Animations::Simple _cancelShown;
Animations::Simple _hiding;
Fn<void()> _hidingDone;
bool _uploading = false;
bool _stopping = false;
bool _over = false;
float64 _progress = 0.;
crl::time _startedAt = 0;
};
} // namespace Ui
+2
View File
@@ -494,6 +494,8 @@ PRIVATE
ui/effects/snowflakes.h
ui/effects/toggle_arrow.cpp
ui/effects/toggle_arrow.h
ui/effects/upload_progress_overlay.cpp
ui/effects/upload_progress_overlay.h
ui/effects/ttl_icon.cpp
ui/effects/ttl_icon.h
ui/search_field_controller.cpp