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