From 813470ff25038704882e3475db73d076965712f1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 28 Jan 2021 08:46:40 +0300 Subject: [PATCH] Replaced submenu for playback speed with slider. --- Telegram/Resources/langs/lang.strings | 1 - Telegram/SourceFiles/core/core_settings.h | 9 +- .../SourceFiles/media/view/media_view.style | 2 + .../view/media_view_playback_controls.cpp | 224 ++++++++++++++---- .../media/view/media_view_playback_controls.h | 3 +- 5 files changed, 188 insertions(+), 51 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ba9334a8e0..7f89d6e4da 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1721,7 +1721,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_mediaview_downloads" = "Downloads"; "lng_mediaview_video_loading" = "Loading - {percent}"; "lng_mediaview_playback_speed" = "Playback speed"; -"lng_mediaview_playback_speed_normal" = "Normal"; "lng_mediaview_rotate_video" = "Rotate video"; "lng_theme_preview_title" = "Theme Preview"; diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 67ffc1f257..b3735451d7 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -507,10 +507,15 @@ public: [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] float64 DefaultDialogsWidthRatio(); [[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) { - return int(std::round(std::clamp(speed * 4., 2., 8.))) - 2; + return int(std::round(std::clamp(speed, 0.5, 2.0) * 100)); } [[nodiscard]] static float64 DeserializePlaybackSpeed(qint32 speed) { - return (std::clamp(speed, 0, 6) + 2) / 4.; + if (speed < 10) { + // The old values in settings. + return (std::clamp(speed, 0, 6) + 2) / 4.; + } else { + return std::clamp(speed, 50, 200) / 100.; + } } void resetOnLastLogout(); diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 4f0344438e..40efafa809 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -301,3 +301,5 @@ pipCloseIcon: icon {{ "player_pip_close", mediaviewPlaybackIconFg }}; pipCloseIconOver: icon {{ "player_pip_close", mediaviewPlaybackIconFgOver }}; pipEnlargeIcon: icon {{ "player_pip_enlarge", mediaviewPlaybackIconFg }}; pipEnlargeIconOver: icon {{ "player_pip_enlarge", mediaviewPlaybackIconFgOver }}; + +speedSliderDividerSize: size(2px, 8px); diff --git a/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp b/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp index 5c6cd0994c..e850b85559 100644 --- a/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp +++ b/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/continuous_sliders.h" #include "ui/effects/fade_animation.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/menu/menu_item_base.h" #include "ui/widgets/popup_menu.h" #include "ui/text/format_values.h" #include "ui/cached_round_corners.h" @@ -21,6 +22,166 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Media { namespace View { +namespace { + +constexpr auto kMinSpeed = 50; +constexpr auto kMaxSpeed = 200; + +constexpr float64 SpeedShiftToValue(float64 value) { + const auto valueAsSpeedF = value * 100.; + const auto valueAsSpeed = int(valueAsSpeedF + 0.5); // constexpr round. + return float64(valueAsSpeed) / (kMaxSpeed - kMinSpeed); +}; + +constexpr float64 SpeedToValue(float64 value) { + const auto valueAsSpeedF = value * 100.; + const auto valueAsSpeed = int(valueAsSpeedF + 0.5); // constexpr round. + return float64(valueAsSpeed - kMinSpeed) / (kMaxSpeed - kMinSpeed); +}; + +constexpr auto kSpeedStickedValues = + std::array, 5>{{ + { SpeedToValue(0.75), SpeedShiftToValue(0.03) }, + { SpeedToValue(1.00), SpeedShiftToValue(0.05) }, + { SpeedToValue(1.25), SpeedShiftToValue(0.03) }, + { SpeedToValue(1.50), SpeedShiftToValue(0.03) }, + { SpeedToValue(1.75), SpeedShiftToValue(0.03) }, + }}; + +class MenuSpeedItem final : public Ui::Menu::ItemBase { +public: + MenuSpeedItem( + not_null parent, + const style::Menu &st, + float64 startSpeed); + + not_null action() const override; + bool isEnabled() const override; + + [[nodiscard]] rpl::producer changeSpeedRequests() const; + +protected: + int contentHeight() const override; + +private: + QRect _itemRect; + QRect _textRect; + + const style::MediaSlider &_sliderSt; + const base::unique_qptr _slider; + const not_null _dummyAction; + const int _height; + + rpl::event_stream _changeSpeedRequests; + +}; + +MenuSpeedItem::MenuSpeedItem( + not_null parent, + const style::Menu &st, + float64 startSpeed) +: Ui::Menu::ItemBase(parent, st) +, _sliderSt(st::mediaviewPlayback) +, _slider(base::make_unique_q( + this, + _sliderSt)) +, _dummyAction(new QAction(parent)) +, _height(st.itemPadding.top() * 2 + + st.itemStyle.font->height + + _sliderSt.seekSize.height() + + st.itemPadding.bottom() * 2) { + + initResizeHook(parent->sizeValue()); + enableMouseSelecting(); + enableMouseSelecting(_slider.get()); + + const auto computeSpeed = [=](float64 value) { + return anim::interpolate(kMinSpeed, kMaxSpeed, value) / 100.; + }; + const auto speedString = [=](float64 value) { + return u"%1: %2x"_q + .arg(tr::lng_mediaview_playback_speed(tr::now)) + .arg(computeSpeed(value)); + }; + + _slider->setAlwaysDisplayMarker(true); + _slider->setValue((std::round(startSpeed * 100.) - kMinSpeed) + / (kMaxSpeed - kMinSpeed)); + + _slider->addDivider( + kSpeedStickedValues[1].first, + st::speedSliderDividerSize); + + { + const auto goodWidth = st.itemPadding.left() + + st.itemPadding.right() + + st.itemStyle.font->width(speedString(0.9)); + setMinWidth(std::clamp(goodWidth, st.widthMin, st.widthMax)); + } + + sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + const auto geometry = QRect(QPoint(), size); + _itemRect = geometry - st.itemPadding; + + const auto height = _itemRect.height(); + _textRect = _itemRect + - style::margins(0, 0, 0, height - st.itemStyle.font->height); + + const auto sliderGeometry = _itemRect + - style::margins(0, height - _sliderSt.seekSize.height(), 0, 0); + _slider->setGeometry(sliderGeometry); + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=](const QRect &clip) { + Painter p(this); + + const auto selected = isSelected(); + p.fillRect(clip, selected ? st.itemBgOver : st.itemBg); + + const auto value = _slider->value(); + + p.setFont(st.itemStyle.font); + p.drawText(_textRect, speedString(value), style::al_left); + }, lifetime()); + + _slider->setChangeProgressCallback([=](float64 value) { + update(_textRect); + }); + + _slider->setChangeFinishedCallback([=](float64 value) { + _changeSpeedRequests.fire_copy(computeSpeed(value)); + }); + + _slider->setAdjustCallback([=](float64 value) { + for (const auto &snap : kSpeedStickedValues) { + if (value > (snap.first - snap.second) + && value < (snap.first + snap.second)) { + return snap.first; + } + } + return value; + }); +} + +not_null MenuSpeedItem::action() const { + return _dummyAction; +} + +bool MenuSpeedItem::isEnabled() const { + return true; +} + +int MenuSpeedItem::contentHeight() const { + return _height; +} + +rpl::producer MenuSpeedItem::changeSpeedRequests() const { + return _changeSpeedRequests.events(); +} + +} // namespace PlaybackControls::PlaybackControls( QWidget *parent, @@ -37,7 +198,7 @@ PlaybackControls::PlaybackControls( , _pictureInPicture(this, st::mediaviewPipButton) , _playedAlready(this, st::mediaviewPlayProgressLabel) , _toPlayLeft(this, st::mediaviewPlayProgressLabel) -, _speedMenuStyle(st::mediaviewControlsPopupMenu) +, _menuStyle(st::mediaviewControlsPopupMenu) , _fadeAnimation(std::make_unique(this)) { _fadeAnimation->show(); _fadeAnimation->setFinishedCallback([=] { @@ -175,61 +336,32 @@ void PlaybackControls::fadeUpdated(float64 opacity) { _volumeController->setFadeOpacity(opacity); } -void PlaybackControls::validateSpeedMenuStyle() { - auto &st = _speedMenuStyle.menu; - const auto &check = st::mediaviewMenuCheck; - const auto normal = tr::lng_mediaview_playback_speed_normal(tr::now); - const auto itemHeight = st.itemPadding.top() - + st.itemStyle.font->height - + st.itemPadding.bottom(); - const auto itemWidth = st.itemPadding.left() - + st.itemStyle.font->width(normal) - + st.itemPadding.right(); - if (itemWidth + st.itemPadding.right() + check.width() > st.widthMin) { - st.widthMin = itemWidth + st.itemPadding.right() + check.width(); - } - const auto realWidth = std::clamp(itemWidth, st.widthMin, st.widthMax); - st.itemIconPosition = QPoint( - realWidth - st.itemPadding.right() - check.width(), - (itemHeight - check.height()) / 2); -} - void PlaybackControls::showMenu() { if (_menu) { _menu = nullptr; return; } - validateSpeedMenuStyle(); + _menu.emplace(this, _menuStyle); + + { + auto speedItem = base::make_unique_q( + _menu, + _menuStyle.menu, + _delegate->playbackControlsCurrentSpeed()); + speedItem->changeSpeedRequests( + ) | rpl::start_with_next([=](float64 speed) { + updatePlaybackSpeed(speed); + }, speedItem->lifetime()); + _menu->addAction(std::move(speedItem)); + } + + _menu->addSeparator(); - auto submenu = std::make_unique( - this, - _speedMenuStyle); - const auto addSpeed = [&](float64 speed, QString text = QString()) { - if (text.isEmpty()) { - text = QString::number(speed); - } - const auto checked = (speed == _delegate->playbackControlsCurrentSpeed()); - const auto action = submenu->addAction( - text, - [=] { updatePlaybackSpeed(speed); }, - checked ? &st::mediaviewMenuCheck : nullptr); - }; - addSpeed(0.5); - addSpeed(0.75); - addSpeed(1., tr::lng_mediaview_playback_speed_normal(tr::now)); - addSpeed(1.25); - addSpeed(1.5); - addSpeed(1.75); - addSpeed(2.); - _menu.emplace(this, st::mediaviewControlsPopupMenu); _menu->addAction(tr::lng_mediaview_rotate_video(tr::now), [=] { _delegate->playbackControlsRotate(); }); - _menu->addSeparator(); - _menu->addAction( - tr::lng_mediaview_playback_speed(tr::now), - std::move(submenu)); + _menu->setForcedOrigin(Ui::PanelAnimation::Origin::BottomLeft); _menu->popup(mapToGlobal(_menuToggle->geometry().topLeft())); } diff --git a/Telegram/SourceFiles/media/view/media_view_playback_controls.h b/Telegram/SourceFiles/media/view/media_view_playback_controls.h index 6e66175383..0b5b71c135 100644 --- a/Telegram/SourceFiles/media/view/media_view_playback_controls.h +++ b/Telegram/SourceFiles/media/view/media_view_playback_controls.h @@ -84,7 +84,6 @@ private: void updatePlayPauseResumeState(const Player::TrackState &state); void updateTimeTexts(const Player::TrackState &state); void refreshTimeTexts(); - void validateSpeedMenuStyle(); void showMenu(); not_null _delegate; @@ -112,7 +111,7 @@ private: object_ptr _toPlayLeft; object_ptr _downloadProgress = { nullptr }; - style::PopupMenu _speedMenuStyle; + const style::PopupMenu &_menuStyle; base::unique_qptr _menu; std::unique_ptr _fadeAnimation;