/* 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 "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_menu_icons.h" namespace Ui { namespace { [[nodiscard]] bool IsCameraAvailable() { return (Core::App().calls().currentCall() == nullptr) && !Webrtc::GetVideoInputList().empty(); } void CameraBox( not_null box, not_null controller, PeerData *peer, bool forceForumShape, Fn &&doneCallback) { using namespace Webrtc; const auto track = Settings::Calls::AddCameraSubsection( std::make_shared(box), 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 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 upload, not_null 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, 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 controller, not_null 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 peer, const style::UserpicButton &st) : RippleButton(parent, st.changeButton.ripple) , _st(st) , _peer(peer) , _role(Role::Custom) , _source(Source::PeerPhoto) { Expects(_role != Role::OpenPhoto); if (_source == Source::Custom) { showCustom({}); } else { processPeerPhoto(); setupPeerViewers(); } _waiting = false; 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 user) const { // Server allows suggesting photos only in non-empty chats. return !user->isSelf() && (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)); })); }; _menu = base::make_unique_q( this, st::popupMenuWithIcons); const auto user = _peer ? _peer->asUser() : nullptr; 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); } if (hasPersonalPhotoLocally()) { _menu->addAction(makeResetToOriginalAction()); } } else { if (!IsCameraAvailable()) { chooseFile(); } else { _menu->addAction(tr::lng_attach_file(tr::now), [=] { chooseFile(); }, &st::menuIconPhoto); _menu->addAction(tr::lng_attach_camera(tr::now), [=] { _window->show(Box( CameraBox, _window, _peer, _forceForumShape, callback(ChosenType::Set))); }, &st::menuIconPhotoSet); } } _menu->popup(QCursor::pos()); } auto UserpicButton::makeResetToOriginalAction() -> base::unique_qptr { auto item = base::make_unique_q( _menu.get(), _menu->st().menu, Menu::CreateAction( _menu.get(), tr::lng_profile_photo_reset(tr::now), [=] { _resetPersonalRequests.fire({}); }), nullptr, nullptr); const auto icon = CreateChild( 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 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( 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 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 CreateUploadSubButton( not_null parent, not_null controller) { const auto background = Ui::CreateChild(parent.get()); const auto upload = Ui::CreateChild( parent.get(), &controller->window(), Ui::UserpicButton::Role::ChoosePhoto, st::uploadUserpicButton); SetupSubButtonBackground(upload, background); return upload; } not_null CreateUploadSubButton( not_null parent, not_null contact, not_null controller) { const auto background = Ui::CreateChild(parent.get()); const auto upload = Ui::CreateChild( parent.get(), controller, contact, Ui::UserpicButton::Role::ChoosePhoto, Ui::UserpicButton::Source::NonPersonalIfHasPersonal, st::uploadUserpicButton); SetupSubButtonBackground(upload, background); return upload; } } // namespace Ui