| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- /*
- 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 = style::DevicePixelRatio();
- const auto left = _mirrored
- ? (_frame.width() - (inner.width() * factor))
- : 0;
- p.drawImage(
- inner,
- _frame,
- QRect(QPoint(left, 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() - padding).size()
- * style::DevicePixelRatio();
- // Should we check 'original' and 'size' aspect ratios?..
- const auto request = Webrtc::FrameRequest{
- .resize = size,
- .outer = size,
- };
- const auto frame = _track->frame(request);
- if (_frame.width() < size.width() || _frame.height() < size.height()) {
- _frame = QImage(size, QImage::Format_ARGB32_Premultiplied);
- _frame.fill(Qt::transparent);
- }
- 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(_mirrored, 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
|