media_view_pip.cpp 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870
  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 "media/view/media_view_pip.h"
  8. #include "media/streaming/media_streaming_player.h"
  9. #include "media/streaming/media_streaming_document.h"
  10. #include "media/streaming/media_streaming_utility.h"
  11. #include "media/view/media_view_playback_progress.h"
  12. #include "media/view/media_view_pip_opengl.h"
  13. #include "media/view/media_view_pip_raster.h"
  14. #include "media/audio/media_audio.h"
  15. #include "data/data_document.h"
  16. #include "data/data_document_media.h"
  17. #include "data/data_file_origin.h"
  18. #include "data/data_session.h"
  19. #include "data/data_media_rotation.h"
  20. #include "main/main_account.h"
  21. #include "main/main_session.h"
  22. #include "core/application.h"
  23. #include "base/platform/base_platform_info.h"
  24. #include "base/power_save_blocker.h"
  25. #include "ui/platform/ui_platform_utility.h"
  26. #include "ui/platform/ui_platform_window_title.h"
  27. #include "ui/widgets/buttons.h"
  28. #include "ui/wrap/fade_wrap.h"
  29. #include "ui/widgets/shadow.h"
  30. #include "ui/text/format_values.h"
  31. #include "ui/gl/gl_surface.h"
  32. #include "ui/painter.h"
  33. #include "ui/ui_utility.h"
  34. #include "window/window_controller.h"
  35. #include "styles/style_widgets.h"
  36. #include "styles/style_window.h"
  37. #include "styles/style_media_view.h"
  38. #include <QtGui/QWindow>
  39. #include <QtGui/QScreen>
  40. #include <QtWidgets/QApplication>
  41. namespace Media {
  42. namespace View {
  43. namespace {
  44. constexpr auto kPipLoaderPriority = 2;
  45. constexpr auto kMsInSecond = 1000;
  46. [[nodiscard]] QRect ScreenFromPosition(QPoint point) {
  47. const auto screen = QGuiApplication::screenAt(point);
  48. const auto use = screen ? screen : QGuiApplication::primaryScreen();
  49. return use
  50. ? use->availableGeometry()
  51. : QRect(0, 0, st::windowDefaultWidth, st::windowDefaultHeight);
  52. }
  53. [[nodiscard]] QSize MaxAllowedSizeForScreen(QSize screenSize) {
  54. // Each side should be less than screen side - 3 * st::pipBorderSkip,
  55. // That way it won't try to snap to both opposite sides of the screen.
  56. const auto skip = 3 * st::pipBorderSkip;
  57. return { screenSize.width() - skip, screenSize.height() - skip };
  58. }
  59. [[nodiscard]] QPoint ClampToEdges(QRect screen, QRect inner) {
  60. const auto skip = st::pipBorderSkip;
  61. const auto area = st::pipBorderSnapArea;
  62. const auto sleft = screen.x() + skip;
  63. const auto stop = screen.y() + skip;
  64. const auto sright = screen.x() + screen.width() - skip;
  65. const auto sbottom = screen.y() + screen.height() - skip;
  66. const auto ileft = inner.x();
  67. const auto itop = inner.y();
  68. const auto iright = inner.x() + inner.width();
  69. const auto ibottom = inner.y() + inner.height();
  70. auto shiftx = 0;
  71. auto shifty = 0;
  72. if (iright + shiftx >= sright - area && iright + shiftx < sright + area) {
  73. shiftx += (sright - iright);
  74. }
  75. if (ileft + shiftx >= sleft - area && ileft + shiftx < sleft + area) {
  76. shiftx += (sleft - ileft);
  77. }
  78. if (ibottom + shifty >= sbottom - area && ibottom + shifty < sbottom + area) {
  79. shifty += (sbottom - ibottom);
  80. }
  81. if (itop + shifty >= stop - area && itop + shifty < stop + area) {
  82. shifty += (stop - itop);
  83. }
  84. return inner.topLeft() + QPoint(shiftx, shifty);
  85. }
  86. [[nodiscard]] QRect Transformed(
  87. QRect original,
  88. QSize minimalSize,
  89. QSize maximalSize,
  90. QPoint delta,
  91. RectPart by) {
  92. const auto width = original.width();
  93. const auto height = original.height();
  94. const auto x1 = width - minimalSize.width();
  95. const auto x2 = maximalSize.width() - width;
  96. const auto y1 = height - minimalSize.height();
  97. const auto y2 = maximalSize.height() - height;
  98. switch (by) {
  99. case RectPart::Center: return original.translated(delta);
  100. case RectPart::TopLeft:
  101. original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
  102. original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
  103. return original;
  104. case RectPart::TopRight:
  105. original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
  106. original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
  107. return original;
  108. case RectPart::BottomRight:
  109. original.setHeight(
  110. original.height() + std::clamp(delta.y(), -y1, y2));
  111. original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
  112. return original;
  113. case RectPart::BottomLeft:
  114. original.setHeight(
  115. original.height() + std::clamp(delta.y(), -y1, y2));
  116. original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
  117. return original;
  118. case RectPart::Left:
  119. original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
  120. return original;
  121. case RectPart::Top:
  122. original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
  123. return original;
  124. case RectPart::Right:
  125. original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
  126. return original;
  127. case RectPart::Bottom:
  128. original.setHeight(
  129. original.height() + std::clamp(delta.y(), -y1, y2));
  130. return original;
  131. }
  132. return original;
  133. Unexpected("RectPart in PiP Transformed.");
  134. }
  135. [[nodiscard]] QRect Constrained(
  136. QRect original,
  137. QSize minimalSize,
  138. QSize maximalSize,
  139. QSize ratio,
  140. RectPart by,
  141. RectParts attached) {
  142. if (by == RectPart::Center) {
  143. return original;
  144. } else if (!original.width() && !original.height()) {
  145. return QRect(original.topLeft(), ratio);
  146. }
  147. const auto widthLarger = (original.width() * ratio.height())
  148. > (original.height() * ratio.width());
  149. const auto desiredSize = ratio.scaled(
  150. original.size(),
  151. (((RectParts(by) & RectPart::AllCorners)
  152. || ((by == RectPart::Top || by == RectPart::Bottom)
  153. && widthLarger)
  154. || ((by == RectPart::Left || by == RectPart::Right)
  155. && !widthLarger))
  156. ? Qt::KeepAspectRatio
  157. : Qt::KeepAspectRatioByExpanding));
  158. const auto newSize = QSize(
  159. std::clamp(
  160. desiredSize.width(),
  161. minimalSize.width(),
  162. maximalSize.width()),
  163. std::clamp(
  164. desiredSize.height(),
  165. minimalSize.height(),
  166. maximalSize.height()));
  167. switch (by) {
  168. case RectPart::TopLeft:
  169. return QRect(
  170. original.topLeft() + QPoint(
  171. original.width() - newSize.width(),
  172. original.height() - newSize.height()),
  173. newSize);
  174. case RectPart::TopRight:
  175. return QRect(
  176. original.topLeft() + QPoint(
  177. 0,
  178. original.height() - newSize.height()),
  179. newSize);
  180. case RectPart::BottomRight:
  181. return QRect(original.topLeft(), newSize);
  182. case RectPart::BottomLeft:
  183. return QRect(
  184. original.topLeft() + QPoint(
  185. original.width() - newSize.width(),
  186. 0),
  187. newSize);
  188. case RectPart::Left:
  189. return QRect(
  190. original.topLeft() + QPoint(
  191. (original.width() - newSize.width()),
  192. ((attached & RectPart::Top)
  193. ? 0
  194. : (attached & RectPart::Bottom)
  195. ? (original.height() - newSize.height())
  196. : (original.height() - newSize.height()) / 2)),
  197. newSize);
  198. case RectPart::Top:
  199. return QRect(
  200. original.topLeft() + QPoint(
  201. ((attached & RectPart::Left)
  202. ? 0
  203. : (attached & RectPart::Right)
  204. ? (original.width() - newSize.width())
  205. : (original.width() - newSize.width()) / 2),
  206. 0),
  207. newSize);
  208. case RectPart::Right:
  209. return QRect(
  210. original.topLeft() + QPoint(
  211. 0,
  212. ((attached & RectPart::Top)
  213. ? 0
  214. : (attached & RectPart::Bottom)
  215. ? (original.height() - newSize.height())
  216. : (original.height() - newSize.height()) / 2)),
  217. newSize);
  218. case RectPart::Bottom:
  219. return QRect(
  220. original.topLeft() + QPoint(
  221. ((attached & RectPart::Left)
  222. ? 0
  223. : (attached & RectPart::Right)
  224. ? (original.width() - newSize.width())
  225. : (original.width() - newSize.width()) / 2),
  226. (original.height() - newSize.height())),
  227. newSize);
  228. }
  229. Unexpected("RectPart in PiP Constrained.");
  230. }
  231. [[nodiscard]] QByteArray Serialize(const PipPanel::Position &position) {
  232. auto result = QByteArray();
  233. auto stream = QDataStream(&result, QIODevice::WriteOnly);
  234. stream.setVersion(QDataStream::Qt_5_3);
  235. stream
  236. << qint32(position.attached.value())
  237. << qint32(position.snapped.value())
  238. << position.screen
  239. << position.geometry;
  240. stream.device()->close();
  241. return result;
  242. }
  243. [[nodiscard]] PipPanel::Position Deserialize(const QByteArray &data) {
  244. auto stream = QDataStream(data);
  245. auto result = PipPanel::Position();
  246. auto attached = qint32(0);
  247. auto snapped = qint32(0);
  248. stream >> attached >> snapped >> result.screen >> result.geometry;
  249. if (stream.status() != QDataStream::Ok) {
  250. return {};
  251. }
  252. result.attached = RectParts::from_raw(attached);
  253. result.snapped = RectParts::from_raw(snapped);
  254. return result;
  255. }
  256. Qt::Edges RectPartToQtEdges(RectPart rectPart) {
  257. switch (rectPart) {
  258. case RectPart::TopLeft:
  259. return Qt::TopEdge | Qt::LeftEdge;
  260. case RectPart::TopRight:
  261. return Qt::TopEdge | Qt::RightEdge;
  262. case RectPart::BottomRight:
  263. return Qt::BottomEdge | Qt::RightEdge;
  264. case RectPart::BottomLeft:
  265. return Qt::BottomEdge | Qt::LeftEdge;
  266. case RectPart::Left:
  267. return Qt::LeftEdge;
  268. case RectPart::Top:
  269. return Qt::TopEdge;
  270. case RectPart::Right:
  271. return Qt::RightEdge;
  272. case RectPart::Bottom:
  273. return Qt::BottomEdge;
  274. }
  275. return Qt::Edges();
  276. }
  277. } // namespace
  278. QRect RotatedRect(QRect rect, int rotation) {
  279. switch (rotation) {
  280. case 0: return rect;
  281. case 90: return QRect(
  282. rect.y(),
  283. -rect.x() - rect.width(),
  284. rect.height(),
  285. rect.width());
  286. case 180: return QRect(
  287. -rect.x() - rect.width(),
  288. -rect.y() - rect.height(),
  289. rect.width(),
  290. rect.height());
  291. case 270: return QRect(
  292. -rect.y() - rect.height(),
  293. rect.x(),
  294. rect.height(),
  295. rect.width());
  296. }
  297. Unexpected("Rotation in RotatedRect.");
  298. }
  299. bool UsePainterRotation(int rotation) {
  300. return !(rotation % 180);
  301. }
  302. QSize FlipSizeByRotation(QSize size, int rotation) {
  303. return (((rotation / 90) % 2) == 1)
  304. ? QSize(size.height(), size.width())
  305. : size;
  306. }
  307. QImage RotateFrameImage(QImage image, int rotation) {
  308. auto transform = QTransform();
  309. transform.rotate(rotation);
  310. return image.transformed(transform);
  311. }
  312. PipPanel::PipPanel(
  313. QWidget *parent,
  314. Fn<Ui::GL::ChosenRenderer(Ui::GL::Capabilities)> renderer)
  315. : _content(Ui::GL::CreateSurface(std::move(renderer)))
  316. , _parent(parent) {
  317. }
  318. void PipPanel::init() {
  319. widget()->setWindowFlags(Qt::Tool
  320. | Qt::WindowStaysOnTopHint
  321. | Qt::FramelessWindowHint
  322. | Qt::WindowDoesNotAcceptFocus);
  323. widget()->setAttribute(Qt::WA_ShowWithoutActivating);
  324. widget()->setAttribute(Qt::WA_MacAlwaysShowToolWindow);
  325. widget()->setAttribute(Qt::WA_NoSystemBackground);
  326. widget()->setAttribute(Qt::WA_TranslucentBackground);
  327. Ui::Platform::IgnoreAllActivation(widget());
  328. Ui::Platform::InitOnTopPanel(widget());
  329. widget()->setMouseTracking(true);
  330. widget()->resize(0, 0);
  331. widget()->hide();
  332. rpl::merge(
  333. rp()->shownValue() | rpl::to_empty,
  334. rp()->paintRequest() | rpl::to_empty
  335. ) | rpl::map([=] {
  336. return widget()->windowHandle()
  337. && widget()->windowHandle()->isExposed();
  338. }) | rpl::distinct_until_changed(
  339. ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
  340. // Workaround Qt's forced transient parent.
  341. Ui::Platform::ClearTransientParent(widget());
  342. Ui::Platform::SetWindowMargins(widget(), _padding);
  343. }, rp()->lifetime());
  344. rp()->screenValue(
  345. ) | rpl::skip(1) | rpl::start_with_next([=](not_null<QScreen*> screen) {
  346. handleScreenChanged(screen);
  347. }, rp()->lifetime());
  348. if (Platform::IsWayland()) {
  349. rp()->sizeValue(
  350. ) | rpl::skip(1) | rpl::start_with_next([=](QSize size) {
  351. handleWaylandResize(size);
  352. }, rp()->lifetime());
  353. }
  354. }
  355. not_null<QWidget*> PipPanel::widget() const {
  356. return _content->rpWidget();
  357. }
  358. not_null<Ui::RpWidgetWrap*> PipPanel::rp() const {
  359. return _content.get();
  360. }
  361. void PipPanel::setAspectRatio(QSize ratio) {
  362. if (_ratio == ratio) {
  363. return;
  364. }
  365. _ratio = ratio;
  366. if (_ratio.isEmpty()) {
  367. _ratio = QSize(1, 1);
  368. }
  369. Ui::Platform::DisableSystemWindowResize(widget(), _ratio);
  370. if (!widget()->size().isEmpty()) {
  371. setPosition(countPosition());
  372. }
  373. }
  374. void PipPanel::setPosition(Position position) {
  375. if (!position.screen.isEmpty()) {
  376. for (const auto screen : QApplication::screens()) {
  377. if (screen->geometry() == position.screen) {
  378. setPositionOnScreen(position, screen->availableGeometry());
  379. return;
  380. }
  381. }
  382. }
  383. setPositionDefault();
  384. }
  385. QRect PipPanel::inner() const {
  386. return widget()->rect().marginsRemoved(_padding);
  387. }
  388. RectParts PipPanel::attached() const {
  389. return _attached;
  390. }
  391. bool PipPanel::useTransparency() const {
  392. return _useTransparency;
  393. }
  394. void PipPanel::setDragDisabled(bool disabled) {
  395. _dragDisabled = disabled;
  396. if (_dragState) {
  397. _dragState = std::nullopt;
  398. }
  399. }
  400. bool PipPanel::dragging() const {
  401. return _dragState.has_value();
  402. }
  403. rpl::producer<> PipPanel::saveGeometryRequests() const {
  404. return _saveGeometryRequests.events();
  405. }
  406. QScreen *PipPanel::myScreen() const {
  407. return widget()->screen();
  408. }
  409. PipPanel::Position PipPanel::countPosition() const {
  410. const auto screen = myScreen();
  411. if (!screen) {
  412. return Position();
  413. }
  414. auto result = Position();
  415. result.screen = screen->geometry();
  416. result.geometry = widget()->geometry().marginsRemoved(_padding);
  417. const auto available = screen->availableGeometry();
  418. const auto skip = st::pipBorderSkip;
  419. const auto left = result.geometry.x();
  420. const auto right = left + result.geometry.width();
  421. const auto top = result.geometry.y();
  422. const auto bottom = top + result.geometry.height();
  423. if ((!_dragState || *_dragState != RectPart::Center)
  424. && !Platform::IsWayland()) {
  425. if (left == available.x()) {
  426. result.attached |= RectPart::Left;
  427. } else if (right == available.x() + available.width()) {
  428. result.attached |= RectPart::Right;
  429. } else if (left == available.x() + skip) {
  430. result.snapped |= RectPart::Left;
  431. } else if (right == available.x() + available.width() - skip) {
  432. result.snapped |= RectPart::Right;
  433. }
  434. if (top == available.y()) {
  435. result.attached |= RectPart::Top;
  436. } else if (bottom == available.y() + available.height()) {
  437. result.attached |= RectPart::Bottom;
  438. } else if (top == available.y() + skip) {
  439. result.snapped |= RectPart::Top;
  440. } else if (bottom == available.y() + available.height() - skip) {
  441. result.snapped |= RectPart::Bottom;
  442. }
  443. }
  444. return result;
  445. }
  446. void PipPanel::setPositionDefault() {
  447. const auto parentScreen = _parent ? _parent->screen() : nullptr;
  448. const auto myScreen = widget()->screen();
  449. if (parentScreen && myScreen != parentScreen) {
  450. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  451. widget()->setScreen(parentScreen);
  452. #else // Qt >= 6.0.0
  453. widget()->windowHandle()->setScreen(parentScreen);
  454. #endif // Qt < 6.0.0
  455. }
  456. auto position = Position();
  457. position.snapped = RectPart::Top | RectPart::Left;
  458. position.screen = parentScreen->geometry();
  459. position.geometry = QRect(0, 0, st::pipDefaultSize, st::pipDefaultSize);
  460. setPositionOnScreen(position, parentScreen->availableGeometry());
  461. }
  462. void PipPanel::setPositionOnScreen(Position position, QRect available) {
  463. const auto screen = available;
  464. const auto requestedSize = position.geometry.size();
  465. const auto max = std::max(requestedSize.width(), requestedSize.height());
  466. // Apply aspect ratio.
  467. const auto scaled = (_ratio.width() > _ratio.height())
  468. ? QSize(max, max * _ratio.height() / _ratio.width())
  469. : QSize(max * _ratio.width() / _ratio.height(), max);
  470. // Apply maximum size.
  471. const auto fit = MaxAllowedSizeForScreen(screen.size());
  472. const auto byWidth = (scaled.width() * fit.height())
  473. > (scaled.height() * fit.width());
  474. const auto normalized = (byWidth && scaled.width() > fit.width())
  475. ? QSize(fit.width(), fit.width() * scaled.height() / scaled.width())
  476. : (!byWidth && scaled.height() > fit.height())
  477. ? QSize(
  478. fit.height() * scaled.width() / scaled.height(),
  479. fit.height())
  480. : scaled;
  481. // Apply minimal size.
  482. const auto min = st::pipMinimalSize;
  483. const auto minimalSize = (_ratio.width() > _ratio.height())
  484. ? QSize(min * _ratio.width() / _ratio.height(), min)
  485. : QSize(min, min * _ratio.height() / _ratio.width());
  486. const auto size = QSize(
  487. std::max(normalized.width(), minimalSize.width()),
  488. std::max(normalized.height(), minimalSize.height()));
  489. // Apply maximal size.
  490. const auto maximalSize = byWidth
  491. ? QSize(fit.width(), fit.width() * _ratio.height() / _ratio.width())
  492. : QSize(fit.height() * _ratio.width() / _ratio.height(), fit.height());
  493. // Apply left-right screen borders.
  494. const auto skip = st::pipBorderSkip;
  495. const auto inner = screen.marginsRemoved({ skip, skip, skip, skip });
  496. auto geometry = QRect(position.geometry.topLeft(), size);
  497. if ((position.attached & RectPart::Left)
  498. || (geometry.x() < screen.x())) {
  499. geometry.moveLeft(screen.x());
  500. } else if ((position.attached & RectPart::Right)
  501. || (geometry.x() + geometry.width() > screen.x() + screen.width())) {
  502. geometry.moveLeft(screen.x() + screen.width() - geometry.width());
  503. } else if (position.snapped & RectPart::Left) {
  504. geometry.moveLeft(inner.x());
  505. } else if (position.snapped & RectPart::Right) {
  506. geometry.moveLeft(inner.x() + inner.width() - geometry.width());
  507. }
  508. // Apply top-bottom screen borders.
  509. if ((position.attached & RectPart::Top) || (geometry.y() < screen.y())) {
  510. geometry.moveTop(screen.y());
  511. } else if ((position.attached & RectPart::Bottom)
  512. || (geometry.y() + geometry.height()
  513. > screen.y() + screen.height())) {
  514. geometry.moveTop(
  515. screen.y() + screen.height() - geometry.height());
  516. } else if (position.snapped & RectPart::Top) {
  517. geometry.moveTop(inner.y());
  518. } else if (position.snapped & RectPart::Bottom) {
  519. geometry.moveTop(inner.y() + inner.height() - geometry.height());
  520. }
  521. geometry += _padding;
  522. setGeometry(geometry);
  523. widget()->setMinimumSize(minimalSize);
  524. widget()->setMaximumSize(
  525. std::max(minimalSize.width(), maximalSize.width()),
  526. std::max(minimalSize.height(), maximalSize.height()));
  527. updateDecorations();
  528. }
  529. void PipPanel::update() {
  530. widget()->update();
  531. }
  532. void PipPanel::setGeometry(QRect geometry) {
  533. widget()->setGeometry(geometry);
  534. }
  535. void PipPanel::handleWaylandResize(QSize size) {
  536. if (_inHandleWaylandResize) {
  537. return;
  538. }
  539. _inHandleWaylandResize = true;
  540. // Apply aspect ratio.
  541. const auto max = std::max(size.width(), size.height());
  542. const auto scaled = (_ratio.width() > _ratio.height())
  543. ? QSize(max, max * _ratio.height() / _ratio.width())
  544. : QSize(max * _ratio.width() / _ratio.height(), max);
  545. // Buffer can't be bigger than the configured
  546. // (suggested by compositor) size.
  547. const auto byWidth = (scaled.width() * size.height())
  548. > (scaled.height() * size.width());
  549. const auto normalized = (byWidth && scaled.width() > size.width())
  550. ? QSize(size.width(), size.width() * scaled.height() / scaled.width())
  551. : (!byWidth && scaled.height() > size.height())
  552. ? QSize(
  553. size.height() * scaled.width() / scaled.height(),
  554. size.height())
  555. : scaled;
  556. widget()->resize(normalized);
  557. QResizeEvent e(normalized, size);
  558. QCoreApplication::sendEvent(widget()->windowHandle(), &e);
  559. _inHandleWaylandResize = false;
  560. }
  561. void PipPanel::handleScreenChanged(not_null<QScreen*> screen) {
  562. const auto screenGeometry = screen->availableGeometry();
  563. const auto minimalSize = _ratio.scaled(
  564. st::pipMinimalSize,
  565. st::pipMinimalSize,
  566. Qt::KeepAspectRatioByExpanding);
  567. const auto maximalSize = _ratio.scaled(
  568. MaxAllowedSizeForScreen(screenGeometry.size()),
  569. Qt::KeepAspectRatio);
  570. widget()->setMinimumSize(minimalSize);
  571. widget()->setMaximumSize(
  572. std::max(minimalSize.width(), maximalSize.width()),
  573. std::max(minimalSize.height(), maximalSize.height()));
  574. }
  575. void PipPanel::handleMousePress(QPoint position, Qt::MouseButton button) {
  576. if (button != Qt::LeftButton) {
  577. return;
  578. }
  579. updateOverState(position);
  580. _pressState = _overState;
  581. _pressPoint = QCursor::pos();
  582. }
  583. void PipPanel::handleMouseRelease(QPoint position, Qt::MouseButton button) {
  584. if (button != Qt::LeftButton || !base::take(_pressState)) {
  585. return;
  586. } else if (base::take(_dragState)) {
  587. finishDrag(QCursor::pos());
  588. updateOverState(position);
  589. }
  590. }
  591. void PipPanel::updateOverState(QPoint point) {
  592. const auto size = st::pipResizeArea;
  593. const auto ignore = _attached | _snapped;
  594. const auto count = [&](RectPart side, int padding) {
  595. return (ignore & side) ? 0 : padding ? padding : size;
  596. };
  597. const auto left = count(RectPart::Left, _padding.left());
  598. const auto top = count(RectPart::Top, _padding.top());
  599. const auto right = count(RectPart::Right, _padding.right());
  600. const auto bottom = count(RectPart::Bottom, _padding.bottom());
  601. const auto width = widget()->width();
  602. const auto height = widget()->height();
  603. const auto overState = [&] {
  604. if (point.x() < left) {
  605. if (point.y() < top) {
  606. return RectPart::TopLeft;
  607. } else if (point.y() >= height - bottom) {
  608. return RectPart::BottomLeft;
  609. } else {
  610. return RectPart::Left;
  611. }
  612. } else if (point.x() >= width - right) {
  613. if (point.y() < top) {
  614. return RectPart::TopRight;
  615. } else if (point.y() >= height - bottom) {
  616. return RectPart::BottomRight;
  617. } else {
  618. return RectPart::Right;
  619. }
  620. } else if (point.y() < top) {
  621. return RectPart::Top;
  622. } else if (point.y() >= height - bottom) {
  623. return RectPart::Bottom;
  624. } else {
  625. return RectPart::Center;
  626. }
  627. }();
  628. if (_overState != overState) {
  629. _overState = overState;
  630. widget()->setCursor([&] {
  631. switch (_overState) {
  632. case RectPart::Center:
  633. return style::cur_pointer;
  634. case RectPart::TopLeft:
  635. case RectPart::BottomRight:
  636. return style::cur_sizefdiag;
  637. case RectPart::TopRight:
  638. case RectPart::BottomLeft:
  639. return style::cur_sizebdiag;
  640. case RectPart::Left:
  641. case RectPart::Right:
  642. return style::cur_sizehor;
  643. case RectPart::Top:
  644. case RectPart::Bottom:
  645. return style::cur_sizever;
  646. }
  647. Unexpected("State in PipPanel::updateOverState.");
  648. }());
  649. }
  650. }
  651. void PipPanel::handleMouseMove(QPoint position) {
  652. if (!_pressState) {
  653. updateOverState(position);
  654. return;
  655. }
  656. const auto point = QCursor::pos();
  657. const auto distance = QApplication::startDragDistance();
  658. if (!_dragState
  659. && (point - _pressPoint).manhattanLength() > distance
  660. && !_dragDisabled) {
  661. _dragState = _pressState;
  662. updateDecorations();
  663. _dragStartGeometry = widget()->geometry().marginsRemoved(_padding);
  664. }
  665. if (_dragState) {
  666. if (Platform::IsWayland()) {
  667. startSystemDrag();
  668. } else {
  669. processDrag(point);
  670. }
  671. }
  672. }
  673. void PipPanel::startSystemDrag() {
  674. Expects(_dragState.has_value());
  675. const auto stateEdges = RectPartToQtEdges(*_dragState);
  676. if (stateEdges) {
  677. widget()->windowHandle()->startSystemResize(stateEdges);
  678. } else {
  679. widget()->windowHandle()->startSystemMove();
  680. }
  681. Ui::SendSynteticMouseEvent(
  682. widget().get(),
  683. QEvent::MouseButtonRelease,
  684. Qt::LeftButton);
  685. }
  686. void PipPanel::processDrag(QPoint point) {
  687. Expects(_dragState.has_value());
  688. const auto dragPart = *_dragState;
  689. const auto screen = (dragPart == RectPart::Center)
  690. ? ScreenFromPosition(point)
  691. : myScreen()
  692. ? myScreen()->availableGeometry()
  693. : QRect();
  694. if (screen.isEmpty()) {
  695. return;
  696. }
  697. const auto minimalSize = _ratio.scaled(
  698. st::pipMinimalSize,
  699. st::pipMinimalSize,
  700. Qt::KeepAspectRatioByExpanding);
  701. const auto maximalSize = _ratio.scaled(
  702. MaxAllowedSizeForScreen(screen.size()),
  703. Qt::KeepAspectRatio);
  704. const auto geometry = Transformed(
  705. _dragStartGeometry,
  706. minimalSize,
  707. maximalSize,
  708. point - _pressPoint,
  709. dragPart);
  710. const auto valid = Constrained(
  711. geometry,
  712. minimalSize,
  713. maximalSize,
  714. _ratio,
  715. dragPart,
  716. _attached);
  717. const auto clamped = (dragPart == RectPart::Center)
  718. ? ClampToEdges(screen, valid)
  719. : valid.topLeft();
  720. widget()->setMinimumSize(minimalSize);
  721. widget()->setMaximumSize(
  722. std::max(minimalSize.width(), maximalSize.width()),
  723. std::max(minimalSize.height(), maximalSize.height()));
  724. if (clamped != valid.topLeft()) {
  725. moveAnimated(clamped);
  726. } else {
  727. const auto newGeometry = valid.marginsAdded(_padding);
  728. _positionAnimation.stop();
  729. setGeometry(newGeometry);
  730. }
  731. }
  732. void PipPanel::finishDrag(QPoint point) {
  733. const auto screen = ScreenFromPosition(point);
  734. const auto inner = widget()->geometry().marginsRemoved(_padding);
  735. const auto position = widget()->pos();
  736. const auto clamped = [&] {
  737. auto result = position;
  738. if (Platform::IsWayland()) {
  739. return result;
  740. }
  741. if (result.x() > screen.x() + screen.width() - inner.width()) {
  742. result.setX(screen.x() + screen.width() - inner.width());
  743. }
  744. if (result.x() < screen.x()) {
  745. result.setX(screen.x());
  746. }
  747. if (result.y() > screen.y() + screen.height() - inner.height()) {
  748. result.setY(screen.y() + screen.height() - inner.height());
  749. }
  750. if (result.y() < screen.y()) {
  751. result.setY(screen.y());
  752. }
  753. return result;
  754. }();
  755. if (position != clamped) {
  756. moveAnimated(clamped);
  757. } else {
  758. _positionAnimation.stop();
  759. updateDecorations();
  760. }
  761. }
  762. void PipPanel::updatePositionAnimated() {
  763. const auto progress = _positionAnimation.value(1.);
  764. if (!_positionAnimation.animating()) {
  765. widget()->move(_positionAnimationTo
  766. - QPoint(_padding.left(), _padding.top()));
  767. if (!_dragState) {
  768. updateDecorations();
  769. }
  770. return;
  771. }
  772. const auto from = QPointF(_positionAnimationFrom);
  773. const auto to = QPointF(_positionAnimationTo);
  774. widget()->move((from + (to - from) * progress).toPoint()
  775. - QPoint(_padding.left(), _padding.top()));
  776. }
  777. void PipPanel::moveAnimated(QPoint to) {
  778. if (_positionAnimation.animating() && _positionAnimationTo == to) {
  779. return;
  780. }
  781. _positionAnimationTo = to;
  782. _positionAnimationFrom = widget()->pos()
  783. + QPoint(_padding.left(), _padding.top());
  784. _positionAnimation.stop();
  785. _positionAnimation.start(
  786. [=] { updatePositionAnimated(); },
  787. 0.,
  788. 1.,
  789. st::slideWrapDuration,
  790. anim::easeOutCirc);
  791. }
  792. void PipPanel::updateDecorations() {
  793. const auto guard = gsl::finally([&] {
  794. if (!_dragState) {
  795. _saveGeometryRequests.fire({});
  796. }
  797. });
  798. const auto position = countPosition();
  799. const auto use = Ui::Platform::TranslucentWindowsSupported();
  800. const auto full = use ? st::callShadow.extend : style::margins();
  801. const auto padding = style::margins(
  802. (position.attached & RectPart::Left) ? 0 : full.left(),
  803. (position.attached & RectPart::Top) ? 0 : full.top(),
  804. (position.attached & RectPart::Right) ? 0 : full.right(),
  805. (position.attached & RectPart::Bottom) ? 0 : full.bottom());
  806. _snapped = position.snapped;
  807. if (_padding == padding && _attached == position.attached) {
  808. return;
  809. }
  810. const auto newGeometry = position.geometry.marginsAdded(padding);
  811. _attached = position.attached;
  812. _padding = padding;
  813. _useTransparency = use;
  814. widget()->setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
  815. if (widget()->windowHandle()) {
  816. Ui::Platform::SetWindowMargins(widget(), _padding);
  817. }
  818. setGeometry(newGeometry);
  819. update();
  820. }
  821. Pip::Pip(
  822. not_null<Delegate*> delegate,
  823. not_null<DocumentData*> data,
  824. Data::FileOrigin origin,
  825. not_null<DocumentData*> chosenQuality,
  826. HistoryItem *context,
  827. VideoQuality quality,
  828. std::shared_ptr<Streaming::Document> shared,
  829. FnMut<void()> closeAndContinue,
  830. FnMut<void()> destroy)
  831. : _delegate(delegate)
  832. , _data(data)
  833. , _origin(origin)
  834. , _chosenQuality(chosenQuality)
  835. , _context(context)
  836. , _quality(quality)
  837. , _instance(
  838. std::in_place,
  839. std::move(shared),
  840. [=] { waitingAnimationCallback(); })
  841. , _panel(
  842. _delegate->pipParentWidget(),
  843. [=](Ui::GL::Capabilities capabilities) {
  844. return chooseRenderer(capabilities);
  845. })
  846. , _playbackProgress(std::make_unique<PlaybackProgress>())
  847. , _dataMedia(_data->createMediaView())
  848. , _rotation(data->owner().mediaRotation().get(data))
  849. , _lastPositiveVolume((Core::App().settings().videoVolume() > 0.)
  850. ? Core::App().settings().videoVolume()
  851. : Core::Settings::kDefaultVolume)
  852. , _closeAndContinue(std::move(closeAndContinue))
  853. , _destroy(std::move(destroy)) {
  854. setupPanel();
  855. setupButtons();
  856. setupStreaming();
  857. _data->session().account().sessionChanges(
  858. ) | rpl::start_with_next([=] {
  859. _destroy();
  860. }, _panel.rp()->lifetime());
  861. if (_context) {
  862. _data->owner().itemRemoved(
  863. ) | rpl::start_with_next([=](not_null<const HistoryItem*> data) {
  864. if (_context != data) {
  865. _context = nullptr;
  866. }
  867. }, _panel.rp()->lifetime());
  868. }
  869. }
  870. Pip::~Pip() = default;
  871. std::shared_ptr<Streaming::Document> Pip::shared() const {
  872. return _instance->shared();
  873. }
  874. void Pip::setupPanel() {
  875. _panel.init();
  876. const auto size = [&] {
  877. if (!_instance->info().video.size.isEmpty()) {
  878. return _instance->info().video.size;
  879. }
  880. const auto media = _data->activeMediaView();
  881. if (media) {
  882. media->goodThumbnailWanted();
  883. }
  884. const auto good = media ? media->goodThumbnail() : nullptr;
  885. const auto original = good ? good->size() : _data->dimensions;
  886. return original.isEmpty() ? QSize(1, 1) : original;
  887. }();
  888. _panel.setAspectRatio(FlipSizeByRotation(size, _rotation));
  889. _panel.setPosition(Deserialize(_delegate->pipLoadGeometry()));
  890. _panel.widget()->show();
  891. _panel.saveGeometryRequests(
  892. ) | rpl::start_with_next([=] {
  893. saveGeometry();
  894. }, _panel.rp()->lifetime());
  895. _panel.rp()->events(
  896. ) | rpl::start_with_next([=](not_null<QEvent*> e) {
  897. const auto mousePosition = [&] {
  898. return static_cast<QMouseEvent*>(e.get())->pos();
  899. };
  900. const auto mouseButton = [&] {
  901. return static_cast<QMouseEvent*>(e.get())->button();
  902. };
  903. switch (e->type()) {
  904. case QEvent::Close: handleClose(); break;
  905. case QEvent::Leave: handleLeave(); break;
  906. case QEvent::MouseMove:
  907. handleMouseMove(mousePosition());
  908. break;
  909. case QEvent::MouseButtonPress:
  910. handleMousePress(mousePosition(), mouseButton());
  911. break;
  912. case QEvent::MouseButtonRelease:
  913. handleMouseRelease(mousePosition(), mouseButton());
  914. break;
  915. case QEvent::MouseButtonDblClick:
  916. handleDoubleClick(mouseButton());
  917. break;
  918. }
  919. }, _panel.rp()->lifetime());
  920. }
  921. void Pip::handleClose() {
  922. crl::on_main(_panel.widget(), [=] {
  923. _destroy();
  924. });
  925. }
  926. void Pip::handleLeave() {
  927. setOverState(OverState::None);
  928. }
  929. void Pip::handleMouseMove(QPoint position) {
  930. const auto weak = Ui::MakeWeak(_panel.widget());
  931. const auto guard = gsl::finally([&] {
  932. if (weak) {
  933. _panel.handleMouseMove(position);
  934. }
  935. });
  936. setOverState(computeState(position));
  937. seekUpdate(position);
  938. volumeControllerUpdate(position);
  939. }
  940. void Pip::setOverState(OverState state) {
  941. if (_over == state) {
  942. return;
  943. }
  944. const auto wasShown = ResolveShownOver(_over);
  945. _over = state;
  946. const auto nowAreShown = (ResolveShownOver(_over) != OverState::None);
  947. if ((wasShown != OverState::None) != nowAreShown) {
  948. _controlsShown.start(
  949. [=] { _panel.update(); },
  950. nowAreShown ? 0. : 1.,
  951. nowAreShown ? 1. : 0.,
  952. st::fadeWrapDuration,
  953. anim::linear);
  954. }
  955. if (!_pressed) {
  956. updateActiveState(wasShown);
  957. }
  958. _panel.update();
  959. }
  960. void Pip::setPressedState(std::optional<OverState> state) {
  961. if (_pressed == state) {
  962. return;
  963. }
  964. const auto wasShown = shownActiveState();
  965. _pressed = state;
  966. updateActiveState(wasShown);
  967. }
  968. Pip::OverState Pip::shownActiveState() const {
  969. return ResolveShownOver(_pressed.value_or(_over));
  970. }
  971. float64 Pip::activeValue(const Button &button) const {
  972. const auto shownState = ResolveShownOver(button.state);
  973. return button.active.value((shownActiveState() == shownState) ? 1. : 0.);
  974. }
  975. void Pip::updateActiveState(OverState wasShown) {
  976. const auto check = [&](Button &button) {
  977. const auto shownState = ResolveShownOver(button.state);
  978. const auto nowIsShown = (shownActiveState() == shownState);
  979. if ((wasShown == shownState) != nowIsShown) {
  980. button.active.start(
  981. [=, &button] { _panel.widget()->update(button.icon); },
  982. nowIsShown ? 0. : 1.,
  983. nowIsShown ? 1. : 0.,
  984. st::fadeWrapDuration,
  985. anim::linear);
  986. }
  987. };
  988. check(_close);
  989. check(_enlarge);
  990. check(_play);
  991. check(_playback);
  992. check(_volumeToggle);
  993. check(_volumeController);
  994. }
  995. Pip::OverState Pip::ResolveShownOver(OverState state) {
  996. return (state == OverState::VolumeController)
  997. ? OverState::VolumeToggle
  998. : state;
  999. }
  1000. void Pip::handleMousePress(QPoint position, Qt::MouseButton button) {
  1001. const auto weak = Ui::MakeWeak(_panel.widget());
  1002. const auto guard = gsl::finally([&] {
  1003. if (weak) {
  1004. _panel.handleMousePress(position, button);
  1005. }
  1006. });
  1007. if (button != Qt::LeftButton) {
  1008. return;
  1009. }
  1010. _pressed = _over;
  1011. if (_over == OverState::Playback || _over == OverState::VolumeController) {
  1012. _panel.setDragDisabled(true);
  1013. }
  1014. seekUpdate(position);
  1015. volumeControllerUpdate(position);
  1016. }
  1017. void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
  1018. const auto weak = Ui::MakeWeak(_panel.widget());
  1019. const auto guard = gsl::finally([&] {
  1020. if (weak) {
  1021. _panel.handleMouseRelease(position, button);
  1022. }
  1023. });
  1024. if (button != Qt::LeftButton) {
  1025. return;
  1026. }
  1027. seekUpdate(position);
  1028. volumeControllerUpdate(position);
  1029. const auto pressed = base::take(_pressed);
  1030. if (pressed && *pressed == OverState::Playback) {
  1031. _panel.setDragDisabled(false);
  1032. seekFinish(_playbackProgress->value());
  1033. } else if (pressed && *pressed == OverState::VolumeController) {
  1034. _panel.setDragDisabled(false);
  1035. _panel.update();
  1036. } else if (_panel.dragging() || !pressed || *pressed != _over) {
  1037. _lastHandledPress = std::nullopt;
  1038. } else {
  1039. _lastHandledPress = _over;
  1040. switch (_over) {
  1041. case OverState::Close: _panel.widget()->close(); break;
  1042. case OverState::Enlarge: _closeAndContinue(); break;
  1043. case OverState::VolumeToggle: volumeToggled(); break;
  1044. case OverState::Other: playbackPauseResume(); break;
  1045. }
  1046. }
  1047. }
  1048. void Pip::handleDoubleClick(Qt::MouseButton button) {
  1049. if (_over != OverState::Other
  1050. || !_lastHandledPress
  1051. || *_lastHandledPress != _over) {
  1052. return;
  1053. }
  1054. playbackPauseResume(); // Un-click the first click.
  1055. _closeAndContinue();
  1056. }
  1057. void Pip::seekUpdate(QPoint position) {
  1058. if (!_pressed || *_pressed != OverState::Playback) {
  1059. return;
  1060. }
  1061. const auto unbound = (position.x() - _playback.icon.x())
  1062. / float64(_playback.icon.width());
  1063. const auto progress = std::clamp(unbound, 0., 1.);
  1064. seekProgress(progress);
  1065. }
  1066. void Pip::seekProgress(float64 value) {
  1067. if (!_lastDurationMs) {
  1068. return;
  1069. }
  1070. _playbackProgress->setValue(value, false);
  1071. const auto positionMs = std::clamp(
  1072. static_cast<crl::time>(value * _lastDurationMs),
  1073. crl::time(0),
  1074. _lastDurationMs);
  1075. if (_seekPositionMs != positionMs) {
  1076. _seekPositionMs = positionMs;
  1077. if (!_instance->player().paused()
  1078. && !_instance->player().finished()) {
  1079. _pausedBySeek = true;
  1080. playbackPauseResume();
  1081. }
  1082. updatePlaybackTexts(_seekPositionMs, _lastDurationMs, kMsInSecond);
  1083. }
  1084. }
  1085. void Pip::seekFinish(float64 value) {
  1086. if (!_lastDurationMs) {
  1087. return;
  1088. }
  1089. const auto positionMs = std::clamp(
  1090. static_cast<crl::time>(value * _lastDurationMs),
  1091. crl::time(0),
  1092. _lastDurationMs);
  1093. _seekPositionMs = -1;
  1094. _startPaused = !_pausedBySeek && !_instance->player().finished();
  1095. restartAtSeekPosition(positionMs);
  1096. }
  1097. void Pip::volumeChanged(float64 volume) {
  1098. if (volume > 0.) {
  1099. _lastPositiveVolume = volume;
  1100. }
  1101. Player::mixer()->setVideoVolume(volume);
  1102. Core::App().settings().setVideoVolume(volume);
  1103. Core::App().saveSettingsDelayed();
  1104. }
  1105. void Pip::volumeToggled() {
  1106. const auto volume = Core::App().settings().videoVolume();
  1107. volumeChanged(volume ? 0. : _lastPositiveVolume);
  1108. _panel.update();
  1109. }
  1110. void Pip::volumeControllerUpdate(QPoint position) {
  1111. if (!_pressed || *_pressed != OverState::VolumeController) {
  1112. return;
  1113. }
  1114. const auto unbound = (position.x() - _volumeController.icon.x())
  1115. / float64(_volumeController.icon.width());
  1116. const auto value = std::clamp(unbound, 0., 1.);
  1117. volumeChanged(value);
  1118. _panel.update();
  1119. }
  1120. void Pip::setupButtons() {
  1121. _close.state = OverState::Close;
  1122. _enlarge.state = OverState::Enlarge;
  1123. _playback.state = OverState::Playback;
  1124. _volumeToggle.state = OverState::VolumeToggle;
  1125. _volumeController.state = OverState::VolumeController;
  1126. _play.state = OverState::Other;
  1127. _panel.rp()->sizeValue(
  1128. ) | rpl::map([=] {
  1129. return _panel.inner();
  1130. }) | rpl::start_with_next([=](QRect rect) {
  1131. const auto skip = st::pipControlSkip;
  1132. _close.area = QRect(
  1133. rect.x(),
  1134. rect.y(),
  1135. st::pipCloseIcon.width() + 2 * skip,
  1136. st::pipCloseIcon.height() + 2 * skip);
  1137. _enlarge.area = QRect(
  1138. _close.area.x() + _close.area.width(),
  1139. rect.y(),
  1140. st::pipEnlargeIcon.width() + 2 * skip,
  1141. st::pipEnlargeIcon.height() + 2 * skip);
  1142. const auto volumeSkip = st::pipPlaybackSkip;
  1143. const auto volumeHeight = 2 * volumeSkip + st::pipPlaybackWide;
  1144. const auto volumeToggleWidth = st::pipVolumeIcon0.width()
  1145. + 2 * skip;
  1146. const auto volumeToggleHeight = st::pipVolumeIcon0.height()
  1147. + 2 * skip;
  1148. const auto volumeWidth = (((st::mediaviewVolumeWidth + 2 * skip)
  1149. + _close.area.width()
  1150. + _enlarge.area.width()
  1151. + volumeToggleWidth) < rect.width())
  1152. ? st::mediaviewVolumeWidth
  1153. : 0;
  1154. _volumeController.area = QRect(
  1155. rect.x() + rect.width() - volumeWidth - 2 * volumeSkip,
  1156. rect.y() + (volumeToggleHeight - volumeHeight) / 2,
  1157. volumeWidth,
  1158. volumeHeight);
  1159. _volumeToggle.area = QRect(
  1160. _volumeController.area.x()
  1161. - st::pipVolumeIcon0.width()
  1162. - skip,
  1163. rect.y(),
  1164. volumeToggleWidth,
  1165. volumeToggleHeight);
  1166. using Ui::Platform::TitleControlsLayout;
  1167. if (!TitleControlsLayout::Instance()->current().onLeft()) {
  1168. _close.area.moveLeft(rect.x()
  1169. + rect.width()
  1170. - (_close.area.x() - rect.x())
  1171. - _close.area.width());
  1172. _enlarge.area.moveLeft(rect.x()
  1173. + rect.width()
  1174. - (_enlarge.area.x() - rect.x())
  1175. - _enlarge.area.width());
  1176. _volumeToggle.area.moveLeft(rect.x());
  1177. _volumeController.area.moveLeft(_volumeToggle.area.x()
  1178. + _volumeToggle.area.width());
  1179. }
  1180. _close.icon = _close.area.marginsRemoved({ skip, skip, skip, skip });
  1181. _enlarge.icon = _enlarge.area.marginsRemoved(
  1182. { skip, skip, skip, skip });
  1183. _volumeToggle.icon = _volumeToggle.area.marginsRemoved(
  1184. { skip, skip, skip, skip });
  1185. _play.icon = QRect(
  1186. rect.x() + (rect.width() - st::pipPlayIcon.width()) / 2,
  1187. rect.y() + (rect.height() - st::pipPlayIcon.height()) / 2,
  1188. st::pipPlayIcon.width(),
  1189. st::pipPlayIcon.height());
  1190. const auto volumeArea = _volumeController.area;
  1191. _volumeController.icon = (volumeArea.width() > 2 * volumeSkip
  1192. && volumeArea.height() > 2 * volumeSkip)
  1193. ? volumeArea.marginsRemoved(
  1194. { volumeSkip, volumeSkip, volumeSkip, volumeSkip })
  1195. : QRect();
  1196. const auto playbackSkip = st::pipPlaybackSkip;
  1197. const auto playbackHeight = 2 * playbackSkip + st::pipPlaybackWide;
  1198. _playback.area = QRect(
  1199. rect.x(),
  1200. rect.y() + rect.height() - playbackHeight,
  1201. rect.width(),
  1202. playbackHeight);
  1203. _playback.icon = _playback.area.marginsRemoved(
  1204. { playbackSkip, playbackSkip, playbackSkip, playbackSkip });
  1205. }, _panel.rp()->lifetime());
  1206. _playbackProgress->setValueChangedCallback([=](
  1207. float64 value,
  1208. float64 receivedTill) {
  1209. _panel.widget()->update(_playback.area);
  1210. });
  1211. }
  1212. void Pip::saveGeometry() {
  1213. _delegate->pipSaveGeometry(Serialize(_panel.countPosition()));
  1214. }
  1215. void Pip::updatePlayPauseResumeState(const Player::TrackState &state) {
  1216. auto showPause = Player::ShowPauseIcon(state.state);
  1217. if (showPause != _showPause) {
  1218. _showPause = showPause;
  1219. _panel.update();
  1220. }
  1221. }
  1222. void Pip::setupStreaming() {
  1223. _instance->setPriority(kPipLoaderPriority);
  1224. _instance->lockPlayer();
  1225. _instance->switchQualityRequests(
  1226. ) | rpl::filter([=](int quality) {
  1227. return !_quality.manual && _quality.height != quality;
  1228. }) | rpl::start_with_next([=](int quality) {
  1229. applyVideoQuality({
  1230. .manual = 0,
  1231. .height = uint32(quality),
  1232. });
  1233. }, _instance->lifetime());
  1234. _instance->player().updates(
  1235. ) | rpl::start_with_next_error([=](Streaming::Update &&update) {
  1236. handleStreamingUpdate(std::move(update));
  1237. }, [=](Streaming::Error &&error) {
  1238. handleStreamingError(std::move(error));
  1239. }, _instance->lifetime());
  1240. updatePlaybackState();
  1241. }
  1242. void Pip::applyVideoQuality(VideoQuality value) {
  1243. if (_quality == value
  1244. || !_dataMedia->canBePlayed(_context)) {
  1245. return;
  1246. }
  1247. const auto resolved = _data->chooseQuality(_context, value);
  1248. if (_chosenQuality == resolved) {
  1249. return;
  1250. }
  1251. auto instance = Streaming::Instance(
  1252. resolved,
  1253. _data,
  1254. _context,
  1255. _origin,
  1256. [=] { waitingAnimationCallback(); });
  1257. if (!instance.valid()) {
  1258. return;
  1259. }
  1260. if (_instance->ready()) {
  1261. _qualityChangeFrame = currentVideoFrameImage();
  1262. }
  1263. if (!_instance->player().active()
  1264. || _instance->player().finished()) {
  1265. _qualityChangeFinished = true;
  1266. }
  1267. _startPaused = _qualityChangeFinished || _instance->player().paused();
  1268. _quality = value;
  1269. Core::App().settings().setVideoQuality(value);
  1270. Core::App().saveSettingsDelayed();
  1271. _chosenQuality = resolved;
  1272. _instance.emplace(std::move(instance));
  1273. setupStreaming();
  1274. restartAtSeekPosition(_lastUpdatePosition);
  1275. }
  1276. QImage Pip::currentVideoFrameImage() const {
  1277. return _instance->player().ready()
  1278. ? _instance->player().currentFrameImage()
  1279. : _instance->info().video.cover;
  1280. }
  1281. Ui::GL::ChosenRenderer Pip::chooseRenderer(
  1282. Ui::GL::Capabilities capabilities) {
  1283. const auto use = Platform::IsMac()
  1284. ? true
  1285. : capabilities.transparency;
  1286. LOG(("OpenGL: %1 (PipPanel)").arg(Logs::b(use)));
  1287. if (use) {
  1288. _opengl = true;
  1289. return {
  1290. .renderer = std::make_unique<RendererGL>(this),
  1291. .backend = Ui::GL::Backend::OpenGL,
  1292. };
  1293. }
  1294. return {
  1295. .renderer = std::make_unique<RendererSW>(this),
  1296. .backend = Ui::GL::Backend::Raster,
  1297. };
  1298. }
  1299. void Pip::paint(not_null<Renderer*> renderer) const {
  1300. const auto controlsShown = _controlsShown.value(
  1301. (_over != OverState::None) ? 1. : 0.);
  1302. auto geometry = ContentGeometry{
  1303. .inner = _panel.inner(),
  1304. .attached = (_panel.useTransparency()
  1305. ? _panel.attached()
  1306. : RectPart::AllSides),
  1307. .fade = controlsShown,
  1308. .outer = _panel.widget()->size(),
  1309. .rotation = _rotation,
  1310. .videoRotation = _instance->info().video.rotation,
  1311. .useTransparency = _panel.useTransparency(),
  1312. };
  1313. if (canUseVideoFrame()) {
  1314. renderer->paintTransformedVideoFrame(geometry);
  1315. _instance->markFrameShown();
  1316. } else {
  1317. const auto content = staticContent();
  1318. if (_preparedCoverState == ThumbState::Cover) {
  1319. geometry.rotation += base::take(geometry.videoRotation);
  1320. }
  1321. renderer->paintTransformedStaticContent(content, geometry);
  1322. }
  1323. if (_instance->waitingShown()) {
  1324. renderer->paintRadialLoading(countRadialRect(), controlsShown);
  1325. }
  1326. if (controlsShown > 0) {
  1327. paintButtons(renderer, controlsShown);
  1328. paintPlayback(renderer, controlsShown);
  1329. paintVolumeController(renderer, controlsShown);
  1330. }
  1331. }
  1332. void Pip::paintButtons(not_null<Renderer*> renderer, float64 shown) const {
  1333. const auto outer = _panel.widget()->width();
  1334. const auto drawOne = [&](
  1335. const Button &button,
  1336. const style::icon &icon,
  1337. const style::icon &iconOver) {
  1338. renderer->paintButton(
  1339. button,
  1340. outer,
  1341. shown,
  1342. activeValue(button),
  1343. icon,
  1344. iconOver);
  1345. };
  1346. renderer->paintButtonsStart();
  1347. drawOne(
  1348. _play,
  1349. _showPause ? st::pipPauseIcon : st::pipPlayIcon,
  1350. _showPause ? st::pipPauseIconOver : st::pipPlayIconOver);
  1351. drawOne(_close, st::pipCloseIcon, st::pipCloseIconOver);
  1352. drawOne(_enlarge, st::pipEnlargeIcon, st::pipEnlargeIconOver);
  1353. const auto volume = Core::App().settings().videoVolume();
  1354. if (volume <= 0.) {
  1355. drawOne(
  1356. _volumeToggle,
  1357. st::pipVolumeIcon0,
  1358. st::pipVolumeIcon0Over);
  1359. } else if (volume < 1 / 2.) {
  1360. drawOne(
  1361. _volumeToggle,
  1362. st::pipVolumeIcon1,
  1363. st::pipVolumeIcon1Over);
  1364. } else {
  1365. drawOne(
  1366. _volumeToggle,
  1367. st::pipVolumeIcon2,
  1368. st::pipVolumeIcon2Over);
  1369. }
  1370. }
  1371. void Pip::paintPlayback(not_null<Renderer*> renderer, float64 shown) const {
  1372. const auto outer = QRect(
  1373. _playback.icon.x(),
  1374. _playback.icon.y() - st::pipPlaybackFont->height,
  1375. _playback.icon.width(),
  1376. st::pipPlaybackFont->height + _playback.icon.height());
  1377. renderer->paintPlayback(outer, shown);
  1378. }
  1379. void Pip::paintPlaybackContent(
  1380. QPainter &p,
  1381. QRect outer,
  1382. float64 shown) const {
  1383. p.setOpacity(shown);
  1384. paintPlaybackProgress(p, outer);
  1385. paintPlaybackTexts(p, outer);
  1386. }
  1387. void Pip::paintPlaybackProgress(QPainter &p, QRect outer) const {
  1388. const auto radius = _playback.icon.height() / 2;
  1389. const auto progress = _playbackProgress->value();
  1390. const auto active = activeValue(_playback);
  1391. const auto height = anim::interpolate(
  1392. st::pipPlaybackWidth,
  1393. _playback.icon.height(),
  1394. active);
  1395. const auto rect = QRect(
  1396. outer.x(),
  1397. (outer.y()
  1398. + st::pipPlaybackFont->height
  1399. + _playback.icon.height()
  1400. - height),
  1401. outer.width(),
  1402. height);
  1403. paintProgressBar(p, rect, progress, radius, active);
  1404. }
  1405. void Pip::paintProgressBar(
  1406. QPainter &p,
  1407. const QRect &rect,
  1408. float64 progress,
  1409. int radius,
  1410. float64 active) const {
  1411. const auto done = int(base::SafeRound(rect.width() * progress));
  1412. PainterHighQualityEnabler hq(p);
  1413. p.setPen(Qt::NoPen);
  1414. if (done > 0) {
  1415. p.setBrush(anim::brush(
  1416. st::mediaviewPipControlsFg,
  1417. st::mediaviewPipPlaybackActive,
  1418. active));
  1419. p.setClipRect(rect.x(), rect.y(), done, rect.height());
  1420. p.drawRoundedRect(
  1421. rect.x(),
  1422. rect.y(),
  1423. std::min(done + radius, rect.width()),
  1424. rect.height(),
  1425. radius,
  1426. radius);
  1427. }
  1428. if (done < rect.width()) {
  1429. const auto from = std::max(rect.x() + done - radius, rect.x());
  1430. p.setBrush(st::mediaviewPipPlaybackInactive);
  1431. p.setClipRect(
  1432. rect.x() + done,
  1433. rect.y(),
  1434. rect.width() - done,
  1435. rect.height());
  1436. p.drawRoundedRect(
  1437. from,
  1438. rect.y(),
  1439. rect.x() + rect.width() - from,
  1440. rect.height(),
  1441. radius,
  1442. radius);
  1443. }
  1444. p.setClipping(false);
  1445. }
  1446. void Pip::paintPlaybackTexts(QPainter &p, QRect outer) const {
  1447. const auto left = outer.x()
  1448. - _playback.icon.x()
  1449. + _playback.area.x()
  1450. + st::pipPlaybackTextSkip;
  1451. const auto right = outer.x()
  1452. - _playback.icon.x()
  1453. + _playback.area.x()
  1454. + _playback.area.width()
  1455. - st::pipPlaybackTextSkip;
  1456. const auto top = outer.y() + st::pipPlaybackFont->ascent;
  1457. p.setFont(st::pipPlaybackFont);
  1458. p.setPen(st::mediaviewPipControlsFgOver);
  1459. p.drawText(left, top, _timeAlready);
  1460. p.drawText(right - _timeLeftWidth, top, _timeLeft);
  1461. }
  1462. void Pip::paintVolumeController(
  1463. not_null<Renderer*> renderer,
  1464. float64 shown) const {
  1465. if (_volumeController.icon.isEmpty()) {
  1466. return;
  1467. }
  1468. renderer->paintVolumeController(_volumeController.icon, shown);
  1469. }
  1470. void Pip::paintVolumeControllerContent(
  1471. QPainter &p,
  1472. QRect outer,
  1473. float64 shown) const {
  1474. p.setOpacity(shown);
  1475. const auto radius = _volumeController.icon.height() / 2;
  1476. const auto volume = Core::App().settings().videoVolume();
  1477. const auto active = activeValue(_volumeController);
  1478. const auto height = anim::interpolate(
  1479. st::pipPlaybackWidth,
  1480. _volumeController.icon.height(),
  1481. active);
  1482. const auto rect = QRect(
  1483. outer.x(),
  1484. outer.y() + radius - height / 2,
  1485. outer.width(),
  1486. height);
  1487. paintProgressBar(p, rect, volume, radius, active);
  1488. }
  1489. void Pip::handleStreamingUpdate(Streaming::Update &&update) {
  1490. using namespace Streaming;
  1491. v::match(update.data, [&](const Information &update) {
  1492. _panel.setAspectRatio(
  1493. FlipSizeByRotation(update.video.size, _rotation));
  1494. _qualityChangeFrame = QImage();
  1495. }, [&](PreloadedVideo) {
  1496. updatePlaybackState();
  1497. }, [&](UpdateVideo update) {
  1498. _panel.update();
  1499. Core::App().updateNonIdle();
  1500. updatePlaybackState();
  1501. _lastUpdatePosition = update.position;
  1502. }, [&](PreloadedAudio) {
  1503. updatePlaybackState();
  1504. }, [&](UpdateAudio) {
  1505. updatePlaybackState();
  1506. }, [](WaitingForData) {
  1507. }, [](SpeedEstimate) {
  1508. }, [](MutedByOther) {
  1509. }, [&](Finished) {
  1510. updatePlaybackState();
  1511. });
  1512. }
  1513. void Pip::updatePlaybackState() {
  1514. const auto state = _instance->player().prepareLegacyState();
  1515. updatePlayPauseResumeState(state);
  1516. if (state.position == kTimeUnknown
  1517. || state.length == kTimeUnknown
  1518. || _pausedBySeek) {
  1519. return;
  1520. }
  1521. _playbackProgress->updateState(state);
  1522. updatePowerSaveBlocker(state);
  1523. qint64 position = 0;
  1524. if (Player::IsStoppedAtEnd(state.state)) {
  1525. position = state.length;
  1526. } else if (!Player::IsStoppedOrStopping(state.state)) {
  1527. position = state.position;
  1528. } else {
  1529. position = 0;
  1530. }
  1531. const auto playFrequency = state.frequency;
  1532. _lastDurationMs = (state.length * crl::time(1000)) / playFrequency;
  1533. if (_seekPositionMs < 0) {
  1534. updatePlaybackTexts(position, state.length, playFrequency);
  1535. }
  1536. }
  1537. void Pip::updatePowerSaveBlocker(const Player::TrackState &state) {
  1538. const auto block = _data->isVideoFile()
  1539. && !IsPausedOrPausing(state.state)
  1540. && !IsStoppedOrStopping(state.state);
  1541. base::UpdatePowerSaveBlocker(
  1542. _powerSaveBlocker,
  1543. block,
  1544. base::PowerSaveBlockType::PreventDisplaySleep,
  1545. [] { return u"Video playback is active"_q; },
  1546. [=] { return _panel.widget()->windowHandle(); });
  1547. }
  1548. void Pip::updatePlaybackTexts(
  1549. int64 position,
  1550. int64 length,
  1551. int64 frequency) {
  1552. const auto playAlready = position / frequency;
  1553. const auto playLeft = (length / frequency) - playAlready;
  1554. const auto already = Ui::FormatDurationText(playAlready);
  1555. const auto minus = QChar(8722);
  1556. const auto left = minus + Ui::FormatDurationText(playLeft);
  1557. if (_timeAlready == already && _timeLeft == left) {
  1558. return;
  1559. }
  1560. _timeAlready = already;
  1561. _timeLeft = left;
  1562. _timeLeftWidth = st::pipPlaybackFont->width(_timeLeft);
  1563. _panel.widget()->update(QRect(
  1564. _playback.area.x(),
  1565. _playback.icon.y() - st::pipPlaybackFont->height,
  1566. _playback.area.width(),
  1567. st::pipPlaybackFont->height));
  1568. }
  1569. void Pip::handleStreamingError(Streaming::Error &&error) {
  1570. _panel.widget()->close();
  1571. }
  1572. void Pip::playbackPauseResume() {
  1573. if (_instance->player().failed()) {
  1574. _panel.widget()->close();
  1575. } else if (_instance->player().finished()
  1576. || !_instance->player().active()) {
  1577. _startPaused = false;
  1578. restartAtSeekPosition(0);
  1579. } else if (_instance->player().paused()) {
  1580. _instance->resume();
  1581. updatePlaybackState();
  1582. } else {
  1583. _instance->pause();
  1584. updatePlaybackState();
  1585. }
  1586. }
  1587. void Pip::restartAtSeekPosition(crl::time position) {
  1588. _lastUpdatePosition = position;
  1589. if (!_instance->info().video.cover.isNull()) {
  1590. _preparedCoverStorage = QImage();
  1591. _preparedCoverState = ThumbState::Empty;
  1592. _instance->saveFrameToCover();
  1593. }
  1594. auto options = Streaming::PlaybackOptions();
  1595. options.position = position;
  1596. options.hwAllowed = Core::App().settings().hardwareAcceleratedVideo();
  1597. options.audioId = _instance->player().prepareLegacyState().id;
  1598. options.speed = _delegate->pipPlaybackSpeed();
  1599. _instance->play(options);
  1600. if (_startPaused) {
  1601. _instance->pause();
  1602. }
  1603. _pausedBySeek = false;
  1604. updatePlaybackState();
  1605. }
  1606. bool Pip::canUseVideoFrame() const {
  1607. return _instance->player().ready()
  1608. && !_instance->info().video.cover.isNull();
  1609. }
  1610. QImage Pip::videoFrame(const FrameRequest &request) const {
  1611. Expects(canUseVideoFrame());
  1612. return _instance->frame(request);
  1613. }
  1614. Streaming::FrameWithInfo Pip::videoFrameWithInfo() const {
  1615. Expects(canUseVideoFrame());
  1616. return _instance->frameWithInfo();
  1617. }
  1618. QImage Pip::staticContent() const {
  1619. const auto &cover = !_qualityChangeFrame.isNull()
  1620. ? _qualityChangeFrame
  1621. : _instance->info().video.cover;
  1622. const auto media = _data->activeMediaView();
  1623. const auto use = media
  1624. ? media
  1625. : _data->inlineThumbnailBytes().isEmpty()
  1626. ? nullptr
  1627. : _data->createMediaView();
  1628. if (use) {
  1629. use->goodThumbnailWanted();
  1630. }
  1631. const auto good = use ? use->goodThumbnail() : nullptr;
  1632. const auto thumb = use ? use->thumbnail() : nullptr;
  1633. const auto blurred = use ? use->thumbnailInline() : nullptr;
  1634. const auto state = !cover.isNull()
  1635. ? ThumbState::Cover
  1636. : good
  1637. ? ThumbState::Good
  1638. : thumb
  1639. ? ThumbState::Thumb
  1640. : blurred
  1641. ? ThumbState::Inline
  1642. : ThumbState::Empty;
  1643. if (!_preparedCoverStorage.isNull() && _preparedCoverState >= state) {
  1644. return _preparedCoverStorage;
  1645. }
  1646. _preparedCoverState = state;
  1647. if (state == ThumbState::Cover) {
  1648. _preparedCoverStorage = cover;
  1649. } else {
  1650. _preparedCoverStorage = (good
  1651. ? good
  1652. : thumb
  1653. ? thumb
  1654. : blurred
  1655. ? blurred
  1656. : Image::BlankMedia().get())->original();
  1657. if (!good) {
  1658. _preparedCoverStorage = Images::Blur(
  1659. std::move(_preparedCoverStorage));
  1660. }
  1661. }
  1662. return _preparedCoverStorage;
  1663. }
  1664. void Pip::paintRadialLoadingContent(
  1665. QPainter &p,
  1666. const QRect &inner,
  1667. QColor fg) const {
  1668. const auto arc = inner.marginsRemoved(QMargins(
  1669. st::radialLine,
  1670. st::radialLine,
  1671. st::radialLine,
  1672. st::radialLine));
  1673. p.setOpacity(_instance->waitingOpacity());
  1674. p.setPen(Qt::NoPen);
  1675. p.setBrush(st::radialBg);
  1676. {
  1677. PainterHighQualityEnabler hq(p);
  1678. p.drawEllipse(inner);
  1679. }
  1680. p.setOpacity(1.);
  1681. Ui::InfiniteRadialAnimation::Draw(
  1682. p,
  1683. _instance->waitingState(),
  1684. arc.topLeft(),
  1685. arc.size(),
  1686. _panel.widget()->width(),
  1687. fg,
  1688. st::radialLine);
  1689. }
  1690. QRect Pip::countRadialRect() const {
  1691. const auto outer = _panel.inner();
  1692. return {
  1693. outer.x() + (outer.width() - st::radialSize.width()) / 2,
  1694. outer.y() + (outer.height() - st::radialSize.height()) / 2,
  1695. st::radialSize.width(),
  1696. st::radialSize.height()
  1697. };
  1698. }
  1699. Pip::OverState Pip::computeState(QPoint position) const {
  1700. if (!_panel.inner().contains(position)) {
  1701. return OverState::None;
  1702. } else if (_close.area.contains(position)) {
  1703. return OverState::Close;
  1704. } else if (_enlarge.area.contains(position)) {
  1705. return OverState::Enlarge;
  1706. } else if (_playback.area.contains(position)) {
  1707. return OverState::Playback;
  1708. } else if (_volumeToggle.area.contains(position)) {
  1709. return OverState::VolumeToggle;
  1710. } else if (_volumeController.area.contains(position)) {
  1711. return OverState::VolumeController;
  1712. } else {
  1713. return OverState::Other;
  1714. }
  1715. }
  1716. void Pip::waitingAnimationCallback() {
  1717. _panel.widget()->update(countRadialRect());
  1718. }
  1719. } // namespace View
  1720. } // namespace Media