diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d72879dd7c..59502c77ba 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2320,6 +2320,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_player_message_yesterday" = "Yesterday at {time}"; "lng_player_message_date" = "{date} at {time}"; +"lng_audio_player_reverse" = "Reverse order"; +"lng_audio_player_shuffle" = "Shuffle"; + "lng_rights_edit_admin" = "Manage permissions"; "lng_rights_edit_admin_header" = "What can this admin do?"; "lng_rights_edit_admin_rank_name" = "Custom title"; diff --git a/Telegram/SourceFiles/media/player/media_player.style b/Telegram/SourceFiles/media/player/media_player.style index 5b75be8c70..46e815a6c5 100644 --- a/Telegram/SourceFiles/media/player/media_player.style +++ b/Telegram/SourceFiles/media/player/media_player.style @@ -286,3 +286,18 @@ mediaPlayerFileLayout: OverviewFileLayout(overviewFileLayout) { mediaPlayerFloatSize: 128px; mediaPlayerFloatMargin: 12px; + +mediaPlayerOrderMenuPosition: point(-2px, -4px); +mediaPlayerOrderMenu: Menu(defaultMenu) { + itemIconPosition: point(13px, 8px); + itemPadding: margins(49px, 9px, 17px, 11px); + itemStyle: boxTextStyle; +} +mediaPlayerOrderMenuActive: Menu(mediaPlayerOrderMenu) { + itemFg: windowActiveTextFg; + itemFgOver: windowActiveTextFg; +} +mediaPlayerOrderIconReverse: icon{{ "player/player_order", windowFg }}; +mediaPlayerOrderIconReverseActive: icon{{ "player/player_order", windowActiveTextFg }}; +mediaPlayerOrderIconShuffle: icon{{ "player/player_shuffle", windowFg }}; +mediaPlayerOrderIconShuffleActive: icon{{ "player/player_shuffle", windowActiveTextFg }}; diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 476a0ccf50..07eb34157d 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -18,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/menu/menu_action.h" #include "ui/wrap/fade_wrap.h" #include "ui/effects/ripple_animation.h" #include "ui/text/format_values.h" @@ -245,6 +247,7 @@ Widget::Widget( not_null controller) : RpWidget(parent) , _controller(controller) +, _orderMenuParent(dropdownsParent) , _nameLabel(this, st::mediaPlayerName) , _rightControls(this, object_ptr(this)) , _timeLabel(rightControls(), st::mediaPlayerTime) @@ -256,7 +259,7 @@ Widget::Widget( , _close(this, st::mediaPlayerClose) , _shadow(this) , _playbackSlider(this, st::mediaPlayerPlayback) -, _volume(dropdownsParent.get()) +, _volume(std::in_place, dropdownsParent.get()) , _playbackProgress(std::make_unique()) , _speedController( std::make_unique( @@ -313,6 +316,16 @@ Widget::Widget( updateRepeatToggleIcon(); }, lifetime()); + _orderToggle->setClickedCallback([=] { + showOrderMenu(); + }); + _orderToggle->events( + ) | rpl::filter([=](not_null e) { + return e->type() == QEvent::Enter; + }) | rpl::start_with_next([=] { + showOrderMenu(); + }, _orderToggle->lifetime()); + Core::App().settings().playerOrderModeValue( ) | rpl::start_with_next([=] { updateOrderToggleIcon(); @@ -362,13 +375,25 @@ Widget::Widget( handleSongUpdate(state); }, lifetime()); - PrepareVolumeDropdown(_volume.data(), controller); - _volumeToggle->installEventFilter(_volume.data()); + PrepareVolumeDropdown(_volume.get(), controller); + _volumeToggle->installEventFilter(_volume.get()); _volume->installEventFilter(this); + hidePlaylistOn(_playPause); + hidePlaylistOn(_close); + setType(AudioMsgId::Type::Song); } +void Widget::hidePlaylistOn(const object_ptr &button) { + button->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=] { + updateOverLabelsState(false); + }, button->lifetime()); +} + void Widget::setupRightControls() { const auto raw = rightControls(); raw->paintRequest( @@ -437,14 +462,101 @@ void Widget::showShadowAndDropdowns() { } } +void Widget::showOrderMenu() { + if (_orderMenu) { + return; + } + _orderMenu.emplace(_orderMenuParent); + _orderMenu->installEventFilter(this); + _orderMenu->setHiddenCallback([weak = Ui::MakeWeak(this), menu = _orderMenu.get()] { + menu->deleteLater(); + if (weak && weak->_orderMenu == menu) { + weak->_orderMenu = nullptr; + weak->_orderToggle->setForceRippled(false); + } + }); + _orderMenu->setShowStartCallback(crl::guard(this, [this, menu = _orderMenu.get()] { + if (_orderMenu == menu) { + _orderToggle->setForceRippled(true); + } + })); + _orderMenu->setHideStartCallback(crl::guard(this, [this, menu = _orderMenu.get()] { + if (_orderMenu == menu) { + _orderToggle->setForceRippled(false); + } + })); + _orderToggle->installEventFilter(_orderMenu); + const auto addOrderAction = [&](OrderMode mode) { + struct Fields { + QString label; + const style::icon &icon; + const style::icon &activeIcon; + }; + const auto current = Core::App().settings().playerOrderMode(); + const auto active = (current == mode); + const auto callback = [=] { + Core::App().settings().setPlayerOrderMode(active + ? OrderMode::Default + : mode); + Core::App().saveSettingsDelayed(); + }; + const auto fields = [&]() -> Fields { + switch (mode) { + case OrderMode::Reverse: return { + .label = tr::lng_audio_player_reverse(tr::now), + .icon = st::mediaPlayerOrderIconReverse, + .activeIcon = st::mediaPlayerOrderIconReverseActive, + }; + case OrderMode::Shuffle: return { + .label = tr::lng_audio_player_shuffle(tr::now), + .icon = st::mediaPlayerOrderIconShuffle, + .activeIcon = st::mediaPlayerOrderIconShuffleActive, + }; + } + Unexpected("Order mode in addOrderAction."); + }(); + const auto parent = _orderMenu.get(); + const auto action = Ui::Menu::CreateAction( + parent, + fields.label, + callback); + auto item = base::make_unique_q( + parent, + (active + ? st::mediaPlayerOrderMenuActive + : st::mediaPlayerOrderMenu), + std::move(action), + &(active ? fields.activeIcon : fields.icon), + &(active ? fields.activeIcon : fields.icon)); + _orderMenu->addAction(std::move(item)); + }; + addOrderAction(OrderMode::Reverse); + addOrderAction(OrderMode::Shuffle); + updateDropdownsGeometry(); + _orderMenu->showAnimated(Ui::PanelAnimation::Origin::TopRight); +} + void Widget::updateDropdownsGeometry() { + const auto dropdownWidth = st::mediaPlayerVolumeSize.width(); const auto position = _volume->parentWidget()->mapFromGlobal( _volumeToggle->mapToGlobal( QPoint( - (_volumeToggle->width() - st::mediaPlayerVolumeSize.width()) / 2, + (_volumeToggle->width() - dropdownWidth) / 2, height()))); const auto playerMargins = _volume->getMargin(); - _volume->move(position - QPoint(playerMargins.left(), playerMargins.top())); + const auto shift = QPoint(playerMargins.left(), playerMargins.top()); + _volume->move(position - shift); + + if (_orderMenu) { + const auto position = _orderMenu->parentWidget()->mapFromGlobal( + _orderToggle->mapToGlobal( + QPoint(_orderToggle->width(), _orderToggle->height()))); + const auto padding = st::defaultInnerDropdown.padding; + _orderMenu->move(position + - QPoint(_orderMenu->width(), 0) + + QPoint(padding.right(), -padding.top()) + + st::mediaPlayerOrderMenuPosition); + } } void Widget::hideShadowAndDropdowns() { @@ -516,11 +628,10 @@ void Widget::updateControlsGeometry() { void Widget::updateControlsWrapGeometry() { const auto fade = st::mediaPlayerControlsFade.width(); - rightControls()->resize( - getTimeRight() + _timeLabel->width() + fade, - _repeatToggle->height()); - _rightControls->moveToRight( - st::mediaPlayerCloseRight + _close->width(), + const auto controls = getTimeRight() + _timeLabel->width() + fade; + rightControls()->resize(controls, _repeatToggle->height()); + _rightControls->move( + width() - st::mediaPlayerCloseRight - _close->width() - controls, st::mediaPlayerPlayTop); } @@ -561,6 +672,7 @@ void Widget::markOver(bool over) { _over = true; _wontBeOver = false; updateControlsWrapVisibility(); + updateOverLabelsState(true); } else { _wontBeOver = true; InvokeQueued(this, [=] { @@ -570,8 +682,8 @@ void Widget::markOver(bool over) { _wontBeOver = false; _over = false; updateControlsWrapVisibility(); - updateOverLabelsState(false); }); + updateOverLabelsState(false); } } @@ -705,7 +817,7 @@ void Widget::updateOrderToggleIcon() { &st::mediaPlayerRepeatDisabledRippleBg); break; case OrderMode::Reverse: - _orderToggle->setIconOverride(nullptr); + _orderToggle->setIconOverride(&st::mediaPlayerReverseIcon); _orderToggle->setRippleColorOverride(nullptr); break; case OrderMode::Shuffle: @@ -736,6 +848,7 @@ bool Widget::hasPlaybackSpeedControl() const { void Widget::updateControlsVisibility() { _repeatToggle->setVisible(_type == AudioMsgId::Type::Song); + _orderToggle->setVisible(_type == AudioMsgId::Type::Song); _volumeToggle->setVisible(_type == AudioMsgId::Type::Song); _playbackSpeed->setVisible(hasPlaybackSpeedControl()); if (!_shadow->isHidden()) { @@ -906,13 +1019,15 @@ void Widget::createPrevNextButtons() { _previousTrack.create(this, st::mediaPlayerPreviousButton); _previousTrack->show(); _previousTrack->setClickedCallback([=]() { - instance()->previous(); + instance()->previous(_type); }); _nextTrack.create(this, st::mediaPlayerNextButton); _nextTrack->show(); _nextTrack->setClickedCallback([=]() { - instance()->next(); + instance()->next(_type); }); + hidePlaylistOn(_previousTrack); + hidePlaylistOn(_nextTrack); updatePlayPrevNextPositions(); } } diff --git a/Telegram/SourceFiles/media/player/media_player_widget.h b/Telegram/SourceFiles/media/player/media_player_widget.h index 9681443866..57346b57dd 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.h +++ b/Telegram/SourceFiles/media/player/media_player_widget.h @@ -18,6 +18,7 @@ class FlatLabel; class LabelSimple; class IconButton; class PlainShadow; +class DropdownMenu; class FilledSlider; template class FadeWrap; @@ -85,6 +86,7 @@ private: [[nodiscard]] int getTimeRight() const; void updateOverLabelsState(QPoint pos); void updateOverLabelsState(bool over); + void hidePlaylistOn(const object_ptr &button); void updatePlayPrevNextPositions(); void updateLabelsGeometry(); @@ -110,7 +112,10 @@ private: void updateTimeLabel(); void markOver(bool over); + void showOrderMenu(); + const not_null _controller; + const not_null _orderMenuParent; crl::time _seekPositionMs = -1; crl::time _lastDurationMs = 0; @@ -149,7 +154,8 @@ private: object_ptr _close; object_ptr _shadow = { nullptr }; object_ptr _playbackSlider; - object_ptr _volume; + base::unique_qptr _volume; + base::unique_qptr _orderMenu; std::unique_ptr _playbackProgress; std::unique_ptr _speedController;