peer_short_info_box.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  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 "boxes/peers/peer_short_info_box.h"
  8. #include "base/event_filter.h"
  9. #include "core/application.h"
  10. #include "info/profile/info_profile_text.h"
  11. #include "info/profile/info_profile_values.h"
  12. #include "lang/lang_keys.h"
  13. #include "media/streaming/media_streaming_instance.h"
  14. #include "media/streaming/media_streaming_player.h"
  15. #include "ui/effects/radial_animation.h"
  16. #include "ui/image/image_prepare.h"
  17. #include "ui/painter.h"
  18. #include "ui/text/text_utilities.h"
  19. #include "ui/widgets/labels.h"
  20. #include "ui/widgets/menu/menu_add_action_callback.h"
  21. #include "ui/widgets/menu/menu_add_action_callback_factory.h"
  22. #include "ui/widgets/popup_menu.h"
  23. #include "ui/widgets/scroll_area.h"
  24. #include "ui/wrap/slide_wrap.h"
  25. #include "ui/wrap/vertical_layout.h"
  26. #include "ui/wrap/wrap.h"
  27. #include "window/window_controller.h"
  28. #include "window/window_session_controller.h"
  29. #include "styles/style_boxes.h"
  30. #include "styles/style_info.h"
  31. #include "styles/style_layers.h"
  32. #include "styles/style_menu_icons.h"
  33. namespace {
  34. using MenuCallback = Ui::Menu::MenuCallback;
  35. constexpr auto kShadowMaxAlpha = 80;
  36. constexpr auto kInactiveBarOpacity = 0.5;
  37. } // namespace
  38. struct PeerShortInfoCover::CustomLabelStyle {
  39. explicit CustomLabelStyle(const style::FlatLabel &original);
  40. style::complex_color textFg;
  41. style::FlatLabel st;
  42. float64 opacity = 1.;
  43. };
  44. struct PeerShortInfoCover::Radial {
  45. explicit Radial(Fn<void()> &&callback);
  46. void toggle(bool visible);
  47. Ui::RadialAnimation radial;
  48. Ui::Animations::Simple shownAnimation;
  49. Fn<void()> callback;
  50. base::Timer showTimer;
  51. bool shown = false;
  52. };
  53. PeerShortInfoCover::Radial::Radial(Fn<void()> &&callback)
  54. : radial(callback)
  55. , callback(callback)
  56. , showTimer([=] { toggle(true); }) {
  57. }
  58. void PeerShortInfoCover::Radial::toggle(bool visible) {
  59. if (shown == visible) {
  60. return;
  61. }
  62. shown = visible;
  63. shownAnimation.start(
  64. callback,
  65. shown ? 0. : 1.,
  66. shown ? 1. : 0.,
  67. st::fadeWrapDuration);
  68. }
  69. PeerShortInfoCover::CustomLabelStyle::CustomLabelStyle(
  70. const style::FlatLabel &original)
  71. : textFg([=, c = original.textFg]{
  72. auto result = c->c;
  73. result.setAlphaF(result.alphaF() * opacity);
  74. return result;
  75. })
  76. , st(original) {
  77. st.textFg = textFg.color();
  78. }
  79. PeerShortInfoCover::PeerShortInfoCover(
  80. not_null<QWidget*> parent,
  81. const style::ShortInfoCover &st,
  82. rpl::producer<QString> name,
  83. rpl::producer<QString> status,
  84. rpl::producer<PeerShortInfoUserpic> userpic,
  85. Fn<bool()> videoPaused)
  86. : _st(st)
  87. , _owned(parent.get())
  88. , _widget(_owned.data())
  89. , _nameStyle(std::make_unique<CustomLabelStyle>(_st.name))
  90. , _name(_widget.get(), std::move(name), _nameStyle->st)
  91. , _statusStyle(std::make_unique<CustomLabelStyle>(_st.status))
  92. , _status(_widget.get(), std::move(status), _statusStyle->st)
  93. , _roundMask(Images::CornersMask(_st.radius))
  94. , _roundMaskRetina(
  95. Images::CornersMask(_st.radius / style::DevicePixelRatio()))
  96. , _videoPaused(std::move(videoPaused)) {
  97. _widget->setCursor(_cursor);
  98. _widget->resize(_st.size, _st.size);
  99. std::move(
  100. userpic
  101. ) | rpl::start_with_next([=](PeerShortInfoUserpic &&value) {
  102. applyUserpic(std::move(value));
  103. applyAdditionalStatus(value.additionalStatus);
  104. }, lifetime());
  105. style::PaletteChanged(
  106. ) | rpl::start_with_next([=] {
  107. refreshBarImages();
  108. }, lifetime());
  109. _widget->paintRequest(
  110. ) | rpl::start_with_next([=] {
  111. auto p = QPainter(_widget.get());
  112. paint(p);
  113. }, lifetime());
  114. base::install_event_filter(_widget.get(), [=](not_null<QEvent*> e) {
  115. if (e->type() != QEvent::MouseButtonPress
  116. && e->type() != QEvent::MouseButtonDblClick) {
  117. return base::EventFilterResult::Continue;
  118. }
  119. const auto mouse = static_cast<QMouseEvent*>(e.get());
  120. const auto x = mouse->pos().x();
  121. if (mouse->button() != Qt::LeftButton) {
  122. return base::EventFilterResult::Continue;
  123. } else if (/*_index > 0 && */x < _st.size / 3) {
  124. _moveRequests.fire(-1);
  125. } else if (/*_index + 1 < _count && */x >= _st.size / 3) {
  126. _moveRequests.fire(1);
  127. }
  128. e->accept();
  129. return base::EventFilterResult::Cancel;
  130. });
  131. refreshLabelsGeometry();
  132. _roundedTopImage = QImage(
  133. QSize(_st.size, _st.radius) * style::DevicePixelRatio(),
  134. QImage::Format_ARGB32_Premultiplied);
  135. _roundedTopImage.setDevicePixelRatio(style::DevicePixelRatio());
  136. _roundedTopImage.fill(Qt::transparent);
  137. }
  138. PeerShortInfoCover::~PeerShortInfoCover() = default;
  139. not_null<Ui::RpWidget*> PeerShortInfoCover::widget() const {
  140. return _widget;
  141. }
  142. object_ptr<Ui::RpWidget> PeerShortInfoCover::takeOwned() {
  143. return std::move(_owned);
  144. }
  145. gsl::span<const QImage, 4> PeerShortInfoCover::roundMask() const {
  146. return _roundMask;
  147. }
  148. void PeerShortInfoCover::setScrollTop(int scrollTop) {
  149. _scrollTop = scrollTop;
  150. _widget->update();
  151. }
  152. rpl::producer<int> PeerShortInfoCover::moveRequests() const {
  153. return _moveRequests.events();
  154. }
  155. rpl::lifetime &PeerShortInfoCover::lifetime() {
  156. return _widget->lifetime();
  157. }
  158. void PeerShortInfoCover::paint(QPainter &p) {
  159. checkStreamedIsStarted();
  160. auto frame = currentVideoFrame();
  161. auto paused = _videoPaused && _videoPaused();
  162. if (!frame.isNull()) {
  163. frame = Images::Round(
  164. std::move(frame),
  165. _roundMaskRetina,
  166. RectPart::TopLeft | RectPart::TopRight);
  167. } else if (_userpicImage.isNull()) {
  168. auto image = QImage(
  169. _widget->size() * style::DevicePixelRatio(),
  170. QImage::Format_ARGB32_Premultiplied);
  171. image.fill(Qt::black);
  172. _userpicImage = Images::Round(
  173. std::move(image),
  174. _roundMask,
  175. RectPart::TopLeft | RectPart::TopRight);
  176. }
  177. paintCoverImage(p, frame.isNull() ? _userpicImage : frame);
  178. paintBars(p);
  179. paintShadow(p);
  180. paintRadial(p);
  181. if (_videoInstance && _videoInstance->ready() && !paused) {
  182. _videoInstance->markFrameShown();
  183. }
  184. }
  185. void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
  186. const auto roundedWidth = _st.size;
  187. const auto roundedHeight = _st.radius;
  188. const auto covered = (_st.size - _scrollTop);
  189. if (covered <= 0) {
  190. return;
  191. } else if (!_scrollTop) {
  192. p.drawImage(_widget->rect(), image);
  193. return;
  194. }
  195. const auto fill = covered - roundedHeight;
  196. const auto top = _widget->height() - fill;
  197. const auto factor = style::DevicePixelRatio();
  198. if (fill > 0) {
  199. const auto t = roundedHeight + _scrollTop;
  200. p.drawImage(
  201. QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor),
  202. image,
  203. QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor));
  204. }
  205. if (covered <= 0) {
  206. return;
  207. }
  208. const auto rounded = std::min(covered, roundedHeight);
  209. const auto from = top - rounded;
  210. auto q = QPainter(&_roundedTopImage);
  211. q.drawImage(
  212. QRect(0, 0, roundedWidth * factor, rounded * factor),
  213. image,
  214. QRect(0, _scrollTop, roundedWidth * factor, rounded * factor));
  215. q.end();
  216. _roundedTopImage = Images::Round(
  217. std::move(_roundedTopImage),
  218. _roundMask,
  219. RectPart::TopLeft | RectPart::TopRight);
  220. p.drawImage(
  221. QRect(0, from, roundedWidth, rounded),
  222. _roundedTopImage,
  223. QRect(0, 0, roundedWidth * factor, rounded * factor));
  224. }
  225. void PeerShortInfoCover::paintBars(QPainter &p) {
  226. const auto height = _st.linePadding * 2 + _st.line;
  227. const auto factor = style::DevicePixelRatio();
  228. if (_shadowTop.isNull()) {
  229. _shadowTop = Images::GenerateShadow(height, kShadowMaxAlpha, 0);
  230. _shadowTop = Images::Round(
  231. _shadowTop.scaled(QSize(_st.size, height) * factor),
  232. _roundMask,
  233. RectPart::TopLeft | RectPart::TopRight);
  234. }
  235. const auto shadowRect = QRect(0, _scrollTop, _st.size, height);
  236. p.drawImage(
  237. shadowRect,
  238. _shadowTop,
  239. QRect(0, 0, _shadowTop.width(), height * factor));
  240. const auto hiddenAt = _st.size - _st.namePosition.y();
  241. if (!_smallWidth || _scrollTop >= hiddenAt) {
  242. return;
  243. }
  244. const auto start = _st.linePadding;
  245. const auto y = _scrollTop + start;
  246. const auto skip = _st.lineSkip;
  247. const auto full = (_st.size - 2 * start - (_count - 1) * skip);
  248. const auto single = full / float64(_count);
  249. const auto masterOpacity = 1. - (_scrollTop / float64(hiddenAt));
  250. const auto inactiveOpacity = masterOpacity * kInactiveBarOpacity;
  251. for (auto i = 0; i != _count; ++i) {
  252. const auto left = start + i * (single + skip);
  253. const auto right = left + single;
  254. const auto x = qRound(left);
  255. const auto small = (qRound(right) == qRound(left) + _smallWidth);
  256. const auto width = small ? _smallWidth : _largeWidth;
  257. const auto &image = small ? _barSmall : _barLarge;
  258. const auto min = 2 * ((_st.line + 1) / 2);
  259. const auto minProgress = min / float64(width);
  260. const auto videoProgress = (_videoInstance && _videoDuration > 0);
  261. const auto progress = (i != _index)
  262. ? 0.
  263. : videoProgress
  264. ? std::max(_videoPosition / float64(_videoDuration), minProgress)
  265. : (_videoInstance ? 0. : 1.);
  266. if (progress == 1. && !videoProgress) {
  267. p.setOpacity(masterOpacity);
  268. p.drawImage(x, y, image);
  269. } else {
  270. p.setOpacity(inactiveOpacity);
  271. p.drawImage(x, y, image);
  272. if (progress > 0.) {
  273. const auto paint = qRound(progress * width);
  274. const auto right = paint / 2;
  275. const auto left = paint - right;
  276. p.setOpacity(masterOpacity);
  277. p.drawImage(
  278. QRect(x, y, left, _st.line),
  279. image,
  280. QRect(0, 0, left * factor, image.height()));
  281. p.drawImage(
  282. QRect(x + left, y, right, _st.line),
  283. image,
  284. QRect(left * factor, 0, right * factor, image.height()));
  285. }
  286. }
  287. }
  288. p.setOpacity(1.);
  289. }
  290. void PeerShortInfoCover::paintShadow(QPainter &p) {
  291. if (_shadowBottom.isNull()) {
  292. _shadowBottom = Images::GenerateShadow(
  293. _st.shadowHeight,
  294. 0,
  295. kShadowMaxAlpha);
  296. }
  297. const auto shadowTop = _st.size - _st.shadowHeight;
  298. if (_scrollTop >= shadowTop) {
  299. _name->hide();
  300. _status->hide();
  301. return;
  302. }
  303. const auto opacity = 1. - (_scrollTop / float64(shadowTop));
  304. _nameStyle->opacity = opacity;
  305. _nameStyle->textFg.refresh();
  306. _name->show();
  307. _statusStyle->opacity = opacity;
  308. _statusStyle->textFg.refresh();
  309. _status->show();
  310. p.setOpacity(opacity);
  311. const auto shadowRect = QRect(
  312. 0,
  313. shadowTop,
  314. _st.size,
  315. _st.shadowHeight);
  316. const auto factor = style::DevicePixelRatio();
  317. p.drawImage(
  318. shadowRect,
  319. _shadowBottom,
  320. QRect(
  321. 0,
  322. 0,
  323. _shadowBottom.width(),
  324. _st.shadowHeight * factor));
  325. p.setOpacity(1.);
  326. }
  327. void PeerShortInfoCover::paintRadial(QPainter &p) {
  328. const auto infinite = _videoInstance && _videoInstance->waitingShown();
  329. if (!_radial && !infinite) {
  330. return;
  331. }
  332. const auto radial = radialRect();
  333. const auto line = _st.radialAnimation.thickness;
  334. const auto arc = radial.marginsRemoved(
  335. { line, line, line, line });
  336. const auto infiniteOpacity = _videoInstance
  337. ? _videoInstance->waitingOpacity()
  338. : 0.;
  339. const auto radialState = _radial
  340. ? _radial->radial.computeState()
  341. : Ui::RadialState();
  342. if (_radial) {
  343. updateRadialState();
  344. }
  345. const auto radialOpacity = _radial
  346. ? (_radial->shownAnimation.value(_radial->shown ? 1. : 0.)
  347. * radialState.shown)
  348. : 0.;
  349. auto hq = PainterHighQualityEnabler(p);
  350. p.setOpacity(std::max(infiniteOpacity, radialOpacity));
  351. p.setPen(Qt::NoPen);
  352. p.setBrush(st::radialBg);
  353. p.drawEllipse(radial);
  354. if (radialOpacity > 0.) {
  355. p.setOpacity(radialOpacity);
  356. auto pen = _st.radialAnimation.color->p;
  357. pen.setWidth(line);
  358. pen.setCapStyle(Qt::RoundCap);
  359. p.setPen(pen);
  360. p.drawArc(arc, radialState.arcFrom, radialState.arcLength);
  361. }
  362. if (infinite) {
  363. p.setOpacity(1.);
  364. Ui::InfiniteRadialAnimation::Draw(
  365. p,
  366. _videoInstance->waitingState(),
  367. arc.topLeft(),
  368. arc.size(),
  369. _st.size,
  370. _st.radialAnimation.color,
  371. line);
  372. }
  373. }
  374. QImage PeerShortInfoCover::currentVideoFrame() const {
  375. const auto size = QSize(_st.size, _st.size);
  376. const auto request = Media::Streaming::FrameRequest{
  377. .resize = size,
  378. .outer = size,
  379. };
  380. return (_videoInstance
  381. && _videoInstance->player().ready()
  382. && !_videoInstance->player().videoSize().isEmpty())
  383. ? _videoInstance->frame(request)
  384. : QImage();
  385. }
  386. void PeerShortInfoCover::applyAdditionalStatus(const QString &status) {
  387. if (status.isEmpty()) {
  388. if (_additionalStatus) {
  389. _additionalStatus.destroy();
  390. refreshLabelsGeometry();
  391. }
  392. return;
  393. }
  394. if (_additionalStatus) {
  395. _additionalStatus->setText(status);
  396. } else {
  397. _additionalStatus.create(_widget.get(), status, _statusStyle->st);
  398. _additionalStatus->show();
  399. refreshLabelsGeometry();
  400. }
  401. }
  402. void PeerShortInfoCover::applyUserpic(PeerShortInfoUserpic &&value) {
  403. if (_index != value.index) {
  404. _index = value.index;
  405. _widget->update();
  406. }
  407. if (_count != value.count) {
  408. _count = value.count;
  409. refreshCoverCursor();
  410. refreshBarImages();
  411. _widget->update();
  412. }
  413. if (value.photo.isNull()) {
  414. const auto videoChanged = _videoInstance
  415. ? (_videoInstance->shared() != value.videoDocument)
  416. : (value.videoDocument != nullptr);
  417. auto frame = videoChanged ? currentVideoFrame() : QImage();
  418. if (!frame.isNull()) {
  419. _userpicImage = Images::Round(
  420. std::move(frame),
  421. _roundMask,
  422. RectPart::TopLeft | RectPart::TopRight);
  423. }
  424. } else if (_userpicImage.cacheKey() != value.photo.cacheKey()) {
  425. _userpicImage = std::move(value.photo);
  426. _widget->update();
  427. }
  428. if (!value.videoDocument) {
  429. clearVideo();
  430. } else if (!_videoInstance
  431. || _videoInstance->shared() != value.videoDocument) {
  432. using namespace Media::Streaming;
  433. _videoInstance = std::make_unique<Instance>(
  434. std::move(value.videoDocument),
  435. [=] { videoWaiting(); });
  436. _videoStartPosition = value.videoStartPosition;
  437. _videoInstance->lockPlayer();
  438. _videoInstance->player().updates(
  439. ) | rpl::start_with_next_error([=](Update &&update) {
  440. handleStreamingUpdate(std::move(update));
  441. }, [=](Error &&error) {
  442. handleStreamingError(std::move(error));
  443. }, _videoInstance->lifetime());
  444. if (_videoInstance->ready()) {
  445. streamingReady(base::duplicate(_videoInstance->info()));
  446. }
  447. if (!_videoInstance->valid()) {
  448. clearVideo();
  449. }
  450. }
  451. _photoLoadingProgress = value.photoLoadingProgress;
  452. updateRadialState();
  453. }
  454. void PeerShortInfoCover::updateRadialState() {
  455. const auto progress = _videoInstance ? 1. : _photoLoadingProgress;
  456. if (_radial) {
  457. _radial->radial.update(progress, (progress == 1.), crl::now());
  458. }
  459. _widget->update(radialRect());
  460. if (progress == 1.) {
  461. if (!_radial) {
  462. return;
  463. }
  464. _radial->showTimer.cancel();
  465. _radial->toggle(false);
  466. if (!_radial->shownAnimation.animating()) {
  467. _radial = nullptr;
  468. }
  469. return;
  470. } else if (!_radial) {
  471. _radial = std::make_unique<Radial>([=] { updateRadialState(); });
  472. _radial->radial.update(progress, false, crl::now());
  473. _radial->showTimer.callOnce(st::fadeWrapDuration);
  474. return;
  475. } else if (!_radial->showTimer.isActive()) {
  476. _radial->toggle(true);
  477. }
  478. }
  479. void PeerShortInfoCover::clearVideo() {
  480. _videoInstance = nullptr;
  481. _videoStartPosition = _videoPosition = _videoDuration = 0;
  482. }
  483. void PeerShortInfoCover::checkStreamedIsStarted() {
  484. if (!_videoInstance) {
  485. return;
  486. } else if (_videoInstance->paused()) {
  487. _videoInstance->resume();
  488. }
  489. if (!_videoInstance
  490. || _videoInstance->active()
  491. || _videoInstance->failed()) {
  492. return;
  493. }
  494. auto options = Media::Streaming::PlaybackOptions();
  495. options.position = _videoStartPosition;
  496. options.mode = Media::Streaming::Mode::Video;
  497. options.loop = true;
  498. _videoInstance->play(options);
  499. }
  500. void PeerShortInfoCover::handleStreamingUpdate(
  501. Media::Streaming::Update &&update) {
  502. using namespace Media::Streaming;
  503. v::match(update.data, [&](Information &update) {
  504. streamingReady(std::move(update));
  505. }, [](PreloadedVideo) {
  506. }, [&](UpdateVideo update) {
  507. _videoPosition = update.position;
  508. _widget->update();
  509. }, [](PreloadedAudio) {
  510. }, [](UpdateAudio) {
  511. }, [](WaitingForData) {
  512. }, [](SpeedEstimate) {
  513. }, [](MutedByOther) {
  514. }, [](Finished) {
  515. });
  516. }
  517. void PeerShortInfoCover::handleStreamingError(
  518. Media::Streaming::Error &&error) {
  519. //_streamedPhoto->setVideoPlaybackFailed();
  520. //_streamedPhoto = nullptr;
  521. clearVideo();
  522. }
  523. void PeerShortInfoCover::streamingReady(Media::Streaming::Information &&info) {
  524. _videoPosition = info.video.state.position;
  525. _videoDuration = info.video.state.duration;
  526. _widget->update();
  527. }
  528. void PeerShortInfoCover::refreshCoverCursor() {
  529. const auto cursor = (_count > 1)
  530. ? style::cur_pointer
  531. : style::cur_default;
  532. if (_cursor != cursor) {
  533. _cursor = cursor;
  534. _widget->setCursor(_cursor);
  535. }
  536. }
  537. void PeerShortInfoCover::refreshBarImages() {
  538. if (_count < 2) {
  539. _smallWidth = _largeWidth = 0;
  540. _barSmall = _barLarge = QImage();
  541. return;
  542. }
  543. const auto width = _st.size - 2 * _st.linePadding;
  544. _smallWidth = (width - (_count - 1) * _st.lineSkip) / _count;
  545. if (_smallWidth < _st.line) {
  546. _smallWidth = _largeWidth = 0;
  547. _barSmall = _barLarge = QImage();
  548. return;
  549. }
  550. _largeWidth = _smallWidth + 1;
  551. const auto makeBar = [&](int size) {
  552. const auto radius = _st.line / 2.;
  553. auto result = QImage(
  554. QSize(size, _st.line) * style::DevicePixelRatio(),
  555. QImage::Format_ARGB32_Premultiplied);
  556. result.setDevicePixelRatio(style::DevicePixelRatio());
  557. result.fill(Qt::transparent);
  558. auto p = QPainter(&result);
  559. auto hq = PainterHighQualityEnabler(p);
  560. p.setPen(Qt::NoPen);
  561. p.setBrush(st::groupCallVideoTextFg);
  562. p.drawRoundedRect(0, 0, size, _st.line, radius, radius);
  563. p.end();
  564. return result;
  565. };
  566. _barSmall = makeBar(_smallWidth);
  567. _barLarge = makeBar(_largeWidth);
  568. }
  569. void PeerShortInfoCover::refreshLabelsGeometry() {
  570. const auto statusTop = _st.size
  571. - _st.statusPosition.y()
  572. - _status->height();
  573. const auto diff = _st.namePosition.y()
  574. - _name->height()
  575. - _st.statusPosition.y();
  576. if (_additionalStatus) {
  577. _additionalStatus->moveToLeft(
  578. _status->x(),
  579. statusTop - diff - _additionalStatus->height());
  580. }
  581. _name->moveToLeft(
  582. _st.namePosition.x(),
  583. _st.size
  584. - _st.namePosition.y()
  585. - _name->height()
  586. - (_additionalStatus ? (diff + _additionalStatus->height()) : 0),
  587. _st.size);
  588. _status->moveToLeft(_st.statusPosition.x(), statusTop, _st.size);
  589. }
  590. QRect PeerShortInfoCover::radialRect() const {
  591. const auto cover = _widget->rect();
  592. const auto size = st::boxLoadingSize;
  593. return QRect(
  594. cover.x() + (cover.width() - size) / 2,
  595. cover.y() + (cover.height() - size) / 2,
  596. size,
  597. size);
  598. }
  599. void PeerShortInfoCover::videoWaiting() {
  600. if (!anim::Disabled()) {
  601. _widget->update(radialRect());
  602. }
  603. }
  604. PeerShortInfoBox::PeerShortInfoBox(
  605. QWidget*,
  606. PeerShortInfoType type,
  607. rpl::producer<PeerShortInfoFields> fields,
  608. rpl::producer<QString> status,
  609. rpl::producer<PeerShortInfoUserpic> userpic,
  610. Fn<bool()> videoPaused,
  611. const style::ShortInfoBox *stOverride)
  612. : _st(stOverride ? *stOverride : st::shortInfoBox)
  613. , _type(type)
  614. , _fields(std::move(fields))
  615. , _topRoundBackground(this)
  616. , _scroll(this, st::shortInfoScroll)
  617. , _rows(
  618. _scroll->setOwnedWidget(
  619. object_ptr<Ui::VerticalLayout>(
  620. _scroll.data())))
  621. , _cover(
  622. _rows.get(),
  623. st::shortInfoCover,
  624. nameValue(),
  625. std::move(status),
  626. std::move(userpic),
  627. std::move(videoPaused)) {
  628. _rows->add(_cover.takeOwned());
  629. _scroll->scrolls(
  630. ) | rpl::start_with_next([=] {
  631. _cover.setScrollTop(_scroll->scrollTop());
  632. }, _cover.lifetime());
  633. }
  634. PeerShortInfoBox::~PeerShortInfoBox() = default;
  635. rpl::producer<> PeerShortInfoBox::openRequests() const {
  636. return _openRequests.events();
  637. }
  638. rpl::producer<int> PeerShortInfoBox::moveRequests() const {
  639. return _cover.moveRequests();
  640. }
  641. void PeerShortInfoBox::prepare() {
  642. addButton(tr::lng_close(), [=] { closeBox(); });
  643. if (_type != PeerShortInfoType::Self) {
  644. // Perhaps a new lang key should be added for opening a group.
  645. addLeftButton(
  646. (_type == PeerShortInfoType::User)
  647. ? tr::lng_profile_send_message()
  648. : (_type == PeerShortInfoType::Group)
  649. ? tr::lng_view_button_group()
  650. : tr::lng_profile_view_channel(),
  651. [=] { _openRequests.fire({}); });
  652. }
  653. prepareRows();
  654. setNoContentMargin(true);
  655. _topRoundBackground->resize(st::shortInfoWidth, st::boxRadius);
  656. _topRoundBackground->paintRequest(
  657. ) | rpl::start_with_next([=] {
  658. if (const auto use = fillRoundedTopHeight()) {
  659. const auto width = _topRoundBackground->width();
  660. const auto top = _topRoundBackground->height() - use;
  661. const auto factor = style::DevicePixelRatio();
  662. QPainter(_topRoundBackground.data()).drawImage(
  663. QRect(0, top, width, use),
  664. _roundedTop,
  665. QRect(0, top * factor, width * factor, use * factor));
  666. }
  667. }, _topRoundBackground->lifetime());
  668. _roundedTop = QImage(
  669. _topRoundBackground->size() * style::DevicePixelRatio(),
  670. QImage::Format_ARGB32_Premultiplied);
  671. _roundedTop.setDevicePixelRatio(style::DevicePixelRatio());
  672. refreshRoundedTopImage(getDelegate()->style().bg->c);
  673. setCustomCornersFilling(RectPart::FullTop);
  674. setDimensionsToContent(st::shortInfoWidth, _rows);
  675. }
  676. void PeerShortInfoBox::prepareRows() {
  677. using namespace Info::Profile;
  678. auto addInfoLineGeneric = [&](
  679. rpl::producer<QString> &&label,
  680. rpl::producer<TextWithEntities> &&text,
  681. const style::FlatLabel &textSt) {
  682. auto line = CreateTextWithLabel(
  683. _rows,
  684. rpl::duplicate(label) | Ui::Text::ToWithEntities(),
  685. rpl::duplicate(text),
  686. _st.label,
  687. textSt,
  688. st::shortInfoLabeledPadding);
  689. _rows->add(object_ptr<Ui::OverrideMargins>(
  690. _rows.get(),
  691. std::move(line.wrap)));
  692. rpl::combine(
  693. std::move(label),
  694. std::move(text)
  695. ) | rpl::start_with_next([=] {
  696. _rows->resizeToWidth(st::shortInfoWidth);
  697. }, _rows->lifetime());
  698. //line.text->setClickHandlerFilter(infoClickFilter);
  699. return line.text;
  700. };
  701. auto addInfoLine = [&](
  702. rpl::producer<QString> &&label,
  703. rpl::producer<TextWithEntities> &&text,
  704. const style::FlatLabel &textSt) {
  705. return addInfoLineGeneric(
  706. std::move(label),
  707. std::move(text),
  708. textSt);
  709. };
  710. auto addInfoOneLine = [&](
  711. rpl::producer<QString> &&label,
  712. rpl::producer<TextWithEntities> &&text,
  713. const QString &contextCopyText) {
  714. auto result = addInfoLine(
  715. std::move(label),
  716. std::move(text),
  717. _st.labeledOneLine);
  718. result->setDoubleClickSelectsParagraph(true);
  719. result->setContextCopyText(contextCopyText);
  720. return result;
  721. };
  722. addInfoOneLine(
  723. tr::lng_settings_channel_label(),
  724. channelValue(),
  725. tr::lng_context_copy_link(tr::now));
  726. addInfoOneLine(
  727. tr::lng_info_link_label(),
  728. linkValue(),
  729. tr::lng_context_copy_link(tr::now));
  730. addInfoOneLine(
  731. tr::lng_info_mobile_label(),
  732. phoneValue() | Ui::Text::ToWithEntities(),
  733. tr::lng_profile_copy_phone(tr::now));
  734. auto label = _fields.current().isBio
  735. ? tr::lng_info_bio_label()
  736. : tr::lng_info_about_label();
  737. addInfoLine(std::move(label), aboutValue(), _st.labeled);
  738. addInfoOneLine(
  739. tr::lng_info_username_label(),
  740. usernameValue() | Ui::Text::ToWithEntities(),
  741. tr::lng_context_copy_mention(tr::now));
  742. addInfoOneLine(
  743. birthdayLabel(),
  744. birthdayValue() | Ui::Text::ToWithEntities(),
  745. tr::lng_mediaview_copy(tr::now));
  746. }
  747. void PeerShortInfoBox::resizeEvent(QResizeEvent *e) {
  748. BoxContent::resizeEvent(e);
  749. _rows->resizeToWidth(st::shortInfoWidth);
  750. _scroll->resize(st::shortInfoWidth, height());
  751. _scroll->move(0, 0);
  752. _topRoundBackground->move(0, 0);
  753. }
  754. int PeerShortInfoBox::fillRoundedTopHeight() {
  755. const auto roundedHeight = _topRoundBackground->height();
  756. const auto scrollTop = _scroll->scrollTop();
  757. const auto covered = (st::shortInfoWidth - scrollTop);
  758. if (covered >= roundedHeight) {
  759. return 0;
  760. }
  761. const auto &color = getDelegate()->style().bg->c;
  762. if (_roundedTopColor != color) {
  763. refreshRoundedTopImage(color);
  764. }
  765. return roundedHeight - covered;
  766. }
  767. void PeerShortInfoBox::refreshRoundedTopImage(const QColor &color) {
  768. _roundedTopColor = color;
  769. _roundedTop.fill(color);
  770. _roundedTop = Images::Round(
  771. std::move(_roundedTop),
  772. _cover.roundMask(),
  773. RectPart::TopLeft | RectPart::TopRight);
  774. }
  775. rpl::producer<MenuCallback> PeerShortInfoBox::fillMenuRequests() const {
  776. return _fillMenuRequests.events();
  777. }
  778. void PeerShortInfoBox::contextMenuEvent(QContextMenuEvent *e) {
  779. _menuHolder = nullptr;
  780. const auto menu = Ui::CreateChild<Ui::PopupMenu>(
  781. this,
  782. st::popupMenuWithIcons);
  783. _fillMenuRequests.fire(Ui::Menu::CreateAddActionCallback(menu));
  784. _menuHolder.reset(menu);
  785. if (menu->empty()) {
  786. _menuHolder = nullptr;
  787. return;
  788. }
  789. menu->popup(e->globalPos());
  790. }
  791. rpl::producer<QString> PeerShortInfoBox::nameValue() const {
  792. return _fields.value(
  793. ) | rpl::map([](const PeerShortInfoFields &fields) {
  794. return fields.name;
  795. }) | rpl::distinct_until_changed();
  796. }
  797. rpl::producer<TextWithEntities> PeerShortInfoBox::channelValue() const {
  798. return _fields.value(
  799. ) | rpl::map([](const PeerShortInfoFields &fields) {
  800. return Ui::Text::Link(fields.channelName, fields.channelLink);
  801. }) | rpl::distinct_until_changed();
  802. }
  803. rpl::producer<TextWithEntities> PeerShortInfoBox::linkValue() const {
  804. return _fields.value(
  805. ) | rpl::map([](const PeerShortInfoFields &fields) {
  806. return Ui::Text::Link(fields.link, fields.link);
  807. }) | rpl::distinct_until_changed();
  808. }
  809. rpl::producer<QString> PeerShortInfoBox::phoneValue() const {
  810. return _fields.value(
  811. ) | rpl::map([](const PeerShortInfoFields &fields) {
  812. return fields.phone;
  813. }) | rpl::distinct_until_changed();
  814. }
  815. rpl::producer<QString> PeerShortInfoBox::usernameValue() const {
  816. return _fields.value(
  817. ) | rpl::map([](const PeerShortInfoFields &fields) {
  818. return fields.username;
  819. }) | rpl::distinct_until_changed();
  820. }
  821. rpl::producer<QString> PeerShortInfoBox::birthdayLabel() const {
  822. return Info::Profile::BirthdayLabelText(_fields.value(
  823. ) | rpl::map([](const PeerShortInfoFields &fields) {
  824. return fields.birthday;
  825. }) | rpl::distinct_until_changed());
  826. }
  827. rpl::producer<QString> PeerShortInfoBox::birthdayValue() const {
  828. return Info::Profile::BirthdayValueText(_fields.value(
  829. ) | rpl::map([](const PeerShortInfoFields &fields) {
  830. return fields.birthday;
  831. }) | rpl::distinct_until_changed());
  832. }
  833. rpl::producer<TextWithEntities> PeerShortInfoBox::aboutValue() const {
  834. return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
  835. return fields.about;
  836. }) | rpl::distinct_until_changed();
  837. }