userpic_button.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "ui/controls/userpic_button.h"
  8. #include "apiwrap.h"
  9. #include "api/api_user_privacy.h"
  10. #include "base/call_delayed.h"
  11. #include "boxes/edit_privacy_box.h"
  12. #include "boxes/peers/edit_peer_info_box.h" // EditPeerInfoBox::Available.
  13. #include "ui/effects/ripple_animation.h"
  14. #include "ui/empty_userpic.h"
  15. #include "data/data_photo.h"
  16. #include "data/data_session.h"
  17. #include "data/data_changes.h"
  18. #include "data/data_user.h"
  19. #include "data/data_histories.h"
  20. #include "data/data_streaming.h"
  21. #include "data/data_file_origin.h"
  22. #include "data/data_photo_media.h"
  23. #include "history/history.h"
  24. #include "calls/calls_instance.h"
  25. #include "core/application.h"
  26. #include "ui/effects/premium_graphics.h"
  27. #include "ui/layers/generic_box.h"
  28. #include "ui/text/text_utilities.h"
  29. #include "ui/widgets/menu/menu_action.h"
  30. #include "ui/painter.h"
  31. #include "ui/ui_utility.h"
  32. #include "editor/photo_editor_common.h"
  33. #include "editor/photo_editor_layer_widget.h"
  34. #include "info/userpic/info_userpic_emoji_builder_common.h"
  35. #include "info/userpic/info_userpic_emoji_builder_menu_item.h"
  36. #include "media/streaming/media_streaming_instance.h"
  37. #include "media/streaming/media_streaming_player.h"
  38. #include "media/streaming/media_streaming_document.h"
  39. #include "settings/settings_calls.h" // Calls::AddCameraSubsection.
  40. #include "settings/settings_privacy_controllers.h"
  41. #include "webrtc/webrtc_environment.h"
  42. #include "webrtc/webrtc_video_track.h"
  43. #include "ui/widgets/popup_menu.h"
  44. #include "window/window_controller.h"
  45. #include "window/window_session_controller.h"
  46. #include "lang/lang_keys.h"
  47. #include "main/main_session.h"
  48. #include "apiwrap.h"
  49. #include "api/api_peer_photo.h"
  50. #include "styles/style_boxes.h"
  51. #include "styles/style_chat.h"
  52. #include "styles/style_menu_icons.h"
  53. #include "styles/style_premium.h"
  54. #include <QtGui/QClipboard>
  55. #include <QtGui/QGuiApplication>
  56. namespace Ui {
  57. namespace {
  58. [[nodiscard]] bool IsCameraAvailable() {
  59. return (Core::App().calls().currentCall() == nullptr)
  60. && !Core::App().mediaDevices().defaultId(
  61. Webrtc::DeviceType::Camera).isEmpty();
  62. }
  63. void CameraBox(
  64. not_null<Ui::GenericBox*> box,
  65. not_null<Window::Controller*> controller,
  66. PeerData *peer,
  67. bool forceForumShape,
  68. Fn<void(QImage &&image)> &&doneCallback) {
  69. using namespace Webrtc;
  70. const auto track = Settings::Calls::AddCameraSubsection(
  71. box->uiShow(),
  72. box->verticalLayout(),
  73. false);
  74. if (!track) {
  75. box->closeBox();
  76. return;
  77. }
  78. track->stateValue(
  79. ) | rpl::start_with_next([=](const VideoState &state) {
  80. if (state == VideoState::Inactive) {
  81. box->closeBox();
  82. }
  83. }, box->lifetime());
  84. auto done = [=, done = std::move(doneCallback)]() mutable {
  85. using namespace Editor;
  86. auto callback = [=, done = std::move(done)](QImage &&image) {
  87. box->closeBox();
  88. done(std::move(image));
  89. };
  90. const auto useForumShape = forceForumShape
  91. || (peer && peer->isForum());
  92. PrepareProfilePhoto(
  93. box,
  94. controller,
  95. {
  96. .confirm = tr::lng_profile_set_photo_button(tr::now),
  97. .cropType = (useForumShape
  98. ? EditorData::CropType::RoundedRect
  99. : EditorData::CropType::Ellipse),
  100. .keepAspectRatio = true,
  101. },
  102. std::move(callback),
  103. track->frame(FrameRequest()).mirrored(true, false));
  104. };
  105. box->setTitle(tr::lng_profile_camera_title());
  106. box->addButton(tr::lng_continue(), std::move(done));
  107. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  108. }
  109. template <typename Callback>
  110. QPixmap CreateSquarePixmap(int width, Callback &&paintCallback) {
  111. const auto size = QSize(width, width) * style::DevicePixelRatio();
  112. auto image = QImage(size, QImage::Format_ARGB32_Premultiplied);
  113. image.setDevicePixelRatio(style::DevicePixelRatio());
  114. image.fill(Qt::transparent);
  115. {
  116. Painter p(&image);
  117. paintCallback(p);
  118. }
  119. return Ui::PixmapFromImage(std::move(image));
  120. };
  121. void SetupSubButtonBackground(
  122. not_null<Ui::UserpicButton*> upload,
  123. not_null<Ui::RpWidget*> background) {
  124. const auto border = st::uploadUserpicButtonBorder;
  125. const auto size = upload->rect().marginsAdded(
  126. { border, border, border, border }
  127. ).size();
  128. background->resize(size);
  129. background->paintRequest(
  130. ) | rpl::start_with_next([=] {
  131. auto p = QPainter(background);
  132. auto hq = PainterHighQualityEnabler(p);
  133. p.setBrush(st::boxBg);
  134. p.setPen(Qt::NoPen);
  135. p.drawEllipse(background->rect());
  136. }, background->lifetime());
  137. upload->positionValue(
  138. ) | rpl::start_with_next([=](QPoint position) {
  139. background->move(position - QPoint(border, border));
  140. }, background->lifetime());
  141. }
  142. } // namespace
  143. UserpicButton::UserpicButton(
  144. QWidget *parent,
  145. not_null<Window::Controller*> window,
  146. Role role,
  147. const style::UserpicButton &st,
  148. bool forceForumShape)
  149. : RippleButton(parent, st.changeButton.ripple)
  150. , _st(st)
  151. , _controller(window->sessionController())
  152. , _window(window)
  153. , _forceForumShape(forceForumShape)
  154. , _role(role) {
  155. Expects(_role == Role::ChangePhoto || _role == Role::ChoosePhoto);
  156. showCustom({});
  157. _waiting = false;
  158. prepare();
  159. }
  160. UserpicButton::UserpicButton(
  161. QWidget *parent,
  162. not_null<Window::SessionController*> controller,
  163. not_null<PeerData*> peer,
  164. Role role,
  165. Source source,
  166. const style::UserpicButton &st)
  167. : RippleButton(parent, st.changeButton.ripple)
  168. , _st(st)
  169. , _controller(controller)
  170. , _window(&controller->window())
  171. , _peer(peer)
  172. , _role(role)
  173. , _source(source) {
  174. if (_source == Source::Custom) {
  175. showCustom({});
  176. } else {
  177. processPeerPhoto();
  178. setupPeerViewers();
  179. }
  180. prepare();
  181. }
  182. UserpicButton::UserpicButton(
  183. QWidget *parent,
  184. not_null<PeerData*> peer,
  185. const style::UserpicButton &st)
  186. : RippleButton(parent, st.changeButton.ripple)
  187. , _st(st)
  188. , _peer(peer)
  189. , _role(Role::Custom)
  190. , _source(Source::PeerPhoto) {
  191. Expects(_role != Role::OpenPhoto);
  192. processPeerPhoto();
  193. setupPeerViewers();
  194. prepare();
  195. }
  196. UserpicButton::~UserpicButton() = default;
  197. void UserpicButton::prepare() {
  198. resize(_st.size);
  199. _notShownYet = _waiting;
  200. if (!_waiting) {
  201. prepareUserpicPixmap();
  202. }
  203. setClickHandlerByRole();
  204. }
  205. void UserpicButton::showCustomOnChosen() {
  206. chosenImages(
  207. ) | rpl::start_with_next([=](ChosenImage &&chosen) {
  208. showCustom(std::move(chosen.image));
  209. }, lifetime());
  210. }
  211. void UserpicButton::requestSuggestAvailability() {
  212. if (const auto user = _peer ? _peer->asUser() : nullptr) {
  213. if (!user->isSelf()) {
  214. const auto history = user->owner().history(user);
  215. if (!history->lastServerMessageKnown()) {
  216. // Server allows suggesting photos only in non-empty chats.
  217. user->owner().histories().requestDialogEntry(history);
  218. }
  219. }
  220. }
  221. }
  222. bool UserpicButton::canSuggestPhoto(not_null<UserData*> user) const {
  223. // Server allows suggesting photos only in non-empty chats.
  224. return !user->isSelf()
  225. && !user->isBot()
  226. && !user->starsPerMessageChecked()
  227. && (user->owner().history(user)->lastServerMessage() != nullptr);
  228. }
  229. bool UserpicButton::hasPersonalPhotoLocally() const {
  230. if (const auto user = _peer->asUser()) {
  231. return _overrideHasPersonalPhoto.value_or(user->hasPersonalPhoto());
  232. }
  233. return false;
  234. }
  235. void UserpicButton::setClickHandlerByRole() {
  236. requestSuggestAvailability();
  237. switch (_role) {
  238. case Role::ChoosePhoto:
  239. case Role::ChangePhoto:
  240. addClickHandler([=] { choosePhotoLocally(); });
  241. break;
  242. case Role::OpenPhoto:
  243. addClickHandler([=] { openPeerPhoto(); });
  244. break;
  245. }
  246. }
  247. void UserpicButton::choosePhotoLocally() {
  248. if (!_window) {
  249. return;
  250. }
  251. const auto callback = [=](ChosenType type) {
  252. return [=](QImage &&image) {
  253. _chosenImages.fire({ std::move(image), type });
  254. };
  255. };
  256. const auto editorData = [=](ChosenType type) {
  257. const auto user = _peer ? _peer->asUser() : nullptr;
  258. const auto name = (user && !user->firstName.isEmpty())
  259. ? user->firstName
  260. : _peer
  261. ? _peer->name()
  262. : QString();
  263. const auto phrase = (type == ChosenType::Suggest)
  264. ? &tr::lng_profile_suggest_sure
  265. : (user && EditPeerInfoBox::Available(user))
  266. ? nullptr
  267. : (user && !user->isSelf())
  268. ? &tr::lng_profile_set_personal_sure
  269. : nullptr;
  270. return Editor::EditorData{
  271. .about = (phrase
  272. ? (*phrase)(
  273. tr::now,
  274. lt_user,
  275. Ui::Text::Bold(name),
  276. Ui::Text::WithEntities)
  277. : TextWithEntities()),
  278. .confirm = ((type == ChosenType::Suggest)
  279. ? tr::lng_profile_suggest_button(tr::now)
  280. : tr::lng_profile_set_photo_button(tr::now)),
  281. .cropType = (useForumShape()
  282. ? Editor::EditorData::CropType::RoundedRect
  283. : Editor::EditorData::CropType::Ellipse),
  284. .keepAspectRatio = true,
  285. };
  286. };
  287. const auto chooseFile = [=](ChosenType type) {
  288. base::call_delayed(
  289. _st.changeButton.ripple.hideDuration,
  290. crl::guard(this, [=] {
  291. PrepareProfilePhotoFromFile(
  292. this,
  293. _window,
  294. editorData(type),
  295. callback(type));
  296. }));
  297. };
  298. const auto user = _peer ? _peer->asUser() : nullptr;
  299. const auto addUserpicBuilder = [&](ChosenType type) {
  300. if (!_controller) {
  301. return;
  302. }
  303. const auto done = [=](UserpicBuilder::Result data) {
  304. auto result = ChosenImage{ base::take(data.image), type };
  305. result.markup.documentId = data.id;
  306. result.markup.colors = base::take(data.colors);
  307. _chosenImages.fire(std::move(result));
  308. };
  309. UserpicBuilder::AddEmojiBuilderAction(
  310. _controller,
  311. _menu,
  312. _controller->session().api().peerPhoto().emojiListValue(user
  313. ? Api::PeerPhoto::EmojiListType::Profile
  314. : Api::PeerPhoto::EmojiListType::Group),
  315. done,
  316. _peer ? _peer->isForum() : false);
  317. };
  318. const auto addFromClipboard = [=](ChosenType type, tr::phrase<> text) {
  319. if (const auto data = QGuiApplication::clipboard()->mimeData()) {
  320. if (data->hasImage()) {
  321. auto openEditor = crl::guard(this, [=, this] {
  322. Editor::PrepareProfilePhoto(
  323. this,
  324. _window,
  325. editorData(type),
  326. callback(type),
  327. qvariant_cast<QImage>(data->imageData()));
  328. });
  329. _menu->addAction(
  330. std::move(text)(tr::now),
  331. std::move(openEditor),
  332. &st::menuIconPhoto);
  333. }
  334. }
  335. };
  336. _menu = base::make_unique_q<Ui::PopupMenu>(
  337. this,
  338. st::popupMenuWithIcons);
  339. if (user && !user->isSelf()) {
  340. _menu->addAction(
  341. tr::lng_profile_set_photo_for(tr::now),
  342. [=] { chooseFile(ChosenType::Set); },
  343. &st::menuIconPhotoSet);
  344. addFromClipboard(
  345. ChosenType::Set,
  346. tr::lng_profile_set_photo_for_from_clipboard);
  347. if (canSuggestPhoto(user)) {
  348. _menu->addAction(
  349. tr::lng_profile_suggest_photo(tr::now),
  350. [=] { chooseFile(ChosenType::Suggest); },
  351. &st::menuIconPhotoSuggest);
  352. addFromClipboard(
  353. ChosenType::Suggest,
  354. tr::lng_profile_suggest_photo_from_clipboard);
  355. }
  356. addUserpicBuilder(ChosenType::Set);
  357. if (hasPersonalPhotoLocally()) {
  358. _menu->addSeparator(&st::expandedMenuSeparator);
  359. _menu->addAction(makeResetToOriginalAction());
  360. }
  361. } else {
  362. const auto hasCamera = IsCameraAvailable();
  363. if (hasCamera || _controller) {
  364. _menu->addAction(tr::lng_attach_file(tr::now), [=] {
  365. chooseFile(ChosenType::Set);
  366. }, &st::menuIconPhoto);
  367. if (hasCamera) {
  368. _menu->addAction(tr::lng_attach_camera(tr::now), [=] {
  369. _window->show(Box(
  370. CameraBox,
  371. _window,
  372. _peer,
  373. _forceForumShape,
  374. callback(ChosenType::Set)));
  375. }, &st::menuIconPhotoSet);
  376. }
  377. addFromClipboard(
  378. ChosenType::Set,
  379. tr::lng_profile_photo_from_clipboard);
  380. addUserpicBuilder(ChosenType::Set);
  381. } else {
  382. chooseFile(ChosenType::Set);
  383. }
  384. if (user && user->isSelf()) {
  385. const auto key = Api::UserPrivacy::Key::ProfilePhoto;
  386. const auto text = tr::lng_edit_privacy_profile_photo_public_set(
  387. tr::now);
  388. user->session().api().userPrivacy().reload(key);
  389. _menu->addAction(std::move(text), [=] {
  390. using namespace Api;
  391. user->session().api().userPrivacy().value(
  392. key
  393. ) | rpl::take(
  394. 1
  395. ) | rpl::start_with_next([=](const UserPrivacy::Rule &value) {
  396. using namespace Settings;
  397. _window->show(Box<EditPrivacyBox>(
  398. _window->sessionController(),
  399. std::make_unique<ProfilePhotoPrivacyController>(),
  400. value));
  401. }, _menu->lifetime());
  402. }, &st::menuIconProfile);
  403. }
  404. }
  405. _menu->popup(QCursor::pos());
  406. }
  407. auto UserpicButton::makeResetToOriginalAction()
  408. -> base::unique_qptr<Menu::ItemBase> {
  409. auto item = base::make_unique_q<Menu::Action>(
  410. _menu.get(),
  411. _menu->st().menu,
  412. Menu::CreateAction(
  413. _menu.get(),
  414. tr::lng_profile_photo_reset(tr::now),
  415. [=] { _resetPersonalRequests.fire({}); }),
  416. nullptr,
  417. nullptr);
  418. const auto icon = CreateChild<UserpicButton>(
  419. item.get(),
  420. _controller,
  421. _peer,
  422. Ui::UserpicButton::Role::Custom,
  423. Ui::UserpicButton::Source::NonPersonalIfHasPersonal,
  424. st::restoreUserpicIcon);
  425. if (_source == Source::Custom) {
  426. icon->showCustom(base::duplicate(_result));
  427. }
  428. icon->setAttribute(Qt::WA_TransparentForMouseEvents);
  429. icon->move(_menu->st().menu.itemIconPosition
  430. + QPoint(
  431. (st::menuIconRemove.width() - icon->width()) / 2,
  432. (st::menuIconRemove.height() - icon->height()) / 2));
  433. return item;
  434. }
  435. void UserpicButton::openPeerPhoto() {
  436. Expects(_peer != nullptr);
  437. Expects(_controller != nullptr);
  438. if (_changeOverlayEnabled && _cursorInChangeOverlay) {
  439. choosePhotoLocally();
  440. return;
  441. }
  442. const auto id = _peer->userpicPhotoId();
  443. if (!id) {
  444. return;
  445. }
  446. const auto photo = _peer->owner().photo(id);
  447. if (photo->date() && _controller) {
  448. _controller->openPhoto(photo, _peer);
  449. }
  450. }
  451. void UserpicButton::setupPeerViewers() {
  452. const auto user = _peer->asUser();
  453. if (user
  454. && (_source == Source::NonPersonalPhoto
  455. || _source == Source::NonPersonalIfHasPersonal)) {
  456. user->session().changes().peerFlagsValue(
  457. user,
  458. Data::PeerUpdate::Flag::FullInfo
  459. ) | rpl::map([=] {
  460. return std::pair(
  461. user->session().api().peerPhoto().nonPersonalPhoto(user),
  462. user->hasPersonalPhoto());
  463. }) | rpl::distinct_until_changed() | rpl::skip(
  464. 1
  465. ) | rpl::start_with_next([=] {
  466. processNewPeerPhoto();
  467. update();
  468. }, _sourceLifetime);
  469. }
  470. if (!user
  471. || _source == Source::PeerPhoto
  472. || _source == Source::NonPersonalIfHasPersonal) {
  473. _peer->session().changes().peerUpdates(
  474. _peer,
  475. Data::PeerUpdate::Flag::Photo
  476. ) | rpl::start_with_next([=] {
  477. processNewPeerPhoto();
  478. update();
  479. }, _sourceLifetime);
  480. }
  481. _peer->session().downloaderTaskFinished(
  482. ) | rpl::filter([=] {
  483. return _waiting;
  484. }) | rpl::start_with_next([=] {
  485. const auto loading = _showPeerUserpic
  486. ? Ui::PeerUserpicLoading(_userpicView)
  487. : (_nonPersonalView && !_nonPersonalView->loaded());
  488. if (!loading) {
  489. _waiting = false;
  490. startNewPhotoShowing();
  491. }
  492. }, _sourceLifetime);
  493. }
  494. void UserpicButton::paintEvent(QPaintEvent *e) {
  495. Painter p(this);
  496. if (!_waiting && _notShownYet) {
  497. _notShownYet = false;
  498. startAnimation();
  499. }
  500. auto photoPosition = countPhotoPosition();
  501. auto photoLeft = photoPosition.x();
  502. auto photoTop = photoPosition.y();
  503. if (showSavedMessages()) {
  504. Ui::EmptyUserpic::PaintSavedMessages(
  505. p,
  506. photoPosition.x(),
  507. photoPosition.y(),
  508. width(),
  509. _st.photoSize);
  510. } else if (showRepliesMessages()) {
  511. Ui::EmptyUserpic::PaintRepliesMessages(
  512. p,
  513. photoPosition.x(),
  514. photoPosition.y(),
  515. width(),
  516. _st.photoSize);
  517. } else {
  518. if (_a_appearance.animating()) {
  519. p.drawPixmapLeft(photoPosition, width(), _oldUserpic);
  520. p.setOpacity(_a_appearance.value(1.));
  521. }
  522. paintUserpicFrame(p, photoPosition);
  523. }
  524. const auto fillTranslatedShape = [&](const style::color &color) {
  525. p.translate(photoLeft, photoTop);
  526. fillShape(p, color);
  527. p.translate(-photoLeft, -photoTop);
  528. };
  529. if (_role == Role::ChangePhoto || _role == Role::ChoosePhoto) {
  530. auto over = isOver() || isDown();
  531. if (over) {
  532. fillTranslatedShape(_userpicHasImage
  533. ? st::msgDateImgBg
  534. : _st.changeButton.textBgOver);
  535. }
  536. paintRipple(
  537. p,
  538. photoLeft,
  539. photoTop,
  540. (_userpicHasImage
  541. ? &st::shadowFg->c
  542. : &_st.changeButton.ripple.color->c));
  543. if (over || !_userpicHasImage) {
  544. auto iconLeft = (_st.changeIconPosition.x() < 0)
  545. ? (_st.photoSize - _st.changeIcon.width()) / 2
  546. : _st.changeIconPosition.x();
  547. auto iconTop = (_st.changeIconPosition.y() < 0)
  548. ? (_st.photoSize - _st.changeIcon.height()) / 2
  549. : _st.changeIconPosition.y();
  550. _st.changeIcon.paint(
  551. p,
  552. photoLeft + iconLeft,
  553. photoTop + iconTop,
  554. width());
  555. }
  556. } else if (_changeOverlayEnabled) {
  557. auto current = _changeOverlayShown.value(
  558. (isOver() || isDown()) ? 1. : 0.);
  559. auto barHeight = anim::interpolate(
  560. 0,
  561. _st.uploadHeight,
  562. current);
  563. if (barHeight > 0) {
  564. auto barLeft = photoLeft;
  565. auto barTop = photoTop + _st.photoSize - barHeight;
  566. auto rect = QRect(
  567. barLeft,
  568. barTop,
  569. _st.photoSize,
  570. barHeight);
  571. p.setClipRect(rect);
  572. fillTranslatedShape(_st.uploadBg);
  573. auto iconLeft = (_st.uploadIconPosition.x() < 0)
  574. ? (_st.photoSize - _st.uploadIcon.width()) / 2
  575. : _st.uploadIconPosition.x();
  576. auto iconTop = (_st.uploadIconPosition.y() < 0)
  577. ? (_st.uploadHeight - _st.uploadIcon.height()) / 2
  578. : _st.uploadIconPosition.y();
  579. if (iconTop < barHeight) {
  580. _st.uploadIcon.paint(
  581. p,
  582. barLeft + iconLeft,
  583. barTop + iconTop,
  584. width());
  585. }
  586. }
  587. }
  588. }
  589. void UserpicButton::paintUserpicFrame(Painter &p, QPoint photoPosition) {
  590. checkStreamedIsStarted();
  591. if (_streamed
  592. && _streamed->player().ready()
  593. && !_streamed->player().videoSize().isEmpty()) {
  594. const auto paused = _controller
  595. ? _controller->isGifPausedAtLeastFor(
  596. Window::GifPauseReason::RoundPlaying)
  597. : false;
  598. auto request = Media::Streaming::FrameRequest();
  599. auto size = QSize{ _st.photoSize, _st.photoSize };
  600. const auto ratio = style::DevicePixelRatio();
  601. request.outer = request.resize = size * ratio;
  602. if (useForumShape()) {
  603. const auto radius = int(_st.photoSize
  604. * Ui::ForumUserpicRadiusMultiplier());
  605. if (_roundingCorners[0].width() != radius * ratio) {
  606. _roundingCorners = Images::CornersMask(radius);
  607. }
  608. request.rounding = Images::CornersMaskRef(_roundingCorners);
  609. } else {
  610. if (_ellipseMask.size() != request.outer) {
  611. _ellipseMask = Images::EllipseMask(size);
  612. }
  613. request.mask = _ellipseMask;
  614. }
  615. p.drawImage(QRect(photoPosition, size), _streamed->frame(request));
  616. if (!paused) {
  617. _streamed->markFrameShown();
  618. }
  619. } else {
  620. p.drawPixmapLeft(photoPosition, width(), _userpic);
  621. }
  622. }
  623. QPoint UserpicButton::countPhotoPosition() const {
  624. auto photoLeft = (_st.photoPosition.x() < 0)
  625. ? (width() - _st.photoSize) / 2
  626. : _st.photoPosition.x();
  627. auto photoTop = (_st.photoPosition.y() < 0)
  628. ? (height() - _st.photoSize) / 2
  629. : _st.photoPosition.y();
  630. return { photoLeft, photoTop };
  631. }
  632. QImage UserpicButton::prepareRippleMask() const {
  633. return Ui::RippleAnimation::EllipseMask(QSize(
  634. _st.photoSize,
  635. _st.photoSize));
  636. }
  637. QPoint UserpicButton::prepareRippleStartPosition() const {
  638. return (_role == Role::ChangePhoto)
  639. ? mapFromGlobal(QCursor::pos()) - countPhotoPosition()
  640. : DisabledRippleStartPosition();
  641. }
  642. void UserpicButton::processPeerPhoto() {
  643. Expects(_peer != nullptr);
  644. const auto user = _peer->asUser();
  645. const auto nonPersonal = (user && _source != Source::PeerPhoto)
  646. ? _peer->session().api().peerPhoto().nonPersonalPhoto(user)
  647. : nullptr;
  648. _showPeerUserpic = (_source == Source::PeerPhoto)
  649. || (user
  650. && !user->hasPersonalPhoto()
  651. && (_source == Source::NonPersonalPhoto
  652. || (_source == Source::NonPersonalIfHasPersonal
  653. && hasPersonalPhotoLocally())));
  654. const auto showNonPersonal = _showPeerUserpic ? nullptr : nonPersonal;
  655. _userpicView = _showPeerUserpic
  656. ? _peer->createUserpicView()
  657. : PeerUserpicView();
  658. _nonPersonalView = showNonPersonal
  659. ? showNonPersonal->createMediaView()
  660. : nullptr;
  661. _waiting = _showPeerUserpic
  662. ? Ui::PeerUserpicLoading(_userpicView)
  663. : (_nonPersonalView && !_nonPersonalView->loaded());
  664. if (_waiting) {
  665. if (_showPeerUserpic) {
  666. _peer->loadUserpic();
  667. } else if (_nonPersonalView) {
  668. showNonPersonal->load(Data::FileOriginFullUser{
  669. peerToUser(user->id),
  670. });
  671. }
  672. }
  673. if (_role == Role::OpenPhoto) {
  674. if (_peer->userpicPhotoUnknown()) {
  675. _peer->updateFullForced();
  676. }
  677. _canOpenPhoto = (_peer->userpicPhotoId() != 0);
  678. updateCursor();
  679. updateVideo();
  680. }
  681. }
  682. void UserpicButton::updateCursor() {
  683. Expects(_role == Role::OpenPhoto);
  684. const auto pointer = _canOpenPhoto
  685. || (_changeOverlayEnabled && _cursorInChangeOverlay);
  686. setPointerCursor(pointer);
  687. }
  688. bool UserpicButton::createStreamingObjects(not_null<PhotoData*> photo) {
  689. Expects(_peer != nullptr);
  690. using namespace Media::Streaming;
  691. const auto origin = _peer->isUser()
  692. ? Data::FileOriginUserPhoto(peerToUser(_peer->id), photo->id)
  693. : Data::FileOrigin(Data::FileOriginPeerPhoto(_peer->id));
  694. _streamed = std::make_unique<Instance>(
  695. photo->owner().streaming().sharedDocument(photo, origin),
  696. nullptr);
  697. _streamed->lockPlayer();
  698. _streamed->player().updates(
  699. ) | rpl::start_with_next_error([=](Update &&update) {
  700. handleStreamingUpdate(std::move(update));
  701. }, [=](Error &&error) {
  702. handleStreamingError(std::move(error));
  703. }, _streamed->lifetime());
  704. if (_streamed->ready()) {
  705. streamingReady(base::duplicate(_streamed->info()));
  706. }
  707. if (!_streamed->valid()) {
  708. clearStreaming();
  709. return false;
  710. }
  711. return true;
  712. }
  713. void UserpicButton::clearStreaming() {
  714. _streamed = nullptr;
  715. _streamedPhoto = nullptr;
  716. }
  717. void UserpicButton::handleStreamingUpdate(Media::Streaming::Update &&update) {
  718. using namespace Media::Streaming;
  719. v::match(update.data, [&](Information &update) {
  720. streamingReady(std::move(update));
  721. }, [](PreloadedVideo) {
  722. }, [&](UpdateVideo) {
  723. this->update();
  724. }, [](PreloadedAudio) {
  725. }, [](UpdateAudio) {
  726. }, [](WaitingForData) {
  727. }, [](SpeedEstimate) {
  728. }, [](MutedByOther) {
  729. }, [](Finished) {
  730. });
  731. }
  732. void UserpicButton::handleStreamingError(Media::Streaming::Error &&error) {
  733. Expects(_peer != nullptr);
  734. _streamedPhoto->setVideoPlaybackFailed();
  735. _streamedPhoto = nullptr;
  736. _streamed = nullptr;
  737. }
  738. void UserpicButton::streamingReady(Media::Streaming::Information &&info) {
  739. update();
  740. }
  741. void UserpicButton::updateVideo() {
  742. Expects(_role == Role::OpenPhoto);
  743. const auto id = _peer->userpicPhotoId();
  744. if (!id) {
  745. clearStreaming();
  746. return;
  747. }
  748. const auto photo = _peer->owner().photo(id);
  749. if (!photo->date() || !photo->videoCanBePlayed()) {
  750. clearStreaming();
  751. return;
  752. } else if (_streamed && _streamedPhoto == photo) {
  753. return;
  754. }
  755. if (!createStreamingObjects(photo)) {
  756. photo->setVideoPlaybackFailed();
  757. return;
  758. }
  759. _streamedPhoto = photo;
  760. checkStreamedIsStarted();
  761. }
  762. void UserpicButton::checkStreamedIsStarted() {
  763. Expects(!_streamed || _streamedPhoto);
  764. if (!_streamed) {
  765. return;
  766. } else if (_streamed->paused()) {
  767. _streamed->resume();
  768. }
  769. if (_streamed && !_streamed->active() && !_streamed->failed()) {
  770. const auto position = _streamedPhoto->videoStartPosition();
  771. auto options = Media::Streaming::PlaybackOptions();
  772. options.position = position;
  773. options.mode = Media::Streaming::Mode::Video;
  774. options.loop = true;
  775. _streamed->play(options);
  776. }
  777. }
  778. void UserpicButton::mouseMoveEvent(QMouseEvent *e) {
  779. RippleButton::mouseMoveEvent(e);
  780. if (_role == Role::OpenPhoto) {
  781. updateCursorInChangeOverlay(e->pos());
  782. }
  783. }
  784. void UserpicButton::updateCursorInChangeOverlay(QPoint localPos) {
  785. auto photoPosition = countPhotoPosition();
  786. auto overlayRect = QRect(
  787. photoPosition.x(),
  788. photoPosition.y() + _st.photoSize - _st.uploadHeight,
  789. _st.photoSize,
  790. _st.uploadHeight);
  791. auto inOverlay = overlayRect.contains(localPos);
  792. setCursorInChangeOverlay(inOverlay);
  793. }
  794. void UserpicButton::leaveEventHook(QEvent *e) {
  795. if (_role == Role::OpenPhoto) {
  796. setCursorInChangeOverlay(false);
  797. }
  798. return RippleButton::leaveEventHook(e);
  799. }
  800. void UserpicButton::setCursorInChangeOverlay(bool inOverlay) {
  801. Expects(_role == Role::OpenPhoto);
  802. if (_cursorInChangeOverlay != inOverlay) {
  803. _cursorInChangeOverlay = inOverlay;
  804. updateCursor();
  805. }
  806. }
  807. void UserpicButton::processNewPeerPhoto() {
  808. if (_source == Source::Custom) {
  809. return;
  810. }
  811. processPeerPhoto();
  812. if (!_waiting) {
  813. grabOldUserpic();
  814. startNewPhotoShowing();
  815. }
  816. }
  817. bool UserpicButton::useForumShape() const {
  818. return _forceForumShape || (_peer && _peer->isForum());
  819. }
  820. void UserpicButton::grabOldUserpic() {
  821. auto photoRect = QRect(
  822. countPhotoPosition(),
  823. QSize(_st.photoSize, _st.photoSize)
  824. );
  825. _oldUserpic = GrabWidget(this, photoRect);
  826. }
  827. void UserpicButton::startNewPhotoShowing() {
  828. const auto oldUniqueKey = _userpicUniqueKey;
  829. prepareUserpicPixmap();
  830. update();
  831. if (_notShownYet) {
  832. return;
  833. } else if (oldUniqueKey != _userpicUniqueKey
  834. || _a_appearance.animating()) {
  835. startAnimation();
  836. }
  837. }
  838. void UserpicButton::startAnimation() {
  839. _a_appearance.stop();
  840. _a_appearance.start([this] { update(); }, 0, 1, _st.duration);
  841. }
  842. void UserpicButton::switchChangePhotoOverlay(
  843. bool enabled,
  844. Fn<void(ChosenImage)> chosen) {
  845. Expects(_role == Role::OpenPhoto);
  846. if (_changeOverlayEnabled != enabled) {
  847. _changeOverlayEnabled = enabled;
  848. if (enabled) {
  849. if (isOver()) {
  850. startChangeOverlayAnimation();
  851. }
  852. updateCursorInChangeOverlay(
  853. mapFromGlobal(QCursor::pos()));
  854. if (chosen) {
  855. chosenImages() | rpl::start_with_next(chosen, lifetime());
  856. }
  857. } else {
  858. _changeOverlayShown.stop();
  859. update();
  860. }
  861. }
  862. }
  863. void UserpicButton::forceForumShape(bool force) {
  864. _forceForumShape = force;
  865. prepare();
  866. }
  867. void UserpicButton::showSavedMessagesOnSelf(bool enabled) {
  868. if (_showSavedMessagesOnSelf != enabled) {
  869. _showSavedMessagesOnSelf = enabled;
  870. update();
  871. }
  872. }
  873. bool UserpicButton::showSavedMessages() const {
  874. return _showSavedMessagesOnSelf && _peer && _peer->isSelf();
  875. }
  876. bool UserpicButton::showRepliesMessages() const {
  877. return _showSavedMessagesOnSelf && _peer && _peer->isRepliesChat();
  878. }
  879. void UserpicButton::startChangeOverlayAnimation() {
  880. auto over = isOver() || isDown();
  881. _changeOverlayShown.start(
  882. [this] { update(); },
  883. over ? 0. : 1.,
  884. over ? 1. : 0.,
  885. st::slideWrapDuration);
  886. update();
  887. }
  888. void UserpicButton::onStateChanged(
  889. State was,
  890. StateChangeSource source) {
  891. RippleButton::onStateChanged(was, source);
  892. if (_changeOverlayEnabled) {
  893. auto mask = (StateFlag::Over | StateFlag::Down);
  894. auto wasOver = (was & mask) != 0;
  895. auto nowOver = (state() & mask) != 0;
  896. if (wasOver != nowOver) {
  897. startChangeOverlayAnimation();
  898. }
  899. }
  900. }
  901. void UserpicButton::showCustom(QImage &&image) {
  902. if (!_notShownYet) {
  903. grabOldUserpic();
  904. }
  905. clearStreaming();
  906. _sourceLifetime.destroy();
  907. _source = Source::Custom;
  908. _userpicHasImage = !image.isNull();
  909. if (_userpicHasImage) {
  910. auto size = QSize(_st.photoSize, _st.photoSize);
  911. auto small = image.scaled(
  912. size * style::DevicePixelRatio(),
  913. Qt::IgnoreAspectRatio,
  914. Qt::SmoothTransformation);
  915. _userpic = Ui::PixmapFromImage(useForumShape()
  916. ? Images::Round(
  917. std::move(small),
  918. Images::CornersMask(_st.photoSize
  919. * Ui::ForumUserpicRadiusMultiplier()))
  920. : Images::Circle(std::move(small)));
  921. } else {
  922. _userpic = CreateSquarePixmap(_st.photoSize, [&](Painter &p) {
  923. fillShape(p, _st.changeButton.textBg);
  924. });
  925. }
  926. _userpic.setDevicePixelRatio(style::DevicePixelRatio());
  927. _userpicUniqueKey = {};
  928. _result = std::move(image);
  929. startNewPhotoShowing();
  930. }
  931. void UserpicButton::showSource(Source source) {
  932. Expects(_peer != nullptr);
  933. Expects(source != Source::Custom); // Show this using showCustom().
  934. Expects(source == Source::PeerPhoto || _peer->isUser());
  935. if (_source != source) {
  936. clearStreaming();
  937. }
  938. _sourceLifetime.destroy();
  939. _source = source;
  940. _result = QImage();
  941. processPeerPhoto();
  942. setupPeerViewers();
  943. prepareUserpicPixmap();
  944. update();
  945. }
  946. void UserpicButton::overrideHasPersonalPhoto(bool has) {
  947. Expects(_peer && _peer->isUser());
  948. _overrideHasPersonalPhoto = has;
  949. }
  950. rpl::producer<> UserpicButton::resetPersonalRequests() const {
  951. return _resetPersonalRequests.events();
  952. }
  953. void UserpicButton::fillShape(QPainter &p, const style::color &color) const {
  954. PainterHighQualityEnabler hq(p);
  955. p.setPen(Qt::NoPen);
  956. p.setBrush(color);
  957. const auto size = _st.photoSize;
  958. if (useForumShape()) {
  959. const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
  960. p.drawRoundedRect(0, 0, size, size, radius, radius);
  961. } else {
  962. p.drawEllipse(0, 0, size, size);
  963. }
  964. }
  965. void UserpicButton::prepareUserpicPixmap() {
  966. if (_source == Source::Custom) {
  967. return;
  968. }
  969. const auto size = _st.photoSize;
  970. _userpicHasImage = _showPeerUserpic
  971. ? (_peer
  972. && (_peer->userpicCloudImage(_userpicView)
  973. || _role != Role::ChangePhoto))
  974. : (_source == Source::NonPersonalPhoto
  975. || (_source == Source::NonPersonalIfHasPersonal
  976. && hasPersonalPhotoLocally()));
  977. _userpic = CreateSquarePixmap(size, [&](Painter &p) {
  978. if (_userpicHasImage) {
  979. if (_showPeerUserpic) {
  980. if (useForumShape()) {
  981. const auto ratio = style::DevicePixelRatio();
  982. if (const auto cloud = _peer->userpicCloudImage(_userpicView)) {
  983. Ui::ValidateUserpicCache(
  984. _userpicView,
  985. cloud,
  986. nullptr,
  987. size * ratio,
  988. true);
  989. p.drawImage(QRect(0, 0, size, size), _userpicView.cached);
  990. } else {
  991. const auto empty = PeerData::GenerateUserpicImage(
  992. _peer,
  993. _userpicView,
  994. size * ratio,
  995. (size * ratio)
  996. * Ui::ForumUserpicRadiusMultiplier());
  997. p.drawImage(QRect(0, 0, size, size), empty);
  998. }
  999. } else {
  1000. _peer->paintUserpic(p, _userpicView, 0, 0, size);
  1001. }
  1002. } else if (_nonPersonalView) {
  1003. using Size = Data::PhotoSize;
  1004. if (const auto full = _nonPersonalView->image(Size::Large)) {
  1005. const auto ratio = style::DevicePixelRatio();
  1006. auto image = full->original().scaled(
  1007. QSize(size, size) * ratio,
  1008. Qt::IgnoreAspectRatio,
  1009. Qt::SmoothTransformation);
  1010. image = useForumShape()
  1011. ? Images::Round(
  1012. std::move(image),
  1013. Images::CornersMask(size
  1014. * Ui::ForumUserpicRadiusMultiplier()))
  1015. : Images::Circle(std::move(image));
  1016. image.setDevicePixelRatio(style::DevicePixelRatio());
  1017. p.drawImage(0, 0, image);
  1018. }
  1019. } else {
  1020. const auto user = _peer->asUser();
  1021. auto empty = Ui::EmptyUserpic(
  1022. Ui::EmptyUserpic::UserpicColor(_peer->colorIndex()),
  1023. ((user && user->isInaccessible())
  1024. ? Ui::EmptyUserpic::InaccessibleName()
  1025. : _peer->name()));
  1026. if (useForumShape()) {
  1027. empty.paintRounded(
  1028. p,
  1029. 0,
  1030. 0,
  1031. size,
  1032. size,
  1033. size * Ui::ForumUserpicRadiusMultiplier());
  1034. } else {
  1035. empty.paintCircle(p, 0, 0, size, size);
  1036. }
  1037. }
  1038. } else {
  1039. fillShape(p, _st.changeButton.textBg);
  1040. }
  1041. });
  1042. _userpicUniqueKey = _userpicHasImage
  1043. ? (_showPeerUserpic
  1044. ? _peer->userpicUniqueKey(_userpicView)
  1045. : _nonPersonalView
  1046. ? InMemoryKey{ _nonPersonalView->owner()->id, 0 }
  1047. : InMemoryKey{ _peer->id.value, _peer->id.value })
  1048. : InMemoryKey();
  1049. }
  1050. not_null<Ui::UserpicButton*> CreateUploadSubButton(
  1051. not_null<Ui::RpWidget*> parent,
  1052. not_null<Window::SessionController*> controller) {
  1053. const auto background = Ui::CreateChild<Ui::RpWidget>(parent.get());
  1054. const auto upload = Ui::CreateChild<Ui::UserpicButton>(
  1055. parent.get(),
  1056. &controller->window(),
  1057. Ui::UserpicButton::Role::ChoosePhoto,
  1058. st::uploadUserpicButton);
  1059. SetupSubButtonBackground(upload, background);
  1060. return upload;
  1061. }
  1062. not_null<Ui::UserpicButton*> CreateUploadSubButton(
  1063. not_null<Ui::RpWidget*> parent,
  1064. not_null<UserData*> contact,
  1065. not_null<Window::SessionController*> controller) {
  1066. const auto background = Ui::CreateChild<Ui::RpWidget>(parent.get());
  1067. const auto upload = Ui::CreateChild<Ui::UserpicButton>(
  1068. parent.get(),
  1069. controller,
  1070. contact,
  1071. Ui::UserpicButton::Role::ChoosePhoto,
  1072. Ui::UserpicButton::Source::NonPersonalIfHasPersonal,
  1073. st::uploadUserpicButton);
  1074. SetupSubButtonBackground(upload, background);
  1075. return upload;
  1076. }
  1077. } // namespace Ui