mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-06 15:13:57 +02:00
Add large / small video animation.
This commit is contained in:
parent
b22363224f
commit
2fe75f8296
9 changed files with 473 additions and 152 deletions
|
@ -972,7 +972,11 @@ void GroupCall::markTrackShown(const VideoEndpoint &endpoint, bool shown) {
|
||||||
_videoStreamShownUpdates.fire_copy({ endpoint, shown });
|
_videoStreamShownUpdates.fire_copy({ endpoint, shown });
|
||||||
}
|
}
|
||||||
if (shown && changed && endpoint.type == VideoEndpointType::Screen) {
|
if (shown && changed && endpoint.type == VideoEndpointType::Screen) {
|
||||||
pinVideoEndpoint(endpoint);
|
crl::on_main(this, [=] {
|
||||||
|
if (_shownVideoTracks.contains(endpoint)) {
|
||||||
|
pinVideoEndpoint(endpoint);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1189,8 +1189,9 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||||
if (const auto real = _call->lookupReal()) {
|
if (const auto real = _call->lookupReal()) {
|
||||||
auto oneFound = false;
|
auto oneFound = false;
|
||||||
auto hasTwoOrMore = false;
|
auto hasTwoOrMore = false;
|
||||||
|
const auto &shown = _call->shownVideoTracks();
|
||||||
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
|
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
|
||||||
if (_call->shownVideoTracks().contains(endpoint)) {
|
if (shown.contains(endpoint)) {
|
||||||
if (oneFound) {
|
if (oneFound) {
|
||||||
hasTwoOrMore = true;
|
hasTwoOrMore = true;
|
||||||
break;
|
break;
|
||||||
|
@ -1202,36 +1203,36 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||||
if (participant && hasTwoOrMore) {
|
if (participant && hasTwoOrMore) {
|
||||||
const auto &large = _call->videoEndpointLarge();
|
const auto &large = _call->videoEndpointLarge();
|
||||||
const auto pinned = _call->videoEndpointPinned();
|
const auto pinned = _call->videoEndpointPinned();
|
||||||
const auto &camera = computeCameraEndpoint(participant);
|
const auto camera = VideoEndpoint{
|
||||||
const auto &screen = computeScreenEndpoint(participant);
|
VideoEndpointType::Camera,
|
||||||
if (!camera.empty()) {
|
participantPeer,
|
||||||
if (pinned && large.id == camera) {
|
computeCameraEndpoint(participant)
|
||||||
|
};
|
||||||
|
const auto screen = VideoEndpoint{
|
||||||
|
VideoEndpointType::Screen,
|
||||||
|
participantPeer,
|
||||||
|
computeScreenEndpoint(participant)
|
||||||
|
};
|
||||||
|
if (shown.contains(camera)) {
|
||||||
|
if (pinned && large == camera) {
|
||||||
result->addAction(
|
result->addAction(
|
||||||
tr::lng_group_call_context_unpin_camera(tr::now),
|
tr::lng_group_call_context_unpin_camera(tr::now),
|
||||||
[=] { _call->pinVideoEndpoint(VideoEndpoint()); });
|
[=] { _call->pinVideoEndpoint({}); });
|
||||||
} else {
|
} else {
|
||||||
result->addAction(
|
result->addAction(
|
||||||
tr::lng_group_call_context_pin_camera(tr::now),
|
tr::lng_group_call_context_pin_camera(tr::now),
|
||||||
[=] { _call->pinVideoEndpoint(VideoEndpoint{
|
[=] { _call->pinVideoEndpoint(camera); });
|
||||||
VideoEndpointType::Camera,
|
|
||||||
participantPeer,
|
|
||||||
camera });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!screen.empty()) {
|
if (shown.contains(screen)) {
|
||||||
if (pinned && large.id == screen) {
|
if (pinned && large == screen) {
|
||||||
result->addAction(
|
result->addAction(
|
||||||
tr::lng_group_call_context_unpin_screen(tr::now),
|
tr::lng_group_call_context_unpin_screen(tr::now),
|
||||||
[=] { _call->pinVideoEndpoint(VideoEndpoint()); });
|
[=] { _call->pinVideoEndpoint({}); });
|
||||||
} else {
|
} else {
|
||||||
result->addAction(
|
result->addAction(
|
||||||
tr::lng_group_call_context_pin_screen(tr::now),
|
tr::lng_group_call_context_pin_screen(tr::now),
|
||||||
[=] { _call->pinVideoEndpoint(VideoEndpoint{
|
[=] { _call->pinVideoEndpoint(screen); });
|
||||||
VideoEndpointType::Screen,
|
|
||||||
participantPeer,
|
|
||||||
screen });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1359,7 +1359,7 @@ void Panel::chooseShareScreenSource() {
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}();
|
}();
|
||||||
if (!screencastFromPeer) {
|
if (!screencastFromPeer || _call->isSharingScreen()) {
|
||||||
choose();
|
choose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <QtGui/QOpenGLShader>
|
#include <QtGui/QOpenGLShader>
|
||||||
|
|
||||||
namespace Calls::Group {
|
namespace Calls::Group {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] QRect InterpolateRect(QRect a, QRect b, float64 ratio) {
|
||||||
|
const auto left = anim::interpolate(a.x(), b.x(), ratio);
|
||||||
|
const auto top = anim::interpolate(a.y(), b.y(), ratio);
|
||||||
|
const auto right = anim::interpolate(
|
||||||
|
a.x() + a.width(),
|
||||||
|
b.x() + b.width(),
|
||||||
|
ratio);
|
||||||
|
const auto bottom = anim::interpolate(
|
||||||
|
a.y() + a.height(),
|
||||||
|
b.y() + b.height(),
|
||||||
|
ratio);
|
||||||
|
return { left, top, right - left, bottom - top };
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Viewport::Viewport(not_null<QWidget*> parent, PanelMode mode)
|
Viewport::Viewport(not_null<QWidget*> parent, PanelMode mode)
|
||||||
: _mode(mode)
|
: _mode(mode)
|
||||||
|
@ -230,7 +247,9 @@ void Viewport::remove(const VideoEndpoint &endpoint) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto removing = i->get();
|
const auto removing = i->get();
|
||||||
if (_large == removing) {
|
const auto largeRemoved = (_large == removing);
|
||||||
|
if (largeRemoved) {
|
||||||
|
prepareLargeChangeAnimation();
|
||||||
_large = nullptr;
|
_large = nullptr;
|
||||||
}
|
}
|
||||||
if (_selected.tile == removing) {
|
if (_selected.tile == removing) {
|
||||||
|
@ -239,18 +258,278 @@ void Viewport::remove(const VideoEndpoint &endpoint) {
|
||||||
if (_pressed.tile == removing) {
|
if (_pressed.tile == removing) {
|
||||||
setPressed({});
|
setPressed({});
|
||||||
}
|
}
|
||||||
|
for (auto &geometry : _startTilesLayout.list) {
|
||||||
|
if (geometry.tile == removing) {
|
||||||
|
geometry.tile = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto &geometry : _finishTilesLayout.list) {
|
||||||
|
if (geometry.tile == removing) {
|
||||||
|
geometry.tile = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
_tiles.erase(i);
|
_tiles.erase(i);
|
||||||
updateTilesGeometry();
|
if (largeRemoved) {
|
||||||
|
startLargeChangeAnimation();
|
||||||
|
} else {
|
||||||
|
updateTilesGeometry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Viewport::prepareLargeChangeAnimation() {
|
||||||
|
if (!wide()) {
|
||||||
|
return;
|
||||||
|
} else if (_largeChangeAnimation.animating()) {
|
||||||
|
updateTilesAnimated();
|
||||||
|
const auto field = _finishTilesLayout.useColumns
|
||||||
|
? &Geometry::columns
|
||||||
|
: &Geometry::rows;
|
||||||
|
for (auto &finish : _finishTilesLayout.list) {
|
||||||
|
const auto tile = finish.tile;
|
||||||
|
if (!tile) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
finish.*field = tile->geometry();
|
||||||
|
}
|
||||||
|
_startTilesLayout = std::move(_finishTilesLayout);
|
||||||
|
_largeChangeAnimation.stop();
|
||||||
|
|
||||||
|
_startTilesLayout.list.erase(
|
||||||
|
ranges::remove(_startTilesLayout.list, nullptr, &Geometry::tile),
|
||||||
|
end(_startTilesLayout.list));
|
||||||
|
} else {
|
||||||
|
_startTilesLayout = applyLarge(std::move(_startTilesLayout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Viewport::startLargeChangeAnimation() {
|
||||||
|
Expects(!_largeChangeAnimation.animating());
|
||||||
|
|
||||||
|
if (!wide()
|
||||||
|
|| anim::Disabled()
|
||||||
|
|| (_startTilesLayout.list.size() < 2)
|
||||||
|
|| !_opengl
|
||||||
|
|| widget()->size().isEmpty()) {
|
||||||
|
updateTilesGeometry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_finishTilesLayout = applyLarge(
|
||||||
|
countWide(widget()->width(), widget()->height()));
|
||||||
|
if (_finishTilesLayout.list.empty()
|
||||||
|
|| _finishTilesLayout.outer != _startTilesLayout.outer) {
|
||||||
|
updateTilesGeometry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_largeChangeAnimation.start(
|
||||||
|
[=] { updateTilesAnimated(); },
|
||||||
|
0.,
|
||||||
|
1.,
|
||||||
|
st::slideDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
Viewport::Layout Viewport::applyLarge(Layout layout) const {
|
||||||
|
auto &list = layout.list;
|
||||||
|
if (!_large) {
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
const auto i = ranges::find(list, _large, &Geometry::tile);
|
||||||
|
if (i == end(list)) {
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
const auto field = layout.useColumns
|
||||||
|
? &Geometry::columns
|
||||||
|
: &Geometry::rows;
|
||||||
|
const auto fullWidth = layout.outer.width();
|
||||||
|
const auto fullHeight = layout.outer.height();
|
||||||
|
const auto largeRect = (*i).*field;
|
||||||
|
const auto largeLeft = largeRect.x();
|
||||||
|
const auto largeTop = largeRect.y();
|
||||||
|
const auto largeRight = largeLeft + largeRect.width();
|
||||||
|
const auto largeBottom = largeTop + largeRect.height();
|
||||||
|
const auto largeCenter = largeRect.center();
|
||||||
|
for (auto &geometry : list) {
|
||||||
|
if (geometry.tile == _large) {
|
||||||
|
geometry.*field = { QPoint(), layout.outer };
|
||||||
|
} else if (layout.useColumns) {
|
||||||
|
auto &rect = geometry.columns;
|
||||||
|
const auto center = rect.center();
|
||||||
|
if (center.x() < largeLeft) {
|
||||||
|
rect = rect.translated(-largeLeft, 0);
|
||||||
|
} else if (center.x() > largeRight) {
|
||||||
|
rect = rect.translated(fullWidth - largeRight, 0);
|
||||||
|
} else if (center.y() < largeTop) {
|
||||||
|
rect = QRect(
|
||||||
|
0,
|
||||||
|
rect.y() - largeTop,
|
||||||
|
fullWidth,
|
||||||
|
rect.height());
|
||||||
|
} else if (center.y() > largeBottom) {
|
||||||
|
rect = QRect(
|
||||||
|
0,
|
||||||
|
rect.y() + (fullHeight - largeBottom),
|
||||||
|
fullWidth,
|
||||||
|
rect.height());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto &rect = geometry.rows;
|
||||||
|
const auto center = rect.center();
|
||||||
|
if (center.y() < largeTop) {
|
||||||
|
rect = rect.translated(0, -largeTop);
|
||||||
|
} else if (center.y() > largeBottom) {
|
||||||
|
rect = rect.translated(0, fullHeight - largeBottom);
|
||||||
|
} else if (center.x() < largeLeft) {
|
||||||
|
rect = QRect(
|
||||||
|
rect.x() - largeLeft,
|
||||||
|
0,
|
||||||
|
rect.width(),
|
||||||
|
fullHeight);
|
||||||
|
} else {
|
||||||
|
rect = QRect(
|
||||||
|
rect.x() + (fullWidth - largeRight),
|
||||||
|
0,
|
||||||
|
rect.width(),
|
||||||
|
fullHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Viewport::updateTilesAnimated() {
|
||||||
|
if (!_largeChangeAnimation.animating()) {
|
||||||
|
updateTilesGeometry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto ratio = _largeChangeAnimation.value(1.);
|
||||||
|
const auto field = _finishTilesLayout.useColumns
|
||||||
|
? &Geometry::columns
|
||||||
|
: &Geometry::rows;
|
||||||
|
for (const auto &finish : _finishTilesLayout.list) {
|
||||||
|
const auto tile = finish.tile;
|
||||||
|
if (!tile) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto i = ranges::find(
|
||||||
|
_startTilesLayout.list,
|
||||||
|
tile,
|
||||||
|
&Geometry::tile);
|
||||||
|
if (i == end(_startTilesLayout.list)) {
|
||||||
|
LOG(("Tiles Animation Error 1!"));
|
||||||
|
_largeChangeAnimation.stop();
|
||||||
|
updateTilesGeometry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto from = (*i).*field;
|
||||||
|
const auto to = finish.*field;
|
||||||
|
tile->setGeometry(
|
||||||
|
InterpolateRect(from, to, ratio),
|
||||||
|
TileAnimation{ from.size(), to.size(), ratio });
|
||||||
|
}
|
||||||
|
widget()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
|
||||||
|
auto result = Layout{ .outer = QSize(outerWidth, outerHeight) };
|
||||||
|
auto &sizes = result.list;
|
||||||
|
sizes.reserve(_tiles.size());
|
||||||
|
for (const auto &tile : _tiles) {
|
||||||
|
const auto video = tile.get();
|
||||||
|
const auto size = video->trackSize();
|
||||||
|
if (!size.isEmpty()) {
|
||||||
|
sizes.push_back(Geometry{ video, size });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sizes.empty()) {
|
||||||
|
return result;
|
||||||
|
} else if (sizes.size() == 1) {
|
||||||
|
sizes.front().rows = { 0, 0, outerWidth, outerHeight };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto columnsBlack = uint64();
|
||||||
|
auto rowsBlack = uint64();
|
||||||
|
const auto count = int(sizes.size());
|
||||||
|
const auto skip = st::groupCallVideoLargeSkip;
|
||||||
|
const auto slices = int(std::ceil(std::sqrt(float64(count))));
|
||||||
|
{
|
||||||
|
auto index = 0;
|
||||||
|
const auto columns = slices;
|
||||||
|
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||||
|
for (auto column = 0; column != columns; ++column) {
|
||||||
|
const auto left = int(std::round(column * sizew));
|
||||||
|
const auto width = int(std::round(column * sizew + sizew - skip))
|
||||||
|
- left;
|
||||||
|
const auto rows = int(std::round((count - index)
|
||||||
|
/ float64(columns - column)));
|
||||||
|
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||||
|
for (auto row = 0; row != rows; ++row) {
|
||||||
|
const auto top = int(std::round(row * sizeh));
|
||||||
|
const auto height = int(std::round(
|
||||||
|
row * sizeh + sizeh - skip)) - top;
|
||||||
|
auto &geometry = sizes[index];
|
||||||
|
geometry.columns = {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
height };
|
||||||
|
const auto scaled = geometry.size.scaled(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
Qt::KeepAspectRatio);
|
||||||
|
columnsBlack += (scaled.width() < width)
|
||||||
|
? (width - scaled.width()) * height
|
||||||
|
: (height - scaled.height()) * width;
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto index = 0;
|
||||||
|
const auto rows = slices;
|
||||||
|
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||||
|
for (auto row = 0; row != rows; ++row) {
|
||||||
|
const auto top = int(std::round(row * sizeh));
|
||||||
|
const auto height = int(std::round(row * sizeh + sizeh - skip))
|
||||||
|
- top;
|
||||||
|
const auto columns = int(std::round((count - index)
|
||||||
|
/ float64(rows - row)));
|
||||||
|
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||||
|
for (auto column = 0; column != columns; ++column) {
|
||||||
|
const auto left = int(std::round(column * sizew));
|
||||||
|
const auto width = int(std::round(
|
||||||
|
column * sizew + sizew - skip)) - left;
|
||||||
|
auto &geometry = sizes[index];
|
||||||
|
geometry.rows = {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
height };
|
||||||
|
const auto scaled = geometry.size.scaled(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
Qt::KeepAspectRatio);
|
||||||
|
rowsBlack += (scaled.width() < width)
|
||||||
|
? (width - scaled.width()) * height
|
||||||
|
: (height - scaled.height()) * width;
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.useColumns = (columnsBlack < rowsBlack);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::showLarge(const VideoEndpoint &endpoint) {
|
void Viewport::showLarge(const VideoEndpoint &endpoint) {
|
||||||
const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
|
const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
|
||||||
const auto large = (i != end(_tiles)) ? i->get() : nullptr;
|
const auto large = (i != end(_tiles)) ? i->get() : nullptr;
|
||||||
if (_large != large) {
|
if (_large != large) {
|
||||||
|
prepareLargeChangeAnimation();
|
||||||
_large = large;
|
_large = large;
|
||||||
updateTopControlsVisibility();
|
updateTopControlsVisibility();
|
||||||
updateTilesGeometry();
|
startLargeChangeAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ensures(!_large || !_large->trackSize().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::updateTilesGeometry() {
|
void Viewport::updateTilesGeometry() {
|
||||||
|
@ -311,111 +590,32 @@ void Viewport::updateTopControlsVisibility() {
|
||||||
void Viewport::updateTilesGeometryWide(int outerWidth, int outerHeight) {
|
void Viewport::updateTilesGeometryWide(int outerWidth, int outerHeight) {
|
||||||
if (!outerHeight) {
|
if (!outerHeight) {
|
||||||
return;
|
return;
|
||||||
|
} else if (_largeChangeAnimation.animating()) {
|
||||||
|
if (_startTilesLayout.outer == QSize(outerWidth, outerHeight)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_largeChangeAnimation.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Geometry {
|
_startTilesLayout = countWide(outerWidth, outerHeight);
|
||||||
QSize size;
|
if (_large && !_large->trackSize().isEmpty()) {
|
||||||
QRect columns;
|
for (const auto &geometry : _startTilesLayout.list) {
|
||||||
QRect rows;
|
if (geometry.tile == _large) {
|
||||||
};
|
setTileGeometry(_large, { 0, 0, outerWidth, outerHeight });
|
||||||
auto sizes = base::flat_map<not_null<VideoTile*>, Geometry>();
|
} else {
|
||||||
sizes.reserve(_tiles.size());
|
geometry.tile->hide();
|
||||||
for (const auto &tile : _tiles) {
|
|
||||||
const auto video = tile.get();
|
|
||||||
const auto size = (_large && video != _large)
|
|
||||||
? QSize()
|
|
||||||
: video->trackSize();
|
|
||||||
if (size.isEmpty()) {
|
|
||||||
setTileGeometry(video, { 0, 0, outerWidth, 0 });
|
|
||||||
} else {
|
|
||||||
sizes.emplace(video, Geometry{ size });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sizes.size() == 1) {
|
|
||||||
setTileGeometry(
|
|
||||||
sizes.front().first,
|
|
||||||
{ 0, 0, outerWidth, outerHeight });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (sizes.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto columnsBlack = uint64();
|
|
||||||
auto rowsBlack = uint64();
|
|
||||||
const auto count = int(sizes.size());
|
|
||||||
const auto skip = st::groupCallVideoLargeSkip;
|
|
||||||
const auto slices = int(std::ceil(std::sqrt(float64(count))));
|
|
||||||
{
|
|
||||||
auto index = 0;
|
|
||||||
const auto columns = slices;
|
|
||||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
|
||||||
for (auto column = 0; column != columns; ++column) {
|
|
||||||
const auto left = int(std::round(column * sizew));
|
|
||||||
const auto width = int(std::round(column * sizew + sizew - skip))
|
|
||||||
- left;
|
|
||||||
const auto rows = int(std::round((count - index)
|
|
||||||
/ float64(columns - column)));
|
|
||||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
|
||||||
for (auto row = 0; row != rows; ++row) {
|
|
||||||
const auto top = int(std::round(row * sizeh));
|
|
||||||
const auto height = int(std::round(
|
|
||||||
row * sizeh + sizeh - skip)) - top;
|
|
||||||
auto &geometry = (sizes.begin() + index)->second;
|
|
||||||
geometry.columns = {
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
width,
|
|
||||||
height };
|
|
||||||
const auto scaled = geometry.size.scaled(
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
Qt::KeepAspectRatio);
|
|
||||||
columnsBlack += (scaled.width() < width)
|
|
||||||
? (width - scaled.width()) * height
|
|
||||||
: (height - scaled.height()) * width;
|
|
||||||
++index;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
{
|
const auto field = _startTilesLayout.useColumns
|
||||||
auto index = 0;
|
? &Geometry::columns
|
||||||
const auto rows = slices;
|
: &Geometry::rows;
|
||||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
for (const auto &geometry : _startTilesLayout.list) {
|
||||||
for (auto row = 0; row != rows; ++row) {
|
if (const auto video = geometry.tile) {
|
||||||
const auto top = int(std::round(row * sizeh));
|
setTileGeometry(video, geometry.*field);
|
||||||
const auto height = int(std::round(row * sizeh + sizeh - skip))
|
|
||||||
- top;
|
|
||||||
const auto columns = int(std::round((count - index)
|
|
||||||
/ float64(rows - row)));
|
|
||||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
|
||||||
for (auto column = 0; column != columns; ++column) {
|
|
||||||
const auto left = int(std::round(column * sizew));
|
|
||||||
const auto width = int(std::round(
|
|
||||||
column * sizew + sizew - skip)) - left;
|
|
||||||
auto &geometry = (sizes.begin() + index)->second;
|
|
||||||
geometry.rows = {
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
width,
|
|
||||||
height };
|
|
||||||
const auto scaled = geometry.size.scaled(
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
Qt::KeepAspectRatio);
|
|
||||||
rowsBlack += (scaled.width() < width)
|
|
||||||
? (width - scaled.width()) * height
|
|
||||||
: (height - scaled.height()) * width;
|
|
||||||
++index;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto layout = (columnsBlack < rowsBlack)
|
|
||||||
? &Geometry::columns
|
|
||||||
: &Geometry::rows;
|
|
||||||
for (const auto &[video, geometry] : sizes) {
|
|
||||||
setTileGeometry(video, geometry.*layout);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::updateTilesGeometryNarrow(int outerWidth) {
|
void Viewport::updateTilesGeometryNarrow(int outerWidth) {
|
||||||
|
@ -431,7 +631,7 @@ void Viewport::updateTilesGeometryNarrow(int outerWidth) {
|
||||||
const auto video = tile.get();
|
const auto video = tile.get();
|
||||||
const auto size = video->trackSize();
|
const auto size = video->trackSize();
|
||||||
if (size.isEmpty()) {
|
if (size.isEmpty()) {
|
||||||
setTileGeometry(video, { 0, y, outerWidth, 0 });
|
video->hide();
|
||||||
} else {
|
} else {
|
||||||
sizes.emplace(video, size);
|
sizes.emplace(video, size);
|
||||||
}
|
}
|
||||||
|
@ -536,7 +736,6 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
|
||||||
|
|
||||||
void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
|
void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
|
||||||
tile->setGeometry(geometry);
|
tile->setGeometry(geometry);
|
||||||
tile->setShown(!geometry.isEmpty());
|
|
||||||
|
|
||||||
const auto min = std::min(geometry.width(), geometry.height());
|
const auto min = std::min(geometry.width(), geometry.height());
|
||||||
const auto kMedium = style::ConvertScale(480);
|
const auto kMedium = style::ConvertScale(480);
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
|
#include "ui/effects/animations.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class AbstractButton;
|
class AbstractButton;
|
||||||
|
@ -91,6 +92,26 @@ private:
|
||||||
class VideoTile;
|
class VideoTile;
|
||||||
class Renderer;
|
class Renderer;
|
||||||
class RendererGL;
|
class RendererGL;
|
||||||
|
using TileId = quintptr;
|
||||||
|
|
||||||
|
struct Geometry {
|
||||||
|
VideoTile *tile = nullptr;
|
||||||
|
QSize size;
|
||||||
|
QRect rows;
|
||||||
|
QRect columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Layout {
|
||||||
|
std::vector<Geometry> list;
|
||||||
|
QSize outer;
|
||||||
|
bool useColumns = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TileAnimation {
|
||||||
|
QSize from;
|
||||||
|
QSize to;
|
||||||
|
float64 ratio = -1.;
|
||||||
|
};
|
||||||
|
|
||||||
struct Selection {
|
struct Selection {
|
||||||
enum class Element {
|
enum class Element {
|
||||||
|
@ -122,6 +143,12 @@ private:
|
||||||
void refreshHasTwoOrMore();
|
void refreshHasTwoOrMore();
|
||||||
void updateTopControlsVisibility();
|
void updateTopControlsVisibility();
|
||||||
|
|
||||||
|
void prepareLargeChangeAnimation();
|
||||||
|
void startLargeChangeAnimation();
|
||||||
|
void updateTilesAnimated();
|
||||||
|
[[nodiscard]] Layout countWide(int outerWidth, int outerHeight) const;
|
||||||
|
[[nodiscard]] Layout applyLarge(Layout layout) const;
|
||||||
|
|
||||||
void setSelected(Selection value);
|
void setSelected(Selection value);
|
||||||
void setPressed(Selection value);
|
void setPressed(Selection value);
|
||||||
|
|
||||||
|
@ -149,6 +176,9 @@ private:
|
||||||
rpl::event_stream<VideoQualityRequest> _qualityRequests;
|
rpl::event_stream<VideoQualityRequest> _qualityRequests;
|
||||||
float64 _controlsShownRatio = 1.;
|
float64 _controlsShownRatio = 1.;
|
||||||
VideoTile *_large = nullptr;
|
VideoTile *_large = nullptr;
|
||||||
|
Ui::Animations::Simple _largeChangeAnimation;
|
||||||
|
Layout _startTilesLayout;
|
||||||
|
Layout _finishTilesLayout;
|
||||||
Selection _selected;
|
Selection _selected;
|
||||||
Selection _pressed;
|
Selection _pressed;
|
||||||
rpl::variable<bool> _mouseInside = false;
|
rpl::variable<bool> _mouseInside = false;
|
||||||
|
|
|
@ -124,14 +124,36 @@ vec4 background() {
|
||||||
: NonEmpty(scaled);
|
: NonEmpty(scaled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QSize InterpolateScaledSize(
|
||||||
|
QSize unscaled,
|
||||||
|
QSize size,
|
||||||
|
float64 ratio) {
|
||||||
|
if (ratio == 0.) {
|
||||||
|
return NonEmpty(unscaled.scaled(
|
||||||
|
size,
|
||||||
|
Qt::KeepAspectRatio));
|
||||||
|
} else if (ratio == 1.) {
|
||||||
|
return NonEmpty(unscaled.scaled(
|
||||||
|
size,
|
||||||
|
Qt::KeepAspectRatioByExpanding));
|
||||||
|
}
|
||||||
|
const auto notExpanded = NonEmpty(unscaled.scaled(
|
||||||
|
size,
|
||||||
|
Qt::KeepAspectRatio));
|
||||||
|
const auto expanded = NonEmpty(unscaled.scaled(
|
||||||
|
size,
|
||||||
|
Qt::KeepAspectRatioByExpanding));
|
||||||
|
return QSize(
|
||||||
|
anim::interpolate(notExpanded.width(), expanded.width(), ratio),
|
||||||
|
anim::interpolate(notExpanded.height(), expanded.height(), ratio));
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::array<std::array<GLfloat, 2>, 4> CountTexCoords(
|
[[nodiscard]] std::array<std::array<GLfloat, 2>, 4> CountTexCoords(
|
||||||
QSize unscaled,
|
QSize unscaled,
|
||||||
QSize size,
|
QSize size,
|
||||||
bool expand,
|
float64 expandRatio,
|
||||||
bool swap = false) {
|
bool swap = false) {
|
||||||
const auto scaled = NonEmpty(unscaled.scaled(
|
const auto scaled = InterpolateScaledSize(unscaled, size, expandRatio);
|
||||||
size,
|
|
||||||
expand ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio));
|
|
||||||
const auto left = (size.width() - scaled.width()) / 2;
|
const auto left = (size.width() - scaled.width()) / 2;
|
||||||
const auto top = (size.height() - scaled.height()) / 2;
|
const auto top = (size.height() - scaled.height()) / 2;
|
||||||
const auto right = left + scaled.width();
|
const auto right = left + scaled.width();
|
||||||
|
@ -457,12 +479,17 @@ void Viewport::RendererGL::paintTile(
|
||||||
data.rotation);
|
data.rotation);
|
||||||
const auto tileSize = geometry.size();
|
const auto tileSize = geometry.size();
|
||||||
const auto swap = (((data.rotation / 90) % 2) == 1);
|
const auto swap = (((data.rotation / 90) % 2) == 1);
|
||||||
const auto expand = !tile->screencast()
|
const auto expand = isExpanded(tile, unscaled, tileSize);
|
||||||
&& (!_owner->wide() || UseExpandForCamera(unscaled, tileSize));
|
const auto animation = tile->animation();
|
||||||
auto texCoords = CountTexCoords(unscaled, tileSize, expand, swap);
|
const auto expandRatio = (animation.ratio >= 0.)
|
||||||
auto blurTexCoords = expand
|
? countExpandRatio(tile, unscaled, animation)
|
||||||
|
: expand
|
||||||
|
? 1.
|
||||||
|
: 0.;
|
||||||
|
auto texCoords = CountTexCoords(unscaled, tileSize, expandRatio, swap);
|
||||||
|
auto blurTexCoords = (expandRatio == 1.)
|
||||||
? texCoords
|
? texCoords
|
||||||
: CountTexCoords(unscaled, tileSize, true);
|
: CountTexCoords(unscaled, tileSize, 1.);
|
||||||
const auto rect = transformRect(geometry);
|
const auto rect = transformRect(geometry);
|
||||||
auto toBlurTexCoords = std::array<std::array<GLfloat, 2>, 4> { {
|
auto toBlurTexCoords = std::array<std::array<GLfloat, 2>, 4> { {
|
||||||
{ { 0.f, 1.f } },
|
{ { 0.f, 1.f } },
|
||||||
|
@ -763,6 +790,29 @@ void Viewport::RendererGL::prepareObjects(
|
||||||
create(1, kFirstBlurPassTextureIndex);
|
create(1, kFirstBlurPassTextureIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Viewport::RendererGL::isExpanded(
|
||||||
|
not_null<VideoTile*> tile,
|
||||||
|
QSize unscaled,
|
||||||
|
QSize tileSize) const {
|
||||||
|
return !tile->screencast()
|
||||||
|
&& (!_owner->wide() || UseExpandForCamera(unscaled, tileSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
float64 Viewport::RendererGL::countExpandRatio(
|
||||||
|
not_null<VideoTile*> tile,
|
||||||
|
QSize unscaled,
|
||||||
|
const TileAnimation &animation) const {
|
||||||
|
const auto expandedFrom = isExpanded(tile, unscaled, animation.from);
|
||||||
|
const auto expandedTo = isExpanded(tile, unscaled, animation.to);
|
||||||
|
return (expandedFrom && expandedTo)
|
||||||
|
? 1.
|
||||||
|
: (!expandedFrom && !expandedTo)
|
||||||
|
? 0.
|
||||||
|
: expandedFrom
|
||||||
|
? (1. - animation.ratio)
|
||||||
|
: animation.ratio;
|
||||||
|
}
|
||||||
|
|
||||||
void Viewport::RendererGL::bindFrame(
|
void Viewport::RendererGL::bindFrame(
|
||||||
QOpenGLFunctions &f,
|
QOpenGLFunctions &f,
|
||||||
const Webrtc::FrameWithInfo &data,
|
const Webrtc::FrameWithInfo &data,
|
||||||
|
|
|
@ -102,6 +102,15 @@ private:
|
||||||
not_null<VideoTile*> tile,
|
not_null<VideoTile*> tile,
|
||||||
TileData &data);
|
TileData &data);
|
||||||
|
|
||||||
|
[[nodiscard]] bool isExpanded(
|
||||||
|
not_null<VideoTile*> tile,
|
||||||
|
QSize unscaled,
|
||||||
|
QSize tileSize) const;
|
||||||
|
[[nodiscard]] float64 countExpandRatio(
|
||||||
|
not_null<VideoTile*> tile,
|
||||||
|
QSize unscaled,
|
||||||
|
const TileAnimation &animation) const;
|
||||||
|
|
||||||
const not_null<Viewport*> _owner;
|
const not_null<Viewport*> _owner;
|
||||||
|
|
||||||
GLfloat _factor = 1.;
|
GLfloat _factor = 1.;
|
||||||
|
|
|
@ -58,13 +58,18 @@ bool Viewport::VideoTile::screencast() const {
|
||||||
return (_endpoint.type == VideoEndpointType::Screen);
|
return (_endpoint.type == VideoEndpointType::Screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::VideoTile::setGeometry(QRect geometry) {
|
void Viewport::VideoTile::setGeometry(
|
||||||
|
QRect geometry,
|
||||||
|
TileAnimation animation) {
|
||||||
|
_shown = true;
|
||||||
_geometry = geometry;
|
_geometry = geometry;
|
||||||
updateTopControlsGeometry();
|
_animation = animation;
|
||||||
|
updateTopControlsPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::VideoTile::setShown(bool shown) {
|
void Viewport::VideoTile::hide() {
|
||||||
_shown = shown;
|
_shown = false;
|
||||||
|
_quality = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::VideoTile::toggleTopControlsShown(bool shown) {
|
void Viewport::VideoTile::toggleTopControlsShown(bool shown) {
|
||||||
|
@ -183,27 +188,38 @@ void Viewport::VideoTile::PaintBackButton(
|
||||||
tr::lng_create_group_back(tr::now));
|
tr::lng_create_group_back(tr::now));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::VideoTile::updateTopControlsGeometry() {
|
void Viewport::VideoTile::updateTopControlsSize() {
|
||||||
const auto &st = st::groupCallVideoTile;
|
const auto &st = st::groupCallVideoTile;
|
||||||
|
|
||||||
const auto pinSize = PinInnerSize(_pinned);
|
const auto pinSize = PinInnerSize(_pinned);
|
||||||
const auto pinWidth = st.pinPosition.x() * 2 + pinSize.width();
|
const auto pinWidth = st.pinPosition.x() * 2 + pinSize.width();
|
||||||
const auto pinHeight = st.pinPosition.y() * 2 + pinSize.height();
|
const auto pinHeight = st.pinPosition.y() * 2 + pinSize.height();
|
||||||
_pinInner = QRect(QPoint(), pinSize).translated(
|
_pinInner = QRect(QPoint(), pinSize);
|
||||||
_geometry.width() - st.pinPosition.x() - pinSize.width(),
|
_pinOuter = QRect(0, 0, pinWidth, pinHeight);
|
||||||
st.pinPosition.y());
|
|
||||||
_pinOuter = QRect(
|
|
||||||
_geometry.width() - pinWidth,
|
|
||||||
0,
|
|
||||||
pinWidth,
|
|
||||||
pinHeight);
|
|
||||||
const auto backSize = BackInnerSize();
|
const auto backSize = BackInnerSize();
|
||||||
const auto backWidth = st.pinPosition.x() * 2 + backSize.width();
|
const auto backWidth = st.pinPosition.x() * 2 + backSize.width();
|
||||||
const auto backHeight = st.pinPosition.y() * 2 + backSize.height();
|
const auto backHeight = st.pinPosition.y() * 2 + backSize.height();
|
||||||
_backInner = QRect(QPoint(), backSize).translated(st.pinPosition);
|
_backInner = QRect(QPoint(), backSize);
|
||||||
_backOuter = QRect(0, 0, backWidth, backHeight);
|
_backOuter = QRect(0, 0, backWidth, backHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Viewport::VideoTile::updateTopControlsPosition() {
|
||||||
|
const auto &st = st::groupCallVideoTile;
|
||||||
|
|
||||||
|
_pinInner = QRect(
|
||||||
|
_geometry.width() - st.pinPosition.x() - _pinInner.width(),
|
||||||
|
st.pinPosition.y(),
|
||||||
|
_pinInner.width(),
|
||||||
|
_pinInner.height());
|
||||||
|
_pinOuter = QRect(
|
||||||
|
_geometry.width() - _pinOuter.width(),
|
||||||
|
0,
|
||||||
|
_pinOuter.width(),
|
||||||
|
_pinOuter.height());
|
||||||
|
_backInner = QRect(st.pinPosition, _backInner.size());
|
||||||
|
}
|
||||||
|
|
||||||
void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
|
void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
|
||||||
std::move(
|
std::move(
|
||||||
pinned
|
pinned
|
||||||
|
@ -211,8 +227,11 @@ void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
|
||||||
return (_pinned != pinned);
|
return (_pinned != pinned);
|
||||||
}) | rpl::start_with_next([=](bool pinned) {
|
}) | rpl::start_with_next([=](bool pinned) {
|
||||||
_pinned = pinned;
|
_pinned = pinned;
|
||||||
updateTopControlsGeometry();
|
updateTopControlsSize();
|
||||||
_update();
|
if (_shown) {
|
||||||
|
updateTopControlsPosition();
|
||||||
|
_update();
|
||||||
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
_track.track->renderNextFrame(
|
_track.track->renderNextFrame(
|
||||||
|
@ -229,6 +248,8 @@ void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
|
||||||
if (const auto size = _track.track->frameSize(); !size.isEmpty()) {
|
if (const auto size = _track.track->frameSize(); !size.isEmpty()) {
|
||||||
_trackSize = size;
|
_trackSize = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTopControlsSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Calls::Group
|
} // namespace Calls::Group
|
||||||
|
|
|
@ -38,6 +38,9 @@ public:
|
||||||
[[nodiscard]] QRect geometry() const {
|
[[nodiscard]] QRect geometry() const {
|
||||||
return _geometry;
|
return _geometry;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] TileAnimation animation() const {
|
||||||
|
return _animation;
|
||||||
|
}
|
||||||
[[nodiscard]] bool pinned() const {
|
[[nodiscard]] bool pinned() const {
|
||||||
return _pinned;
|
return _pinned;
|
||||||
}
|
}
|
||||||
|
@ -59,8 +62,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool screencast() const;
|
[[nodiscard]] bool screencast() const;
|
||||||
void setGeometry(QRect geometry);
|
void setGeometry(
|
||||||
void setShown(bool shown);
|
QRect geometry,
|
||||||
|
TileAnimation animation = TileAnimation());
|
||||||
|
void hide();
|
||||||
void toggleTopControlsShown(bool shown);
|
void toggleTopControlsShown(bool shown);
|
||||||
bool updateRequestedQuality(VideoQuality quality);
|
bool updateRequestedQuality(VideoQuality quality);
|
||||||
|
|
||||||
|
@ -89,13 +94,15 @@ public:
|
||||||
private:
|
private:
|
||||||
void setup(rpl::producer<bool> pinned);
|
void setup(rpl::producer<bool> pinned);
|
||||||
[[nodiscard]] int topControlsSlide() const;
|
[[nodiscard]] int topControlsSlide() const;
|
||||||
void updateTopControlsGeometry();
|
void updateTopControlsSize();
|
||||||
|
void updateTopControlsPosition();
|
||||||
|
|
||||||
const VideoEndpoint _endpoint;
|
const VideoEndpoint _endpoint;
|
||||||
const Fn<void()> _update;
|
const Fn<void()> _update;
|
||||||
|
|
||||||
VideoTileTrack _track;
|
VideoTileTrack _track;
|
||||||
QRect _geometry;
|
QRect _geometry;
|
||||||
|
TileAnimation _animation;
|
||||||
rpl::variable<QSize> _trackSize;
|
rpl::variable<QSize> _trackSize;
|
||||||
QRect _pinOuter;
|
QRect _pinOuter;
|
||||||
QRect _pinInner;
|
QRect _pinInner;
|
||||||
|
|
Loading…
Add table
Reference in a new issue