calls_group_viewport.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  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 "calls/group/calls_group_viewport.h"
  8. #include "calls/group/calls_group_viewport_tile.h"
  9. #include "calls/group/calls_group_viewport_opengl.h"
  10. #include "calls/group/calls_group_viewport_raster.h"
  11. #include "calls/group/calls_group_common.h"
  12. #include "calls/group/calls_group_call.h"
  13. #include "calls/group/calls_group_members_row.h"
  14. #include "media/view/media_view_pip.h"
  15. #include "base/platform/base_platform_info.h"
  16. #include "webrtc/webrtc_video_track.h"
  17. #include "ui/integration.h"
  18. #include "ui/painter.h"
  19. #include "ui/abstract_button.h"
  20. #include "ui/gl/gl_surface.h"
  21. #include "ui/effects/animations.h"
  22. #include "ui/effects/cross_line.h"
  23. #include "data/data_group_call.h" // MuteButtonTooltip.
  24. #include "lang/lang_keys.h"
  25. #include "styles/style_calls.h"
  26. #include <QtGui/QtEvents>
  27. #include <QOpenGLShader>
  28. namespace Calls::Group {
  29. namespace {
  30. [[nodiscard]] QRect InterpolateRect(QRect a, QRect b, float64 ratio) {
  31. const auto left = anim::interpolate(a.x(), b.x(), ratio);
  32. const auto top = anim::interpolate(a.y(), b.y(), ratio);
  33. const auto right = anim::interpolate(
  34. a.x() + a.width(),
  35. b.x() + b.width(),
  36. ratio);
  37. const auto bottom = anim::interpolate(
  38. a.y() + a.height(),
  39. b.y() + b.height(),
  40. ratio);
  41. return { left, top, right - left, bottom - top };
  42. }
  43. } // namespace
  44. Viewport::Viewport(
  45. not_null<QWidget*> parent,
  46. PanelMode mode,
  47. Ui::GL::Backend backend)
  48. : _mode(mode)
  49. , _content(Ui::GL::CreateSurface(parent, chooseRenderer(backend))) {
  50. setup();
  51. }
  52. Viewport::~Viewport() = default;
  53. not_null<QWidget*> Viewport::widget() const {
  54. return _content->rpWidget();
  55. }
  56. not_null<Ui::RpWidgetWrap*> Viewport::rp() const {
  57. return _content.get();
  58. }
  59. void Viewport::setup() {
  60. const auto raw = widget();
  61. raw->resize(0, 0);
  62. raw->setAttribute(Qt::WA_OpaquePaintEvent);
  63. raw->setMouseTracking(true);
  64. _content->sizeValue(
  65. ) | rpl::filter([=] {
  66. return wide();
  67. }) | rpl::start_with_next([=] {
  68. updateTilesGeometry();
  69. }, lifetime());
  70. _content->events(
  71. ) | rpl::start_with_next([=](not_null<QEvent*> e) {
  72. const auto type = e->type();
  73. if (type == QEvent::Enter) {
  74. Ui::Integration::Instance().registerLeaveSubscription(raw);
  75. _mouseInside = true;
  76. } else if (type == QEvent::Leave) {
  77. Ui::Integration::Instance().unregisterLeaveSubscription(raw);
  78. setSelected({});
  79. _mouseInside = false;
  80. } else if (type == QEvent::MouseButtonPress) {
  81. handleMousePress(
  82. static_cast<QMouseEvent*>(e.get())->pos(),
  83. static_cast<QMouseEvent*>(e.get())->button());
  84. } else if (type == QEvent::MouseButtonRelease) {
  85. handleMouseRelease(
  86. static_cast<QMouseEvent*>(e.get())->pos(),
  87. static_cast<QMouseEvent*>(e.get())->button());
  88. } else if (type == QEvent::MouseMove) {
  89. handleMouseMove(static_cast<QMouseEvent*>(e.get())->pos());
  90. }
  91. }, lifetime());
  92. }
  93. void Viewport::setGeometry(bool fullscreen, QRect geometry) {
  94. Expects(wide());
  95. const auto changed = (_fullscreen != fullscreen);
  96. if (changed) {
  97. _fullscreen = fullscreen;
  98. }
  99. if (widget()->geometry() != geometry) {
  100. _geometryStaleAfterModeChange = false;
  101. widget()->setGeometry(geometry);
  102. } else if (_geometryStaleAfterModeChange || changed) {
  103. _geometryStaleAfterModeChange = false;
  104. updateTilesGeometry();
  105. }
  106. }
  107. void Viewport::resizeToWidth(int width) {
  108. Expects(!wide());
  109. updateTilesGeometry(width);
  110. }
  111. void Viewport::setScrollTop(int scrollTop) {
  112. if (_scrollTop == scrollTop) {
  113. return;
  114. }
  115. _scrollTop = scrollTop;
  116. updateTilesGeometry();
  117. }
  118. bool Viewport::wide() const {
  119. return (_mode == PanelMode::Wide);
  120. }
  121. void Viewport::setMode(PanelMode mode, not_null<QWidget*> parent) {
  122. if (_mode == mode && widget()->parent() == parent) {
  123. return;
  124. }
  125. _mode = mode;
  126. _scrollTop = 0;
  127. setControlsShown(1.);
  128. if (widget()->parent() != parent) {
  129. const auto hidden = widget()->isHidden();
  130. widget()->setParent(parent);
  131. if (!hidden) {
  132. widget()->show();
  133. }
  134. }
  135. if (!wide()) {
  136. for (const auto &tile : _tiles) {
  137. tile->toggleTopControlsShown(false);
  138. }
  139. } else if (_selected.tile) {
  140. _selected.tile->toggleTopControlsShown(true);
  141. }
  142. }
  143. void Viewport::handleMousePress(QPoint position, Qt::MouseButton button) {
  144. handleMouseMove(position);
  145. setPressed(_selected);
  146. }
  147. void Viewport::handleMouseRelease(QPoint position, Qt::MouseButton button) {
  148. handleMouseMove(position);
  149. const auto pressed = _pressed;
  150. setPressed({});
  151. if (const auto tile = pressed.tile) {
  152. if (pressed == _selected) {
  153. if (button == Qt::RightButton) {
  154. tile->row()->showContextMenu();
  155. } else if (!wide()
  156. || (_hasTwoOrMore && !_large)
  157. || pressed.element != Selection::Element::PinButton) {
  158. _clicks.fire_copy(tile->endpoint());
  159. } else if (pressed.element == Selection::Element::PinButton) {
  160. _pinToggles.fire(!tile->pinned());
  161. }
  162. }
  163. }
  164. }
  165. void Viewport::handleMouseMove(QPoint position) {
  166. updateSelected(position);
  167. }
  168. void Viewport::updateSelected(QPoint position) {
  169. if (!widget()->rect().contains(position)) {
  170. setSelected({});
  171. return;
  172. }
  173. for (const auto &tile : _tiles) {
  174. const auto geometry = tile->visible()
  175. ? tile->geometry()
  176. : QRect();
  177. if (geometry.contains(position)) {
  178. const auto pin = wide()
  179. && tile->pinOuter().contains(position - geometry.topLeft());
  180. const auto back = wide()
  181. && tile->backOuter().contains(position - geometry.topLeft());
  182. setSelected({
  183. .tile = tile.get(),
  184. .element = (pin
  185. ? Selection::Element::PinButton
  186. : back
  187. ? Selection::Element::BackButton
  188. : Selection::Element::Tile),
  189. });
  190. return;
  191. }
  192. }
  193. setSelected({});
  194. }
  195. void Viewport::updateSelected() {
  196. updateSelected(widget()->mapFromGlobal(QCursor::pos()));
  197. }
  198. void Viewport::setControlsShown(float64 shown) {
  199. _controlsShownRatio = shown;
  200. widget()->update();
  201. }
  202. void Viewport::setCursorShown(bool shown) {
  203. if (_cursorHidden == shown) {
  204. _cursorHidden = !shown;
  205. updateCursor();
  206. }
  207. }
  208. void Viewport::add(
  209. const VideoEndpoint &endpoint,
  210. VideoTileTrack track,
  211. rpl::producer<QSize> trackSize,
  212. rpl::producer<bool> pinned,
  213. bool self) {
  214. _tiles.push_back(std::make_unique<VideoTile>(
  215. endpoint,
  216. track,
  217. std::move(trackSize),
  218. std::move(pinned),
  219. [=] { widget()->update(); },
  220. self));
  221. _tiles.back()->trackSizeValue(
  222. ) | rpl::filter([](QSize size) {
  223. return !size.isEmpty();
  224. }) | rpl::start_with_next([=] {
  225. updateTilesGeometry();
  226. }, _tiles.back()->lifetime());
  227. _tiles.back()->track()->stateValue(
  228. ) | rpl::start_with_next([=] {
  229. updateTilesGeometry();
  230. }, _tiles.back()->lifetime());
  231. }
  232. void Viewport::remove(const VideoEndpoint &endpoint) {
  233. const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
  234. if (i == end(_tiles)) {
  235. return;
  236. }
  237. const auto removing = i->get();
  238. const auto largeRemoved = (_large == removing);
  239. if (largeRemoved) {
  240. prepareLargeChangeAnimation();
  241. _large = nullptr;
  242. }
  243. if (_selected.tile == removing) {
  244. setSelected({});
  245. }
  246. if (_pressed.tile == removing) {
  247. setPressed({});
  248. }
  249. for (auto &geometry : _startTilesLayout.list) {
  250. if (geometry.tile == removing) {
  251. geometry.tile = nullptr;
  252. }
  253. }
  254. for (auto &geometry : _finishTilesLayout.list) {
  255. if (geometry.tile == removing) {
  256. geometry.tile = nullptr;
  257. }
  258. }
  259. _tiles.erase(i);
  260. if (largeRemoved) {
  261. startLargeChangeAnimation();
  262. } else {
  263. updateTilesGeometry();
  264. }
  265. }
  266. void Viewport::prepareLargeChangeAnimation() {
  267. if (!wide()) {
  268. return;
  269. } else if (_largeChangeAnimation.animating()) {
  270. updateTilesAnimated();
  271. const auto field = _finishTilesLayout.useColumns
  272. ? &Geometry::columns
  273. : &Geometry::rows;
  274. for (auto &finish : _finishTilesLayout.list) {
  275. const auto tile = finish.tile;
  276. if (!tile) {
  277. continue;
  278. }
  279. finish.*field = tile->geometry();
  280. }
  281. _startTilesLayout = std::move(_finishTilesLayout);
  282. _largeChangeAnimation.stop();
  283. _startTilesLayout.list.erase(
  284. ranges::remove(_startTilesLayout.list, nullptr, &Geometry::tile),
  285. end(_startTilesLayout.list));
  286. } else {
  287. _startTilesLayout = applyLarge(std::move(_startTilesLayout));
  288. }
  289. }
  290. void Viewport::startLargeChangeAnimation() {
  291. Expects(!_largeChangeAnimation.animating());
  292. if (!wide()
  293. || anim::Disabled()
  294. || (_startTilesLayout.list.size() < 2)
  295. || !_opengl
  296. || widget()->size().isEmpty()) {
  297. updateTilesGeometry();
  298. return;
  299. }
  300. _finishTilesLayout = applyLarge(
  301. countWide(widget()->width(), widget()->height()));
  302. if (_finishTilesLayout.list.empty()
  303. || _finishTilesLayout.outer != _startTilesLayout.outer) {
  304. updateTilesGeometry();
  305. return;
  306. }
  307. _largeChangeAnimation.start(
  308. [=] { updateTilesAnimated(); },
  309. 0.,
  310. 1.,
  311. st::slideDuration);
  312. }
  313. Viewport::Layout Viewport::applyLarge(Layout layout) const {
  314. auto &list = layout.list;
  315. if (!_large) {
  316. return layout;
  317. }
  318. const auto i = ranges::find(list, _large, &Geometry::tile);
  319. if (i == end(list)) {
  320. return layout;
  321. }
  322. const auto field = layout.useColumns
  323. ? &Geometry::columns
  324. : &Geometry::rows;
  325. const auto fullWidth = layout.outer.width();
  326. const auto fullHeight = layout.outer.height();
  327. const auto largeRect = (*i).*field;
  328. const auto largeLeft = largeRect.x();
  329. const auto largeTop = largeRect.y();
  330. const auto largeRight = largeLeft + largeRect.width();
  331. const auto largeBottom = largeTop + largeRect.height();
  332. for (auto &geometry : list) {
  333. if (geometry.tile == _large) {
  334. geometry.*field = { QPoint(), layout.outer };
  335. } else if (layout.useColumns) {
  336. auto &rect = geometry.columns;
  337. const auto center = rect.center();
  338. if (center.x() < largeLeft) {
  339. rect = rect.translated(-largeLeft, 0);
  340. } else if (center.x() > largeRight) {
  341. rect = rect.translated(fullWidth - largeRight, 0);
  342. } else if (center.y() < largeTop) {
  343. rect = QRect(
  344. 0,
  345. rect.y() - largeTop,
  346. fullWidth,
  347. rect.height());
  348. } else if (center.y() > largeBottom) {
  349. rect = QRect(
  350. 0,
  351. rect.y() + (fullHeight - largeBottom),
  352. fullWidth,
  353. rect.height());
  354. }
  355. } else {
  356. auto &rect = geometry.rows;
  357. const auto center = rect.center();
  358. if (center.y() < largeTop) {
  359. rect = rect.translated(0, -largeTop);
  360. } else if (center.y() > largeBottom) {
  361. rect = rect.translated(0, fullHeight - largeBottom);
  362. } else if (center.x() < largeLeft) {
  363. rect = QRect(
  364. rect.x() - largeLeft,
  365. 0,
  366. rect.width(),
  367. fullHeight);
  368. } else {
  369. rect = QRect(
  370. rect.x() + (fullWidth - largeRight),
  371. 0,
  372. rect.width(),
  373. fullHeight);
  374. }
  375. }
  376. }
  377. return layout;
  378. }
  379. void Viewport::updateTilesAnimated() {
  380. if (!_largeChangeAnimation.animating()) {
  381. updateTilesGeometry();
  382. return;
  383. }
  384. const auto ratio = _largeChangeAnimation.value(1.);
  385. const auto field = _finishTilesLayout.useColumns
  386. ? &Geometry::columns
  387. : &Geometry::rows;
  388. for (const auto &finish : _finishTilesLayout.list) {
  389. const auto tile = finish.tile;
  390. if (!tile) {
  391. continue;
  392. }
  393. const auto i = ranges::find(
  394. _startTilesLayout.list,
  395. tile,
  396. &Geometry::tile);
  397. if (i == end(_startTilesLayout.list)) {
  398. LOG(("Tiles Animation Error 1!"));
  399. _largeChangeAnimation.stop();
  400. updateTilesGeometry();
  401. return;
  402. }
  403. const auto from = (*i).*field;
  404. const auto to = finish.*field;
  405. tile->setGeometry(
  406. InterpolateRect(from, to, ratio),
  407. TileAnimation{ from.size(), to.size(), ratio });
  408. }
  409. widget()->update();
  410. }
  411. Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
  412. auto result = Layout{ .outer = QSize(outerWidth, outerHeight) };
  413. auto &sizes = result.list;
  414. sizes.reserve(_tiles.size());
  415. for (const auto &tile : _tiles) {
  416. const auto video = tile.get();
  417. const auto size = video->trackOrUserpicSize();
  418. if (!size.isEmpty()) {
  419. sizes.push_back(Geometry{ video, size });
  420. }
  421. }
  422. if (sizes.empty()) {
  423. return result;
  424. } else if (sizes.size() == 1) {
  425. sizes.front().rows = { 0, 0, outerWidth, outerHeight };
  426. return result;
  427. }
  428. auto columnsBlack = uint64();
  429. auto rowsBlack = uint64();
  430. const auto count = int(sizes.size());
  431. const auto skip = st::groupCallVideoLargeSkip;
  432. const auto slices = int(std::ceil(std::sqrt(float64(count))));
  433. {
  434. auto index = 0;
  435. const auto columns = slices;
  436. const auto sizew = (outerWidth + skip) / float64(columns);
  437. for (auto column = 0; column != columns; ++column) {
  438. const auto left = int(base::SafeRound(column * sizew));
  439. const auto width = int(
  440. base::SafeRound(column * sizew + sizew - skip)) - left;
  441. const auto rows = int(base::SafeRound((count - index)
  442. / float64(columns - column)));
  443. const auto sizeh = (outerHeight + skip) / float64(rows);
  444. for (auto row = 0; row != rows; ++row) {
  445. const auto top = int(base::SafeRound(row * sizeh));
  446. const auto height = int(base::SafeRound(
  447. row * sizeh + sizeh - skip)) - top;
  448. auto &geometry = sizes[index];
  449. geometry.columns = {
  450. left,
  451. top,
  452. width,
  453. height };
  454. const auto scaled = geometry.size.scaled(
  455. width,
  456. height,
  457. Qt::KeepAspectRatio);
  458. columnsBlack += (scaled.width() < width)
  459. ? (width - scaled.width()) * height
  460. : (height - scaled.height()) * width;
  461. ++index;
  462. }
  463. }
  464. }
  465. {
  466. auto index = 0;
  467. const auto rows = slices;
  468. const auto sizeh = (outerHeight + skip) / float64(rows);
  469. for (auto row = 0; row != rows; ++row) {
  470. const auto top = int(base::SafeRound(row * sizeh));
  471. const auto height = int(
  472. base::SafeRound(row * sizeh + sizeh - skip)) - top;
  473. const auto columns = int(base::SafeRound((count - index)
  474. / float64(rows - row)));
  475. const auto sizew = (outerWidth + skip) / float64(columns);
  476. for (auto column = 0; column != columns; ++column) {
  477. const auto left = int(base::SafeRound(column * sizew));
  478. const auto width = int(base::SafeRound(
  479. column * sizew + sizew - skip)) - left;
  480. auto &geometry = sizes[index];
  481. geometry.rows = {
  482. left,
  483. top,
  484. width,
  485. height };
  486. const auto scaled = geometry.size.scaled(
  487. width,
  488. height,
  489. Qt::KeepAspectRatio);
  490. rowsBlack += (scaled.width() < width)
  491. ? (width - scaled.width()) * height
  492. : (height - scaled.height()) * width;
  493. ++index;
  494. }
  495. }
  496. }
  497. result.useColumns = (columnsBlack < rowsBlack);
  498. return result;
  499. }
  500. void Viewport::showLarge(const VideoEndpoint &endpoint) {
  501. // If a video get's switched off, GroupCall first unpins it,
  502. // then removes it from Large endpoint, then removes from active tracks.
  503. //
  504. // If we want to animate large video removal properly, we need to
  505. // delay this update and start animation directly from removing of the
  506. // track from the active list. Otherwise final state won't be correct.
  507. _updateLargeScheduled = [=] {
  508. const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
  509. const auto large = (i != end(_tiles)) ? i->get() : nullptr;
  510. if (_large != large) {
  511. prepareLargeChangeAnimation();
  512. _large = large;
  513. updateTopControlsVisibility();
  514. startLargeChangeAnimation();
  515. }
  516. Ensures(!_large || !_large->trackOrUserpicSize().isEmpty());
  517. };
  518. crl::on_main(widget(), [=] {
  519. if (!_updateLargeScheduled) {
  520. return;
  521. }
  522. base::take(_updateLargeScheduled)();
  523. });
  524. }
  525. void Viewport::updateTilesGeometry() {
  526. updateTilesGeometry(widget()->width());
  527. }
  528. void Viewport::updateTilesGeometry(int outerWidth) {
  529. const auto mouseInside = _mouseInside.current();
  530. const auto guard = gsl::finally([&] {
  531. if (mouseInside) {
  532. updateSelected();
  533. }
  534. widget()->update();
  535. });
  536. const auto outerHeight = widget()->height();
  537. if (_tiles.empty() || !outerWidth) {
  538. _fullHeight = 0;
  539. return;
  540. }
  541. if (wide()) {
  542. updateTilesGeometryWide(outerWidth, outerHeight);
  543. refreshHasTwoOrMore();
  544. _fullHeight = 0;
  545. } else {
  546. updateTilesGeometryNarrow(outerWidth);
  547. }
  548. }
  549. void Viewport::refreshHasTwoOrMore() {
  550. auto hasTwoOrMore = false;
  551. auto oneFound = false;
  552. for (const auto &tile : _tiles) {
  553. if (!tile->trackOrUserpicSize().isEmpty()) {
  554. if (oneFound) {
  555. hasTwoOrMore = true;
  556. break;
  557. }
  558. oneFound = true;
  559. }
  560. }
  561. if (_hasTwoOrMore == hasTwoOrMore) {
  562. return;
  563. }
  564. _hasTwoOrMore = hasTwoOrMore;
  565. updateCursor();
  566. updateTopControlsVisibility();
  567. }
  568. void Viewport::updateTopControlsVisibility() {
  569. if (_selected.tile) {
  570. _selected.tile->toggleTopControlsShown(
  571. _hasTwoOrMore && wide() && _large && _large == _selected.tile);
  572. }
  573. }
  574. void Viewport::updateTilesGeometryWide(int outerWidth, int outerHeight) {
  575. if (!outerHeight) {
  576. return;
  577. } else if (_largeChangeAnimation.animating()) {
  578. if (_startTilesLayout.outer == QSize(outerWidth, outerHeight)) {
  579. return;
  580. }
  581. _largeChangeAnimation.stop();
  582. }
  583. _startTilesLayout = countWide(outerWidth, outerHeight);
  584. if (_large && !_large->trackOrUserpicSize().isEmpty()) {
  585. for (const auto &geometry : _startTilesLayout.list) {
  586. if (geometry.tile == _large) {
  587. setTileGeometry(_large, { 0, 0, outerWidth, outerHeight });
  588. } else {
  589. geometry.tile->hide();
  590. }
  591. }
  592. } else {
  593. const auto field = _startTilesLayout.useColumns
  594. ? &Geometry::columns
  595. : &Geometry::rows;
  596. for (const auto &geometry : _startTilesLayout.list) {
  597. if (const auto video = geometry.tile) {
  598. setTileGeometry(video, geometry.*field);
  599. }
  600. }
  601. }
  602. }
  603. void Viewport::updateTilesGeometryNarrow(int outerWidth) {
  604. if (outerWidth <= st::groupCallNarrowMembersWidth) {
  605. updateTilesGeometryColumn(outerWidth);
  606. return;
  607. }
  608. const auto y = -_scrollTop;
  609. auto sizes = base::flat_map<not_null<VideoTile*>, QSize>();
  610. sizes.reserve(_tiles.size());
  611. for (const auto &tile : _tiles) {
  612. const auto video = tile.get();
  613. const auto size = video->trackOrUserpicSize();
  614. if (size.isEmpty()) {
  615. video->hide();
  616. } else {
  617. sizes.emplace(video, size);
  618. }
  619. }
  620. if (sizes.empty()) {
  621. _fullHeight = 0;
  622. return;
  623. } else if (sizes.size() == 1) {
  624. const auto size = sizes.front().second;
  625. const auto heightMin = (outerWidth * 9) / 16;
  626. const auto heightMax = (outerWidth * 3) / 4;
  627. const auto scaled = size.scaled(
  628. QSize(outerWidth, heightMax),
  629. Qt::KeepAspectRatio);
  630. const auto height = std::max(scaled.height(), heightMin);
  631. const auto skip = st::groupCallVideoSmallSkip;
  632. setTileGeometry(sizes.front().first, { 0, y, outerWidth, height });
  633. _fullHeight = height + skip;
  634. return;
  635. }
  636. const auto min = (st::groupCallWidth
  637. - st::groupCallMembersMargin.left()
  638. - st::groupCallMembersMargin.right()
  639. - st::groupCallVideoSmallSkip) / 2;
  640. const auto square = (outerWidth - st::groupCallVideoSmallSkip) / 2;
  641. const auto skip = (outerWidth - 2 * square);
  642. const auto put = [&](not_null<VideoTile*> tile, int column, int row) {
  643. setTileGeometry(tile, {
  644. (column == 2) ? 0 : column ? (outerWidth - square) : 0,
  645. y + row * (min + skip),
  646. (column == 2) ? outerWidth : square,
  647. min,
  648. });
  649. };
  650. const auto rows = (sizes.size() + 1) / 2;
  651. if (sizes.size() == 3) {
  652. put(sizes.front().first, 2, 0);
  653. put((sizes.begin() + 1)->first, 0, 1);
  654. put((sizes.begin() + 2)->first, 1, 1);
  655. } else {
  656. auto row = 0;
  657. auto column = 0;
  658. for (const auto &[video, endpoint] : sizes) {
  659. put(video, column, row);
  660. if (column) {
  661. ++row;
  662. column = (row + 1 == rows && sizes.size() % 2) ? 2 : 0;
  663. } else {
  664. column = 1;
  665. }
  666. }
  667. }
  668. _fullHeight = rows * (min + skip);
  669. }
  670. void Viewport::updateTilesGeometryColumn(int outerWidth) {
  671. const auto y = -_scrollTop;
  672. auto top = 0;
  673. const auto layoutNext = [&](not_null<VideoTile*> tile) {
  674. const auto size = tile->trackOrUserpicSize();
  675. const auto shown = !size.isEmpty() && _large && tile != _large;
  676. const auto height = st::groupCallNarrowVideoHeight;
  677. if (!shown) {
  678. tile->hide();
  679. } else {
  680. setTileGeometry(tile, { 0, y + top, outerWidth, height });
  681. top += height + st::groupCallVideoSmallSkip;
  682. }
  683. };
  684. const auto topPeer = _large ? _large->row()->peer().get() : nullptr;
  685. const auto reorderNeeded = [&] {
  686. if (!topPeer) {
  687. return false;
  688. }
  689. for (const auto &tile : _tiles) {
  690. if (tile.get() != _large && tile->row()->peer() == topPeer) {
  691. return (tile.get() != _tiles.front().get())
  692. && !tile->trackOrUserpicSize().isEmpty();
  693. }
  694. }
  695. return false;
  696. }();
  697. if (reorderNeeded) {
  698. _tilesForOrder.clear();
  699. _tilesForOrder.reserve(_tiles.size());
  700. for (const auto &tile : _tiles) {
  701. _tilesForOrder.push_back(tile.get());
  702. }
  703. ranges::stable_partition(
  704. _tilesForOrder,
  705. [&](not_null<VideoTile*> tile) {
  706. return (tile->row()->peer() == topPeer);
  707. });
  708. for (const auto &tile : _tilesForOrder) {
  709. layoutNext(tile);
  710. }
  711. } else {
  712. for (const auto &tile : _tiles) {
  713. layoutNext(tile.get());
  714. }
  715. }
  716. _fullHeight = top;
  717. }
  718. void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
  719. tile->setGeometry(geometry);
  720. const auto min = std::min(geometry.width(), geometry.height());
  721. const auto kMedium = style::ConvertScale(540);
  722. const auto kSmall = style::ConvertScale(240);
  723. const auto &endpoint = tile->endpoint();
  724. const auto forceThumbnailQuality = !wide()
  725. && (ranges::count(_tiles, false, &VideoTile::hidden) > 1);
  726. const auto forceFullQuality = wide() && (tile.get() == _large);
  727. const auto quality = forceThumbnailQuality
  728. ? VideoQuality::Thumbnail
  729. : (forceFullQuality || min >= kMedium)
  730. ? VideoQuality::Full
  731. : (min >= kSmall)
  732. ? VideoQuality::Medium
  733. : VideoQuality::Thumbnail;
  734. if (tile->updateRequestedQuality(quality)) {
  735. _qualityRequests.fire(VideoQualityRequest{
  736. .endpoint = endpoint,
  737. .quality = quality,
  738. });
  739. }
  740. }
  741. void Viewport::setSelected(Selection value) {
  742. if (_selected == value) {
  743. return;
  744. }
  745. if (_selected.tile) {
  746. _selected.tile->toggleTopControlsShown(false);
  747. }
  748. _selected = value;
  749. updateTopControlsVisibility();
  750. updateCursor();
  751. }
  752. void Viewport::updateCursor() {
  753. const auto pointer = _selected.tile && (!wide() || _hasTwoOrMore);
  754. widget()->setCursor(_cursorHidden
  755. ? Qt::BlankCursor
  756. : pointer
  757. ? style::cur_pointer
  758. : style::cur_default);
  759. }
  760. void Viewport::setPressed(Selection value) {
  761. if (_pressed == value) {
  762. return;
  763. }
  764. _pressed = value;
  765. }
  766. Ui::GL::ChosenRenderer Viewport::chooseRenderer(Ui::GL::Backend backend) {
  767. _opengl = (backend == Ui::GL::Backend::OpenGL);
  768. return {
  769. .renderer = (_opengl
  770. ? std::unique_ptr<Ui::GL::Renderer>(
  771. std::make_unique<RendererGL>(this))
  772. : std::make_unique<RendererSW>(this)),
  773. .backend = backend,
  774. };
  775. }
  776. bool Viewport::requireARGB32() const {
  777. return !_opengl;
  778. }
  779. int Viewport::fullHeight() const {
  780. return _fullHeight.current();
  781. }
  782. rpl::producer<int> Viewport::fullHeightValue() const {
  783. return _fullHeight.value();
  784. }
  785. rpl::producer<bool> Viewport::pinToggled() const {
  786. return _pinToggles.events();
  787. }
  788. rpl::producer<VideoEndpoint> Viewport::clicks() const {
  789. return _clicks.events();
  790. }
  791. rpl::producer<VideoQualityRequest> Viewport::qualityRequests() const {
  792. return _qualityRequests.events();
  793. }
  794. rpl::producer<bool> Viewport::mouseInsideValue() const {
  795. return _mouseInside.value();
  796. }
  797. rpl::lifetime &Viewport::lifetime() {
  798. return _content->lifetime();
  799. }
  800. rpl::producer<QString> MuteButtonTooltip(not_null<GroupCall*> call) {
  801. //return rpl::single(std::make_tuple(
  802. // (Data::GroupCall*)nullptr,
  803. // call->scheduleDate()
  804. //)) | rpl::then(call->real(
  805. //) | rpl::map([](not_null<Data::GroupCall*> real) {
  806. // using namespace rpl::mappers;
  807. // return real->scheduleDateValue(
  808. // ) | rpl::map([=](TimeId scheduleDate) {
  809. // return std::make_tuple(real.get(), scheduleDate);
  810. // });
  811. //}) | rpl::flatten_latest(
  812. //)) | rpl::map([=](
  813. // Data::GroupCall *real,
  814. // TimeId scheduleDate) -> rpl::producer<QString> {
  815. // if (scheduleDate) {
  816. // return rpl::combine(
  817. // call->canManageValue(),
  818. // (real
  819. // ? real->scheduleStartSubscribedValue()
  820. // : rpl::single(false))
  821. // ) | rpl::map([](bool canManage, bool subscribed) {
  822. // return canManage
  823. // ? tr::lng_group_call_start_now()
  824. // : subscribed
  825. // ? tr::lng_group_call_cancel_reminder()
  826. // : tr::lng_group_call_set_reminder();
  827. // }) | rpl::flatten_latest();
  828. // }
  829. if (call->rtmp()) {
  830. return nullptr;
  831. }
  832. return call->mutedValue(
  833. ) | rpl::map([](MuteState muted) {
  834. switch (muted) {
  835. case MuteState::Active:
  836. case MuteState::PushToTalk:
  837. return tr::lng_group_call_you_are_live();
  838. case MuteState::ForceMuted:
  839. return tr::lng_group_call_tooltip_force_muted();
  840. case MuteState::RaisedHand:
  841. return tr::lng_group_call_tooltip_raised_hand();
  842. case MuteState::Muted:
  843. return tr::lng_group_call_tooltip_microphone();
  844. }
  845. Unexpected("Value in MuteState in showNiceTooltip.");
  846. }) | rpl::flatten_latest();
  847. //}) | rpl::flatten_latest();
  848. }
  849. } // namespace Calls::Group