media_player_button.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  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/player/media_player_button.h"
  8. #include "media/media_common.h"
  9. #include "ui/effects/ripple_animation.h"
  10. #include "ui/painter.h"
  11. #include "styles/style_media_player.h"
  12. #include "styles/style_media_view.h"
  13. #include <QtCore/QtMath>
  14. namespace Media::Player {
  15. namespace {
  16. [[nodiscard]] QString SpeedText(float64 speed) {
  17. return QString::number(base::SafeRound(speed * 10) / 10.) + 'X';
  18. }
  19. } // namespace
  20. PlayButtonLayout::PlayButtonLayout(
  21. const style::MediaPlayerButton &st,
  22. Fn<void()> callback)
  23. : _st(st)
  24. , _callback(std::move(callback)) {
  25. }
  26. void PlayButtonLayout::setState(State state) {
  27. if (_nextState == state) {
  28. return;
  29. }
  30. _nextState = state;
  31. if (!_transformProgress.animating()) {
  32. _oldState = _state;
  33. _state = _nextState;
  34. _transformBackward = false;
  35. if (_state != _oldState) {
  36. startTransform(0., 1.);
  37. if (_callback) _callback();
  38. }
  39. } else if (_oldState == _nextState) {
  40. qSwap(_oldState, _state);
  41. startTransform(_transformBackward ? 0. : 1., _transformBackward ? 1. : 0.);
  42. _transformBackward = !_transformBackward;
  43. }
  44. }
  45. void PlayButtonLayout::finishTransform() {
  46. _transformProgress.stop();
  47. _transformBackward = false;
  48. if (_callback) _callback();
  49. }
  50. void PlayButtonLayout::paint(QPainter &p, const QBrush &brush) {
  51. if (_transformProgress.animating()) {
  52. auto from = _oldState, to = _state;
  53. auto backward = _transformBackward;
  54. auto progress = _transformProgress.value(1.);
  55. if (from == State::Cancel || (from == State::Pause && to == State::Play)) {
  56. qSwap(from, to);
  57. backward = !backward;
  58. }
  59. if (backward) progress = 1. - progress;
  60. Assert(from != to);
  61. if (from == State::Play) {
  62. if (to == State::Pause) {
  63. paintPlayToPause(p, brush, progress);
  64. } else {
  65. Assert(to == State::Cancel);
  66. paintPlayToCancel(p, brush, progress);
  67. }
  68. } else {
  69. Assert(from == State::Pause && to == State::Cancel);
  70. paintPauseToCancel(p, brush, progress);
  71. }
  72. } else {
  73. switch (_state) {
  74. case State::Play: paintPlay(p, brush); break;
  75. case State::Pause: paintPlayToPause(p, brush, 1.); break;
  76. case State::Cancel: paintPlayToCancel(p, brush, 1.); break;
  77. }
  78. }
  79. }
  80. void PlayButtonLayout::paintPlay(QPainter &p, const QBrush &brush) {
  81. auto playLeft = 0. + _st.playPosition.x();
  82. auto playTop = 0. + _st.playPosition.y();
  83. auto playWidth = _st.playOuter.width() - 2 * playLeft;
  84. auto playHeight = _st.playOuter.height() - 2 * playTop;
  85. PainterHighQualityEnabler hq(p);
  86. p.setPen(Qt::NoPen);
  87. QPainterPath pathPlay;
  88. pathPlay.moveTo(playLeft, playTop);
  89. pathPlay.lineTo(playLeft + playWidth, playTop + (playHeight / 2.));
  90. pathPlay.lineTo(playLeft, playTop + playHeight);
  91. pathPlay.lineTo(playLeft, playTop);
  92. p.fillPath(pathPlay, brush);
  93. }
  94. void PlayButtonLayout::paintPlayToPause(QPainter &p, const QBrush &brush, float64 progress) {
  95. auto playLeft = 0. + _st.playPosition.x();
  96. auto playTop = 0. + _st.playPosition.y();
  97. auto playWidth = _st.playOuter.width() - 2 * playLeft;
  98. auto playHeight = _st.playOuter.height() - 2 * playTop;
  99. auto pauseLeft = 0. + _st.pausePosition.x();
  100. auto pauseTop = 0. + _st.pausePosition.y();
  101. auto pauseWidth = _st.pauseOuter.width() - 2 * pauseLeft;
  102. auto pauseHeight = _st.pauseOuter.height() - 2 * pauseTop;
  103. auto pauseStroke = 0. + _st.pauseStroke;
  104. p.setPen(Qt::NoPen);
  105. PainterHighQualityEnabler hq(p);
  106. QPointF pathLeftPause[] = {
  107. { pauseLeft, pauseTop },
  108. { pauseLeft + pauseStroke, pauseTop },
  109. { pauseLeft + pauseStroke, pauseTop + pauseHeight },
  110. { pauseLeft, pauseTop + pauseHeight },
  111. };
  112. QPointF pathLeftPlay[] = {
  113. { playLeft, playTop },
  114. { playLeft + (playWidth / 2.), playTop + (playHeight / 4.) },
  115. { playLeft + (playWidth / 2.), playTop + (3 * playHeight / 4.) },
  116. { playLeft, playTop + playHeight },
  117. };
  118. p.fillPath(anim::interpolate(pathLeftPlay, pathLeftPause, progress), brush);
  119. QPointF pathRightPause[] = {
  120. { pauseLeft + pauseWidth - pauseStroke, pauseTop },
  121. { pauseLeft + pauseWidth, pauseTop },
  122. { pauseLeft + pauseWidth, pauseTop + pauseHeight },
  123. { pauseLeft + pauseWidth - pauseStroke, pauseTop + pauseHeight },
  124. };
  125. QPointF pathRightPlay[] = {
  126. { playLeft + (playWidth / 2.), playTop + (playHeight / 4.) },
  127. { playLeft + playWidth, playTop + (playHeight / 2.) },
  128. { playLeft + playWidth, playTop + (playHeight / 2.) },
  129. { playLeft + (playWidth / 2.), playTop + (3 * playHeight / 4.) },
  130. };
  131. p.fillPath(anim::interpolate(pathRightPlay, pathRightPause, progress), brush);
  132. }
  133. void PlayButtonLayout::paintPlayToCancel(QPainter &p, const QBrush &brush, float64 progress) {
  134. auto playLeft = 0. + _st.playPosition.x();
  135. auto playTop = 0. + _st.playPosition.y();
  136. auto playWidth = _st.playOuter.width() - 2 * playLeft;
  137. auto playHeight = _st.playOuter.height() - 2 * playTop;
  138. auto cancelLeft = 0. + _st.cancelPosition.x();
  139. auto cancelTop = 0. + _st.cancelPosition.y();
  140. auto cancelWidth = _st.cancelOuter.width() - 2 * cancelLeft;
  141. auto cancelHeight = _st.cancelOuter.height() - 2 * cancelTop;
  142. auto cancelStroke = (0. + _st.cancelStroke) / M_SQRT2;
  143. p.setPen(Qt::NoPen);
  144. PainterHighQualityEnabler hq(p);
  145. QPointF pathPlay[] = {
  146. { playLeft, playTop },
  147. { playLeft, playTop },
  148. { playLeft + (playWidth / 2.), playTop + (playHeight / 4.) },
  149. { playLeft + playWidth, playTop + (playHeight / 2.) },
  150. { playLeft + playWidth, playTop + (playHeight / 2.) },
  151. { playLeft + playWidth, playTop + (playHeight / 2.) },
  152. { playLeft + playWidth, playTop + (playHeight / 2.) },
  153. { playLeft + playWidth, playTop + (playHeight / 2.) },
  154. { playLeft + (playWidth / 2.), playTop + (3 * playHeight / 4.) },
  155. { playLeft, playTop + playHeight },
  156. { playLeft, playTop + playHeight },
  157. { playLeft, playTop + (playHeight / 2.) },
  158. };
  159. QPointF pathCancel[] = {
  160. { cancelLeft, cancelTop + cancelStroke },
  161. { cancelLeft + cancelStroke, cancelTop },
  162. { cancelLeft + (cancelWidth / 2.), cancelTop + (cancelHeight / 2.) - cancelStroke },
  163. { cancelLeft + cancelWidth - cancelStroke, cancelTop },
  164. { cancelLeft + cancelWidth, cancelTop + cancelStroke },
  165. { cancelLeft + (cancelWidth / 2.) + cancelStroke, cancelTop + (cancelHeight / 2.) },
  166. { cancelLeft + cancelWidth, cancelTop + cancelHeight - cancelStroke },
  167. { cancelLeft + cancelWidth - cancelStroke, cancelTop + cancelHeight },
  168. { cancelLeft + (cancelWidth / 2.), cancelTop + (cancelHeight / 2.) + cancelStroke },
  169. { cancelLeft + cancelStroke, cancelTop + cancelHeight },
  170. { cancelLeft, cancelTop + cancelHeight - cancelStroke },
  171. { cancelLeft + (cancelWidth / 2.) - cancelStroke, cancelTop + (cancelHeight / 2.) },
  172. };
  173. p.fillPath(anim::interpolate(pathPlay, pathCancel, progress), brush);
  174. }
  175. void PlayButtonLayout::paintPauseToCancel(QPainter &p, const QBrush &brush, float64 progress) {
  176. auto pauseLeft = 0. + _st.pausePosition.x();
  177. auto pauseTop = 0. + _st.pausePosition.y();
  178. auto pauseWidth = _st.pauseOuter.width() - 2 * pauseLeft;
  179. auto pauseHeight = _st.pauseOuter.height() - 2 * pauseTop;
  180. auto pauseStroke = 0. + _st.pauseStroke;
  181. auto cancelLeft = 0. + _st.cancelPosition.x();
  182. auto cancelTop = 0. + _st.cancelPosition.y();
  183. auto cancelWidth = _st.cancelOuter.width() - 2 * cancelLeft;
  184. auto cancelHeight = _st.cancelOuter.height() - 2 * cancelTop;
  185. auto cancelStroke = (0. + _st.cancelStroke) / M_SQRT2;
  186. p.setPen(Qt::NoPen);
  187. PainterHighQualityEnabler hq(p);
  188. QPointF pathLeftPause[] = {
  189. { pauseLeft, pauseTop },
  190. { pauseLeft + pauseStroke, pauseTop },
  191. { pauseLeft + pauseStroke, pauseTop + pauseHeight },
  192. { pauseLeft, pauseTop + pauseHeight },
  193. };
  194. QPointF pathLeftCancel[] = {
  195. { cancelLeft, cancelTop + cancelStroke },
  196. { cancelLeft + cancelStroke, cancelTop },
  197. { cancelLeft + cancelWidth, cancelTop + cancelHeight - cancelStroke },
  198. { cancelLeft + cancelWidth - cancelStroke, cancelTop + cancelHeight },
  199. };
  200. p.fillPath(anim::interpolate(pathLeftPause, pathLeftCancel, progress), brush);
  201. QPointF pathRightPause[] = {
  202. { pauseLeft + pauseWidth - pauseStroke, pauseTop },
  203. { pauseLeft + pauseWidth, pauseTop },
  204. { pauseLeft + pauseWidth, pauseTop + pauseHeight },
  205. { pauseLeft + pauseWidth - pauseStroke, pauseTop + pauseHeight },
  206. };
  207. QPointF pathRightCancel[] = {
  208. { cancelLeft + cancelWidth - cancelStroke, cancelTop },
  209. { cancelLeft + cancelWidth, cancelTop + cancelStroke },
  210. { cancelLeft + cancelStroke, cancelTop + cancelHeight },
  211. { cancelLeft, cancelTop + cancelHeight - cancelStroke },
  212. };
  213. p.fillPath(anim::interpolate(pathRightPause, pathRightCancel, progress), brush);
  214. }
  215. void PlayButtonLayout::animationCallback() {
  216. if (!_transformProgress.animating()) {
  217. auto finalState = _nextState;
  218. _nextState = _state;
  219. setState(finalState);
  220. }
  221. _callback();
  222. }
  223. void PlayButtonLayout::startTransform(float64 from, float64 to) {
  224. _transformProgress.start(
  225. [=] { animationCallback(); },
  226. from,
  227. to,
  228. _st.duration);
  229. }
  230. SpeedButtonLayout::SpeedButtonLayout(
  231. const style::MediaSpeedButton &st,
  232. Fn<void()> callback,
  233. float64 speed)
  234. : _st(st)
  235. , _speed(speed)
  236. , _metrics(_st.font->f)
  237. , _text(SpeedText(speed))
  238. , _textWidth(_metrics.horizontalAdvance(_text))
  239. , _callback(std::move(callback)) {
  240. const auto result = style::FindAdjustResult(_st.font->f);
  241. _adjustedAscent = result ? result->ascent : _metrics.ascent();
  242. _adjustedHeight = result ? result->height : _metrics.height();
  243. }
  244. void SpeedButtonLayout::setSpeed(float64 speed) {
  245. speed = base::SafeRound(speed * 10.) / 10.;
  246. if (!EqualSpeeds(_speed, speed)) {
  247. _speed = speed;
  248. _text = SpeedText(_speed);
  249. _textWidth = _metrics.horizontalAdvance(_text);
  250. if (_callback) _callback();
  251. }
  252. }
  253. void SpeedButtonLayout::paint(QPainter &p, bool over, bool active) {
  254. const auto &color = active ? _st.activeFg : over ? _st.overFg : _st.fg;
  255. const auto inner = QRect(QPoint(), _st.size).marginsRemoved(_st.padding);
  256. _st.icon.paintInCenter(p, inner, color->c);
  257. p.setPen(color);
  258. p.setFont(_st.font);
  259. p.drawText(
  260. QPointF(inner.topLeft()) + QPointF(
  261. (inner.width() - _textWidth) / 2.,
  262. (inner.height() - _adjustedHeight) / 2. + _adjustedAscent),
  263. _text);
  264. }
  265. SpeedButton::SpeedButton(QWidget *parent, const style::MediaSpeedButton &st)
  266. : RippleButton(parent, st.ripple)
  267. , _st(st)
  268. , _layout(st, [=] { update(); }, 2.)
  269. , _isDefault(true) {
  270. resize(_st.size);
  271. }
  272. void SpeedButton::setSpeed(float64 speed) {
  273. _isDefault = EqualSpeeds(speed, 1.);
  274. _layout.setSpeed(speed);
  275. update();
  276. }
  277. void SpeedButton::paintEvent(QPaintEvent *e) {
  278. auto p = QPainter(this);
  279. paintRipple(
  280. p,
  281. QPoint(_st.padding.left(), _st.padding.top()),
  282. _isDefault ? nullptr : &_st.rippleActiveColor->c);
  283. _layout.paint(p, isOver(), !_isDefault);
  284. }
  285. QPoint SpeedButton::prepareRippleStartPosition() const {
  286. const auto inner = rect().marginsRemoved(_st.padding);
  287. const auto result = mapFromGlobal(QCursor::pos()) - inner.topLeft();
  288. return inner.contains(result)
  289. ? result
  290. : DisabledRippleStartPosition();
  291. }
  292. QImage SpeedButton::prepareRippleMask() const {
  293. return Ui::RippleAnimation::RoundRectMask(
  294. rect().marginsRemoved(_st.padding).size(),
  295. _st.rippleRadius);
  296. }
  297. SettingsButton::SettingsButton(
  298. QWidget *parent,
  299. const style::MediaSpeedButton &st)
  300. : RippleButton(parent, st.ripple)
  301. , _st(st)
  302. , _isDefaultSpeed(true) {
  303. resize(_st.size);
  304. }
  305. void SettingsButton::setSpeed(float64 speed) {
  306. if (_speed != speed) {
  307. _speed = speed;
  308. _isDefaultSpeed = EqualSpeeds(speed, 1.);
  309. update();
  310. }
  311. }
  312. void SettingsButton::setQuality(int quality) {
  313. if (_quality != quality) {
  314. _quality = quality;
  315. update();
  316. }
  317. }
  318. void SettingsButton::setActive(bool active) {
  319. if (_active == active) {
  320. return;
  321. }
  322. _active = active;
  323. _activeAnimation.start([=] {
  324. update();
  325. }, active ? 0. : 1., active ? 1. : 0., st::mediaviewOverDuration);
  326. }
  327. void SettingsButton::onStateChanged(State was, StateChangeSource source) {
  328. RippleButton::onStateChanged(was, source);
  329. const auto nowOver = isOver();
  330. const auto wasOver = static_cast<bool>(was & StateFlag::Over);
  331. if (nowOver != wasOver) {
  332. _overAnimation.start([=] {
  333. update();
  334. }, nowOver ? 0. : 1., nowOver ? 1. : 0., st::mediaviewOverDuration);
  335. }
  336. }
  337. void SettingsButton::paintEvent(QPaintEvent *e) {
  338. auto p = QPainter(this);
  339. paintRipple(
  340. p,
  341. QPoint(_st.padding.left(), _st.padding.top()),
  342. _isDefaultSpeed ? nullptr : &_st.rippleActiveColor->c);
  343. prepareFrame();
  344. p.drawImage(0, 0, _frameCache);
  345. }
  346. void SettingsButton::prepareFrame() {
  347. const auto ratio = style::DevicePixelRatio();
  348. if (_frameCache.size() != _st.size * ratio) {
  349. _frameCache = QImage(
  350. _st.size * ratio,
  351. QImage::Format_ARGB32_Premultiplied);
  352. _frameCache.setDevicePixelRatio(ratio);
  353. }
  354. _frameCache.fill(Qt::transparent);
  355. auto p = QPainter(&_frameCache);
  356. const auto inner = QRect(
  357. QPoint(),
  358. _st.size
  359. ).marginsRemoved(_st.padding);
  360. auto hq = std::optional<PainterHighQualityEnabler>();
  361. const auto over = _overAnimation.value(isOver() ? 1. : 0.);
  362. const auto color = anim::color(_st.fg, _st.overFg, over);
  363. const auto active = _activeAnimation.value(_active ? 1. : 0.);
  364. if (active > 0.) {
  365. const auto shift = QRectF(inner).center();
  366. p.save();
  367. p.translate(shift);
  368. p.rotate(active * 60.);
  369. p.translate(-shift);
  370. hq.emplace(p);
  371. }
  372. _st.icon.paintInCenter(p, inner, color);
  373. if (active > 0.) {
  374. p.restore();
  375. hq.reset();
  376. }
  377. const auto rounded = int(base::SafeRound(_speed * 10));
  378. if (rounded != 10) {
  379. const auto text = (rounded % 10)
  380. ? QString::number(rounded / 10.)
  381. : u"%1X"_q.arg(rounded / 10);
  382. paintBadge(p, text, RectPart::TopLeft, color);
  383. }
  384. const auto text = (!_quality)
  385. ? QString()
  386. : (_quality > 2000)
  387. ? u"4K"_q
  388. : (_quality > 1000)
  389. ? u"FHD"_q
  390. : (_quality > 700)
  391. ? u"HD"_q
  392. : u"SD"_q;
  393. if (!text.isEmpty()) {
  394. paintBadge(p, text, RectPart::BottomRight, color);
  395. }
  396. }
  397. void SettingsButton::paintBadge(
  398. QPainter &p,
  399. const QString &text,
  400. RectPart origin,
  401. QColor color) {
  402. auto hq = PainterHighQualityEnabler(p);
  403. const auto xpadding = style::ConvertScale(2.);
  404. const auto ypadding = 0;
  405. const auto skip = style::ConvertScale(2.);
  406. const auto width = _st.font->width(text);
  407. const auto height = _st.font->height;
  408. const auto radius = height / 3.;
  409. const auto left = (origin == RectPart::TopLeft)
  410. || (origin == RectPart::BottomLeft);
  411. const auto top = (origin == RectPart::TopLeft)
  412. || (origin == RectPart::TopRight);
  413. const auto x = left ? 0 : (_st.size.width() - width - 2 * xpadding);
  414. const auto y = top
  415. ? skip
  416. : (_st.size.height() - height - 2 * ypadding - skip);
  417. p.setCompositionMode(QPainter::CompositionMode_Source);
  418. const auto stroke = style::ConvertScaleExact(1.);
  419. p.setPen(QPen(Qt::transparent, stroke));
  420. p.setFont(_st.font);
  421. p.setBrush(color);
  422. p.drawRoundedRect(
  423. QRectF(
  424. x - stroke / 2.,
  425. y - stroke / 2.,
  426. width + 2 * xpadding + stroke,
  427. height + 2 * ypadding + stroke),
  428. radius,
  429. radius);
  430. p.setPen(Qt::transparent);
  431. p.drawText(x + xpadding, y + ypadding + _st.font->ascent, text);
  432. }
  433. QPoint SettingsButton::prepareRippleStartPosition() const {
  434. const auto inner = rect().marginsRemoved(_st.padding);
  435. const auto result = mapFromGlobal(QCursor::pos()) - inner.topLeft();
  436. return inner.contains(result)
  437. ? result
  438. : DisabledRippleStartPosition();
  439. }
  440. QImage SettingsButton::prepareRippleMask() const {
  441. return Ui::RippleAnimation::RoundRectMask(
  442. rect().marginsRemoved(_st.padding).size(),
  443. _st.rippleRadius);
  444. }
  445. } // namespace Media::Player