mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-24 01:06:59 +00:00
Raster render of wide mode in single widget.
This commit is contained in:
parent
4774f438a9
commit
ec468431b4
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -112,10 +112,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] QImage GenerateShadow(
|
||||
int height,
|
||||
int topAlpha,
|
||||
int bottomAlpha,
|
||||
QColor color = QColor(0, 0, 0));
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
@ -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,
|
||||
|
@ -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 };
|
||||
|
459
Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
Normal file
459
Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
Normal 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
|
124
Telegram/SourceFiles/calls/group/calls_group_viewport.h
Normal file
124
Telegram/SourceFiles/calls/group/calls_group_viewport.h
Normal 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
|
247
Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
Normal file
247
Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
Normal 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
|
@ -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
|
221
Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp
Normal file
221
Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp
Normal 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
|
@ -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
|
126
Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
Normal file
126
Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
Normal 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
|
82
Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
Normal file
82
Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
Normal 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
|
Loading…
Reference in New Issue
Block a user