tdesktop/Telegram/SourceFiles/ui/controls/userpic_button.cpp

1087 lines
28 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/userpic_button.h"
#include "base/call_delayed.h"
#include "ui/effects/ripple_animation.h"
#include "ui/empty_userpic.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_user.h"
#include "data/data_histories.h"
#include "data/data_streaming.h"
#include "data/data_file_origin.h"
#include "data/data_photo_media.h"
#include "history/history.h"
#include "calls/calls_instance.h"
#include "core/application.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/painter.h"
#include "editor/photo_editor_common.h"
#include "editor/photo_editor_layer_widget.h"
#include "info/userpic/info_userpic_emoji_builder_common.h"
#include "info/userpic/info_userpic_emoji_builder_menu_item.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_document.h"
#include "settings/settings_calls.h" // Calls::AddCameraSubsection.
#include "webrtc/webrtc_media_devices.h" // Webrtc::GetVideoInputList.
#include "webrtc/webrtc_video_track.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
namespace Ui {
namespace {
[[nodiscard]] bool IsCameraAvailable() {
return (Core::App().calls().currentCall() == nullptr)
&& !Webrtc::GetVideoInputList().empty();
}
void CameraBox(
not_null<Ui::GenericBox*> box,
not_null<Window::Controller*> controller,
PeerData *peer,
bool forceForumShape,
Fn<void(QImage &&image)> &&doneCallback) {
using namespace Webrtc;
const auto track = Settings::Calls::AddCameraSubsection(
box->uiShow(),
box->verticalLayout(),
false);
if (!track) {
box->closeBox();
return;
}
track->stateValue(
) | rpl::start_with_next([=](const VideoState &state) {
if (state == VideoState::Inactive) {
box->closeBox();
}
}, box->lifetime());
auto done = [=, done = std::move(doneCallback)]() mutable {
using namespace Editor;
auto callback = [=, done = std::move(done)](QImage &&image) {
box->closeBox();
done(std::move(image));
};
const auto useForumShape = forceForumShape
|| (peer && peer->isForum());
PrepareProfilePhoto(
box,
controller,
{
.confirm = tr::lng_profile_set_photo_button(tr::now),
.cropType = (useForumShape
? EditorData::CropType::RoundedRect
: EditorData::CropType::Ellipse),
.keepAspectRatio = true,
},
std::move(callback),
track->frame(FrameRequest()).mirrored(true, false));
};
box->setTitle(tr::lng_profile_camera_title());
box->addButton(tr::lng_continue(), std::move(done));
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
template <typename Callback>
QPixmap CreateSquarePixmap(int width, Callback &&paintCallback) {
auto size = QSize(width, width) * cIntRetinaFactor();
auto image = QImage(size, QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(cRetinaFactor());
image.fill(Qt::transparent);
{
Painter p(&image);
paintCallback(p);
}
return Ui::PixmapFromImage(std::move(image));
};
void SetupSubButtonBackground(
not_null<Ui::UserpicButton*> upload,
not_null<Ui::RpWidget*> background) {
const auto border = st::uploadUserpicButtonBorder;
const auto size = upload->rect().marginsAdded(
{ border, border, border, border }
).size();
background->resize(size);
background->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(background);
auto hq = PainterHighQualityEnabler(p);
p.setBrush(st::boxBg);
p.setPen(Qt::NoPen);
p.drawEllipse(background->rect());
}, background->lifetime());
upload->positionValue(
) | rpl::start_with_next([=](QPoint position) {
background->move(position - QPoint(border, border));
}, background->lifetime());
}
} // namespace
UserpicButton::UserpicButton(
QWidget *parent,
not_null<Window::Controller*> window,
Role role,
const style::UserpicButton &st,
bool forceForumShape)
: RippleButton(parent, st.changeButton.ripple)
, _st(st)
, _controller(window->sessionController())
, _window(window)
, _forceForumShape(forceForumShape)
, _role(role) {
Expects(_role == Role::ChangePhoto || _role == Role::ChoosePhoto);
showCustom({});
_waiting = false;
prepare();
}
UserpicButton::UserpicButton(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
Role role,
Source source,
const style::UserpicButton &st)
: RippleButton(parent, st.changeButton.ripple)
, _st(st)
, _controller(controller)
, _window(&controller->window())
, _peer(peer)
, _role(role)
, _source(source) {
if (_source == Source::Custom) {
showCustom({});
} else {
processPeerPhoto();
setupPeerViewers();
}
prepare();
}
UserpicButton::UserpicButton(
QWidget *parent,
not_null<PeerData*> peer,
const style::UserpicButton &st)
: RippleButton(parent, st.changeButton.ripple)
, _st(st)
, _peer(peer)
, _role(Role::Custom)
, _source(Source::PeerPhoto) {
Expects(_role != Role::OpenPhoto);
processPeerPhoto();
setupPeerViewers();
prepare();
}
UserpicButton::~UserpicButton() = default;
void UserpicButton::prepare() {
resize(_st.size);
_notShownYet = _waiting;
if (!_waiting) {
prepareUserpicPixmap();
}
setClickHandlerByRole();
}
void UserpicButton::showCustomOnChosen() {
chosenImages(
) | rpl::start_with_next([=](ChosenImage &&chosen) {
showCustom(std::move(chosen.image));
}, lifetime());
}
void UserpicButton::requestSuggestAvailability() {
if (const auto user = _peer ? _peer->asUser() : nullptr) {
if (!user->isSelf()) {
const auto history = user->owner().history(user);
if (!history->lastServerMessageKnown()) {
// Server allows suggesting photos only in non-empty chats.
user->owner().histories().requestDialogEntry(history);
}
}
}
}
bool UserpicButton::canSuggestPhoto(not_null<UserData*> user) const {
// Server allows suggesting photos only in non-empty chats.
return !user->isSelf()
&& !user->isBot()
&& (user->owner().history(user)->lastServerMessage() != nullptr);
}
bool UserpicButton::hasPersonalPhotoLocally() const {
if (const auto user = _peer->asUser()) {
return _overrideHasPersonalPhoto.value_or(user->hasPersonalPhoto());
}
return false;
}
void UserpicButton::setClickHandlerByRole() {
requestSuggestAvailability();
switch (_role) {
case Role::ChoosePhoto:
case Role::ChangePhoto:
addClickHandler([=] { choosePhotoLocally(); });
break;
case Role::OpenPhoto:
addClickHandler([=] { openPeerPhoto(); });
break;
}
}
void UserpicButton::choosePhotoLocally() {
if (!_window) {
return;
}
const auto callback = [=](ChosenType type) {
return [=](QImage &&image) {
_chosenImages.fire({ std::move(image), type });
};
};
const auto chooseFile = [=](ChosenType type = ChosenType::Set) {
base::call_delayed(
_st.changeButton.ripple.hideDuration,
crl::guard(this, [=] {
using namespace Editor;
const auto user = _peer ? _peer->asUser() : nullptr;
const auto name = (user && !user->firstName.isEmpty())
? user->firstName
: _peer
? _peer->name()
: QString();
const auto phrase = (type == ChosenType::Suggest)
? &tr::lng_profile_suggest_sure
: (user && !user->isSelf())
? &tr::lng_profile_set_personal_sure
: nullptr;
PrepareProfilePhotoFromFile(
this,
_window,
{
.about = (phrase
? (*phrase)(
tr::now,
lt_user,
Ui::Text::Bold(name),
Ui::Text::WithEntities)
: TextWithEntities()),
.confirm = ((type == ChosenType::Suggest)
? tr::lng_profile_suggest_button(tr::now)
: tr::lng_profile_set_photo_button(tr::now)),
.cropType = (useForumShape()
? EditorData::CropType::RoundedRect
: EditorData::CropType::Ellipse),
.keepAspectRatio = true,
},
callback(type));
}));
};
const auto user = _peer ? _peer->asUser() : nullptr;
const auto addUserpicBuilder = [&](ChosenType type) {
if (!_controller) {
return;
}
const auto done = [=](UserpicBuilder::Result data) {
auto result = ChosenImage{ base::take(data.image), type };
result.markup.documentId = data.id;
result.markup.colors = base::take(data.colors);
_chosenImages.fire(std::move(result));
};
UserpicBuilder::AddEmojiBuilderAction(
_controller,
_menu,
_controller->session().api().peerPhoto().emojiListValue(user
? Api::PeerPhoto::EmojiListType::Profile
: Api::PeerPhoto::EmojiListType::Group),
done,
_peer ? _peer->isForum() : false);
};
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
st::popupMenuWithIcons);
if (user && !user->isSelf()) {
_menu->addAction(
tr::lng_profile_set_photo_for(tr::now),
[=] { chooseFile(); },
&st::menuIconPhotoSet);
if (canSuggestPhoto(user)) {
_menu->addAction(
tr::lng_profile_suggest_photo(tr::now),
[=] { chooseFile(ChosenType::Suggest); },
&st::menuIconPhotoSuggest);
}
addUserpicBuilder(ChosenType::Set);
if (hasPersonalPhotoLocally()) {
_menu->addSeparator(&st::expandedMenuSeparator);
_menu->addAction(makeResetToOriginalAction());
}
} else {
const auto hasCamera = IsCameraAvailable();
if (hasCamera || _controller) {
_menu->addAction(tr::lng_attach_file(tr::now), [=] {
chooseFile();
}, &st::menuIconPhoto);
if (hasCamera) {
_menu->addAction(tr::lng_attach_camera(tr::now), [=] {
_window->show(Box(
CameraBox,
_window,
_peer,
_forceForumShape,
callback(ChosenType::Set)));
}, &st::menuIconPhotoSet);
}
addUserpicBuilder(ChosenType::Set);
} else {
chooseFile();
}
}
_menu->popup(QCursor::pos());
}
auto UserpicButton::makeResetToOriginalAction()
-> base::unique_qptr<Menu::ItemBase> {
auto item = base::make_unique_q<Menu::Action>(
_menu.get(),
_menu->st().menu,
Menu::CreateAction(
_menu.get(),
tr::lng_profile_photo_reset(tr::now),
[=] { _resetPersonalRequests.fire({}); }),
nullptr,
nullptr);
const auto icon = CreateChild<UserpicButton>(
item.get(),
_controller,
_peer,
Ui::UserpicButton::Role::Custom,
Ui::UserpicButton::Source::NonPersonalIfHasPersonal,
st::restoreUserpicIcon);
if (_source == Source::Custom) {
icon->showCustom(base::duplicate(_result));
}
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
icon->move(_menu->st().menu.itemIconPosition
+ QPoint(
(st::menuIconRemove.width() - icon->width()) / 2,
(st::menuIconRemove.height() - icon->height()) / 2));
return item;
}
void UserpicButton::openPeerPhoto() {
Expects(_peer != nullptr);
Expects(_controller != nullptr);
if (_changeOverlayEnabled && _cursorInChangeOverlay) {
choosePhotoLocally();
return;
}
const auto id = _peer->userpicPhotoId();
if (!id) {
return;
}
const auto photo = _peer->owner().photo(id);
if (photo->date && _controller) {
_controller->openPhoto(photo, _peer);
}
}
void UserpicButton::setupPeerViewers() {
const auto user = _peer->asUser();
if (user
&& (_source == Source::NonPersonalPhoto
|| _source == Source::NonPersonalIfHasPersonal)) {
user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::FullInfo
) | rpl::map([=] {
return std::pair(
user->session().api().peerPhoto().nonPersonalPhoto(user),
user->hasPersonalPhoto());
}) | rpl::distinct_until_changed() | rpl::skip(
1
) | rpl::start_with_next([=] {
processNewPeerPhoto();
update();
}, _sourceLifetime);
}
if (!user
|| _source == Source::PeerPhoto
|| _source == Source::NonPersonalIfHasPersonal) {
_peer->session().changes().peerUpdates(
_peer,
Data::PeerUpdate::Flag::Photo
) | rpl::start_with_next([=] {
processNewPeerPhoto();
update();
}, _sourceLifetime);
}
_peer->session().downloaderTaskFinished(
) | rpl::filter([=] {
return _waiting;
}) | rpl::start_with_next([=] {
const auto loading = _showPeerUserpic
? Ui::PeerUserpicLoading(_userpicView)
: (_nonPersonalView && !_nonPersonalView->loaded());
if (!loading) {
_waiting = false;
startNewPhotoShowing();
}
}, _sourceLifetime);
}
void UserpicButton::paintEvent(QPaintEvent *e) {
Painter p(this);
if (!_waiting && _notShownYet) {
_notShownYet = false;
startAnimation();
}
auto photoPosition = countPhotoPosition();
auto photoLeft = photoPosition.x();
auto photoTop = photoPosition.y();
if (showSavedMessages()) {
Ui::EmptyUserpic::PaintSavedMessages(
p,
photoPosition.x(),
photoPosition.y(),
width(),
_st.photoSize);
} else if (showRepliesMessages()) {
Ui::EmptyUserpic::PaintRepliesMessages(
p,
photoPosition.x(),
photoPosition.y(),
width(),
_st.photoSize);
} else {
if (_a_appearance.animating()) {
p.drawPixmapLeft(photoPosition, width(), _oldUserpic);
p.setOpacity(_a_appearance.value(1.));
}
paintUserpicFrame(p, photoPosition);
}
const auto fillTranslatedShape = [&](const style::color &color) {
p.translate(photoLeft, photoTop);
fillShape(p, color);
p.translate(-photoLeft, -photoTop);
};
if (_role == Role::ChangePhoto || _role == Role::ChoosePhoto) {
auto over = isOver() || isDown();
if (over) {
fillTranslatedShape(_userpicHasImage
? st::msgDateImgBg
: _st.changeButton.textBgOver);
}
paintRipple(
p,
photoLeft,
photoTop,
(_userpicHasImage
? &st::shadowFg->c
: &_st.changeButton.ripple.color->c));
if (over || !_userpicHasImage) {
auto iconLeft = (_st.changeIconPosition.x() < 0)
? (_st.photoSize - _st.changeIcon.width()) / 2
: _st.changeIconPosition.x();
auto iconTop = (_st.changeIconPosition.y() < 0)
? (_st.photoSize - _st.changeIcon.height()) / 2
: _st.changeIconPosition.y();
_st.changeIcon.paint(
p,
photoLeft + iconLeft,
photoTop + iconTop,
width());
}
} else if (_changeOverlayEnabled) {
auto current = _changeOverlayShown.value(
(isOver() || isDown()) ? 1. : 0.);
auto barHeight = anim::interpolate(
0,
_st.uploadHeight,
current);
if (barHeight > 0) {
auto barLeft = photoLeft;
auto barTop = photoTop + _st.photoSize - barHeight;
auto rect = QRect(
barLeft,
barTop,
_st.photoSize,
barHeight);
p.setClipRect(rect);
fillTranslatedShape(_st.uploadBg);
auto iconLeft = (_st.uploadIconPosition.x() < 0)
? (_st.photoSize - _st.uploadIcon.width()) / 2
: _st.uploadIconPosition.x();
auto iconTop = (_st.uploadIconPosition.y() < 0)
? (_st.uploadHeight - _st.uploadIcon.height()) / 2
: _st.uploadIconPosition.y();
if (iconTop < barHeight) {
_st.uploadIcon.paint(
p,
barLeft + iconLeft,
barTop + iconTop,
width());
}
}
}
}
void UserpicButton::paintUserpicFrame(Painter &p, QPoint photoPosition) {
checkStreamedIsStarted();
if (_streamed
&& _streamed->player().ready()
&& !_streamed->player().videoSize().isEmpty()) {
const auto paused = _controller
? _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::RoundPlaying)
: false;
auto request = Media::Streaming::FrameRequest();
auto size = QSize{ _st.photoSize, _st.photoSize };
const auto ratio = style::DevicePixelRatio();
request.outer = request.resize = size * ratio;
if (useForumShape()) {
const auto radius = int(_st.photoSize
* Ui::ForumUserpicRadiusMultiplier());
if (_roundingCorners[0].width() != radius * ratio) {
_roundingCorners = Images::CornersMask(radius);
}
request.rounding = Images::CornersMaskRef(_roundingCorners);
} else {
if (_ellipseMask.size() != request.outer) {
_ellipseMask = Images::EllipseMask(size);
}
request.mask = _ellipseMask;
}
p.drawImage(QRect(photoPosition, size), _streamed->frame(request));
if (!paused) {
_streamed->markFrameShown();
}
} else {
p.drawPixmapLeft(photoPosition, width(), _userpic);
}
}
QPoint UserpicButton::countPhotoPosition() const {
auto photoLeft = (_st.photoPosition.x() < 0)
? (width() - _st.photoSize) / 2
: _st.photoPosition.x();
auto photoTop = (_st.photoPosition.y() < 0)
? (height() - _st.photoSize) / 2
: _st.photoPosition.y();
return { photoLeft, photoTop };
}
QImage UserpicButton::prepareRippleMask() const {
return Ui::RippleAnimation::EllipseMask(QSize(
_st.photoSize,
_st.photoSize));
}
QPoint UserpicButton::prepareRippleStartPosition() const {
return (_role == Role::ChangePhoto)
? mapFromGlobal(QCursor::pos()) - countPhotoPosition()
: DisabledRippleStartPosition();
}
void UserpicButton::processPeerPhoto() {
Expects(_peer != nullptr);
const auto user = _peer->asUser();
const auto nonPersonal = (user && _source != Source::PeerPhoto)
? _peer->session().api().peerPhoto().nonPersonalPhoto(user)
: nullptr;
_showPeerUserpic = (_source == Source::PeerPhoto)
|| (user
&& !user->hasPersonalPhoto()
&& (_source == Source::NonPersonalPhoto
|| (_source == Source::NonPersonalIfHasPersonal
&& hasPersonalPhotoLocally())));
const auto showNonPersonal = _showPeerUserpic ? nullptr : nonPersonal;
_userpicView = _showPeerUserpic
? _peer->createUserpicView()
: PeerUserpicView();
_nonPersonalView = showNonPersonal
? showNonPersonal->createMediaView()
: nullptr;
_waiting = _showPeerUserpic
? Ui::PeerUserpicLoading(_userpicView)
: (_nonPersonalView && !_nonPersonalView->loaded());
if (_waiting) {
if (_showPeerUserpic) {
_peer->loadUserpic();
} else if (_nonPersonalView) {
showNonPersonal->load(Data::FileOriginFullUser{
peerToUser(user->id),
});
}
}
if (_role == Role::OpenPhoto) {
if (_peer->userpicPhotoUnknown()) {
_peer->updateFullForced();
}
_canOpenPhoto = (_peer->userpicPhotoId() != 0);
updateCursor();
updateVideo();
}
}
void UserpicButton::updateCursor() {
Expects(_role == Role::OpenPhoto);
const auto pointer = _canOpenPhoto
|| (_changeOverlayEnabled && _cursorInChangeOverlay);
setPointerCursor(pointer);
}
bool UserpicButton::createStreamingObjects(not_null<PhotoData*> photo) {
Expects(_peer != nullptr);
using namespace Media::Streaming;
const auto origin = _peer->isUser()
? Data::FileOriginUserPhoto(peerToUser(_peer->id), photo->id)
: Data::FileOrigin(Data::FileOriginPeerPhoto(_peer->id));
_streamed = std::make_unique<Instance>(
photo->owner().streaming().sharedDocument(photo, origin),
nullptr);
_streamed->lockPlayer();
_streamed->player().updates(
) | rpl::start_with_next_error([=](Update &&update) {
handleStreamingUpdate(std::move(update));
}, [=](Error &&error) {
handleStreamingError(std::move(error));
}, _streamed->lifetime());
if (_streamed->ready()) {
streamingReady(base::duplicate(_streamed->info()));
}
if (!_streamed->valid()) {
clearStreaming();
return false;
}
return true;
}
void UserpicButton::clearStreaming() {
_streamed = nullptr;
_streamedPhoto = nullptr;
}
void UserpicButton::handleStreamingUpdate(Media::Streaming::Update &&update) {
using namespace Media::Streaming;
v::match(update.data, [&](Information &update) {
streamingReady(std::move(update));
}, [&](const PreloadedVideo &update) {
}, [&](const UpdateVideo &update) {
this->update();
}, [&](const PreloadedAudio &update) {
}, [&](const UpdateAudio &update) {
}, [&](const WaitingForData &update) {
}, [&](MutedByOther) {
}, [&](Finished) {
});
}
void UserpicButton::handleStreamingError(Media::Streaming::Error &&error) {
Expects(_peer != nullptr);
_streamedPhoto->setVideoPlaybackFailed();
_streamedPhoto = nullptr;
_streamed = nullptr;
}
void UserpicButton::streamingReady(Media::Streaming::Information &&info) {
update();
}
void UserpicButton::updateVideo() {
Expects(_role == Role::OpenPhoto);
const auto id = _peer->userpicPhotoId();
if (!id) {
clearStreaming();
return;
}
const auto photo = _peer->owner().photo(id);
if (!photo->date || !photo->videoCanBePlayed()) {
clearStreaming();
return;
} else if (_streamed && _streamedPhoto == photo) {
return;
}
if (!createStreamingObjects(photo)) {
photo->setVideoPlaybackFailed();
return;
}
_streamedPhoto = photo;
checkStreamedIsStarted();
}
void UserpicButton::checkStreamedIsStarted() {
Expects(!_streamed || _streamedPhoto);
if (!_streamed) {
return;
} else if (_streamed->paused()) {
_streamed->resume();
}
if (_streamed && !_streamed->active() && !_streamed->failed()) {
const auto position = _streamedPhoto->videoStartPosition();
auto options = Media::Streaming::PlaybackOptions();
options.position = position;
options.mode = Media::Streaming::Mode::Video;
options.loop = true;
_streamed->play(options);
}
}
void UserpicButton::mouseMoveEvent(QMouseEvent *e) {
RippleButton::mouseMoveEvent(e);
if (_role == Role::OpenPhoto) {
updateCursorInChangeOverlay(e->pos());
}
}
void UserpicButton::updateCursorInChangeOverlay(QPoint localPos) {
auto photoPosition = countPhotoPosition();
auto overlayRect = QRect(
photoPosition.x(),
photoPosition.y() + _st.photoSize - _st.uploadHeight,
_st.photoSize,
_st.uploadHeight);
auto inOverlay = overlayRect.contains(localPos);
setCursorInChangeOverlay(inOverlay);
}
void UserpicButton::leaveEventHook(QEvent *e) {
if (_role == Role::OpenPhoto) {
setCursorInChangeOverlay(false);
}
return RippleButton::leaveEventHook(e);
}
void UserpicButton::setCursorInChangeOverlay(bool inOverlay) {
Expects(_role == Role::OpenPhoto);
if (_cursorInChangeOverlay != inOverlay) {
_cursorInChangeOverlay = inOverlay;
updateCursor();
}
}
void UserpicButton::processNewPeerPhoto() {
if (_source == Source::Custom) {
return;
}
processPeerPhoto();
if (!_waiting) {
grabOldUserpic();
startNewPhotoShowing();
}
}
bool UserpicButton::useForumShape() const {
return _forceForumShape || (_peer && _peer->isForum());
}
void UserpicButton::grabOldUserpic() {
auto photoRect = QRect(
countPhotoPosition(),
QSize(_st.photoSize, _st.photoSize)
);
_oldUserpic = GrabWidget(this, photoRect);
}
void UserpicButton::startNewPhotoShowing() {
const auto oldUniqueKey = _userpicUniqueKey;
prepareUserpicPixmap();
update();
if (_notShownYet) {
return;
} else if (oldUniqueKey != _userpicUniqueKey
|| _a_appearance.animating()) {
startAnimation();
}
}
void UserpicButton::startAnimation() {
_a_appearance.stop();
_a_appearance.start([this] { update(); }, 0, 1, _st.duration);
}
void UserpicButton::switchChangePhotoOverlay(
bool enabled,
Fn<void(ChosenImage)> chosen) {
Expects(_role == Role::OpenPhoto);
if (_changeOverlayEnabled != enabled) {
_changeOverlayEnabled = enabled;
if (enabled) {
if (isOver()) {
startChangeOverlayAnimation();
}
updateCursorInChangeOverlay(
mapFromGlobal(QCursor::pos()));
if (chosen) {
chosenImages() | rpl::start_with_next(chosen, lifetime());
}
} else {
_changeOverlayShown.stop();
update();
}
}
}
void UserpicButton::showSavedMessagesOnSelf(bool enabled) {
if (_showSavedMessagesOnSelf != enabled) {
_showSavedMessagesOnSelf = enabled;
update();
}
}
bool UserpicButton::showSavedMessages() const {
return _showSavedMessagesOnSelf && _peer && _peer->isSelf();
}
bool UserpicButton::showRepliesMessages() const {
return _showSavedMessagesOnSelf && _peer && _peer->isRepliesChat();
}
void UserpicButton::startChangeOverlayAnimation() {
auto over = isOver() || isDown();
_changeOverlayShown.start(
[this] { update(); },
over ? 0. : 1.,
over ? 1. : 0.,
st::slideWrapDuration);
update();
}
void UserpicButton::onStateChanged(
State was,
StateChangeSource source) {
RippleButton::onStateChanged(was, source);
if (_changeOverlayEnabled) {
auto mask = (StateFlag::Over | StateFlag::Down);
auto wasOver = (was & mask) != 0;
auto nowOver = (state() & mask) != 0;
if (wasOver != nowOver) {
startChangeOverlayAnimation();
}
}
}
void UserpicButton::showCustom(QImage &&image) {
if (!_notShownYet) {
grabOldUserpic();
}
clearStreaming();
_sourceLifetime.destroy();
_source = Source::Custom;
_userpicHasImage = !image.isNull();
if (_userpicHasImage) {
auto size = QSize(_st.photoSize, _st.photoSize);
auto small = image.scaled(
size * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
_userpic = Ui::PixmapFromImage(useForumShape()
? Images::Round(
std::move(small),
Images::CornersMask(_st.photoSize
* Ui::ForumUserpicRadiusMultiplier()))
: Images::Circle(std::move(small)));
} else {
_userpic = CreateSquarePixmap(_st.photoSize, [&](Painter &p) {
fillShape(p, _st.changeButton.textBg);
});
}
_userpic.setDevicePixelRatio(cRetinaFactor());
_userpicUniqueKey = {};
_result = std::move(image);
startNewPhotoShowing();
}
void UserpicButton::showSource(Source source) {
Expects(_peer != nullptr);
Expects(source != Source::Custom); // Show this using showCustom().
Expects(source == Source::PeerPhoto || _peer->isUser());
if (_source != source) {
clearStreaming();
}
_sourceLifetime.destroy();
_source = source;
_result = QImage();
processPeerPhoto();
setupPeerViewers();
prepareUserpicPixmap();
update();
}
void UserpicButton::overrideHasPersonalPhoto(bool has) {
Expects(_peer && _peer->isUser());
_overrideHasPersonalPhoto = has;
}
rpl::producer<> UserpicButton::resetPersonalRequests() const {
return _resetPersonalRequests.events();
}
void UserpicButton::fillShape(QPainter &p, const style::color &color) const {
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(color);
const auto size = _st.photoSize;
if (useForumShape()) {
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
p.drawRoundedRect(0, 0, size, size, radius, radius);
} else {
p.drawEllipse(0, 0, size, size);
}
}
void UserpicButton::prepareUserpicPixmap() {
if (_source == Source::Custom) {
return;
}
const auto size = _st.photoSize;
_userpicHasImage = _showPeerUserpic
? (_peer
&& (_peer->userpicCloudImage(_userpicView)
|| _role != Role::ChangePhoto))
: (_source == Source::NonPersonalPhoto
|| (_source == Source::NonPersonalIfHasPersonal
&& hasPersonalPhotoLocally()));
_userpic = CreateSquarePixmap(size, [&](Painter &p) {
if (_userpicHasImage) {
if (_showPeerUserpic) {
_peer->paintUserpic(p, _userpicView, 0, 0, size);
} else if (_nonPersonalView) {
using Size = Data::PhotoSize;
if (const auto full = _nonPersonalView->image(Size::Large)) {
const auto ratio = style::DevicePixelRatio();
auto image = full->original().scaled(
QSize(size, size) * ratio,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
image = useForumShape()
? Images::Round(
std::move(image),
Images::CornersMask(size
* Ui::ForumUserpicRadiusMultiplier()))
: Images::Circle(std::move(image));
image.setDevicePixelRatio(style::DevicePixelRatio());
p.drawImage(0, 0, image);
}
} else {
const auto user = _peer->asUser();
auto empty = Ui::EmptyUserpic(
Ui::EmptyUserpic::UserpicColor(
Data::PeerColorIndex(_peer->id)),
((user && user->isInaccessible())
? Ui::EmptyUserpic::InaccessibleName()
: _peer->name()));
if (useForumShape()) {
empty.paintRounded(
p,
0,
0,
size,
size,
size * Ui::ForumUserpicRadiusMultiplier());
} else {
empty.paintCircle(p, 0, 0, size, size);
}
}
} else {
fillShape(p, _st.changeButton.textBg);
}
});
_userpicUniqueKey = _userpicHasImage
? (_showPeerUserpic
? _peer->userpicUniqueKey(_userpicView)
: _nonPersonalView
? InMemoryKey{ _nonPersonalView->owner()->id, 0 }
: InMemoryKey{ _peer->id.value, _peer->id.value })
: InMemoryKey();
}
not_null<Ui::UserpicButton*> CreateUploadSubButton(
not_null<Ui::RpWidget*> parent,
not_null<Window::SessionController*> controller) {
const auto background = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto upload = Ui::CreateChild<Ui::UserpicButton>(
parent.get(),
&controller->window(),
Ui::UserpicButton::Role::ChoosePhoto,
st::uploadUserpicButton);
SetupSubButtonBackground(upload, background);
return upload;
}
not_null<Ui::UserpicButton*> CreateUploadSubButton(
not_null<Ui::RpWidget*> parent,
not_null<UserData*> contact,
not_null<Window::SessionController*> controller) {
const auto background = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto upload = Ui::CreateChild<Ui::UserpicButton>(
parent.get(),
controller,
contact,
Ui::UserpicButton::Role::ChoosePhoto,
Ui::UserpicButton::Source::NonPersonalIfHasPersonal,
st::uploadUserpicButton);
SetupSubButtonBackground(upload, background);
return upload;
}
} // namespace Ui