tdesktop/Telegram/SourceFiles/calls/calls_video_bubble.cpp

269 lines
7.1 KiB
C++

/*
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/calls_video_bubble.h"
#include "webrtc/webrtc_video_track.h"
#include "ui/image/image_prepare.h"
#include "ui/widgets/shadow.h"
#include "styles/style_calls.h"
#include "styles/style_widgets.h"
#include "styles/style_layers.h"
namespace Calls {
VideoBubble::VideoBubble(
not_null<QWidget*> parent,
not_null<Webrtc::VideoTrack*> track)
: _content(parent)
, _track(track)
, _state(Webrtc::VideoState::Inactive) {
setup();
}
void VideoBubble::setup() {
_content.show();
applyDragMode(_dragMode);
_content.paintRequest(
) | rpl::start_with_next([=] {
paint();
}, lifetime());
_track->stateValue(
) | rpl::start_with_next([=](Webrtc::VideoState state) {
setState(state);
}, lifetime());
_track->renderNextFrame(
) | rpl::start_with_next([=] {
if (_track->frameSize().isEmpty()) {
_track->markFrameShown();
} else {
updateVisibility();
// We update whole parent widget in this case.
// In case we update only bubble without the parent incoming
// video frame we may get full parent of old frame with a
// rectangular piece of a new frame rendered with that update().
//_content.update();
}
}, lifetime());
}
void VideoBubble::updateGeometry(
DragMode mode,
QRect boundingRect,
QSize sizeMin,
QSize sizeMax) {
Expects(!boundingRect.isEmpty());
Expects(sizeMax.isEmpty() || !sizeMin.isEmpty());
Expects(sizeMax.isEmpty() || sizeMin.width() <= sizeMax.width());
Expects(sizeMax.isEmpty() || sizeMin.height() <= sizeMax.height());
if (sizeMin.isEmpty()) {
sizeMin = boundingRect.size();
}
if (sizeMax.isEmpty()) {
sizeMax = sizeMin;
}
if (_dragMode != mode) {
applyDragMode(mode);
}
if (_boundingRect != boundingRect) {
applyBoundingRect(boundingRect);
}
if (_min != sizeMin || _max != sizeMax) {
applySizeConstraints(sizeMin, sizeMax);
}
if (_geometryDirty && !_lastFrameSize.isEmpty()) {
updateSizeToFrame(base::take(_lastFrameSize));
}
}
QRect VideoBubble::geometry() const {
return _content.isHidden() ? QRect() : _content.geometry();
}
void VideoBubble::applyBoundingRect(QRect rect) {
_boundingRect = rect;
_geometryDirty = true;
}
void VideoBubble::applyDragMode(DragMode mode) {
_dragMode = mode;
if (_dragMode == DragMode::None) {
_dragging = false;
_content.setCursor(style::cur_default);
}
_content.setAttribute(
Qt::WA_TransparentForMouseEvents,
true/*(_dragMode == DragMode::None)*/);
if (_dragMode == DragMode::SnapToCorners) {
_corner = RectPart::BottomRight;
} else {
_corner = RectPart::None;
_lastDraggableSize = _size;
}
_size = QSize();
_geometryDirty = true;
}
void VideoBubble::applySizeConstraints(QSize min, QSize max) {
_min = min;
_max = max;
_geometryDirty = true;
}
void VideoBubble::paint() {
auto p = QPainter(&_content);
prepareFrame();
if (!_frame.isNull()) {
const auto padding = st::boxRoundShadow.extend;
const auto inner = _content.rect().marginsRemoved(padding);
Ui::Shadow::paint(p, inner, _content.width(), st::boxRoundShadow);
const auto factor = cIntRetinaFactor();
p.drawImage(
inner,
_frame,
QRect(
QPoint(_frame.width() - (inner.width() * factor), 0),
inner.size() * factor));
}
_track->markFrameShown();
}
void VideoBubble::prepareFrame() {
const auto original = _track->frameSize();
if (original.isEmpty()) {
_frame = QImage();
return;
}
const auto padding = st::boxRoundShadow.extend;
const auto size = _content.rect().marginsRemoved(padding).size()
* cIntRetinaFactor();
// Should we check 'original' and 'size' aspect ratios?..
const auto request = Webrtc::FrameRequest{
.resize = size,
.outer = size,
};
const auto frame = _track->frame(request).mirrored(!_mirrored, false);
if (_frame.width() < size.width() || _frame.height() < size.height()) {
_frame = QImage(
size * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
}
Assert(_frame.width() >= frame.width()
&& _frame.height() >= frame.height());
const auto dstPerLine = _frame.bytesPerLine();
const auto srcPerLine = frame.bytesPerLine();
const auto lineSize = frame.width() * 4;
auto dst = _frame.bits();
auto src = frame.bits();
const auto till = src + frame.height() * srcPerLine;
for (; src != till; src += srcPerLine, dst += dstPerLine) {
memcpy(dst, src, lineSize);
}
_frame = Images::Round(
std::move(_frame),
ImageRoundRadius::Large,
RectPart::AllCorners,
QRect(QPoint(), size)
).mirrored(true, false);
}
void VideoBubble::setState(Webrtc::VideoState state) {
if (state == Webrtc::VideoState::Paused) {
using namespace Images;
static constexpr auto kRadius = 24;
_pausedFrame = Images::BlurLargeImage(_track->frame({}), kRadius);
if (_pausedFrame.isNull()) {
state = Webrtc::VideoState::Inactive;
}
}
_state = state;
updateVisibility();
}
void VideoBubble::updateSizeToFrame(QSize frame) {
Expects(!frame.isEmpty());
if (_lastFrameSize == frame) {
return;
}
_lastFrameSize = frame;
auto size = !_size.isEmpty()
? QSize(
std::clamp(_size.width(), _min.width(), _max.width()),
std::clamp(_size.height(), _min.height(), _max.height()))
: (_dragMode == DragMode::None || _lastDraggableSize.isEmpty())
? QSize()
: _lastDraggableSize;
if (size.isEmpty()) {
size = frame.scaled((_min + _max) / 2, Qt::KeepAspectRatio);
} else {
const auto area = size.width() * size.height();
const auto w = int(base::SafeRound(std::max(
std::sqrt((frame.width() * float64(area)) / (frame.height() * 1.)),
1.)));
const auto h = area / w;
size = QSize(w, h);
if (w > _max.width() || h > _max.height()) {
size = size.scaled(_max, Qt::KeepAspectRatio);
}
}
size = QSize(std::max(1, size.width()), std::max(1, size.height()));
setInnerSize(size);
}
void VideoBubble::setInnerSize(QSize size) {
if (_size == size && !_geometryDirty) {
return;
}
_geometryDirty = false;
_size = size;
const auto topLeft = [&] {
switch (_corner) {
case RectPart::None:
return _boundingRect.topLeft() + QPoint(
(_boundingRect.width() - size.width()) / 2,
(_boundingRect.height() - size.height()) / 2);
case RectPart::TopLeft:
return _boundingRect.topLeft();
case RectPart::TopRight:
return QPoint(
_boundingRect.x() + _boundingRect.width() - size.width(),
_boundingRect.y());
case RectPart::BottomRight:
return QPoint(
_boundingRect.x() + _boundingRect.width() - size.width(),
_boundingRect.y() + _boundingRect.height() - size.height());
case RectPart::BottomLeft:
return QPoint(
_boundingRect.x(),
_boundingRect.y() + _boundingRect.height() - size.height());
}
Unexpected("Corner value in VideoBubble::setInnerSize.");
}();
const auto inner = QRect(topLeft, size);
_content.setGeometry(inner.marginsAdded(st::boxRoundShadow.extend));
}
void VideoBubble::updateVisibility() {
const auto size = _track->frameSize();
const auto visible = (_state != Webrtc::VideoState::Inactive)
&& !size.isEmpty();
if (visible) {
updateSizeToFrame(size);
}
_content.setVisible(visible);
}
} // namespace Calls