media_player_dropdown.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  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_dropdown.h"
  8. #include "base/invoke_queued.h"
  9. #include "base/timer.h"
  10. #include "lang/lang_keys.h"
  11. #include "media/player/media_player_button.h"
  12. #include "ui/cached_round_corners.h"
  13. #include "ui/widgets/menu/menu.h"
  14. #include "ui/widgets/menu/menu_action.h"
  15. #include "ui/widgets/continuous_sliders.h"
  16. #include "ui/widgets/dropdown_menu.h"
  17. #include "ui/widgets/shadow.h"
  18. #include "ui/painter.h"
  19. #include "ui/ui_utility.h"
  20. #include "styles/style_media_player.h"
  21. #include "styles/style_widgets.h"
  22. namespace Media::Player {
  23. namespace {
  24. constexpr auto kSpeedDebounceTimeout = crl::time(1000);
  25. [[nodiscard]] float64 SpeedToSliderValue(float64 speed) {
  26. return (speed - kSpeedMin) / (kSpeedMax - kSpeedMin);
  27. }
  28. [[nodiscard]] float64 SliderValueToSpeed(float64 value) {
  29. const auto speed = value * (kSpeedMax - kSpeedMin) + kSpeedMin;
  30. return base::SafeRound(speed * 10) / 10.;
  31. }
  32. constexpr auto kSpeedStickedValues
  33. = std::array<std::pair<float64, float64>, 7>{{
  34. { 0.8, 0.05 },
  35. { 1.0, 0.05 },
  36. { 1.2, 0.05 },
  37. { 1.5, 0.05 },
  38. { 1.7, 0.05 },
  39. { 2.0, 0.05 },
  40. { 2.2, 0.05 },
  41. }};
  42. class SpeedSliderItem final : public Ui::Menu::ItemBase {
  43. public:
  44. SpeedSliderItem(
  45. not_null<RpWidget*> parent,
  46. const style::MediaSpeedMenu &st,
  47. rpl::producer<float64> value);
  48. not_null<QAction*> action() const override;
  49. bool isEnabled() const override;
  50. [[nodiscard]] float64 current() const;
  51. [[nodiscard]] rpl::producer<float64> changing() const;
  52. [[nodiscard]] rpl::producer<float64> changed() const;
  53. [[nodiscard]] rpl::producer<float64> debouncedChanges() const;
  54. protected:
  55. int contentHeight() const override;
  56. private:
  57. void setExternalValue(float64 speed);
  58. void setSliderValue(float64 speed);
  59. const base::unique_qptr<Ui::MediaSlider> _slider;
  60. const not_null<QAction*> _dummyAction;
  61. const style::MediaSpeedMenu &_st;
  62. Ui::Text::String _text;
  63. int _height = 0;
  64. rpl::event_stream<float64> _changing;
  65. rpl::event_stream<float64> _changed;
  66. rpl::event_stream<float64> _debounced;
  67. base::Timer _debounceTimer;
  68. rpl::variable<float64> _last = 0.;
  69. };
  70. SpeedSliderItem::SpeedSliderItem(
  71. not_null<RpWidget*> parent,
  72. const style::MediaSpeedMenu &st,
  73. rpl::producer<float64> value)
  74. : Ui::Menu::ItemBase(parent, st.dropdown.menu)
  75. , _slider(base::make_unique_q<Ui::MediaSlider>(this, st.slider))
  76. , _dummyAction(new QAction(parent))
  77. , _st(st)
  78. , _height(st.sliderPadding.top()
  79. + st.dropdown.menu.itemStyle.font->height
  80. + st.sliderPadding.bottom())
  81. , _debounceTimer([=] { _debounced.fire(current()); }) {
  82. initResizeHook(parent->sizeValue());
  83. enableMouseSelecting();
  84. enableMouseSelecting(_slider.get());
  85. setPointerCursor(false);
  86. setMinWidth(st.sliderPadding.left()
  87. + st.sliderWidth
  88. + st.sliderPadding.right());
  89. _slider->setAlwaysDisplayMarker(true);
  90. sizeValue(
  91. ) | rpl::start_with_next([=](const QSize &size) {
  92. const auto geometry = QRect(QPoint(), size);
  93. const auto padding = _st.sliderPadding;
  94. const auto inner = geometry - padding;
  95. _slider->setGeometry(
  96. padding.left(),
  97. inner.y(),
  98. (geometry.width() - padding.left() - padding.right()),
  99. inner.height());
  100. }, lifetime());
  101. paintRequest(
  102. ) | rpl::start_with_next([=](const QRect &clip) {
  103. auto p = Painter(this);
  104. p.fillRect(clip, _st.dropdown.menu.itemBg);
  105. const auto left = (_st.sliderPadding.left() - _text.maxWidth()) / 2;
  106. const auto top = _st.dropdown.menu.itemPadding.top();
  107. p.setPen(_st.dropdown.menu.itemFg);
  108. _text.drawLeftElided(p, left, top, _text.maxWidth(), width());
  109. }, lifetime());
  110. _slider->setChangeProgressCallback([=](float64 value) {
  111. const auto speed = SliderValueToSpeed(value);
  112. if (!EqualSpeeds(current(), speed)) {
  113. _last = speed;
  114. _changing.fire_copy(speed);
  115. _debounceTimer.callOnce(kSpeedDebounceTimeout);
  116. }
  117. });
  118. _slider->setChangeFinishedCallback([=](float64 value) {
  119. const auto speed = SliderValueToSpeed(value);
  120. _last = speed;
  121. _changed.fire_copy(speed);
  122. _debounced.fire_copy(speed);
  123. _debounceTimer.cancel();
  124. });
  125. std::move(
  126. value
  127. ) | rpl::start_with_next([=](float64 external) {
  128. setExternalValue(external);
  129. }, lifetime());
  130. _last.value(
  131. ) | rpl::start_with_next([=](float64 value) {
  132. const auto text = QString::number(value, 'f', 1) + 'x';
  133. if (_text.toString() != text) {
  134. _text.setText(_st.sliderStyle, text);
  135. update();
  136. }
  137. }, lifetime());
  138. _slider->setAdjustCallback([=](float64 value) {
  139. const auto speed = SliderValueToSpeed(value);
  140. for (const auto &snap : kSpeedStickedValues) {
  141. if (speed > (snap.first - snap.second)
  142. && speed < (snap.first + snap.second)) {
  143. return SpeedToSliderValue(snap.first);
  144. }
  145. }
  146. return value;
  147. });
  148. }
  149. void FillSpeedMenu(
  150. not_null<Ui::Menu::Menu*> menu,
  151. const style::MediaSpeedMenu &st,
  152. rpl::producer<float64> value,
  153. Fn<void(float64)> callback,
  154. bool onlySlider) {
  155. auto slider = base::make_unique_q<SpeedSliderItem>(
  156. menu,
  157. st,
  158. rpl::duplicate(value));
  159. slider->debouncedChanges(
  160. ) | rpl::start_with_next(callback, slider->lifetime());
  161. struct State {
  162. rpl::variable<float64> realtime;
  163. };
  164. const auto state = slider->lifetime().make_state<State>();
  165. state->realtime = rpl::single(
  166. slider->current()
  167. ) | rpl::then(rpl::merge(
  168. slider->changing(),
  169. slider->changed()
  170. ));
  171. menu->addAction(std::move(slider));
  172. if (onlySlider) {
  173. return;
  174. }
  175. menu->addSeparator(&st.dropdown.menu.separator);
  176. struct SpeedPoint {
  177. float64 speed = 0.;
  178. tr::phrase<> text;
  179. const style::icon &icon;
  180. const style::icon &iconActive;
  181. };
  182. const auto points = std::vector<SpeedPoint>{
  183. {
  184. 0.5,
  185. tr::lng_voice_speed_slow,
  186. st.slow,
  187. st.slowActive },
  188. {
  189. 1.0,
  190. tr::lng_voice_speed_normal,
  191. st.normal,
  192. st.normalActive },
  193. {
  194. 1.2,
  195. tr::lng_voice_speed_medium,
  196. st.medium,
  197. st.mediumActive },
  198. {
  199. 1.5,
  200. tr::lng_voice_speed_fast,
  201. st.fast,
  202. st.fastActive },
  203. {
  204. 1.7,
  205. tr::lng_voice_speed_very_fast,
  206. st.veryFast,
  207. st.veryFastActive },
  208. {
  209. 2.0,
  210. tr::lng_voice_speed_super_fast,
  211. st.superFast,
  212. st.superFastActive },
  213. };
  214. for (const auto &point : points) {
  215. const auto speed = point.speed;
  216. const auto text = point.text(tr::now);
  217. const auto icon = &point.icon;
  218. const auto iconActive = &point.iconActive;
  219. auto action = base::make_unique_q<Ui::Menu::Action>(
  220. menu,
  221. st.dropdown.menu,
  222. Ui::Menu::CreateAction(menu, text, [=] { callback(speed); }),
  223. &point.icon,
  224. &point.icon);
  225. const auto raw = action.get();
  226. const auto check = Ui::CreateChild<Ui::RpWidget>(raw);
  227. check->resize(st.activeCheck.size());
  228. check->paintRequest(
  229. ) | rpl::start_with_next([check, icon = &st.activeCheck] {
  230. auto p = QPainter(check);
  231. icon->paint(p, 0, 0, check->width());
  232. }, check->lifetime());
  233. raw->sizeValue(
  234. ) | rpl::start_with_next([=, skip = st.activeCheckSkip](QSize size) {
  235. check->moveToRight(
  236. skip,
  237. (size.height() - check->height()) / 2,
  238. size.width());
  239. }, check->lifetime());
  240. check->setAttribute(Qt::WA_TransparentForMouseEvents);
  241. state->realtime.value(
  242. ) | rpl::start_with_next([=](float64 now) {
  243. const auto chosen = EqualSpeeds(speed, now);
  244. const auto overriden = chosen ? iconActive : icon;
  245. raw->setIcon(overriden, overriden);
  246. raw->action()->setEnabled(!chosen);
  247. check->setVisible(chosen);
  248. }, raw->lifetime());
  249. menu->addAction(std::move(action));
  250. }
  251. }
  252. void SpeedSliderItem::setExternalValue(float64 speed) {
  253. if (!_slider->isChanging()) {
  254. setSliderValue(speed);
  255. }
  256. }
  257. void SpeedSliderItem::setSliderValue(float64 speed) {
  258. const auto value = SpeedToSliderValue(speed);
  259. _slider->setValue(value);
  260. _last = speed;
  261. _changed.fire_copy(speed);
  262. }
  263. not_null<QAction*> SpeedSliderItem::action() const {
  264. return _dummyAction;
  265. }
  266. bool SpeedSliderItem::isEnabled() const {
  267. return false;
  268. }
  269. int SpeedSliderItem::contentHeight() const {
  270. return _height;
  271. }
  272. float64 SpeedSliderItem::current() const {
  273. return _last.current();
  274. }
  275. rpl::producer<float64> SpeedSliderItem::changing() const {
  276. return _changing.events();
  277. }
  278. rpl::producer<float64> SpeedSliderItem::changed() const {
  279. return _changed.events();
  280. }
  281. rpl::producer<float64> SpeedSliderItem::debouncedChanges() const {
  282. return _debounced.events();
  283. }
  284. } // namespace
  285. Dropdown::Dropdown(QWidget *parent)
  286. : RpWidget(parent)
  287. , _hideTimer([=] { startHide(); })
  288. , _showTimer([=] { startShow(); }) {
  289. hide();
  290. macWindowDeactivateEvents(
  291. ) | rpl::filter([=] {
  292. return !isHidden();
  293. }) | rpl::start_with_next([=] {
  294. leaveEvent(nullptr);
  295. }, lifetime());
  296. hide();
  297. auto margin = getMargin();
  298. resize(margin.left() + st::mediaPlayerVolumeSize.width() + margin.right(), margin.top() + st::mediaPlayerVolumeSize.height() + margin.bottom());
  299. }
  300. QMargins Dropdown::getMargin() const {
  301. const auto top1 = st::mediaPlayerHeight
  302. + st::lineWidth
  303. - st::mediaPlayerPlayTop
  304. - st::mediaPlayerVolumeToggle.height;
  305. const auto top2 = st::mediaPlayerPlayback.fullWidth;
  306. const auto top = std::max(top1, top2);
  307. return QMargins(st::mediaPlayerVolumeMargin, top, st::mediaPlayerVolumeMargin, st::mediaPlayerVolumeMargin);
  308. }
  309. bool Dropdown::overlaps(const QRect &globalRect) {
  310. if (isHidden() || _a_appearance.animating()) return false;
  311. return rect().marginsRemoved(getMargin()).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
  312. }
  313. void Dropdown::paintEvent(QPaintEvent *e) {
  314. auto p = QPainter(this);
  315. if (!_cache.isNull()) {
  316. bool animating = _a_appearance.animating();
  317. if (animating) {
  318. p.setOpacity(_a_appearance.value(_hiding ? 0. : 1.));
  319. } else if (_hiding || isHidden()) {
  320. hidingFinished();
  321. return;
  322. }
  323. p.drawPixmap(0, 0, _cache);
  324. if (!animating) {
  325. showChildren();
  326. _cache = QPixmap();
  327. }
  328. return;
  329. }
  330. // draw shadow
  331. auto shadowedRect = rect().marginsRemoved(getMargin());
  332. auto shadowedSides = RectPart::Left | RectPart::Right | RectPart::Bottom;
  333. Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides);
  334. const auto &corners = Ui::CachedCornerPixmaps(Ui::MenuCorners);
  335. const auto fill = Ui::CornersPixmaps{
  336. .p = { QPixmap(), QPixmap(), corners.p[2], corners.p[3] },
  337. };
  338. Ui::FillRoundRect(
  339. p,
  340. shadowedRect.x(),
  341. 0,
  342. shadowedRect.width(),
  343. shadowedRect.y() + shadowedRect.height(),
  344. st::menuBg,
  345. fill);
  346. }
  347. void Dropdown::enterEventHook(QEnterEvent *e) {
  348. _hideTimer.cancel();
  349. if (_a_appearance.animating()) {
  350. startShow();
  351. } else {
  352. _showTimer.callOnce(0);
  353. }
  354. return RpWidget::enterEventHook(e);
  355. }
  356. void Dropdown::leaveEventHook(QEvent *e) {
  357. _showTimer.cancel();
  358. if (_a_appearance.animating()) {
  359. startHide();
  360. } else {
  361. _hideTimer.callOnce(300);
  362. }
  363. return RpWidget::leaveEventHook(e);
  364. }
  365. void Dropdown::otherEnter() {
  366. _hideTimer.cancel();
  367. if (_a_appearance.animating()) {
  368. startShow();
  369. } else {
  370. _showTimer.callOnce(0);
  371. }
  372. }
  373. void Dropdown::otherLeave() {
  374. _showTimer.cancel();
  375. if (_a_appearance.animating()) {
  376. startHide();
  377. } else {
  378. _hideTimer.callOnce(0);
  379. }
  380. }
  381. void Dropdown::startShow() {
  382. if (isHidden()) {
  383. show();
  384. } else if (!_hiding) {
  385. return;
  386. }
  387. _hiding = false;
  388. startAnimation();
  389. }
  390. void Dropdown::startHide() {
  391. if (_hiding) {
  392. return;
  393. }
  394. _hiding = true;
  395. startAnimation();
  396. }
  397. void Dropdown::startAnimation() {
  398. if (_cache.isNull()) {
  399. showChildren();
  400. _cache = Ui::GrabWidget(this);
  401. }
  402. hideChildren();
  403. _a_appearance.start(
  404. [=] { appearanceCallback(); },
  405. _hiding ? 1. : 0.,
  406. _hiding ? 0. : 1.,
  407. st::defaultInnerDropdown.duration);
  408. }
  409. void Dropdown::appearanceCallback() {
  410. if (!_a_appearance.animating() && _hiding) {
  411. _hiding = false;
  412. hidingFinished();
  413. } else {
  414. update();
  415. }
  416. }
  417. void Dropdown::hidingFinished() {
  418. hide();
  419. _cache = QPixmap();
  420. }
  421. bool Dropdown::eventFilter(QObject *obj, QEvent *e) {
  422. if (e->type() == QEvent::Enter) {
  423. otherEnter();
  424. } else if (e->type() == QEvent::Leave) {
  425. otherLeave();
  426. }
  427. return false;
  428. }
  429. WithDropdownController::WithDropdownController(
  430. not_null<Ui::AbstractButton*> button,
  431. not_null<QWidget*> menuParent,
  432. const style::DropdownMenu &menuSt,
  433. Qt::Alignment menuAlign,
  434. Fn<void(bool)> menuOverCallback)
  435. : _button(button)
  436. , _menuParent(menuParent)
  437. , _menuSt(menuSt)
  438. , _menuAlign(menuAlign)
  439. , _menuOverCallback(std::move(menuOverCallback)) {
  440. button->events(
  441. ) | rpl::filter([=](not_null<QEvent*> e) {
  442. return (e->type() == QEvent::Enter)
  443. || (e->type() == QEvent::Leave);
  444. }) | rpl::start_with_next([=](not_null<QEvent*> e) {
  445. _overButton = (e->type() == QEvent::Enter);
  446. if (_overButton) {
  447. InvokeQueued(button, [=] {
  448. if (_overButton) {
  449. showMenu();
  450. }
  451. });
  452. }
  453. }, button->lifetime());
  454. }
  455. not_null<Ui::AbstractButton*> WithDropdownController::button() const {
  456. return _button;
  457. }
  458. Ui::DropdownMenu *WithDropdownController::menu() const {
  459. return _menu.get();
  460. }
  461. void WithDropdownController::updateDropdownGeometry() {
  462. if (!_menu) {
  463. return;
  464. }
  465. const auto bwidth = _button->width();
  466. const auto bheight = _button->height();
  467. const auto mwidth = _menu->width();
  468. const auto mheight = _menu->height();
  469. const auto padding = _menuSt.wrap.padding;
  470. const auto x = st::mediaPlayerMenuPosition.x();
  471. const auto y = st::mediaPlayerMenuPosition.y();
  472. const auto position = _menu->parentWidget()->mapFromGlobal(
  473. _button->mapToGlobal(QPoint())
  474. ) + [&] {
  475. switch (_menuAlign) {
  476. case style::al_topleft: return QPoint(
  477. -padding.left() - x,
  478. bheight - padding.top() + y);
  479. case style::al_topright: return QPoint(
  480. bwidth - mwidth + padding.right() + x,
  481. bheight - padding.top() + y);
  482. case style::al_bottomright: return QPoint(
  483. bwidth - mwidth + padding.right() + x,
  484. -mheight + padding.bottom() - y);
  485. case style::al_bottomleft: return QPoint(
  486. -padding.left() - x,
  487. -mheight + padding.bottom() - y);
  488. }
  489. Unexpected("Menu align value.");
  490. }();
  491. _menu->move(position);
  492. }
  493. rpl::producer<bool> WithDropdownController::menuToggledValue() const {
  494. return _menuToggled.value();
  495. }
  496. void WithDropdownController::hideTemporarily() {
  497. if (_menu && !_menu->isHidden()) {
  498. _temporarilyHidden = true;
  499. _menu->hide();
  500. }
  501. }
  502. void WithDropdownController::showBack() {
  503. if (_temporarilyHidden) {
  504. _temporarilyHidden = false;
  505. if (_menu && _menu->isHidden()) {
  506. _menu->show();
  507. }
  508. }
  509. }
  510. void WithDropdownController::showMenu() {
  511. if (_menu) {
  512. return;
  513. }
  514. _menu.emplace(_menuParent, _menuSt);
  515. const auto raw = _menu.get();
  516. _menu->events(
  517. ) | rpl::start_with_next([this](not_null<QEvent*> e) {
  518. const auto type = e->type();
  519. if (type == QEvent::Enter) {
  520. _menuOverCallback(true);
  521. } else if (type == QEvent::Leave) {
  522. _menuOverCallback(false);
  523. }
  524. }, _menu->lifetime());
  525. _menu->setHiddenCallback([=]{
  526. if (_menu.get() == raw) {
  527. _menuToggled = false;
  528. }
  529. Ui::PostponeCall(raw, [this] {
  530. _menu = nullptr;
  531. _menuToggled = false;
  532. });
  533. });
  534. _menu->setShowStartCallback([=] {
  535. _menuToggled = true;
  536. });
  537. _menu->setHideStartCallback([=] {
  538. _menuToggled = false;
  539. });
  540. _button->installEventFilter(raw);
  541. fillMenu(raw);
  542. updateDropdownGeometry();
  543. const auto origin = [&] {
  544. using Origin = Ui::PanelAnimation::Origin;
  545. switch (_menuAlign) {
  546. case style::al_topleft: return Origin::TopLeft;
  547. case style::al_topright: return Origin::TopRight;
  548. case style::al_bottomright: return Origin::BottomRight;
  549. case style::al_bottomleft: return Origin::BottomLeft;
  550. }
  551. Unexpected("Menu align value.");
  552. }();
  553. _menu->showAnimated(origin);
  554. _menuToggled = true;
  555. }
  556. OrderController::OrderController(
  557. not_null<Ui::IconButton*> button,
  558. not_null<QWidget*> menuParent,
  559. Fn<void(bool)> menuOverCallback,
  560. rpl::producer<OrderMode> value,
  561. Fn<void(OrderMode)> change)
  562. : WithDropdownController(
  563. button,
  564. menuParent,
  565. st::mediaPlayerMenu,
  566. style::al_topright,
  567. std::move(menuOverCallback))
  568. , _button(button)
  569. , _appOrder(std::move(value))
  570. , _change(std::move(change)) {
  571. button->setClickedCallback([=] {
  572. showMenu();
  573. });
  574. _appOrder.value(
  575. ) | rpl::start_with_next([=] {
  576. updateIcon();
  577. }, button->lifetime());
  578. }
  579. void OrderController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
  580. const auto addOrderAction = [&](OrderMode mode) {
  581. struct Fields {
  582. QString label;
  583. const style::icon &icon;
  584. const style::icon &activeIcon;
  585. };
  586. const auto active = (_appOrder.current() == mode);
  587. const auto callback = [change = _change, mode, active] {
  588. change(active ? OrderMode::Default : mode);
  589. };
  590. const auto fields = [&]() -> Fields {
  591. switch (mode) {
  592. case OrderMode::Reverse: return {
  593. .label = tr::lng_audio_player_reverse(tr::now),
  594. .icon = st::mediaPlayerOrderIconReverse,
  595. .activeIcon = st::mediaPlayerOrderIconReverseActive,
  596. };
  597. case OrderMode::Shuffle: return {
  598. .label = tr::lng_audio_player_shuffle(tr::now),
  599. .icon = st::mediaPlayerOrderIconShuffle,
  600. .activeIcon = st::mediaPlayerOrderIconShuffleActive,
  601. };
  602. }
  603. Unexpected("Order mode in addOrderAction.");
  604. }();
  605. menu->addAction(base::make_unique_q<Ui::Menu::Action>(
  606. menu,
  607. (active
  608. ? st::mediaPlayerOrderMenuActive
  609. : st::mediaPlayerOrderMenu),
  610. Ui::Menu::CreateAction(menu, fields.label, callback),
  611. &(active ? fields.activeIcon : fields.icon),
  612. &(active ? fields.activeIcon : fields.icon)));
  613. };
  614. addOrderAction(OrderMode::Reverse);
  615. addOrderAction(OrderMode::Shuffle);
  616. }
  617. void OrderController::updateIcon() {
  618. switch (_appOrder.current()) {
  619. case OrderMode::Default:
  620. _button->setIconOverride(
  621. &st::mediaPlayerReverseDisabledIcon,
  622. &st::mediaPlayerReverseDisabledIconOver);
  623. _button->setRippleColorOverride(
  624. &st::mediaPlayerRepeatDisabledRippleBg);
  625. break;
  626. case OrderMode::Reverse:
  627. _button->setIconOverride(&st::mediaPlayerReverseIcon);
  628. _button->setRippleColorOverride(nullptr);
  629. break;
  630. case OrderMode::Shuffle:
  631. _button->setIconOverride(&st::mediaPlayerShuffleIcon);
  632. _button->setRippleColorOverride(nullptr);
  633. break;
  634. }
  635. }
  636. SpeedController::SpeedController(
  637. not_null<Ui::AbstractButton*> button,
  638. const style::MediaSpeedButton &st,
  639. not_null<QWidget*> menuParent,
  640. Fn<void(bool)> menuOverCallback,
  641. Fn<float64(bool lastNonDefault)> value,
  642. Fn<void(float64)> change,
  643. std::vector<int> qualities,
  644. Fn<VideoQuality()> quality,
  645. Fn<void(int)> changeQuality)
  646. : WithDropdownController(
  647. button,
  648. menuParent,
  649. st.menu.dropdown,
  650. st.menuAlign,
  651. std::move(menuOverCallback))
  652. , _st(st)
  653. , _lookup(std::move(value))
  654. , _change(std::move(change))
  655. , _qualities(std::move(qualities))
  656. , _lookupQuality(std::move(quality))
  657. , _changeQuality(std::move(changeQuality)) {
  658. Expects(_qualities.empty() || (_lookupQuality && _changeQuality));
  659. button->setClickedCallback([=] {
  660. if (_lookup && !_lookupQuality && !_changeQuality) {
  661. toggleDefault();
  662. save();
  663. if (const auto current = menu()) {
  664. current->otherEnter();
  665. }
  666. } else {
  667. showMenu();
  668. }
  669. });
  670. if (const auto lookup = _lookup) {
  671. setSpeed(lookup(false));
  672. _speed = lookup(true);
  673. }
  674. }
  675. rpl::producer<> SpeedController::saved() const {
  676. return _saved.events();
  677. }
  678. rpl::producer<float64> SpeedController::realtimeValue() const {
  679. return _speedChanged.events_starting_with(speed());
  680. }
  681. float64 SpeedController::speed() const {
  682. return _isDefault ? 1. : _speed;
  683. }
  684. bool SpeedController::isDefault() const {
  685. return _isDefault;
  686. }
  687. float64 SpeedController::lastNonDefaultSpeed() const {
  688. return _speed;
  689. }
  690. void SpeedController::toggleDefault() {
  691. _isDefault = !_isDefault;
  692. _speedChanged.fire(speed());
  693. }
  694. void SpeedController::setSpeed(float64 newSpeed) {
  695. if (!(_isDefault = EqualSpeeds(newSpeed, 1.))) {
  696. _speed = newSpeed;
  697. }
  698. _speedChanged.fire(speed());
  699. }
  700. void SpeedController::save() {
  701. if (const auto change = _change) {
  702. change(speed());
  703. }
  704. _saved.fire({});
  705. }
  706. void SpeedController::setQuality(VideoQuality quality) {
  707. _quality = quality;
  708. _changeQuality(quality.manual ? quality.height : 0);
  709. }
  710. void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
  711. if (_lookup) {
  712. FillSpeedMenu(
  713. menu->menu(),
  714. _st.menu,
  715. _speedChanged.events_starting_with(speed()),
  716. [=](float64 speed) { setSpeed(speed); save(); },
  717. !_qualities.empty());
  718. }
  719. if (_qualities.empty()) {
  720. return;
  721. }
  722. _quality = _lookupQuality();
  723. const auto raw = menu->menu();
  724. const auto &st = _st.menu;
  725. if (_lookup) {
  726. raw->addSeparator(&st.dropdown.menu.separator);
  727. }
  728. const auto add = [&](int quality) {
  729. const auto automatic = tr::lng_mediaview_quality_auto(tr::now);
  730. const auto text = quality ? u"%1p"_q.arg(quality) : automatic;
  731. auto action = base::make_unique_q<Ui::Menu::Action>(
  732. raw,
  733. st.qualityMenu,
  734. Ui::Menu::CreateAction(
  735. raw,
  736. text,
  737. [=] { _changeQuality(quality); }),
  738. nullptr,
  739. nullptr);
  740. const auto raw = action.get();
  741. const auto check = Ui::CreateChild<Ui::RpWidget>(raw);
  742. check->resize(st.activeCheck.size());
  743. check->paintRequest(
  744. ) | rpl::start_with_next([check, icon = &st.activeCheck] {
  745. auto p = QPainter(check);
  746. icon->paint(p, 0, 0, check->width());
  747. }, check->lifetime());
  748. raw->sizeValue(
  749. ) | rpl::start_with_next([=, skip = st.activeCheckSkip](QSize size) {
  750. check->moveToRight(
  751. skip,
  752. (size.height() - check->height()) / 2,
  753. size.width());
  754. }, check->lifetime());
  755. check->setAttribute(Qt::WA_TransparentForMouseEvents);
  756. _quality.value(
  757. ) | rpl::start_with_next([=](VideoQuality now) {
  758. const auto chosen = now.manual
  759. ? (now.height == quality)
  760. : !quality;
  761. raw->action()->setEnabled(!chosen);
  762. if (!quality) {
  763. raw->action()->setText(automatic
  764. + (now.manual ? QString() : u"\t%1p"_q.arg(now.height)));
  765. }
  766. check->setVisible(chosen);
  767. }, raw->lifetime());
  768. menu->addAction(std::move(action));
  769. };
  770. add(0);
  771. for (const auto quality : _qualities) {
  772. add(quality);
  773. }
  774. }
  775. } // namespace Media::Player