Raster render of wide mode in single widget.

This commit is contained in:
John Preston 2021-05-23 17:08:52 +04:00
parent 4774f438a9
commit ec468431b4
14 changed files with 1424 additions and 241 deletions

View File

@ -289,6 +289,14 @@ PRIVATE
calls/group/calls_group_panel.h
calls/group/calls_group_settings.cpp
calls/group/calls_group_settings.h
calls/group/calls_group_viewport.cpp
calls/group/calls_group_viewport.h
calls/group/calls_group_viewport_opengl.cpp
calls/group/calls_group_viewport_opengl.h
calls/group/calls_group_viewport_raster.cpp
calls/group/calls_group_viewport_raster.h
calls/group/calls_group_viewport_tile.cpp
calls/group/calls_group_viewport_tile.h
calls/group/calls_volume_item.cpp
calls/group/calls_volume_item.h
calls/calls_box_controller.cpp

View File

@ -124,6 +124,16 @@ inline bool operator>=(
return !(a < b);
}
struct VideoPinToggle {
VideoEndpoint endpoint;
bool pinned = false;
};
struct VideoQualityRequest {
VideoEndpoint endpoint;
Group::VideoQuality quality = Group::VideoQuality();
};
struct VideoParams {
std::string endpoint;
QByteArray json;

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_large_video.h"
#include "calls/group/calls_group_viewport.h" // GenerateShadow.
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_members_row.h"
#include "media/view/media_view_pip.h"
@ -20,8 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "styles/style_calls.h"
#include "core/application.h"
#include <QtGui/QWindow>
#include <QtGui/QOpenGLShader>
#include <QtGui/QOpenGLShaderProgram>
@ -590,9 +589,9 @@ void LargeVideo::setGeometry(int x, int y, int width, int height) {
if (width > 0 && height > 0) {
const auto kMedium = style::ConvertScale(380);
const auto kSmall = style::ConvertScale(200);
_requestedQuality = (width > kMedium || height > kMedium)
_requestedQuality = (width > kMedium && height > kMedium)
? VideoQuality::Full
: (width > kSmall || height > kSmall)
: (width > kSmall && height > kSmall)
? VideoQuality::Medium
: VideoQuality::Thumbnail;
}
@ -901,51 +900,4 @@ void LargeVideo::paintControls(Painter &p, QRect clip) {
_track.row->name().drawLeftElided(p, nameLeft, nameTop, hasWidth, width);
}
QImage GenerateShadow(
int height,
int topAlpha,
int bottomAlpha,
QColor color) {
Expects(topAlpha >= 0 && topAlpha < 256);
Expects(bottomAlpha >= 0 && bottomAlpha < 256);
Expects(height * style::DevicePixelRatio() < 65536);
const auto base = (uint32(color.red()) << 16)
| (uint32(color.green()) << 8)
| uint32(color.blue());
const auto premultiplied = (topAlpha == bottomAlpha) || !base;
auto result = QImage(
QSize(1, height * style::DevicePixelRatio()),
(premultiplied
? QImage::Format_ARGB32_Premultiplied
: QImage::Format_ARGB32));
if (topAlpha == bottomAlpha) {
color.setAlpha(topAlpha);
result.fill(color);
return result;
}
constexpr auto kShift = 16;
constexpr auto kMultiply = (1U << kShift);
const auto values = std::abs(topAlpha - bottomAlpha);
const auto rows = uint32(result.height());
const auto step = (values * kMultiply) / (rows - 1);
const auto till = rows * uint32(step);
Assert(result.bytesPerLine() == sizeof(uint32));
auto ints = reinterpret_cast<uint32*>(result.bits());
if (topAlpha < bottomAlpha) {
for (auto i = uint32(0); i != till; i += step) {
*ints++ = base | ((topAlpha + (i >> kShift)) << 24);
}
} else {
for (auto i = uint32(0); i != till; i += step) {
*ints++ = base | ((topAlpha - (i >> kShift)) << 24);
}
}
if (!premultiplied) {
result = std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
return result;
}
} // namespace Calls::Group

View File

@ -112,10 +112,4 @@ private:
};
[[nodiscard]] QImage GenerateShadow(
int height,
int topAlpha,
int bottomAlpha,
QColor color = QColor(0, 0, 0));
} // namespace Calls::Group

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_settings.h"
#include "calls/group/calls_group_menu.h"
#include "calls/group/calls_group_large_video.h"
#include "calls/group/calls_group_viewport.h"
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "ui/platform/ui_platform_window_title.h"
#include "ui/platform/ui_platform_utility.h"
@ -379,11 +380,6 @@ std::unique_ptr<PeerListRow> InviteContactsController::createRow(
} // namespace
struct Panel::VideoTile {
std::unique_ptr<LargeVideo> video;
VideoEndpoint endpoint;
};
struct Panel::ControlsBackgroundNarrow {
explicit ControlsBackgroundNarrow(not_null<QWidget*> parent)
: shadow(parent)
@ -442,10 +438,8 @@ Panel::Panel(not_null<GroupCall*> call)
}
Panel::~Panel() {
if (_menu) {
_menu.destroy();
}
_videoTiles.clear();
_menu.destroy();
_viewport = nullptr;
}
void Panel::setupRealCallViewers() {
@ -999,7 +993,7 @@ void Panel::setupMembers() {
_call->videoEndpointPinnedValue(
) | rpl::start_with_next([=](const VideoEndpoint &pinned) {
if (mode() == PanelMode::Wide) {
refreshTilesGeometry();
_viewport->showLarge(pinned);
} else if (pinned) {
enlargeVideo();
}
@ -1093,195 +1087,62 @@ void Panel::raiseControls() {
_mute->raise();
}
void Panel::refreshTilesGeometry() {
const auto outer = _pinnedVideoWrap->size();
if (_videoTiles.empty()
|| outer.isEmpty()
|| mode() == PanelMode::Default) {
return;
}
struct Geometry {
QSize size;
QRect columns;
QRect rows;
};
const auto &pinned = _call->videoEndpointPinned();
auto sizes = base::flat_map<not_null<LargeVideo*>, Geometry>();
sizes.reserve(_videoTiles.size());
for (const auto &tile : _videoTiles) {
const auto video = tile.video.get();
const auto size = (pinned && tile.endpoint != pinned)
? QSize()
: video->trackSize();
if (size.isEmpty()) {
video->setGeometry(0, 0, outer.width(), 0);
} else {
sizes.emplace(video, Geometry{ size });
}
}
if (sizes.size() == 1) {
sizes.front().first->setGeometry(0, 0, outer.width(), outer.height());
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 = (outer.width() + 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 = (outer.height() + 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;
}
}
}
{
auto index = 0;
const auto rows = slices;
const auto sizeh = (outer.height() + 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 = (outer.width() + 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;
}
}
}
for (const auto &[video, geometry] : sizes) {
const auto &rect = (columnsBlack < rowsBlack)
? geometry.columns
: geometry.rows;
video->setGeometry(rect.x(), rect.y(), rect.width(), rect.height());
}
}
void Panel::setupPinnedVideo() {
using namespace rpl::mappers;
_pinnedVideoWrap = std::make_unique<Ui::RpWidget>(widget());
const auto raw = _pinnedVideoWrap.get();
_viewport = std::make_unique<Viewport>(widget(), _mode.current());
const auto raw = _viewport.get();
const auto setupTile = [=](
const VideoEndpoint &endpoint,
const GroupCall::VideoTrack &track) {
const auto row = _members->lookupRow(track.peer);
Assert(row != nullptr);
auto video = std::make_unique<LargeVideo>(
raw,
st::groupCallLargeVideoWide,
(mode() == PanelMode::Wide),
rpl::single(LargeVideoTrack{ track.track.get(), row }),
_viewport->add(
endpoint,
LargeVideoTrack{ track.track.get(), row },
_call->videoEndpointPinnedValue() | rpl::map(_1 == endpoint));
video->pinToggled(
) | rpl::start_with_next([=](bool pinned) {
_call->pinVideoEndpoint(pinned ? endpoint : VideoEndpoint{});
}, video->lifetime());
video->requestedQuality(
) | rpl::start_with_next([=](VideoQuality quality) {
_call->requestVideoQuality(endpoint, quality);
}, video->lifetime());
video->trackSizeValue(
) | rpl::start_with_next([=] {
refreshTilesGeometry();
}, video->lifetime());
return VideoTile{
.video = std::move(video),
.endpoint = endpoint,
};
};
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
_videoTiles.push_back(setupTile(endpoint, track));
setupTile(endpoint, track);
}
_call->videoStreamActiveUpdates(
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
if (_call->activeVideoTracks().contains(endpoint)) {
// Add async (=> the participant row is definitely in Members).
crl::on_main(raw, [=] {
crl::on_main(widget(), [=] {
const auto &tracks = _call->activeVideoTracks();
const auto i = tracks.find(endpoint);
if (i != end(tracks)) {
_videoTiles.push_back(setupTile(endpoint, i->second));
setupTile(endpoint, i->second);
}
});
} else {
// Remove sync.
const auto eraseTill = end(_videoTiles);
const auto eraseFrom = ranges::remove(
_videoTiles,
endpoint,
&VideoTile::endpoint);
if (eraseFrom != eraseTill) {
_videoTiles.erase(eraseFrom, eraseTill);
refreshTilesGeometry();
}
_viewport->remove(endpoint);
}
}, raw->lifetime());
raw->sizeValue() | rpl::start_with_next([=] {
refreshTilesGeometry();
raw->pinToggled(
) | rpl::start_with_next([=](const VideoPinToggle &value) {
_call->pinVideoEndpoint(
value.pinned ? value.endpoint : VideoEndpoint{});
}, raw->lifetime());
raw->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::Enter) {
Ui::Integration::Instance().registerLeaveSubscription(raw);
toggleWideControls(true);
} else if (e->type() == QEvent::Leave) {
Ui::Integration::Instance().unregisterLeaveSubscription(raw);
toggleWideControls(false);
}
raw->clicks(
) | rpl::filter([=] {
return (_mode.current() == PanelMode::Default);
}) | rpl::start_with_next([=] {
enlargeVideo();
}, raw->lifetime());
raw->qualityRequests(
) | rpl::start_with_next([=](const VideoQualityRequest &request) {
_call->requestVideoQuality(request.endpoint, request.quality);
}, raw->lifetime());
raw->mouseInsideValue(
) | rpl::start_with_next([=](bool inside) {
toggleWideControls(inside);
}, raw->lifetime());
raiseControls();
@ -1811,14 +1672,10 @@ bool Panel::updateMode() {
if (_members) {
_members->setMode(mode);
}
if (_pinnedVideoWrap) {
if (_viewport) {
_wideControlsShown = _showWideControls = true;
_wideControlsAnimation.stop();
_pinnedVideoWrap->setVisible(mode == PanelMode::Wide);
for (const auto &tile : _videoTiles) {
tile.video->setVisible(mode == PanelMode::Wide);
tile.video->setControlsShown(1.);
}
_viewport->setMode(mode);
}
updateButtonsStyles();
refreshControlsBackground();
@ -2085,8 +1942,8 @@ void Panel::updateButtonsGeometry() {
_wideControlsShown ? 1. : 0.);
toggle(shown > 0.);
for (const auto &tile : _videoTiles) {
tile.video->setControlsShown(shown);
if (_viewport) {
_viewport->setControlsShown(shown);
}
const auto buttonsTop = widget()->height() - anim::interpolate(
@ -2206,7 +2063,7 @@ void Panel::updateMembersGeometry() {
top,
membersWidth,
std::min(desiredHeight, widget()->height() - top - skip));
_pinnedVideoWrap->setGeometry(
_viewport->widget()->setGeometry(
skip,
top,
widget()->width() - membersWidth - 3 * skip,

View File

@ -54,7 +54,7 @@ struct CallBodyLayout;
namespace Calls::Group {
class Members;
class LargeVideo;
class Viewport;
enum class PanelMode;
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
@ -70,7 +70,6 @@ public:
private:
using State = GroupCall::State;
struct VideoTile;
struct ControlsBackgroundNarrow;
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
@ -155,8 +154,7 @@ private:
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
object_ptr<Members> _members = { nullptr };
std::unique_ptr<Ui::RpWidget> _pinnedVideoWrap;
std::vector<VideoTile> _videoTiles;
std::unique_ptr<Viewport> _viewport;
rpl::lifetime _trackControlsLifetime;
rpl::lifetime _trackControlsOverStateLifetime;
object_ptr<Ui::FlatLabel> _startsIn = { nullptr };

View File

@ -0,0 +1,459 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_viewport.h"
#include "calls/group/calls_group_large_video.h" // LargeVideoTrack.
#include "calls/group/calls_group_viewport_tile.h"
#include "calls/group/calls_group_viewport_opengl.h"
#include "calls/group/calls_group_viewport_raster.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_members_row.h"
#include "media/view/media_view_pip.h"
#include "base/platform/base_platform_info.h"
#include "webrtc/webrtc_video_track.h"
#include "ui/painter.h"
#include "ui/abstract_button.h"
#include "ui/gl/gl_surface.h"
#include "ui/effects/animations.h"
#include "ui/effects/cross_line.h"
#include "lang/lang_keys.h"
#include "styles/style_calls.h"
#include <QtGui/QtEvents>
#include <QtGui/QOpenGLShader>
namespace Calls::Group {
Viewport::Viewport(QWidget *parent, PanelMode mode)
: _mode(mode)
, _content(Ui::GL::CreateSurface(
parent,
[=](Ui::GL::Capabilities capabilities) {
return chooseRenderer(capabilities);
})) {
setup();
}
Viewport::~Viewport() {
base::take(_tiles);
}
not_null<QWidget*> Viewport::widget() const {
return _content->rpWidget();
}
not_null<Ui::RpWidgetWrap*> Viewport::rp() const {
return _content.get();
}
void Viewport::setup() {
const auto raw = widget();
raw->resize(0, 0);
raw->setAttribute(Qt::WA_OpaquePaintEvent);
raw->setMouseTracking(true);
_content->sizeValue(
) | rpl::start_with_next([=] {
updateTilesGeometry();
}, lifetime());
_content->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::Enter) {
Ui::Integration::Instance().registerLeaveSubscription(raw);
_mouseInside = true;
} else if (type == QEvent::Leave) {
Ui::Integration::Instance().unregisterLeaveSubscription(raw);
setSelected({});
_mouseInside = false;
} else if (type == QEvent::MouseButtonPress) {
handleMousePress(
static_cast<QMouseEvent*>(e.get())->pos(),
static_cast<QMouseEvent*>(e.get())->button());
} else if (type == QEvent::MouseButtonRelease) {
handleMouseRelease(
static_cast<QMouseEvent*>(e.get())->pos(),
static_cast<QMouseEvent*>(e.get())->button());
} else if (type == QEvent::MouseMove) {
handleMouseMove(static_cast<QMouseEvent*>(e.get())->pos());
}
}, lifetime());
}
bool Viewport::wide() const {
return (_mode.current() == PanelMode::Wide);
}
void Viewport::setMode(PanelMode mode) {
if (_mode.current() == mode) {
return;
}
_mode = mode;
widget()->setVisible(wide()); // #TODO calls
setControlsShown(1.);
updateTilesGeometry();
if (_mouseInside.current()) {
handleMouseMove(widget()->mapFromGlobal(QCursor::pos()));
}
if (!wide()) {
for (const auto &tile : _tiles) {
tile->togglePinShown(false);
}
} else if (_selected.tile) {
_selected.tile->togglePinShown(true);
}
}
void Viewport::handleMousePress(QPoint position, Qt::MouseButton button) {
handleMouseMove(position);
if (button == Qt::LeftButton) {
setPressed(_selected);
}
}
void Viewport::handleMouseRelease(QPoint position, Qt::MouseButton button) {
handleMouseMove(position);
const auto pressed = _pressed;
setPressed({});
if (const auto tile = pressed.tile) {
if (pressed == _selected) {
if (!wide()) {
_clicks.fire_copy(tile->endpoint());
} else if (pressed.element == Selection::Element::PinButton) {
_pinToggles.fire({
.endpoint = tile->endpoint(),
.pinned = !tile->pinned(),
});
}
}
}
}
void Viewport::handleMouseMove(QPoint position) {
if (!widget()->rect().contains(position)) {
setSelected({});
return;
}
for (const auto &tile : _tiles) {
const auto geometry = tile->geometry();
if (geometry.contains(position)) {
const auto pin = wide()
&& tile->pinOuter().contains(position - geometry.topLeft());
setSelected({
.tile = tile.get(),
.element = (pin
? Selection::Element::PinButton
: Selection::Element::Tile),
});
return;
}
}
setSelected({});
}
void Viewport::setControlsShown(float64 shown) {
_controlsShownRatio = shown;
widget()->update();
}
void Viewport::add(
const VideoEndpoint &endpoint,
LargeVideoTrack track,
rpl::producer<bool> pinned) {
_tiles.push_back(std::make_unique<VideoTile>(
endpoint,
track,
std::move(pinned),
[=] { widget()->update(); }));
//video->pinToggled( // #TODO calls
//) | rpl::start_with_next([=](bool pinned) {
// _call->pinVideoEndpoint(pinned ? endpoint : VideoEndpoint{});
//}, video->lifetime());
_tiles.back()->trackSizeValue(
) | rpl::start_with_next([=] {
updateTilesGeometry();
}, _tiles.back()->lifetime());
}
void Viewport::remove(const VideoEndpoint &endpoint) {
const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
if (i == end(_tiles)) {
return;
}
const auto removing = i->get();
if (_large == removing) {
_large = nullptr;
}
if (_selected.tile == removing) {
setSelected({});
}
if (_pressed.tile == removing) {
setPressed({});
}
_tiles.erase(i);
updateTilesGeometry();
}
void Viewport::showLarge(const VideoEndpoint &endpoint) {
const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
const auto large = (i != end(_tiles)) ? i->get() : nullptr;
if (_large != large) {
_large = large;
updateTilesGeometry();
}
}
void Viewport::updateTilesGeometry() {
const auto outer = widget()->size();
if (_tiles.empty() || outer.isEmpty()) {
return;
}
const auto guard = gsl::finally([&] { widget()->update(); });
struct Geometry {
QSize size;
QRect columns;
QRect rows;
};
auto sizes = base::flat_map<not_null<VideoTile*>, Geometry>();
sizes.reserve(_tiles.size());
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, outer.width(), 0 });
} else {
sizes.emplace(video, Geometry{ size });
}
}
if (sizes.size() == 1) {
setTileGeometry(
sizes.front().first,
{ 0, 0, outer.width(), outer.height() });
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 = (outer.width() + 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 = (outer.height() + 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;
}
}
}
{
auto index = 0;
const auto rows = slices;
const auto sizeh = (outer.height() + 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 = (outer.width() + 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::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
tile->setGeometry(geometry);
const auto min = std::min(geometry.width(), geometry.height());
const auto kMedium = style::ConvertScale(480);
const auto kSmall = style::ConvertScale(240);
const auto quality = (min >= kMedium)
? VideoQuality::Full
: (min >= kSmall)
? VideoQuality::Medium
: VideoQuality::Thumbnail;
if (tile->updateRequestedQuality(quality)) {
_qualityRequests.fire(VideoQualityRequest{
.endpoint = tile->endpoint(),
.quality = quality,
});
}
}
void Viewport::setSelected(Selection value) {
if (_selected == value) {
return;
}
if (_selected.tile) {
_selected.tile->togglePinShown(false);
}
_selected = value;
if (_selected.tile && wide()) {
_selected.tile->togglePinShown(true);
}
const auto pointer = _selected.tile
&& (!wide() || _selected.element == Selection::Element::PinButton);
widget()->setCursor(pointer ? style::cur_pointer : style::cur_default);
}
void Viewport::setPressed(Selection value) {
if (_pressed == value) {
return;
}
_pressed = value;
}
Ui::GL::ChosenRenderer Viewport::chooseRenderer(
Ui::GL::Capabilities capabilities) {
const auto use = Platform::IsMac()
? true
: Platform::IsWindows()
? capabilities.supported
: capabilities.transparency;
LOG(("OpenGL: %1 (Calls::Group::Viewport)").arg(Logs::b(use)));
if (use) {
return {
.renderer = std::make_unique<Renderer>(this),
.backend = Ui::GL::Backend::OpenGL,
};
}
return {
.renderer = std::make_unique<Renderer>(this),
.backend = Ui::GL::Backend::Raster,
};
}
[[nodiscard]] rpl::producer<VideoPinToggle> Viewport::pinToggled() const {
return _pinToggles.events();
}
[[nodiscard]] rpl::producer<VideoEndpoint> Viewport::clicks() const {
return _clicks.events();
}
[[nodiscard]] rpl::producer<VideoQualityRequest> Viewport::qualityRequests() const {
return _qualityRequests.events();
}
[[nodiscard]] rpl::producer<bool> Viewport::mouseInsideValue() const {
return _mouseInside.value();
}
[[nodiscard]] rpl::lifetime &Viewport::lifetime() {
return _content->lifetime();
}
QImage GenerateShadow(
int height,
int topAlpha,
int bottomAlpha,
QColor color) {
Expects(topAlpha >= 0 && topAlpha < 256);
Expects(bottomAlpha >= 0 && bottomAlpha < 256);
Expects(height * style::DevicePixelRatio() < 65536);
const auto base = (uint32(color.red()) << 16)
| (uint32(color.green()) << 8)
| uint32(color.blue());
const auto premultiplied = (topAlpha == bottomAlpha) || !base;
auto result = QImage(
QSize(1, height * style::DevicePixelRatio()),
(premultiplied
? QImage::Format_ARGB32_Premultiplied
: QImage::Format_ARGB32));
if (topAlpha == bottomAlpha) {
color.setAlpha(topAlpha);
result.fill(color);
return result;
}
constexpr auto kShift = 16;
constexpr auto kMultiply = (1U << kShift);
const auto values = std::abs(topAlpha - bottomAlpha);
const auto rows = uint32(result.height());
const auto step = (values * kMultiply) / (rows - 1);
const auto till = rows * uint32(step);
Assert(result.bytesPerLine() == sizeof(uint32));
auto ints = reinterpret_cast<uint32*>(result.bits());
if (topAlpha < bottomAlpha) {
for (auto i = uint32(0); i != till; i += step) {
*ints++ = base | ((topAlpha + (i >> kShift)) << 24);
}
} else {
for (auto i = uint32(0); i != till; i += step) {
*ints++ = base | ((topAlpha - (i >> kShift)) << 24);
}
}
if (!premultiplied) {
result = std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
return result;
}
} // namespace Calls::Group

View File

@ -0,0 +1,124 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/rp_widget.h"
namespace style {
struct GroupCallLargeVideo;
} // namespace style
namespace Ui {
class AbstractButton;
class RpWidgetWrap;
namespace GL {
struct Capabilities;
struct ChosenRenderer;
} // namespace GL
} // namespace Ui
namespace Calls {
struct VideoEndpoint;
struct VideoPinToggle;
struct VideoQualityRequest;
} // namespace Calls
namespace Webrtc {
class VideoTrack;
} // namespace Webrtc
namespace Calls::Group {
class MembersRow;
enum class PanelMode;
enum class VideoQuality;
struct LargeVideoTrack;
class Viewport final {
public:
Viewport(QWidget *parent, PanelMode mode);
~Viewport();
[[nodiscard]] not_null<QWidget*> widget() const;
[[nodiscard]] not_null<Ui::RpWidgetWrap*> rp() const;
void setMode(PanelMode mode);
void setControlsShown(float64 shown);
void add(
const VideoEndpoint &endpoint,
LargeVideoTrack track,
rpl::producer<bool> pinned);
void remove(const VideoEndpoint &endpoint);
void showLarge(const VideoEndpoint &endpoint);
[[nodiscard]] rpl::producer<VideoPinToggle> pinToggled() const;
[[nodiscard]] rpl::producer<VideoEndpoint> clicks() const;
[[nodiscard]] rpl::producer<VideoQualityRequest> qualityRequests() const;
[[nodiscard]] rpl::producer<bool> mouseInsideValue() const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
struct PinButton;
class VideoTile;
class Renderer;
class RendererGL;
struct Selection {
enum class Element {
None,
Tile,
PinButton,
};
VideoTile *tile = nullptr;
Element element = Element::None;
inline bool operator==(Selection other) const {
return (tile == other.tile) && (element == other.element);
}
};
void setup();
[[nodiscard]] bool wide() const;
void updateTilesGeometry();
void setTileGeometry(not_null<VideoTile*> tile, QRect geometry);
void setSelected(Selection value);
void setPressed(Selection value);
void handleMousePress(QPoint position, Qt::MouseButton button);
void handleMouseRelease(QPoint position, Qt::MouseButton button);
void handleMouseMove(QPoint position);
[[nodiscard]] Ui::GL::ChosenRenderer chooseRenderer(
Ui::GL::Capabilities capabilities);
rpl::variable<PanelMode> _mode;
const std::unique_ptr<Ui::RpWidgetWrap> _content;
std::vector<std::unique_ptr<VideoTile>> _tiles;
QImage _shadow;
rpl::event_stream<VideoEndpoint> _clicks;
rpl::event_stream<VideoPinToggle> _pinToggles;
rpl::event_stream<VideoQualityRequest> _qualityRequests;
float64 _controlsShownRatio = 1.;
VideoTile *_large = nullptr;
Selection _selected;
Selection _pressed;
rpl::variable<bool> _mouseInside = false;
};
[[nodiscard]] QImage GenerateShadow(
int height,
int topAlpha,
int bottomAlpha,
QColor color = QColor(0, 0, 0));
} // namespace Calls::Group

View File

@ -0,0 +1,247 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_viewport_opengl.h"
#include <QtGui/QOpenGLShader>
namespace Calls::Group {
namespace {
struct ShaderPart {
QString header;
QString body;
};
[[nodiscard]] QString VertexShader(const std::vector<ShaderPart> &parts) {
const auto accumulate = [&](auto proj) {
return ranges::accumulate(parts, QString(), std::plus<>(), proj);
};
return R"(
#version 130
in vec2 position;
)" + accumulate(&ShaderPart::header) + R"(
void main() {
vec4 result = vec4(position, 0., 1.);
)" + accumulate(&ShaderPart::body) + R"(
gl_Position = result;
}
)";
}
[[nodiscard]] QString FragmentShader(const std::vector<ShaderPart> &parts) {
const auto accumulate = [&](auto proj) {
return ranges::accumulate(parts, QString(), std::plus<>(), proj);
};
return R"(
#version 130
out vec4 fragColor;
)" + accumulate(&ShaderPart::header) + R"(
void main() {
vec4 result = vec4(0., 0., 0., 0.);
)" + accumulate(&ShaderPart::body) + R"(
fragColor = result;
}
)";
}
[[nodiscard]] ShaderPart VertexPassTextureCoord() {
return {
.header = R"(
in vec2 texcoord;
out vec2 v_texcoord;
)",
.body = R"(
v_texcoord = texcoord;
)",
};
}
[[nodiscard]] ShaderPart FragmentSampleTexture() {
return {
.header = R"(
in vec2 v_texcoord;
uniform sampler2D s_texture;
)",
.body = R"(
result = texture(s_texture, v_texcoord);
result = vec4(result.b, result.g, result.r, result.a);
)",
};
}
[[nodiscard]] ShaderPart VertexViewportTransform() {
return {
.header = R"(
uniform vec2 viewport;
vec4 transform(vec4 position) {
return vec4(
vec2(-1, -1) + 2 * position.xy / viewport,
position.z,
position.w);
}
)",
.body = R"(
result = transform(result);
)",
};
}
[[nodiscard]] ShaderPart FragmentRoundCorners() {
return {
.header = R"(
uniform vec2 viewport;
uniform float roundRadius;
float roundedCorner() {
vec2 viewportHalf = viewport / 2;
vec2 fromViewportCenter = abs(gl_FragCoord.xy - viewportHalf);
vec2 vectorRadius = vec2(roundRadius + 0.5, roundRadius + 0.5);
vec2 fromCenterWithRadius = fromViewportCenter + vectorRadius;
vec2 fromRoundingCenter = max(fromCenterWithRadius, viewportHalf)
- viewportHalf;
float d = length(fromRoundingCenter) - roundRadius;
return 1. - smoothstep(0., 1., d);
}
)",
.body = R"(
result = vec4(result.r, result.g, result.b, result.a * roundedCorner());
)",
};
}
[[nodiscard]] ShaderPart FragmentStaticColor() {
return {
.header = R"(
uniform vec4 s_color;
)",
.body = R"(
result = s_color;
)",
};
}
not_null<QOpenGLShader*> MakeShader(
not_null<QOpenGLShaderProgram*> program,
QOpenGLShader::ShaderType type,
const QString &source) {
const auto result = new QOpenGLShader(type, program);
if (!result->compileSourceCode(source)) {
LOG(("Shader Compilation Failed: %1, error %2."
).arg(source
).arg(result->log()));
}
program->addShader(result);
return result;
}
void LinkProgram(
not_null<QOpenGLShaderProgram*> program,
const QString &vertexSource,
const QString &fragmentSource) {
MakeShader(program, QOpenGLShader::Vertex, vertexSource);
MakeShader(program, QOpenGLShader::Fragment, fragmentSource);
if (!program->link()) {
LOG(("Shader Link Failed: %1.").arg(program->log()));
}
}
class Quads final {
public:
void fill(QRect rect);
void paint(
not_null<QOpenGLFunctions*> f,
not_null<QOpenGLBuffer*> buffer,
not_null<QOpenGLShaderProgram*> program,
QSize viewport,
const QColor &color,
Fn<void()> additional = nullptr);
private:
static constexpr auto kMaxTriangles = 8;
std::array<GLfloat, 6 * kMaxTriangles> coordinates{ 0 };
int triangles = 0;
};
void Quads::fill(QRect rect) {
Expects(triangles + 2 <= kMaxTriangles);
auto i = triangles * 6;
coordinates[i + 0] = coordinates[i + 10] = rect.x();
coordinates[i + 1] = coordinates[i + 11] = rect.y();
coordinates[i + 2] = rect.x() + rect.width();
coordinates[i + 3] = rect.y();
coordinates[i + 4] = coordinates[i + 6] = rect.x() + rect.width();
coordinates[i + 5] = coordinates[i + 7] = rect.y() + rect.height();
coordinates[i + 8] = rect.x();
coordinates[i + 9] = rect.y() + rect.height();
triangles += 2;
}
void Quads::paint(
not_null<QOpenGLFunctions*> f,
not_null<QOpenGLBuffer*> buffer,
not_null<QOpenGLShaderProgram*> program,
QSize viewport,
const QColor &color,
Fn<void()> additional) {
if (!triangles) {
return;
}
buffer->bind();
buffer->allocate(coordinates.data(), triangles * 6 * sizeof(GLfloat));
f->glUseProgram(program->programId());
program->setUniformValue("viewport", QSizeF(viewport));
program->setUniformValue("s_color", QVector4D(
color.redF(),
color.greenF(),
color.blueF(),
color.alphaF()));
GLint position = program->attributeLocation("position");
f->glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (void*)0);
f->glEnableVertexAttribArray(position);
if (additional) {
additional();
}
f->glDrawArrays(GL_TRIANGLES, 0, triangles * 3);
f->glDisableVertexAttribArray(position);
}
} // namespace
Viewport::RendererGL::RendererGL(not_null<Viewport*> owner)
: _owner(owner) {
}
void Viewport::RendererGL::init(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f) {
}
void Viewport::RendererGL::deinit(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f) {
}
void Viewport::RendererGL::resize(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f,
int w,
int h) {
}
void Viewport::RendererGL::paint(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f) {
}
} // namespace Calls::Group

View File

@ -0,0 +1,52 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "calls/group/calls_group_viewport.h"
#include "ui/gl/gl_surface.h"
#include <QtGui/QOpenGLBuffer>
#include <QtGui/QOpenGLShaderProgram>
namespace Calls::Group {
class Viewport::RendererGL final : public Ui::GL::Renderer {
public:
explicit RendererGL(not_null<Viewport*> owner);
void init(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f) override;
void deinit(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f) override;
void resize(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f,
int w,
int h) override;
void paint(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f) override;
private:
const not_null<Viewport*> _owner;
std::optional<QOpenGLBuffer> _frameBuffer;
std::optional<QOpenGLBuffer> _fillBuffer;
std::optional<QOpenGLBuffer> _bgBuffer;
std::optional<QOpenGLShaderProgram> _frameProgram;
std::optional<QOpenGLShaderProgram> _fillProgram;
std::optional<QOpenGLShaderProgram> _bgProgram;
};
} // namespace Calls::Group

View File

@ -0,0 +1,221 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_viewport_raster.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_viewport_tile.h"
#include "calls/group/calls_group_members_row.h"
#include "media/view/media_view_pip.h"
#include "webrtc/webrtc_video_track.h"
#include "lang/lang_keys.h"
#include "styles/style_calls.h"
#include "styles/palette.h"
namespace Calls::Group {
namespace {
constexpr auto kShadowMaxAlpha = 80;
} // namespace
Viewport::Renderer::Renderer(not_null<Viewport*> owner)
: _owner(owner)
, _pinIcon(st::groupCallLargeVideoPin)
, _pinBackground(
(st::groupCallLargeVideoWide.pinPadding.top()
+ st::groupCallLargeVideoPin.icon.height()
+ st::groupCallLargeVideoWide.pinPadding.bottom()) / 2,
st::radialBg)
, _pinTextOn(st::semiboldTextStyle, tr::lng_pinned_unpin(tr::now))
, _pinTextOff(st::semiboldTextStyle, tr::lng_pinned_pin(tr::now)) {
}
void Viewport::Renderer::paintFallback(
Painter &&p,
const QRegion &clip,
Ui::GL::Backend backend) {
auto bg = clip;
const auto guard = gsl::finally([&] {
for (const auto rect : bg) {
p.fillRect(rect, st::groupCallBg);
}
});
auto hq = PainterHighQualityEnabler(p);
const auto bounding = clip.boundingRect();
const auto opengl = (backend == Ui::GL::Backend::OpenGL);
for (const auto &tile : _owner->_tiles) {
paintTile(p, tile.get(), bounding, opengl, bg);
}
}
void Viewport::Renderer::paintTile(
Painter &p,
not_null<VideoTile*> tile,
const QRect &clip,
bool opengl,
QRegion &bg) {
const auto track = tile->track();
const auto [image, rotation] = track->frameOriginalWithRotation();
if (image.isNull()) {
return;
}
const auto fill = [&](QRect rect) {
const auto intersected = rect.intersected(clip);
if (!intersected.isEmpty()) {
p.fillRect(intersected, st::groupCallMembersBg);
bg -= intersected;
}
};
using namespace Media::View;
const auto geometry = tile->geometry();
const auto x = geometry.x();
const auto y = geometry.y();
const auto width = geometry.width();
const auto height = geometry.height();
const auto scaled = FlipSizeByRotation(
image.size(),
rotation
).scaled(QSize(width, height), Qt::KeepAspectRatio);
const auto left = (width - scaled.width()) / 2;
const auto top = (height - scaled.height()) / 2;
const auto target = QRect(QPoint(x + left, y + top), scaled);
if (UsePainterRotation(rotation, opengl)) {
if (rotation) {
p.save();
p.rotate(rotation);
}
p.drawImage(RotatedRect(target, rotation), image);
if (rotation) {
p.restore();
}
} else if (rotation) {
p.drawImage(target, RotateFrameImage(image, rotation));
} else {
p.drawImage(target, image);
}
bg -= target;
track->markFrameShown();
if (left > 0) {
fill({ x, y, left, height });
}
if (const auto right = left + scaled.width(); right < width) {
fill({ x + right, y, width - right, height });
}
if (top > 0) {
fill({ x, y, width, top });
}
if (const auto bottom = top + scaled.height(); bottom < height) {
fill({ x, y + bottom, width, height - bottom });
}
paintTileControls(p, x, y, width, height, tile);
}
void Viewport::Renderer::paintTileControls(
Painter &p,
int x,
int y,
int width,
int height,
not_null<VideoTile*> tile) {
p.setClipRect(x, y, width, height);
const auto guard = gsl::finally([&] { p.setClipping(false); });
// Pin.
const auto wide = _owner->wide();
if (wide) {
const auto inner = tile->pinInner().translated(x, y);
const auto pinned = tile->pinned();
const auto &icon = st::groupCallLargeVideoPin.icon;
const auto &st = st::groupCallLargeVideoWide;
_pinBackground.paint(p, inner);
_pinIcon.paint(
p,
inner.marginsRemoved(st.pinPadding).topLeft(),
pinned ? 1. : 0.);
p.setPen(st::groupCallVideoTextFg);
const auto &text = (pinned ? _pinTextOn : _pinTextOff);
text.drawLeft(
p,
(inner.x()
+ st.pinPadding.left()
+ icon.width()
+ st.pinTextPosition.x()),
(inner.y()
+ st.pinPadding.top()
+ st.pinTextPosition.y()),
text.maxWidth(),
_owner->widget()->width());
}
const auto &st = wide
? st::groupCallLargeVideoWide
: st::groupCallLargeVideoNarrow;
const auto fullShift = st.namePosition.y() + st::normalFont->height;
const auto shown = _owner->_controlsShownRatio;
if (shown == 0.) {
return;
}
const auto shift = anim::interpolate(fullShift, 0, shown);
auto &shadow = wide ? _shadowWide : _shadowNarrow;
// Shadow.
if (shadow.isNull()) {
shadow = GenerateShadow(st.shadowHeight, 0, kShadowMaxAlpha);
}
const auto shadowRect = QRect(
x,
y + (height - anim::interpolate(0, st.shadowHeight, shown)),
width,
st.shadowHeight);
const auto shadowFill = shadowRect.intersected({ x, y, width, height });
if (shadowFill.isEmpty()) {
return;
}
const auto factor = style::DevicePixelRatio();
p.drawImage(
shadowFill,
shadow,
QRect(
0,
(shadowFill.y() - shadowRect.y()) * factor,
shadow.width(),
shadowFill.height() * factor));
const auto row = tile->row();
row->lazyInitialize(st::groupCallMembersListItem);
// Mute.
const auto &icon = st::groupCallLargeVideoCrossLine.icon;
const auto iconLeft = x + width - st.iconPosition.x() - icon.width();
const auto iconTop = y + (height
- st.iconPosition.y()
- icon.height()
+ shift);
row->paintMuteIcon(
p,
{ iconLeft, iconTop, icon.width(), icon.height() },
MembersRowStyle::LargeVideo);
// Name.
p.setPen(st::groupCallVideoTextFg);
const auto hasWidth = width
- st.iconPosition.x() - icon.width()
- st.namePosition.x();
const auto nameLeft = x + st.namePosition.x();
const auto nameTop = y + (height
- st.namePosition.y()
- st::semiboldFont->height
+ shift);
row->name().drawLeftElided(p, nameLeft, nameTop, hasWidth, width);
}
} // namespace Calls::Group

View File

@ -0,0 +1,53 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "calls/group/calls_group_viewport.h"
#include "ui/round_rect.h"
#include "ui/effects/cross_line.h"
#include "ui/gl/gl_surface.h"
#include "ui/text/text.h"
namespace Calls::Group {
class Viewport::Renderer final : public Ui::GL::Renderer {
public:
explicit Renderer(not_null<Viewport*> owner);
void paintFallback(
Painter &&p,
const QRegion &clip,
Ui::GL::Backend backend) override;
private:
void paintTile(
Painter &p,
not_null<VideoTile*> tile,
const QRect &clip,
bool opengl,
QRegion &bg);
void paintTileControls(
Painter &p,
int x,
int y,
int width,
int height,
not_null<VideoTile*> tile);
const not_null<Viewport*> _owner;
QImage _shadowWide;
QImage _shadowNarrow;
Ui::CrossLineAnimation _pinIcon;
Ui::RoundRect _pinBackground;
Ui::Text::String _pinTextOn;
Ui::Text::String _pinTextOff;
};
} // namespace Calls::Group

View File

@ -0,0 +1,126 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_viewport_tile.h"
#include "webrtc/webrtc_video_track.h"
#include "lang/lang_keys.h"
#include "styles/style_calls.h"
namespace Calls::Group {
Viewport::VideoTile::VideoTile(
const VideoEndpoint &endpoint,
LargeVideoTrack track,
rpl::producer<bool> pinned,
Fn<void()> update)
: _endpoint(endpoint)
, _update(std::move(update))
, _track(track) {
Expects(track.track != nullptr);
Expects(track.row != nullptr);
setup(std::move(pinned));
}
QRect Viewport::VideoTile::pinInner() const {
return _pinInner.translated(0, -pinSlide());
}
QRect Viewport::VideoTile::pinOuter() const {
return _pinOuter;
}
int Viewport::VideoTile::pinSlide() const {
return anim::interpolate(
st::groupCallLargeVideoWide.pinPosition.y() + _pinInner.height(),
0,
_pinShownAnimation.value(_pinShown ? 1. : 0.));
}
void Viewport::VideoTile::setGeometry(QRect geometry) {
_geometry = geometry;
updatePinnedGeometry();
}
void Viewport::VideoTile::togglePinShown(bool shown) {
if (_pinShown == shown) {
return;
}
_pinShown = shown;
_pinShownAnimation.start(
_update,
shown ? 0. : 1.,
shown ? 1. : 0.,
st::slideWrapDuration);
}
bool Viewport::VideoTile::updateRequestedQuality(VideoQuality quality) {
if (!_quality || *_quality == quality) {
return false;
}
_quality = quality;
return true;
}
void Viewport::VideoTile::updatePinnedGeometry() {
const auto &st = st::groupCallLargeVideoWide;
const auto &icon = st::groupCallLargeVideoPin.icon;
const auto innerWidth = icon.width()
+ st.pinTextPosition.x()
+ st::semiboldFont->width(_pinned
? tr::lng_pinned_unpin(tr::now)
: tr::lng_pinned_pin(tr::now));
const auto innerHeight = icon.height();
const auto buttonWidth = st.pinPadding.left()
+ innerWidth
+ st.pinPadding.right();
const auto buttonHeight = st.pinPadding.top()
+ innerHeight
+ st.pinPadding.bottom();
const auto fullWidth = st.pinPosition.x() * 2 + buttonWidth;
const auto fullHeight = st.pinPosition.y() * 2 + buttonHeight;
_pinInner = QRect(
_geometry.width() - st.pinPosition.x() - buttonWidth,
st.pinPosition.y(),
buttonWidth,
buttonHeight);
_pinOuter = QRect(
_geometry.width() - fullWidth,
0,
fullWidth,
fullHeight);
}
void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
std::move(
pinned
) | rpl::filter([=](bool pinned) {
return (_pinned != pinned);
}) | rpl::start_with_next([=](bool pinned) {
_pinned = pinned;
updatePinnedGeometry();
_update();
}, _lifetime);
_track.track->renderNextFrame(
) | rpl::start_with_next([=] {
const auto size = _track.track->frameSize();
if (size.isEmpty()) {
_track.track->markFrameShown();
} else {
_trackSize = size;
}
_update();
}, _lifetime);
if (const auto size = _track.track->frameSize(); !size.isEmpty()) {
_trackSize = size;
}
}
} // namespace Calls::Group

View File

@ -0,0 +1,82 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "calls/group/calls_group_viewport.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_large_video.h" // LargeVideoTrack.
#include "ui/effects/animations.h"
#include "ui/effects/cross_line.h"
#include "ui/round_rect.h"
namespace Calls::Group {
class Viewport::VideoTile final {
public:
VideoTile(
const VideoEndpoint &endpoint,
LargeVideoTrack track,
rpl::producer<bool> pinned,
Fn<void()> update);
[[nodiscard]] not_null<Webrtc::VideoTrack*> track() const {
return _track.track;
}
[[nodiscard]] not_null<MembersRow*> row() const {
return _track.row;
}
[[nodiscard]] QRect geometry() const {
return _geometry;
}
[[nodiscard]] bool pinned() const {
return _pinned;
}
[[nodiscard]] QRect pinOuter() const;
[[nodiscard]] QRect pinInner() const;
[[nodiscard]] const VideoEndpoint &endpoint() const {
return _endpoint;
}
[[nodiscard]] QSize trackSize() const {
return _trackSize.current();
}
[[nodiscard]] rpl::producer<QSize> trackSizeValue() const {
return _trackSize.value();
}
void setGeometry(QRect geometry);
void togglePinShown(bool shown);
bool updateRequestedQuality(VideoQuality quality);
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
private:
void setup(rpl::producer<bool> pinned);
[[nodiscard]] int pinSlide() const;
void updatePinnedGeometry();
const VideoEndpoint _endpoint;
const Fn<void()> _update;
LargeVideoTrack _track;
QRect _geometry;
rpl::variable<QSize> _trackSize;
QRect _pinOuter;
QRect _pinInner;
Ui::Animations::Simple _pinShownAnimation;
bool _pinShown = false;
bool _pinned = false;
std::optional<VideoQuality> _quality;
rpl::lifetime _lifetime;
};
} // namespace Calls::Group