calls_video_bubble.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "calls/calls_video_bubble.h"
  8. #include "webrtc/webrtc_video_track.h"
  9. #include "ui/image/image_prepare.h"
  10. #include "ui/widgets/shadow.h"
  11. #include "styles/style_calls.h"
  12. #include "styles/style_widgets.h"
  13. #include "styles/style_layers.h"
  14. namespace Calls {
  15. VideoBubble::VideoBubble(
  16. not_null<QWidget*> parent,
  17. not_null<Webrtc::VideoTrack*> track)
  18. : _content(parent)
  19. , _track(track)
  20. , _state(Webrtc::VideoState::Inactive) {
  21. setup();
  22. }
  23. void VideoBubble::setup() {
  24. _content.show();
  25. applyDragMode(_dragMode);
  26. _content.paintRequest(
  27. ) | rpl::start_with_next([=] {
  28. paint();
  29. }, lifetime());
  30. _track->stateValue(
  31. ) | rpl::start_with_next([=](Webrtc::VideoState state) {
  32. setState(state);
  33. }, lifetime());
  34. _track->renderNextFrame(
  35. ) | rpl::start_with_next([=] {
  36. if (_track->frameSize().isEmpty()) {
  37. _track->markFrameShown();
  38. } else {
  39. updateVisibility();
  40. // We update whole parent widget in this case.
  41. // In case we update only bubble without the parent incoming
  42. // video frame we may get full parent of old frame with a
  43. // rectangular piece of a new frame rendered with that update().
  44. //_content.update();
  45. }
  46. }, lifetime());
  47. }
  48. void VideoBubble::updateGeometry(
  49. DragMode mode,
  50. QRect boundingRect,
  51. QSize sizeMin,
  52. QSize sizeMax) {
  53. Expects(!boundingRect.isEmpty());
  54. Expects(sizeMax.isEmpty() || !sizeMin.isEmpty());
  55. Expects(sizeMax.isEmpty() || sizeMin.width() <= sizeMax.width());
  56. Expects(sizeMax.isEmpty() || sizeMin.height() <= sizeMax.height());
  57. if (sizeMin.isEmpty()) {
  58. sizeMin = boundingRect.size();
  59. }
  60. if (sizeMax.isEmpty()) {
  61. sizeMax = sizeMin;
  62. }
  63. if (_dragMode != mode) {
  64. applyDragMode(mode);
  65. }
  66. if (_boundingRect != boundingRect) {
  67. applyBoundingRect(boundingRect);
  68. }
  69. if (_min != sizeMin || _max != sizeMax) {
  70. applySizeConstraints(sizeMin, sizeMax);
  71. }
  72. if (_geometryDirty && !_lastFrameSize.isEmpty()) {
  73. updateSizeToFrame(base::take(_lastFrameSize));
  74. }
  75. }
  76. QRect VideoBubble::geometry() const {
  77. return _content.isHidden() ? QRect() : _content.geometry();
  78. }
  79. void VideoBubble::applyBoundingRect(QRect rect) {
  80. _boundingRect = rect;
  81. _geometryDirty = true;
  82. }
  83. void VideoBubble::applyDragMode(DragMode mode) {
  84. _dragMode = mode;
  85. if (_dragMode == DragMode::None) {
  86. _dragging = false;
  87. _content.setCursor(style::cur_default);
  88. }
  89. _content.setAttribute(
  90. Qt::WA_TransparentForMouseEvents,
  91. true/*(_dragMode == DragMode::None)*/);
  92. if (_dragMode == DragMode::SnapToCorners) {
  93. _corner = RectPart::BottomRight;
  94. } else {
  95. _corner = RectPart::None;
  96. _lastDraggableSize = _size;
  97. }
  98. _size = QSize();
  99. _geometryDirty = true;
  100. }
  101. void VideoBubble::applySizeConstraints(QSize min, QSize max) {
  102. _min = min;
  103. _max = max;
  104. _geometryDirty = true;
  105. }
  106. void VideoBubble::paint() {
  107. auto p = QPainter(&_content);
  108. prepareFrame();
  109. if (!_frame.isNull()) {
  110. const auto padding = st::boxRoundShadow.extend;
  111. const auto inner = _content.rect().marginsRemoved(padding);
  112. Ui::Shadow::paint(p, inner, _content.width(), st::boxRoundShadow);
  113. const auto factor = style::DevicePixelRatio();
  114. const auto left = _mirrored
  115. ? (_frame.width() - (inner.width() * factor))
  116. : 0;
  117. p.drawImage(
  118. inner,
  119. _frame,
  120. QRect(QPoint(left, 0), inner.size() * factor));
  121. }
  122. _track->markFrameShown();
  123. }
  124. void VideoBubble::prepareFrame() {
  125. const auto original = _track->frameSize();
  126. if (original.isEmpty()) {
  127. _frame = QImage();
  128. return;
  129. }
  130. const auto padding = st::boxRoundShadow.extend;
  131. const auto size = (_content.rect() - padding).size()
  132. * style::DevicePixelRatio();
  133. // Should we check 'original' and 'size' aspect ratios?..
  134. const auto request = Webrtc::FrameRequest{
  135. .resize = size,
  136. .outer = size,
  137. };
  138. const auto frame = _track->frame(request);
  139. if (_frame.width() < size.width() || _frame.height() < size.height()) {
  140. _frame = QImage(size, QImage::Format_ARGB32_Premultiplied);
  141. _frame.fill(Qt::transparent);
  142. }
  143. Assert(_frame.width() >= frame.width()
  144. && _frame.height() >= frame.height());
  145. const auto dstPerLine = _frame.bytesPerLine();
  146. const auto srcPerLine = frame.bytesPerLine();
  147. const auto lineSize = frame.width() * 4;
  148. auto dst = _frame.bits();
  149. auto src = frame.bits();
  150. const auto till = src + frame.height() * srcPerLine;
  151. for (; src != till; src += srcPerLine, dst += dstPerLine) {
  152. memcpy(dst, src, lineSize);
  153. }
  154. _frame = Images::Round(
  155. std::move(_frame),
  156. ImageRoundRadius::Large,
  157. RectPart::AllCorners,
  158. QRect(QPoint(), size)
  159. ).mirrored(_mirrored, false);
  160. }
  161. void VideoBubble::setState(Webrtc::VideoState state) {
  162. if (state == Webrtc::VideoState::Paused) {
  163. using namespace Images;
  164. static constexpr auto kRadius = 24;
  165. _pausedFrame = Images::BlurLargeImage(_track->frame({}), kRadius);
  166. if (_pausedFrame.isNull()) {
  167. state = Webrtc::VideoState::Inactive;
  168. }
  169. }
  170. _state = state;
  171. updateVisibility();
  172. }
  173. void VideoBubble::updateSizeToFrame(QSize frame) {
  174. Expects(!frame.isEmpty());
  175. if (_lastFrameSize == frame) {
  176. return;
  177. }
  178. _lastFrameSize = frame;
  179. auto size = !_size.isEmpty()
  180. ? QSize(
  181. std::clamp(_size.width(), _min.width(), _max.width()),
  182. std::clamp(_size.height(), _min.height(), _max.height()))
  183. : (_dragMode == DragMode::None || _lastDraggableSize.isEmpty())
  184. ? QSize()
  185. : _lastDraggableSize;
  186. if (size.isEmpty()) {
  187. size = frame.scaled((_min + _max) / 2, Qt::KeepAspectRatio);
  188. } else {
  189. const auto area = size.width() * size.height();
  190. const auto w = int(base::SafeRound(std::max(
  191. std::sqrt((frame.width() * float64(area)) / (frame.height() * 1.)),
  192. 1.)));
  193. const auto h = area / w;
  194. size = QSize(w, h);
  195. if (w > _max.width() || h > _max.height()) {
  196. size = size.scaled(_max, Qt::KeepAspectRatio);
  197. }
  198. }
  199. size = QSize(std::max(1, size.width()), std::max(1, size.height()));
  200. setInnerSize(size);
  201. }
  202. void VideoBubble::setInnerSize(QSize size) {
  203. if (_size == size && !_geometryDirty) {
  204. return;
  205. }
  206. _geometryDirty = false;
  207. _size = size;
  208. const auto topLeft = [&] {
  209. switch (_corner) {
  210. case RectPart::None:
  211. return _boundingRect.topLeft() + QPoint(
  212. (_boundingRect.width() - size.width()) / 2,
  213. (_boundingRect.height() - size.height()) / 2);
  214. case RectPart::TopLeft:
  215. return _boundingRect.topLeft();
  216. case RectPart::TopRight:
  217. return QPoint(
  218. _boundingRect.x() + _boundingRect.width() - size.width(),
  219. _boundingRect.y());
  220. case RectPart::BottomRight:
  221. return QPoint(
  222. _boundingRect.x() + _boundingRect.width() - size.width(),
  223. _boundingRect.y() + _boundingRect.height() - size.height());
  224. case RectPart::BottomLeft:
  225. return QPoint(
  226. _boundingRect.x(),
  227. _boundingRect.y() + _boundingRect.height() - size.height());
  228. }
  229. Unexpected("Corner value in VideoBubble::setInnerSize.");
  230. }();
  231. const auto inner = QRect(topLeft, size);
  232. _content.setGeometry(inner.marginsAdded(st::boxRoundShadow.extend));
  233. }
  234. void VideoBubble::updateVisibility() {
  235. const auto size = _track->frameSize();
  236. const auto visible = (_state != Webrtc::VideoState::Inactive)
  237. && !size.isEmpty();
  238. if (visible) {
  239. updateSizeToFrame(size);
  240. }
  241. _content.setVisible(visible);
  242. }
  243. } // namespace Calls