Reuse new speed change control for video.
Before Width: | Height: | Size: 281 B After Width: | Height: | Size: 349 B |
Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 585 B |
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 871 B |
Before Width: | Height: | Size: 359 B After Width: | Height: | Size: 387 B |
Before Width: | Height: | Size: 639 B After Width: | Height: | Size: 629 B |
Before Width: | Height: | Size: 919 B After Width: | Height: | Size: 733 B |
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 395 B |
Before Width: | Height: | Size: 594 B After Width: | Height: | Size: 643 B |
Before Width: | Height: | Size: 879 B After Width: | Height: | Size: 747 B |
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 325 B |
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 475 B |
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 310 B After Width: | Height: | Size: 377 B |
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 555 B |
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 530 B After Width: | Height: | Size: 750 B |
Before Width: | Height: | Size: 877 B After Width: | Height: | Size: 986 B |
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 388 B |
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 614 B |
Before Width: | Height: | Size: 646 B After Width: | Height: | Size: 976 B |
Before Width: | Height: | Size: 445 B After Width: | Height: | Size: 354 B |
Before Width: | Height: | Size: 664 B After Width: | Height: | Size: 584 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 868 B |
Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 517 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 973 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 370 B |
Before Width: | Height: | Size: 668 B After Width: | Height: | Size: 639 B |
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 858 B |
|
@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "base/platform/base_platform_info.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "media/media_common.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
|
@ -119,10 +119,6 @@ void LogPosition(const WindowPosition &position, const QString &name) {
|
|||
return position;
|
||||
}
|
||||
|
||||
float64 Settings::PlaybackSpeed::Default() {
|
||||
return Media::Audio::kSpedUpDefault;
|
||||
}
|
||||
|
||||
Settings::Settings()
|
||||
: _sendSubmitWay(Ui::InputSubmitSettings::Enter)
|
||||
, _floatPlayerColumn(Window::Column::Second)
|
||||
|
@ -814,17 +810,17 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
_closeToTaskbar = (closeToTaskbar == 1);
|
||||
_customDeviceModel = customDeviceModel;
|
||||
_accountsOrder = accountsOrder;
|
||||
const auto uncheckedPlayerRepeatMode = static_cast<Media::Player::RepeatMode>(playerRepeatMode);
|
||||
const auto uncheckedPlayerRepeatMode = static_cast<Media::RepeatMode>(playerRepeatMode);
|
||||
switch (uncheckedPlayerRepeatMode) {
|
||||
case Media::Player::RepeatMode::None:
|
||||
case Media::Player::RepeatMode::One:
|
||||
case Media::Player::RepeatMode::All: _playerRepeatMode = uncheckedPlayerRepeatMode; break;
|
||||
case Media::RepeatMode::None:
|
||||
case Media::RepeatMode::One:
|
||||
case Media::RepeatMode::All: _playerRepeatMode = uncheckedPlayerRepeatMode; break;
|
||||
}
|
||||
const auto uncheckedPlayerOrderMode = static_cast<Media::Player::OrderMode>(playerOrderMode);
|
||||
const auto uncheckedPlayerOrderMode = static_cast<Media::OrderMode>(playerOrderMode);
|
||||
switch (uncheckedPlayerOrderMode) {
|
||||
case Media::Player::OrderMode::Default:
|
||||
case Media::Player::OrderMode::Reverse:
|
||||
case Media::Player::OrderMode::Shuffle: _playerOrderMode = uncheckedPlayerOrderMode; break;
|
||||
case Media::OrderMode::Default:
|
||||
case Media::OrderMode::Reverse:
|
||||
case Media::OrderMode::Shuffle: _playerOrderMode = uncheckedPlayerOrderMode; break;
|
||||
}
|
||||
_macWarnBeforeQuit = (macWarnBeforeQuit == 1);
|
||||
_hardwareAcceleratedVideo = (hardwareAcceleratedVideo == 1);
|
||||
|
@ -1178,7 +1174,7 @@ float64 Settings::DefaultDialogsWidthRatio() {
|
|||
}
|
||||
|
||||
qint32 Settings::SerializePlaybackSpeed(PlaybackSpeed speed) {
|
||||
using namespace Media::Audio;
|
||||
using namespace Media;
|
||||
|
||||
const auto value = int(base::SafeRound(
|
||||
std::clamp(speed.value, kSpeedMin, kSpeedMax) * 100));
|
||||
|
@ -1186,7 +1182,7 @@ qint32 Settings::SerializePlaybackSpeed(PlaybackSpeed speed) {
|
|||
}
|
||||
|
||||
auto Settings::DeserializePlaybackSpeed(qint32 speed) -> PlaybackSpeed {
|
||||
using namespace Media::Audio;
|
||||
using namespace Media;
|
||||
|
||||
auto enabled = true;
|
||||
const auto validate = [&](float64 result) {
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "core/core_settings_proxy.h"
|
||||
#include "media/media_common.h"
|
||||
#include "window/themes/window_themes_embedded.h"
|
||||
#include "ui/chat/attach/attach_send_files_way.h"
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
|
@ -37,11 +38,6 @@ namespace Calls::Group {
|
|||
enum class StickedTooltip;
|
||||
} // namespace Calls::Group
|
||||
|
||||
namespace Media::Player {
|
||||
enum class RepeatMode;
|
||||
enum class OrderMode;
|
||||
} // namespace Media::Player
|
||||
|
||||
namespace Core {
|
||||
|
||||
struct WindowPosition {
|
||||
|
@ -718,28 +714,28 @@ public:
|
|||
[[nodiscard]] rpl::producer<QString> deviceModelChanges() const;
|
||||
[[nodiscard]] rpl::producer<QString> deviceModelValue() const;
|
||||
|
||||
void setPlayerRepeatMode(Media::Player::RepeatMode mode) {
|
||||
void setPlayerRepeatMode(Media::RepeatMode mode) {
|
||||
_playerRepeatMode = mode;
|
||||
}
|
||||
[[nodiscard]] Media::Player::RepeatMode playerRepeatMode() const {
|
||||
[[nodiscard]] Media::RepeatMode playerRepeatMode() const {
|
||||
return _playerRepeatMode.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<Media::Player::RepeatMode> playerRepeatModeValue() const {
|
||||
[[nodiscard]] rpl::producer<Media::RepeatMode> playerRepeatModeValue() const {
|
||||
return _playerRepeatMode.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<Media::Player::RepeatMode> playerRepeatModeChanges() const {
|
||||
[[nodiscard]] rpl::producer<Media::RepeatMode> playerRepeatModeChanges() const {
|
||||
return _playerRepeatMode.changes();
|
||||
}
|
||||
void setPlayerOrderMode(Media::Player::OrderMode mode) {
|
||||
void setPlayerOrderMode(Media::OrderMode mode) {
|
||||
_playerOrderMode = mode;
|
||||
}
|
||||
[[nodiscard]] Media::Player::OrderMode playerOrderMode() const {
|
||||
[[nodiscard]] Media::OrderMode playerOrderMode() const {
|
||||
return _playerOrderMode.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<Media::Player::OrderMode> playerOrderModeValue() const {
|
||||
[[nodiscard]] rpl::producer<Media::OrderMode> playerOrderModeValue() const {
|
||||
return _playerOrderMode.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<Media::Player::OrderMode> playerOrderModeChanges() const {
|
||||
[[nodiscard]] rpl::producer<Media::OrderMode> playerOrderModeChanges() const {
|
||||
return _playerOrderMode.changes();
|
||||
}
|
||||
[[nodiscard]] std::vector<uint64> accountsOrder() const {
|
||||
|
@ -811,9 +807,7 @@ public:
|
|||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
|
||||
struct PlaybackSpeed {
|
||||
[[nodiscard]] static float64 Default();
|
||||
|
||||
float64 value = Default();
|
||||
float64 value = Media::kSpedUpDefault;
|
||||
bool enabled = false;
|
||||
};
|
||||
[[nodiscard]] static qint32 SerializePlaybackSpeed(PlaybackSpeed speed);
|
||||
|
@ -909,8 +903,8 @@ private:
|
|||
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
|
||||
rpl::variable<bool> _closeToTaskbar = false;
|
||||
rpl::variable<QString> _customDeviceModel;
|
||||
rpl::variable<Media::Player::RepeatMode> _playerRepeatMode;
|
||||
rpl::variable<Media::Player::OrderMode> _playerOrderMode;
|
||||
rpl::variable<Media::RepeatMode> _playerRepeatMode;
|
||||
rpl::variable<Media::OrderMode> _playerOrderMode;
|
||||
bool _macWarnBeforeQuit = true;
|
||||
std::vector<uint64> _accountsOrder;
|
||||
#ifdef Q_OS_MAC
|
||||
|
|
|
@ -491,7 +491,7 @@ void Mixer::Track::updateWithSpeedPosition() {
|
|||
int64 Mixer::Track::SpeedIndependentPosition(
|
||||
int64 position,
|
||||
float64 speed) {
|
||||
Expects(speed <= Audio::kSpeedMax);
|
||||
Expects(speed <= kSpeedMax);
|
||||
|
||||
return int64(base::SafeRound(position * speed));
|
||||
}
|
||||
|
@ -499,7 +499,7 @@ int64 Mixer::Track::SpeedIndependentPosition(
|
|||
int64 Mixer::Track::SpeedDependentPosition(
|
||||
int64 position,
|
||||
float64 speed) {
|
||||
Expects(speed >= Audio::kSpeedMin);
|
||||
Expects(speed >= kSpeedMin);
|
||||
|
||||
return int64(base::SafeRound(position / speed));
|
||||
}
|
||||
|
|
|
@ -35,10 +35,6 @@ namespace Audio {
|
|||
|
||||
class Instance;
|
||||
|
||||
inline constexpr auto kSpeedMin = 0.5;
|
||||
inline constexpr auto kSpeedMax = 2.5;
|
||||
inline constexpr auto kSpedUpDefault = 1.7;
|
||||
|
||||
// Thread: Main.
|
||||
void Start(not_null<Instance*> instance);
|
||||
void Finish(not_null<Instance*> instance);
|
||||
|
|
|
@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "media/audio/media_audio_ffmpeg_loader.h"
|
||||
|
||||
#include "base/bytes.h"
|
||||
#include "core/file_location.h"
|
||||
#include "ffmpeg/ffmpeg_utility.h"
|
||||
#include "base/bytes.h"
|
||||
#include "media/media_common.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavfilter/buffersink.h>
|
||||
|
@ -540,7 +541,7 @@ bool AbstractAudioFFMpegLoader::ensureResampleSpaceAvailable(int samples) {
|
|||
}
|
||||
|
||||
bool AbstractAudioFFMpegLoader::changeSpeedFilter(float64 speed) {
|
||||
speed = std::clamp(speed, Audio::kSpeedMin, Audio::kSpeedMax);
|
||||
speed = std::clamp(speed, kSpeedMin, kSpeedMax);
|
||||
if (_filterSpeed == speed) {
|
||||
return false;
|
||||
}
|
||||
|
|
28
Telegram/SourceFiles/media/media_common.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Media {
|
||||
|
||||
enum class RepeatMode {
|
||||
None,
|
||||
One,
|
||||
All,
|
||||
};
|
||||
|
||||
enum class OrderMode {
|
||||
Default,
|
||||
Reverse,
|
||||
Shuffle,
|
||||
};
|
||||
|
||||
inline constexpr auto kSpeedMin = 0.5;
|
||||
inline constexpr auto kSpeedMax = 2.5;
|
||||
inline constexpr auto kSpedUpDefault = 1.7;
|
||||
|
||||
} // namespace Media
|
|
@ -27,31 +27,42 @@ MediaPlayerButton {
|
|||
duration: int;
|
||||
}
|
||||
|
||||
MediaSpeedButton {
|
||||
width: pixels;
|
||||
height: pixels;
|
||||
font: font;
|
||||
icon: icon;
|
||||
}
|
||||
|
||||
MediaSpeedMenu {
|
||||
menu: Menu;
|
||||
iconFg: color;
|
||||
iconFgActive: color;
|
||||
textFgActive: color;
|
||||
dropdown: DropdownMenu;
|
||||
activeCheck: icon;
|
||||
activeCheckSkip: pixels;
|
||||
sliderStyle: TextStyle;
|
||||
sliderPadding: margins;
|
||||
sliderWidth: pixels;
|
||||
slider: MediaSlider;
|
||||
|
||||
slow: icon;
|
||||
slowActive: icon;
|
||||
normal: icon;
|
||||
normalActive: icon;
|
||||
medium: icon;
|
||||
mediumActive: icon;
|
||||
fast: icon;
|
||||
fastActive: icon;
|
||||
veryFast: icon;
|
||||
veryFastActive: icon;
|
||||
superFast: icon;
|
||||
superFastActive: icon;
|
||||
}
|
||||
|
||||
mediaSpeedButton: MediaSpeedButton {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font: font(11px semibold);
|
||||
icon: icon{{ "player/player_speed", menuIconFg }};
|
||||
MediaSpeedButton {
|
||||
size: size;
|
||||
padding: margins;
|
||||
font: font;
|
||||
fg: color;
|
||||
overFg: color;
|
||||
activeFg: color;
|
||||
icon: icon;
|
||||
ripple: RippleAnimation;
|
||||
rippleActiveColor: color;
|
||||
rippleRadius: pixels;
|
||||
menu: MediaSpeedMenu;
|
||||
menuAlign: align;
|
||||
}
|
||||
|
||||
mediaPlayerButton: MediaPlayerButton {
|
||||
|
@ -142,13 +153,6 @@ mediaPlayerCancelIcon: icon{
|
|||
{ "player/panel_close", mediaPlayerActiveFg }
|
||||
};
|
||||
|
||||
mediaPlayerSpeedSize: size(30px, 30px);
|
||||
mediaPlayerSpeedRadius: 4px;
|
||||
mediaPlayerSpeedRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: lightButtonBgOver;
|
||||
}
|
||||
mediaPlayerSpeedDisabledRippleBg: windowBgOver;
|
||||
|
||||
mediaPlayerMenu: DropdownMenu(defaultDropdownMenu) {
|
||||
wrap: InnerDropdown(defaultInnerDropdown) {
|
||||
scrollPadding: margins(0px, 4px, 0px, 4px);
|
||||
|
@ -157,18 +161,17 @@ mediaPlayerMenu: DropdownMenu(defaultDropdownMenu) {
|
|||
}
|
||||
mediaPlayerMenuCheck: icon {{ "player/player_check", mediaPlayerActiveFg }};
|
||||
|
||||
mediaSpeedMenu: MediaSpeedMenu {
|
||||
menu: Menu(menuWithIcons) {
|
||||
separator: MenuSeparator(defaultMenuSeparator) {
|
||||
padding: margins(0px, 4px, 0px, 4px);
|
||||
width: 6px;
|
||||
mediaPlayerSpeedMenu: MediaSpeedMenu {
|
||||
dropdown: DropdownMenu(mediaPlayerMenu) {
|
||||
menu: Menu(menuWithIcons) {
|
||||
separator: MenuSeparator(defaultMenuSeparator) {
|
||||
padding: margins(0px, 4px, 0px, 4px);
|
||||
width: 6px;
|
||||
}
|
||||
itemPadding: margins(54px, 7px, 54px, 9px);
|
||||
itemFgDisabled: mediaPlayerActiveFg;
|
||||
}
|
||||
itemPadding: margins(54px, 7px, 54px, 9px);
|
||||
itemFgDisabled: mediaPlayerActiveFg;
|
||||
}
|
||||
iconFg: menuIconColor;
|
||||
iconFgActive: mediaPlayerActiveFg;
|
||||
textFgActive: mediaPlayerActiveFg;
|
||||
activeCheck: mediaPlayerMenuCheck;
|
||||
activeCheckSkip: 8px;
|
||||
sliderStyle: TextStyle(defaultTextStyle) {
|
||||
|
@ -186,19 +189,37 @@ mediaSpeedMenu: MediaSpeedMenu {
|
|||
width: 6px;
|
||||
seekSize: size(6px, 6px);
|
||||
}
|
||||
|
||||
slow: playerSpeedSlow;
|
||||
slowActive: playerSpeedSlowActive;
|
||||
normal: playerSpeedNormal;
|
||||
normalActive: playerSpeedNormalActive;
|
||||
medium: playerSpeedMedium;
|
||||
mediumActive: playerSpeedMediumActive;
|
||||
fast: playerSpeedFast;
|
||||
fastActive: playerSpeedFastActive;
|
||||
veryFast: playerSpeedVeryFast;
|
||||
veryFastActive: playerSpeedVeryFastActive;
|
||||
superFast: playerSpeedSuperFast;
|
||||
superFastActive: playerSpeedSuperFastActive;
|
||||
}
|
||||
|
||||
mediaPlayerSpeedButton: MediaSpeedButton {
|
||||
size: size(30px, 30px);
|
||||
padding: margins(0px, 6px, 0px, 0px);
|
||||
font: font(11px bold);
|
||||
fg: menuIconFg;
|
||||
overFg: menuIconFgOver;
|
||||
activeFg: mediaPlayerActiveFg;
|
||||
icon: icon{{ "player/player_speed", menuIconFg }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
rippleActiveColor: lightButtonBgOver;
|
||||
rippleRadius: 4px;
|
||||
menu: mediaPlayerSpeedMenu;
|
||||
menuAlign: align(topright);
|
||||
}
|
||||
mediaSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }};
|
||||
mediaSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }};
|
||||
mediaSpeedNormal: icon {{ "player/speed/audiospeed_menu_1.0", menuIconColor }};
|
||||
mediaSpeedNormalActive: icon {{ "player/speed/audiospeed_menu_1.0", mediaPlayerActiveFg }};
|
||||
mediaSpeedMedium: icon {{ "player/speed/audiospeed_menu_1.2", menuIconColor }};
|
||||
mediaSpeedMediumActive: icon {{ "player/speed/audiospeed_menu_1.2", mediaPlayerActiveFg }};
|
||||
mediaSpeedFast: icon {{ "player/speed/audiospeed_menu_1.5", menuIconColor }};
|
||||
mediaSpeedFastActive: icon {{ "player/speed/audiospeed_menu_1.5", mediaPlayerActiveFg }};
|
||||
mediaSpeedVeryFast: icon {{ "player/speed/audiospeed_menu_1.7", menuIconColor }};
|
||||
mediaSpeedVeryFastActive: icon {{ "player/speed/audiospeed_menu_1.7", mediaPlayerActiveFg }};
|
||||
mediaSpeedSuperFast: icon {{ "player/speed/audiospeed_menu_2.0", menuIconColor }};
|
||||
mediaSpeedSuperFastActive: icon {{ "player/speed/audiospeed_menu_2.0", mediaPlayerActiveFg }};
|
||||
|
||||
mediaPlayerVolumeIcon0: icon {
|
||||
{ "player/player_mini_off", mediaPlayerActiveFg },
|
||||
|
@ -292,7 +313,7 @@ mediaPlayerFileLayout: OverviewFileLayout(overviewFileLayout) {
|
|||
mediaPlayerFloatSize: 128px;
|
||||
mediaPlayerFloatMargin: 12px;
|
||||
|
||||
mediaPlayerMenuPosition: point(-2px, -2px);
|
||||
mediaPlayerMenuPosition: point(-2px, -1px);
|
||||
mediaPlayerOrderMenu: Menu(defaultMenu) {
|
||||
itemIconPosition: point(13px, 8px);
|
||||
itemPadding: margins(49px, 9px, 17px, 11px);
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "media/player/media_player_button.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_media_player.h"
|
||||
|
||||
|
@ -263,82 +264,73 @@ SpeedButtonLayout::SpeedButtonLayout(
|
|||
float64 speed)
|
||||
: _st(st)
|
||||
, _speed(speed)
|
||||
, _oldSpeed(speed)
|
||||
, _nextSpeed(speed)
|
||||
, _metrics(_st.font->f)
|
||||
, _text(SpeedText(speed))
|
||||
, _textWidth(_metrics.horizontalAdvance(_text))
|
||||
, _oldText(_text)
|
||||
, _oldTextWidth(_textWidth)
|
||||
, _callback(std::move(callback)) {
|
||||
}
|
||||
|
||||
void SpeedButtonLayout::setSpeed(float64 speed) {
|
||||
speed = base::SafeRound(speed * 10.) / 10.;
|
||||
if (_nextSpeed == speed) {
|
||||
return;
|
||||
}
|
||||
|
||||
_nextSpeed = speed;
|
||||
if (!_transformProgress.animating()) {
|
||||
_oldSpeed = _speed;
|
||||
_oldColor = _lastPaintColor;
|
||||
_oldText = _text;
|
||||
_oldTextWidth = _textWidth;
|
||||
_speed = _nextSpeed;
|
||||
if (_speed != speed) {
|
||||
_speed = speed;
|
||||
_text = SpeedText(_speed);
|
||||
_textWidth = _metrics.horizontalAdvance(_text);
|
||||
_transformBackward = false;
|
||||
if (_speed != _speed) {
|
||||
startTransform(0., 1.);
|
||||
if (_callback) _callback();
|
||||
}
|
||||
} else if (_oldSpeed == _nextSpeed) {
|
||||
std::swap(_oldSpeed, _speed);
|
||||
std::swap(_oldColor, _lastPaintColor);
|
||||
std::swap(_oldText, _text);
|
||||
std::swap(_oldTextWidth, _textWidth);
|
||||
startTransform(
|
||||
_transformBackward ? 0. : 1.,
|
||||
_transformBackward ? 1. : 0.);
|
||||
_transformBackward = !_transformBackward;
|
||||
if (_callback) _callback();
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedButtonLayout::finishTransform() {
|
||||
_transformProgress.stop();
|
||||
_transformBackward = false;
|
||||
if (_callback) _callback();
|
||||
}
|
||||
|
||||
void SpeedButtonLayout::paint(QPainter &p, const QColor &color) {
|
||||
_lastPaintColor = color;
|
||||
|
||||
_st.icon.paint(p, 0, 0, _st.width, color);
|
||||
void SpeedButtonLayout::paint(QPainter &p, bool over, bool active) {
|
||||
const auto &color = active ? _st.activeFg : over ? _st.overFg : _st.fg;
|
||||
const auto inner = QRect(QPoint(), _st.size).marginsRemoved(_st.padding);
|
||||
_st.icon.paintInCenter(p, inner, color->c);
|
||||
|
||||
p.setPen(color);
|
||||
p.setFont(_st.font);
|
||||
|
||||
p.drawText(
|
||||
QPointF(
|
||||
(_st.width - _textWidth) / 2.,
|
||||
(_st.height - _metrics.height()) / 2. + _metrics.ascent()),
|
||||
QPointF(inner.topLeft()) + QPointF(
|
||||
(inner.width() - _textWidth) / 2.,
|
||||
(inner.height() - _metrics.height()) / 2. + _metrics.ascent()),
|
||||
_text);
|
||||
}
|
||||
|
||||
void SpeedButtonLayout::animationCallback() {
|
||||
if (!_transformProgress.animating()) {
|
||||
const auto finalSpeed = _nextSpeed;
|
||||
_nextSpeed = _speed;
|
||||
setSpeed(finalSpeed);
|
||||
}
|
||||
_callback();
|
||||
SpeedButton::SpeedButton(QWidget *parent, const style::MediaSpeedButton &st)
|
||||
: RippleButton(parent, st.ripple)
|
||||
, _st(st)
|
||||
, _layout(st, [=] { update(); }, 2.)
|
||||
, _isDefault(true) {
|
||||
resize(_st.size);
|
||||
}
|
||||
|
||||
void SpeedButtonLayout::startTransform(float64 from, float64 to) {
|
||||
// No animation for now.
|
||||
_transformProgress.stop();
|
||||
animationCallback();
|
||||
void SpeedButton::setSpeed(float64 speed, anim::type animated) {
|
||||
_isDefault = (speed == 1.);
|
||||
_layout.setSpeed(speed);
|
||||
update();
|
||||
}
|
||||
|
||||
void SpeedButton::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
paintRipple(
|
||||
p,
|
||||
QPoint(_st.padding.left(), _st.padding.top()),
|
||||
_isDefault ? nullptr : &_st.rippleActiveColor->c);
|
||||
_layout.paint(p, isOver(), !_isDefault);
|
||||
}
|
||||
|
||||
QPoint SpeedButton::prepareRippleStartPosition() const {
|
||||
const auto inner = rect().marginsRemoved(_st.padding);
|
||||
const auto result = mapFromGlobal(QCursor::pos()) - inner.topLeft();
|
||||
return inner.contains(result)
|
||||
? result
|
||||
: DisabledRippleStartPosition();
|
||||
}
|
||||
|
||||
QImage SpeedButton::prepareRippleMask() const {
|
||||
return Ui::RippleAnimation::RoundRectMask(
|
||||
rect().marginsRemoved(_st.padding).size(),
|
||||
_st.rippleRadius);
|
||||
}
|
||||
|
||||
} // namespace Media::Player
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
|
||||
#include <QtGui/QFontMetrics>
|
||||
|
||||
|
@ -60,32 +61,42 @@ public:
|
|||
float64 speed);
|
||||
|
||||
void setSpeed(float64 speed);
|
||||
void finishTransform();
|
||||
void paint(QPainter &p, const QColor &color);
|
||||
void paint(QPainter &p, bool over, bool active);
|
||||
|
||||
private:
|
||||
void animationCallback();
|
||||
void startTransform(float64 from, float64 to);
|
||||
|
||||
const style::MediaSpeedButton &_st;
|
||||
|
||||
float64 _speed = 1.;
|
||||
float64 _oldSpeed = 1.;
|
||||
float64 _nextSpeed = 1.;
|
||||
std::optional<QColor> _lastPaintColor;
|
||||
std::optional<QColor> _oldColor;
|
||||
Ui::Animations::Simple _transformProgress;
|
||||
bool _transformBackward = false;
|
||||
|
||||
QFontMetricsF _metrics;
|
||||
|
||||
QString _text;
|
||||
float64 _textWidth = 0;
|
||||
QString _oldText;
|
||||
float64 _oldTextWidth = 0;
|
||||
|
||||
Fn<void()> _callback;
|
||||
|
||||
};
|
||||
|
||||
class SpeedButton final : public Ui::RippleButton {
|
||||
public:
|
||||
SpeedButton(QWidget *parent, const style::MediaSpeedButton &st);
|
||||
|
||||
[[nodiscard]] const style::MediaSpeedButton &st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
void setSpeed(float64 speed, anim::type animated = anim::type::normal);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
QImage prepareRippleMask() const override;
|
||||
|
||||
const style::MediaSpeedButton &_st;
|
||||
SpeedButtonLayout _layout;
|
||||
bool _isDefault = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::Player
|
||||
|
|
|
@ -7,24 +7,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "media/player/media_player_dropdown.h"
|
||||
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/timer.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "media/player/media_player_button.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/widgets/menu/menu.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_media_player.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include "base/debug_log.h"
|
||||
|
||||
namespace Media::Player {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSpeedMin = 0.5;
|
||||
constexpr auto kSpeedMax = 2.5;
|
||||
constexpr auto kSpeedDebounceTimeout = crl::time(1000);
|
||||
|
||||
[[nodiscard]] float64 SpeedToSliderValue(float64 speed) {
|
||||
|
@ -87,12 +86,12 @@ SpeedSliderItem::SpeedSliderItem(
|
|||
not_null<RpWidget*> parent,
|
||||
const style::MediaSpeedMenu &st,
|
||||
rpl::producer<float64> value)
|
||||
: Ui::Menu::ItemBase(parent, st.menu)
|
||||
: Ui::Menu::ItemBase(parent, st.dropdown.menu)
|
||||
, _slider(base::make_unique_q<Ui::MediaSlider>(this, st.slider))
|
||||
, _dummyAction(new QAction(parent))
|
||||
, _st(st)
|
||||
, _height(st.sliderPadding.top()
|
||||
+ st.menu.itemStyle.font->height
|
||||
+ st.dropdown.menu.itemStyle.font->height
|
||||
+ st.sliderPadding.bottom())
|
||||
, _debounceTimer([=] { _debounced.fire(current()); }) {
|
||||
initResizeHook(parent->sizeValue());
|
||||
|
@ -120,11 +119,11 @@ SpeedSliderItem::SpeedSliderItem(
|
|||
) | rpl::start_with_next([=](const QRect &clip) {
|
||||
auto p = Painter(this);
|
||||
|
||||
p.fillRect(clip, _st.menu.itemBg);
|
||||
p.fillRect(clip, _st.dropdown.menu.itemBg);
|
||||
|
||||
const auto left = (_st.sliderPadding.left() - _text.maxWidth()) / 2;
|
||||
const auto top = _st.menu.itemPadding.top();
|
||||
p.setPen(_st.menu.itemFg);
|
||||
const auto top = _st.dropdown.menu.itemPadding.top();
|
||||
p.setPen(_st.dropdown.menu.itemFg);
|
||||
_text.drawLeftElided(p, left, top, _text.maxWidth(), width());
|
||||
}, lifetime());
|
||||
|
||||
|
@ -172,6 +171,111 @@ SpeedSliderItem::SpeedSliderItem(
|
|||
});
|
||||
}
|
||||
|
||||
void FillSpeedMenu(
|
||||
not_null<Ui::Menu::Menu*> menu,
|
||||
const style::MediaSpeedMenu &st,
|
||||
rpl::producer<float64> value,
|
||||
Fn<void(float64)> callback) {
|
||||
auto slider = base::make_unique_q<SpeedSliderItem>(
|
||||
menu,
|
||||
st,
|
||||
rpl::duplicate(value));
|
||||
|
||||
slider->debouncedChanges(
|
||||
) | rpl::start_with_next(callback, slider->lifetime());
|
||||
|
||||
struct State {
|
||||
rpl::variable<float64> realtime;
|
||||
};
|
||||
const auto state = slider->lifetime().make_state<State>();
|
||||
state->realtime = rpl::single(
|
||||
slider->current()
|
||||
) | rpl::then(rpl::merge(
|
||||
slider->changing(),
|
||||
slider->changed()
|
||||
));
|
||||
|
||||
menu->addAction(std::move(slider));
|
||||
menu->addSeparator(&st.dropdown.menu.separator);
|
||||
|
||||
struct SpeedPoint {
|
||||
float64 speed = 0.;
|
||||
tr::phrase<> text;
|
||||
const style::icon &icon;
|
||||
const style::icon &iconActive;
|
||||
};
|
||||
const auto points = std::vector<SpeedPoint>{
|
||||
{
|
||||
0.5,
|
||||
tr::lng_voice_speed_slow,
|
||||
st.slow,
|
||||
st.slowActive },
|
||||
{
|
||||
1.0,
|
||||
tr::lng_voice_speed_normal,
|
||||
st.normal,
|
||||
st.normalActive },
|
||||
{
|
||||
1.2,
|
||||
tr::lng_voice_speed_medium,
|
||||
st.medium,
|
||||
st.mediumActive },
|
||||
{
|
||||
1.5,
|
||||
tr::lng_voice_speed_fast,
|
||||
st.fast,
|
||||
st.fastActive },
|
||||
{
|
||||
1.7,
|
||||
tr::lng_voice_speed_very_fast,
|
||||
st.veryFast,
|
||||
st.veryFastActive },
|
||||
{
|
||||
2.0,
|
||||
tr::lng_voice_speed_super_fast,
|
||||
st.superFast,
|
||||
st.superFastActive },
|
||||
};
|
||||
for (const auto &point : points) {
|
||||
const auto speed = point.speed;
|
||||
const auto text = point.text(tr::now);
|
||||
const auto icon = &point.icon;
|
||||
const auto iconActive = &point.iconActive;
|
||||
auto action = base::make_unique_q<Ui::Menu::Action>(
|
||||
menu,
|
||||
st.dropdown.menu,
|
||||
Ui::Menu::CreateAction(menu, text, [=] { callback(speed); }),
|
||||
&point.icon,
|
||||
&point.icon);
|
||||
const auto raw = action.get();
|
||||
const auto check = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
const auto skip = st.activeCheckSkip;
|
||||
check->resize(st.activeCheck.size());
|
||||
check->paintRequest(
|
||||
) | rpl::start_with_next([check, icon = &st.activeCheck] {
|
||||
auto p = QPainter(check);
|
||||
icon->paint(p, 0, 0, check->width());
|
||||
}, check->lifetime());
|
||||
raw->sizeValue(
|
||||
) | rpl::start_with_next([=, skip = st.activeCheckSkip](QSize size) {
|
||||
check->moveToRight(
|
||||
skip,
|
||||
(size.height() - check->height()) / 2,
|
||||
size.width());
|
||||
}, check->lifetime());
|
||||
check->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
state->realtime.value(
|
||||
) | rpl::start_with_next([=](float64 now) {
|
||||
const auto chosen = (speed == now);
|
||||
const auto overriden = chosen ? iconActive : icon;
|
||||
raw->setIcon(overriden, overriden);
|
||||
raw->action()->setEnabled(!chosen);
|
||||
check->setVisible(chosen);
|
||||
}, raw->lifetime());
|
||||
menu->addAction(std::move(action));
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedSliderItem::setExternalValue(float64 speed) {
|
||||
if (!_slider->isChanging()) {
|
||||
setSliderValue(speed);
|
||||
|
@ -379,109 +483,284 @@ bool Dropdown::eventFilter(QObject *obj, QEvent *e) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void FillSpeedMenu(
|
||||
not_null<Ui::Menu::Menu*> menu,
|
||||
const style::MediaSpeedMenu &st,
|
||||
rpl::producer<float64> value,
|
||||
Fn<void(float64)> callback) {
|
||||
auto slider = base::make_unique_q<SpeedSliderItem>(
|
||||
menu,
|
||||
st,
|
||||
rpl::duplicate(value));
|
||||
WithDropdownController::WithDropdownController(
|
||||
not_null<Ui::AbstractButton*> button,
|
||||
not_null<QWidget*> menuParent,
|
||||
const style::DropdownMenu &menuSt,
|
||||
Qt::Alignment menuAlign,
|
||||
Fn<void(bool)> menuOverCallback)
|
||||
: _button(button)
|
||||
, _menuParent(menuParent)
|
||||
, _menuSt(menuSt)
|
||||
, _menuAlign(menuAlign)
|
||||
, _menuOverCallback(std::move(menuOverCallback)) {
|
||||
button->events(
|
||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||
return (e->type() == QEvent::Enter)
|
||||
|| (e->type() == QEvent::Leave);
|
||||
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
_overButton = (e->type() == QEvent::Enter);
|
||||
if (_overButton) {
|
||||
InvokeQueued(button, [=] {
|
||||
if (_overButton) {
|
||||
showMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
slider->debouncedChanges(
|
||||
) | rpl::start_with_next(callback, slider->lifetime());
|
||||
not_null<Ui::AbstractButton*> WithDropdownController::button() const {
|
||||
return _button;
|
||||
}
|
||||
|
||||
struct State {
|
||||
rpl::variable<float64> realtime;
|
||||
};
|
||||
const auto state = slider->lifetime().make_state<State>();
|
||||
state->realtime = rpl::single(
|
||||
slider->current()
|
||||
) | rpl::then(rpl::merge(
|
||||
slider->changing(),
|
||||
slider->changed()
|
||||
));
|
||||
Ui::DropdownMenu *WithDropdownController::menu() const {
|
||||
return _menu.get();
|
||||
}
|
||||
|
||||
menu->addAction(std::move(slider));
|
||||
menu->addSeparator(&st.menu.separator);
|
||||
void WithDropdownController::updateDropdownGeometry() {
|
||||
if (!_menu) {
|
||||
return;
|
||||
}
|
||||
const auto bwidth = _button->width();
|
||||
const auto bheight = _button->height();
|
||||
const auto mwidth = _menu->width();
|
||||
const auto mheight = _menu->height();
|
||||
const auto padding = _menuSt.wrap.padding;
|
||||
const auto x = st::mediaPlayerMenuPosition.x();
|
||||
const auto y = st::mediaPlayerMenuPosition.y();
|
||||
const auto position = _menu->parentWidget()->mapFromGlobal(
|
||||
_button->mapToGlobal(QPoint())
|
||||
) + [&] {
|
||||
switch (_menuAlign) {
|
||||
case style::al_topleft: return QPoint(
|
||||
-padding.left() - x,
|
||||
bheight - padding.top() + y);
|
||||
case style::al_topright: return QPoint(
|
||||
bwidth - mwidth + padding.right() + x,
|
||||
bheight - padding.top() + y);
|
||||
case style::al_bottomright: return QPoint(
|
||||
bwidth - mwidth + padding.right() + x,
|
||||
-mheight + padding.bottom() - y);
|
||||
case style::al_bottomleft: return QPoint(
|
||||
-padding.left() - x,
|
||||
-mheight + padding.bottom() - y);
|
||||
}
|
||||
Unexpected("Menu align value.");
|
||||
}();
|
||||
_menu->move(position);
|
||||
}
|
||||
|
||||
struct SpeedPoint {
|
||||
float64 speed = 0.;
|
||||
tr::phrase<> text;
|
||||
const style::icon &icon;
|
||||
const style::icon &iconActive;
|
||||
};
|
||||
const auto points = std::vector<SpeedPoint>{
|
||||
{
|
||||
0.5,
|
||||
tr::lng_voice_speed_slow,
|
||||
st::mediaSpeedSlow,
|
||||
st::mediaSpeedSlowActive },
|
||||
{
|
||||
1.0,
|
||||
tr::lng_voice_speed_normal,
|
||||
st::mediaSpeedNormal,
|
||||
st::mediaSpeedNormalActive },
|
||||
{
|
||||
1.2,
|
||||
tr::lng_voice_speed_medium,
|
||||
st::mediaSpeedMedium,
|
||||
st::mediaSpeedMediumActive },
|
||||
{
|
||||
1.5,
|
||||
tr::lng_voice_speed_fast,
|
||||
st::mediaSpeedFast,
|
||||
st::mediaSpeedFastActive },
|
||||
{
|
||||
1.7,
|
||||
tr::lng_voice_speed_very_fast,
|
||||
st::mediaSpeedVeryFast,
|
||||
st::mediaSpeedVeryFastActive },
|
||||
{
|
||||
2.0,
|
||||
tr::lng_voice_speed_super_fast,
|
||||
st::mediaSpeedSuperFast,
|
||||
st::mediaSpeedSuperFastActive },
|
||||
};
|
||||
for (const auto &point : points) {
|
||||
const auto speed = point.speed;
|
||||
const auto text = point.text(tr::now);
|
||||
const auto icon = &point.icon;
|
||||
const auto iconActive = &point.iconActive;
|
||||
auto action = base::make_unique_q<Ui::Menu::Action>(
|
||||
menu,
|
||||
st::mediaSpeedMenu.menu,
|
||||
Ui::Menu::CreateAction(menu, text, [=] { callback(speed); }),
|
||||
&point.icon,
|
||||
&point.icon);
|
||||
const auto raw = action.get();
|
||||
const auto check = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
const auto skip = st.activeCheckSkip;
|
||||
check->resize(st.activeCheck.size());
|
||||
check->paintRequest(
|
||||
) | rpl::start_with_next([check, icon = &st.activeCheck] {
|
||||
auto p = QPainter(check);
|
||||
icon->paint(p, 0, 0, check->width());
|
||||
}, check->lifetime());
|
||||
raw->sizeValue(
|
||||
) | rpl::start_with_next([=, skip = st.activeCheckSkip](QSize size) {
|
||||
check->moveToRight(
|
||||
skip,
|
||||
(size.height() - check->height()) / 2,
|
||||
size.width());
|
||||
}, check->lifetime());
|
||||
check->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
state->realtime.value(
|
||||
) | rpl::start_with_next([=](float64 now) {
|
||||
const auto chosen = (speed == now);
|
||||
const auto overriden = chosen ? iconActive : icon;
|
||||
raw->setIcon(overriden, overriden);
|
||||
raw->action()->setEnabled(!chosen);
|
||||
check->setVisible(chosen);
|
||||
}, raw->lifetime());
|
||||
menu->addAction(std::move(action));
|
||||
void WithDropdownController::hideTemporarily() {
|
||||
if (_menu && !_menu->isHidden()) {
|
||||
_temporarilyHidden = true;
|
||||
_menu->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void WithDropdownController::showBack() {
|
||||
if (_temporarilyHidden) {
|
||||
_temporarilyHidden = false;
|
||||
if (_menu && _menu->isHidden()) {
|
||||
_menu->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WithDropdownController::showMenu() {
|
||||
if (_menu) {
|
||||
return;
|
||||
}
|
||||
_menu.emplace(_menuParent, _menuSt);
|
||||
const auto raw = _menu.get();
|
||||
_menu->events(
|
||||
) | rpl::start_with_next([this](not_null<QEvent*> e) {
|
||||
const auto type = e->type();
|
||||
if (type == QEvent::Enter) {
|
||||
_menuOverCallback(true);
|
||||
} else if (type == QEvent::Leave) {
|
||||
_menuOverCallback(false);
|
||||
}
|
||||
}, _menu->lifetime());
|
||||
_menu->setHiddenCallback([=]{
|
||||
Ui::PostponeCall(raw, [this] {
|
||||
_menu = nullptr;
|
||||
});
|
||||
});
|
||||
_button->installEventFilter(raw);
|
||||
fillMenu(raw);
|
||||
updateDropdownGeometry();
|
||||
const auto origin = [&] {
|
||||
using Origin = Ui::PanelAnimation::Origin;
|
||||
switch (_menuAlign) {
|
||||
case style::al_topleft: return Origin::TopLeft;
|
||||
case style::al_topright: return Origin::TopRight;
|
||||
case style::al_bottomright: return Origin::BottomRight;
|
||||
case style::al_bottomleft: return Origin::BottomLeft;
|
||||
}
|
||||
Unexpected("Menu align value.");
|
||||
}();
|
||||
_menu->showAnimated(origin);
|
||||
}
|
||||
|
||||
OrderController::OrderController(
|
||||
not_null<Ui::IconButton*> button,
|
||||
not_null<QWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback,
|
||||
rpl::producer<OrderMode> value,
|
||||
Fn<void(OrderMode)> change)
|
||||
: WithDropdownController(
|
||||
button,
|
||||
menuParent,
|
||||
st::mediaPlayerMenu,
|
||||
style::al_topright,
|
||||
std::move(menuOverCallback))
|
||||
, _button(button)
|
||||
, _appOrder(std::move(value))
|
||||
, _change(std::move(change)) {
|
||||
button->setClickedCallback([=] {
|
||||
showMenu();
|
||||
});
|
||||
|
||||
_appOrder.value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateIcon();
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
void OrderController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
|
||||
const auto addOrderAction = [&](OrderMode mode) {
|
||||
struct Fields {
|
||||
QString label;
|
||||
const style::icon &icon;
|
||||
const style::icon &activeIcon;
|
||||
};
|
||||
const auto active = (_appOrder.current() == mode);
|
||||
const auto callback = [change = _change, mode, active] {
|
||||
change(active ? OrderMode::Default : mode);
|
||||
};
|
||||
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.");
|
||||
}();
|
||||
menu->addAction(base::make_unique_q<Ui::Menu::Action>(
|
||||
menu,
|
||||
(active
|
||||
? st::mediaPlayerOrderMenuActive
|
||||
: st::mediaPlayerOrderMenu),
|
||||
Ui::Menu::CreateAction(menu, fields.label, callback),
|
||||
&(active ? fields.activeIcon : fields.icon),
|
||||
&(active ? fields.activeIcon : fields.icon)));
|
||||
};
|
||||
addOrderAction(OrderMode::Reverse);
|
||||
addOrderAction(OrderMode::Shuffle);
|
||||
}
|
||||
|
||||
void OrderController::updateIcon() {
|
||||
switch (_appOrder.current()) {
|
||||
case OrderMode::Default:
|
||||
_button->setIconOverride(
|
||||
&st::mediaPlayerReverseDisabledIcon,
|
||||
&st::mediaPlayerReverseDisabledIconOver);
|
||||
_button->setRippleColorOverride(
|
||||
&st::mediaPlayerRepeatDisabledRippleBg);
|
||||
break;
|
||||
case OrderMode::Reverse:
|
||||
_button->setIconOverride(&st::mediaPlayerReverseIcon);
|
||||
_button->setRippleColorOverride(nullptr);
|
||||
break;
|
||||
case OrderMode::Shuffle:
|
||||
_button->setIconOverride(&st::mediaPlayerShuffleIcon);
|
||||
_button->setRippleColorOverride(nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SpeedController::SpeedController(
|
||||
not_null<SpeedButton*> button,
|
||||
not_null<QWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback,
|
||||
Fn<float64(bool lastNonDefault)> value,
|
||||
Fn<void(float64)> change)
|
||||
: WithDropdownController(
|
||||
button,
|
||||
menuParent,
|
||||
button->st().menu.dropdown,
|
||||
button->st().menuAlign,
|
||||
std::move(menuOverCallback))
|
||||
, _st(button->st())
|
||||
, _lookup(std::move(value))
|
||||
, _change(std::move(change)) {
|
||||
button->setClickedCallback([=] {
|
||||
toggleDefault();
|
||||
save();
|
||||
if (const auto current = menu()) {
|
||||
current->otherEnter();
|
||||
}
|
||||
});
|
||||
|
||||
setSpeed(_lookup(false));
|
||||
_speed = _lookup(true);
|
||||
|
||||
button->setSpeed(_speed, anim::type::instant);
|
||||
|
||||
_speedChanged.events_starting_with(
|
||||
speed()
|
||||
) | rpl::start_with_next([=](float64 speed) {
|
||||
button->setSpeed(speed);
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
rpl::producer<> SpeedController::saved() const {
|
||||
return _saved.events();
|
||||
}
|
||||
|
||||
float64 SpeedController::speed() const {
|
||||
return _isDefault ? 1. : _speed;
|
||||
}
|
||||
|
||||
bool SpeedController::isDefault() const {
|
||||
return _isDefault;
|
||||
}
|
||||
|
||||
float64 SpeedController::lastNonDefaultSpeed() const {
|
||||
return _speed;
|
||||
}
|
||||
|
||||
void SpeedController::toggleDefault() {
|
||||
_isDefault = !_isDefault;
|
||||
_speedChanged.fire(speed());
|
||||
}
|
||||
|
||||
void SpeedController::setSpeed(float64 newSpeed) {
|
||||
if (!(_isDefault = (newSpeed == 1.))) {
|
||||
_speed = newSpeed;
|
||||
}
|
||||
_speedChanged.fire(speed());
|
||||
}
|
||||
|
||||
void SpeedController::save() {
|
||||
_change(speed());
|
||||
_saved.fire({});
|
||||
}
|
||||
|
||||
void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
|
||||
FillSpeedMenu(
|
||||
menu->menu(),
|
||||
_st.menu,
|
||||
_speedChanged.events_starting_with(speed()),
|
||||
[=](float64 speed) { setSpeed(speed); save(); });
|
||||
}
|
||||
|
||||
} // namespace Media::Player
|
||||
|
|
|
@ -7,20 +7,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "media/media_common.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace style {
|
||||
struct MediaSpeedMenu;
|
||||
struct MediaSpeedButton;
|
||||
struct DropdownMenu;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
class DropdownMenu;
|
||||
class AbstractButton;
|
||||
class IconButton;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Menu {
|
||||
class Menu;
|
||||
} // namespace Ui::Menu
|
||||
|
||||
namespace Media::Player {
|
||||
|
||||
class SpeedButton;
|
||||
|
||||
class Dropdown final : public Ui::RpWidget {
|
||||
public:
|
||||
explicit Dropdown(QWidget *parent);
|
||||
|
@ -57,10 +68,89 @@ private:
|
|||
|
||||
};
|
||||
|
||||
void FillSpeedMenu(
|
||||
not_null<Ui::Menu::Menu*> menu,
|
||||
const style::MediaSpeedMenu &st,
|
||||
rpl::producer<float64> value,
|
||||
Fn<void(float64)> callback);
|
||||
class WithDropdownController {
|
||||
public:
|
||||
WithDropdownController(
|
||||
not_null<Ui::AbstractButton*> button,
|
||||
not_null<QWidget*> menuParent,
|
||||
const style::DropdownMenu &menuSt,
|
||||
Qt::Alignment menuAlign,
|
||||
Fn<void(bool)> menuOverCallback);
|
||||
virtual ~WithDropdownController() = default;
|
||||
|
||||
[[nodiscard]] not_null<Ui::AbstractButton*> button() const;
|
||||
Ui::DropdownMenu *menu() const;
|
||||
|
||||
void updateDropdownGeometry();
|
||||
|
||||
void hideTemporarily();
|
||||
void showBack();
|
||||
|
||||
protected:
|
||||
void showMenu();
|
||||
|
||||
private:
|
||||
virtual void fillMenu(not_null<Ui::DropdownMenu*> menu) = 0;
|
||||
|
||||
const not_null<Ui::AbstractButton*> _button;
|
||||
const not_null<QWidget*> _menuParent;
|
||||
const style::DropdownMenu &_menuSt;
|
||||
const Qt::Alignment _menuAlign = Qt::AlignTop | Qt::AlignRight;
|
||||
const Fn<void(bool)> _menuOverCallback;
|
||||
base::unique_qptr<Ui::DropdownMenu> _menu;
|
||||
bool _temporarilyHidden = false;
|
||||
bool _overButton = false;
|
||||
|
||||
};
|
||||
|
||||
class OrderController final : public WithDropdownController {
|
||||
public:
|
||||
OrderController(
|
||||
not_null<Ui::IconButton*> button,
|
||||
not_null<QWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback,
|
||||
rpl::producer<OrderMode> value,
|
||||
Fn<void(OrderMode)> change);
|
||||
|
||||
private:
|
||||
void fillMenu(not_null<Ui::DropdownMenu*> menu) override;
|
||||
void updateIcon();
|
||||
|
||||
const not_null<Ui::IconButton*> _button;
|
||||
rpl::variable<OrderMode> _appOrder;
|
||||
Fn<void(OrderMode)> _change;
|
||||
|
||||
};
|
||||
|
||||
class SpeedController final : public WithDropdownController {
|
||||
public:
|
||||
SpeedController(
|
||||
not_null<SpeedButton*> button,
|
||||
not_null<QWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback,
|
||||
Fn<float64(bool lastNonDefault)> value,
|
||||
Fn<void(float64)> change);
|
||||
|
||||
[[nodiscard]] rpl::producer<> saved() const;
|
||||
|
||||
private:
|
||||
void fillMenu(not_null<Ui::DropdownMenu*> menu) override;
|
||||
|
||||
[[nodiscard]] float64 speed() const;
|
||||
[[nodiscard]] bool isDefault() const;
|
||||
[[nodiscard]] float64 lastNonDefaultSpeed() const;
|
||||
void toggleDefault();
|
||||
void setSpeed(float64 newSpeed);
|
||||
void save();
|
||||
|
||||
const style::MediaSpeedButton &_st;
|
||||
Fn<float64(bool lastNonDefault)> _lookup;
|
||||
Fn<void(float64)> _change;
|
||||
float64 _speed = kSpedUpDefault;
|
||||
bool _isDefault = true;
|
||||
rpl::event_stream<float64> _speedChanged;
|
||||
rpl::event_stream<> _saved;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::Player
|
||||
|
|
|
@ -52,13 +52,6 @@ constexpr auto kRememberShuffledOrderItems = 16;
|
|||
|
||||
constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes.
|
||||
|
||||
auto VoicePlaybackSpeed() {
|
||||
return std::clamp(
|
||||
Core::App().settings().voicePlaybackSpeed(),
|
||||
Media::Audio::kSpeedMin,
|
||||
Media::Audio::kSpeedMax);
|
||||
}
|
||||
|
||||
base::options::toggle OptionDisableAutoplayNext({
|
||||
.id = kOptionDisableAutoplayNext,
|
||||
.name = "Disable auto-play of the next track",
|
||||
|
@ -847,7 +840,7 @@ Streaming::PlaybackOptions Instance::streamingOptions(
|
|||
? Streaming::Mode::Both
|
||||
: Streaming::Mode::Audio;
|
||||
result.speed = audioId.changeablePlaybackSpeed()
|
||||
? VoicePlaybackSpeed()
|
||||
? Core::App().settings().voicePlaybackSpeed()
|
||||
: 1.;
|
||||
result.audioId = audioId;
|
||||
if (position >= 0) {
|
||||
|
@ -1143,7 +1136,8 @@ void Instance::updateVoicePlaybackSpeed() {
|
|||
return;
|
||||
}
|
||||
if (const auto streamed = data->streamed.get()) {
|
||||
streamed->instance.setSpeed(VoicePlaybackSpeed());
|
||||
streamed->instance.setSpeed(
|
||||
Core::App().settings().voicePlaybackSpeed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,11 @@ class AudioMsgId;
|
|||
class DocumentData;
|
||||
class History;
|
||||
|
||||
namespace Media {
|
||||
enum class RepeatMode;
|
||||
enum class OrderMode;
|
||||
} // namespace Media
|
||||
|
||||
namespace Media {
|
||||
namespace Audio {
|
||||
class Instance;
|
||||
|
@ -45,18 +50,6 @@ namespace Player {
|
|||
|
||||
extern const char kOptionDisableAutoplayNext[];
|
||||
|
||||
enum class RepeatMode {
|
||||
None,
|
||||
One,
|
||||
All,
|
||||
};
|
||||
|
||||
enum class OrderMode {
|
||||
Default,
|
||||
Reverse,
|
||||
Shuffle,
|
||||
};
|
||||
|
||||
class Instance;
|
||||
struct TrackState;
|
||||
|
||||
|
|
|
@ -42,383 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Media {
|
||||
namespace Player {
|
||||
|
||||
class WithDropdownController {
|
||||
public:
|
||||
WithDropdownController(
|
||||
not_null<Ui::AbstractButton*> button,
|
||||
not_null<Ui::RpWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback);
|
||||
virtual ~WithDropdownController() = default;
|
||||
|
||||
[[nodiscard]] not_null<Ui::AbstractButton*> button() const;
|
||||
Ui::DropdownMenu *menu() const;
|
||||
|
||||
void updateDropdownGeometry();
|
||||
|
||||
void hideTemporarily();
|
||||
void showBack();
|
||||
|
||||
protected:
|
||||
void showMenu();
|
||||
|
||||
private:
|
||||
virtual void fillMenu(not_null<Ui::DropdownMenu*> menu) = 0;
|
||||
|
||||
const not_null<Ui::AbstractButton*> _button;
|
||||
const not_null<Ui::RpWidget*> _menuParent;
|
||||
const Fn<void(bool)> _menuOverCallback;
|
||||
base::unique_qptr<Ui::DropdownMenu> _menu;
|
||||
bool _temporarilyHidden = false;
|
||||
bool _overButton = false;
|
||||
|
||||
};
|
||||
|
||||
class Widget::SpeedButton final : public Ui::RippleButton {
|
||||
public:
|
||||
SpeedButton(QWidget *parent);
|
||||
|
||||
void setSpeed(float64 speed, anim::type animated = anim::type::normal);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
QImage prepareRippleMask() const override;
|
||||
|
||||
SpeedButtonLayout _layout;
|
||||
QPoint _layoutPosition;
|
||||
bool _isDefault = false;
|
||||
|
||||
};
|
||||
|
||||
class Widget::OrderController final : public WithDropdownController {
|
||||
public:
|
||||
OrderController(
|
||||
not_null<Ui::IconButton*> button,
|
||||
not_null<Ui::RpWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback);
|
||||
|
||||
private:
|
||||
void fillMenu(not_null<Ui::DropdownMenu*> menu) override;
|
||||
void updateIcon();
|
||||
|
||||
const not_null<Ui::IconButton*> _button;
|
||||
|
||||
};
|
||||
|
||||
class Widget::SpeedController final : public WithDropdownController {
|
||||
public:
|
||||
SpeedController(
|
||||
not_null<SpeedButton*> button,
|
||||
not_null<Ui::RpWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback);
|
||||
|
||||
[[nodiscard]] rpl::producer<> saved() const;
|
||||
|
||||
private:
|
||||
void fillMenu(not_null<Ui::DropdownMenu*> menu) override;
|
||||
|
||||
[[nodiscard]] float64 speed() const;
|
||||
[[nodiscard]] bool isDefault() const;
|
||||
[[nodiscard]] float64 lastNonDefaultSpeed() const;
|
||||
void toggleDefault();
|
||||
void setSpeed(float64 newSpeed);
|
||||
void save();
|
||||
|
||||
float64 _speed = 1.7;
|
||||
bool _isDefault = true;
|
||||
rpl::event_stream<float64> _speedChanged;
|
||||
rpl::event_stream<> _saved;
|
||||
|
||||
};
|
||||
|
||||
WithDropdownController::WithDropdownController(
|
||||
not_null<Ui::AbstractButton*> button,
|
||||
not_null<Ui::RpWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback)
|
||||
: _button(button)
|
||||
, _menuParent(menuParent)
|
||||
, _menuOverCallback(std::move(menuOverCallback)) {
|
||||
button->events(
|
||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||
return (e->type() == QEvent::Enter)
|
||||
|| (e->type() == QEvent::Leave);
|
||||
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
_overButton = (e->type() == QEvent::Enter);
|
||||
if (_overButton) {
|
||||
InvokeQueued(button, [=] {
|
||||
if (_overButton) {
|
||||
showMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
not_null<Ui::AbstractButton*> WithDropdownController::button() const {
|
||||
return _button;
|
||||
}
|
||||
|
||||
Ui::DropdownMenu *WithDropdownController::menu() const {
|
||||
return _menu.get();
|
||||
}
|
||||
|
||||
void WithDropdownController::updateDropdownGeometry() {
|
||||
if (!_menu) {
|
||||
return;
|
||||
}
|
||||
const auto position = _menu->parentWidget()->mapFromGlobal(
|
||||
_button->mapToGlobal(
|
||||
QPoint(_button->width(), _button->height())));
|
||||
const auto padding = st::mediaPlayerMenu.wrap.padding;
|
||||
_menu->move(position
|
||||
- QPoint(_menu->width(), 0)
|
||||
+ QPoint(padding.right(), -padding.top())
|
||||
+ st::mediaPlayerMenuPosition);
|
||||
}
|
||||
|
||||
void WithDropdownController::hideTemporarily() {
|
||||
if (_menu && !_menu->isHidden()) {
|
||||
_temporarilyHidden = true;
|
||||
_menu->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void WithDropdownController::showBack() {
|
||||
if (_temporarilyHidden) {
|
||||
_temporarilyHidden = false;
|
||||
if (_menu && _menu->isHidden()) {
|
||||
_menu->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WithDropdownController::showMenu() {
|
||||
if (_menu) {
|
||||
return;
|
||||
}
|
||||
_menu.emplace(_menuParent, st::mediaPlayerMenu);
|
||||
const auto raw = _menu.get();
|
||||
_menu->events(
|
||||
) | rpl::start_with_next([this](not_null<QEvent*> e) {
|
||||
const auto type = e->type();
|
||||
if (type == QEvent::Enter) {
|
||||
_menuOverCallback(true);
|
||||
} else if (type == QEvent::Leave) {
|
||||
_menuOverCallback(false);
|
||||
}
|
||||
}, _menu->lifetime());
|
||||
_menu->setHiddenCallback([=]{
|
||||
Ui::PostponeCall(raw, [this] {
|
||||
_menu = nullptr;
|
||||
});
|
||||
});
|
||||
_button->installEventFilter(raw);
|
||||
fillMenu(raw);
|
||||
updateDropdownGeometry();
|
||||
_menu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
|
||||
}
|
||||
|
||||
Widget::SpeedButton::SpeedButton(QWidget *parent)
|
||||
: RippleButton(parent, st::mediaPlayerSpeedRipple)
|
||||
, _layout(st::mediaSpeedButton, [=] { update(); }, 2.)
|
||||
, _isDefault(true) {
|
||||
resize(st::mediaPlayerSpeedSize);
|
||||
_layoutPosition = QPoint(
|
||||
(st::mediaPlayerSpeedSize.width() - st::mediaSpeedButton.width) / 2,
|
||||
st::mediaPlayerSpeedSize.height() - st::mediaSpeedButton.height);
|
||||
}
|
||||
|
||||
void Widget::SpeedButton::setSpeed(float64 speed, anim::type animated) {
|
||||
_isDefault = (speed == 1.);
|
||||
_layout.setSpeed(speed);
|
||||
if (animated == anim::type::instant) {
|
||||
_layout.finishTransform();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void Widget::SpeedButton::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
const auto innerHeight = st::mediaSpeedButton.icon.height();
|
||||
paintRipple(
|
||||
p,
|
||||
QPoint(0, height() - innerHeight),
|
||||
_isDefault ? &st::mediaPlayerSpeedDisabledRippleBg->c : nullptr);
|
||||
|
||||
const auto &color = !_isDefault
|
||||
? st::mediaPlayerActiveFg
|
||||
: isOver()
|
||||
? st::menuIconFgOver
|
||||
: st::menuIconFg;
|
||||
p.translate(_layoutPosition);
|
||||
_layout.paint(p, color->c);
|
||||
}
|
||||
|
||||
QPoint Widget::SpeedButton::prepareRippleStartPosition() const {
|
||||
const auto innerHeight = st::mediaSpeedButton.icon.height();
|
||||
const auto result = mapFromGlobal(QCursor::pos())
|
||||
- QPoint(0, height() - innerHeight);
|
||||
const auto rect = QRect(0, 0, width(), innerHeight);
|
||||
return rect.contains(result)
|
||||
? result
|
||||
: DisabledRippleStartPosition();
|
||||
}
|
||||
|
||||
QImage Widget::SpeedButton::prepareRippleMask() const {
|
||||
const auto innerHeight = st::mediaSpeedButton.icon.height();
|
||||
return Ui::RippleAnimation::RoundRectMask(
|
||||
{ width(), innerHeight },
|
||||
st::mediaPlayerSpeedRadius);
|
||||
}
|
||||
|
||||
Widget::OrderController::OrderController(
|
||||
not_null<Ui::IconButton*> button,
|
||||
not_null<Ui::RpWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback)
|
||||
: WithDropdownController(button, menuParent, std::move(menuOverCallback))
|
||||
, _button(button) {
|
||||
button->setClickedCallback([=] {
|
||||
showMenu();
|
||||
});
|
||||
|
||||
Core::App().settings().playerOrderModeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateIcon();
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
void Widget::OrderController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
|
||||
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.");
|
||||
}();
|
||||
menu->addAction(base::make_unique_q<Ui::Menu::Action>(
|
||||
menu,
|
||||
(active
|
||||
? st::mediaPlayerOrderMenuActive
|
||||
: st::mediaPlayerOrderMenu),
|
||||
Ui::Menu::CreateAction(menu, fields.label, callback),
|
||||
&(active ? fields.activeIcon : fields.icon),
|
||||
&(active ? fields.activeIcon : fields.icon)));
|
||||
};
|
||||
addOrderAction(OrderMode::Reverse);
|
||||
addOrderAction(OrderMode::Shuffle);
|
||||
}
|
||||
|
||||
void Widget::OrderController::updateIcon() {
|
||||
switch (Core::App().settings().playerOrderMode()) {
|
||||
case OrderMode::Default:
|
||||
_button->setIconOverride(
|
||||
&st::mediaPlayerReverseDisabledIcon,
|
||||
&st::mediaPlayerReverseDisabledIconOver);
|
||||
_button->setRippleColorOverride(
|
||||
&st::mediaPlayerRepeatDisabledRippleBg);
|
||||
break;
|
||||
case OrderMode::Reverse:
|
||||
_button->setIconOverride(&st::mediaPlayerReverseIcon);
|
||||
_button->setRippleColorOverride(nullptr);
|
||||
break;
|
||||
case OrderMode::Shuffle:
|
||||
_button->setIconOverride(&st::mediaPlayerShuffleIcon);
|
||||
_button->setRippleColorOverride(nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Widget::SpeedController::SpeedController(
|
||||
not_null<SpeedButton*> button,
|
||||
not_null<Ui::RpWidget*> menuParent,
|
||||
Fn<void(bool)> menuOverCallback)
|
||||
: WithDropdownController(button, menuParent, std::move(menuOverCallback)) {
|
||||
button->setClickedCallback([=] {
|
||||
toggleDefault();
|
||||
save();
|
||||
if (const auto current = menu()) {
|
||||
current->otherEnter();
|
||||
}
|
||||
});
|
||||
|
||||
setSpeed(Core::App().settings().voicePlaybackSpeed());
|
||||
_speed = Core::App().settings().voicePlaybackSpeed(true);
|
||||
|
||||
button->setSpeed(_speed, anim::type::instant);
|
||||
|
||||
_speedChanged.events_starting_with(
|
||||
speed()
|
||||
) | rpl::start_with_next([=](float64 speed) {
|
||||
button->setSpeed(speed);
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
rpl::producer<> Widget::SpeedController::saved() const {
|
||||
return _saved.events();
|
||||
}
|
||||
|
||||
float64 Widget::SpeedController::speed() const {
|
||||
return _isDefault ? 1. : _speed;
|
||||
}
|
||||
|
||||
bool Widget::SpeedController::isDefault() const {
|
||||
return _isDefault;
|
||||
}
|
||||
|
||||
float64 Widget::SpeedController::lastNonDefaultSpeed() const {
|
||||
return _speed;
|
||||
}
|
||||
|
||||
void Widget::SpeedController::toggleDefault() {
|
||||
_isDefault = !_isDefault;
|
||||
_speedChanged.fire(speed());
|
||||
}
|
||||
|
||||
void Widget::SpeedController::setSpeed(float64 newSpeed) {
|
||||
if (!(_isDefault = (newSpeed == 1.))) {
|
||||
_speed = newSpeed;
|
||||
}
|
||||
_speedChanged.fire(speed());
|
||||
}
|
||||
|
||||
void Widget::SpeedController::save() {
|
||||
Core::App().settings().setVoicePlaybackSpeed(speed());
|
||||
Core::App().saveSettingsDelayed();
|
||||
_saved.fire({});
|
||||
}
|
||||
|
||||
void Widget::SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
|
||||
FillSpeedMenu(
|
||||
menu->menu(),
|
||||
st::mediaSpeedMenu,
|
||||
_speedChanged.events_starting_with(speed()),
|
||||
[=](float64 speed) { setSpeed(speed); save(); });
|
||||
}
|
||||
|
||||
Widget::Widget(
|
||||
QWidget *parent,
|
||||
not_null<Ui::RpWidget*> dropdownsParent,
|
||||
|
@ -433,7 +56,7 @@ Widget::Widget(
|
|||
, _volumeToggle(rightControls(), st::mediaPlayerVolumeToggle)
|
||||
, _repeatToggle(rightControls(), st::mediaPlayerRepeatButton)
|
||||
, _orderToggle(rightControls(), st::mediaPlayerRepeatButton)
|
||||
, _speedToggle(rightControls())
|
||||
, _speedToggle(rightControls(), st::mediaPlayerSpeedButton)
|
||||
, _close(this, st::mediaPlayerClose)
|
||||
, _shadow(this)
|
||||
, _playbackSlider(this, st::mediaPlayerPlayback)
|
||||
|
@ -443,12 +66,16 @@ Widget::Widget(
|
|||
std::make_unique<OrderController>(
|
||||
_orderToggle.data(),
|
||||
dropdownsParent,
|
||||
[=](bool over) { markOver(over); }))
|
||||
[=](bool over) { markOver(over); },
|
||||
Core::App().settings().playerOrderModeValue(),
|
||||
[=](OrderMode value) { saveOrder(value); }))
|
||||
, _speedController(
|
||||
std::make_unique<SpeedController>(
|
||||
_speedToggle.data(),
|
||||
dropdownsParent,
|
||||
[=](bool over) { markOver(over); })) {
|
||||
[=](bool over) { markOver(over); },
|
||||
[=](bool lastNonDefault) { return speedLookup(lastNonDefault); },
|
||||
[=](float64 speed) { saveSpeed(speed); })) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
setMouseTracking(true);
|
||||
resize(width(), st::mediaPlayerHeight + st::lineWidth);
|
||||
|
@ -789,6 +416,20 @@ void Widget::markOver(bool over) {
|
|||
}
|
||||
}
|
||||
|
||||
void Widget::saveOrder(OrderMode mode) {
|
||||
Core::App().settings().setPlayerOrderMode(mode);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}
|
||||
|
||||
float64 Widget::speedLookup(bool lastNonDefault) const {
|
||||
return Core::App().settings().voicePlaybackSpeed(lastNonDefault);
|
||||
}
|
||||
|
||||
void Widget::saveSpeed(float64 speed) {
|
||||
Core::App().settings().setVoicePlaybackSpeed(speed);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}
|
||||
|
||||
void Widget::mouseMoveEvent(QMouseEvent *e) {
|
||||
updateOverLabelsState(e->pos());
|
||||
}
|
||||
|
|
|
@ -23,6 +23,10 @@ template <typename Widget>
|
|||
class FadeWrap;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Media {
|
||||
enum class OrderMode;
|
||||
} // namespace Media
|
||||
|
||||
namespace Media::View {
|
||||
class PlaybackProgress;
|
||||
} // namespace Media::View
|
||||
|
@ -34,6 +38,9 @@ class SessionController;
|
|||
namespace Media::Player {
|
||||
|
||||
class Dropdown;
|
||||
class SpeedButton;
|
||||
class OrderController;
|
||||
class SpeedController;
|
||||
struct TrackState;
|
||||
|
||||
class Widget final : public Ui::RpWidget {
|
||||
|
@ -103,6 +110,10 @@ private:
|
|||
void updateTimeLabel();
|
||||
void markOver(bool over);
|
||||
|
||||
void saveOrder(OrderMode mode);
|
||||
[[nodiscard]] float64 speedLookup(bool lastNonDefault) const;
|
||||
void saveSpeed(float64 speed);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<Ui::RpWidget*> _orderMenuParent;
|
||||
|
||||
|
@ -128,9 +139,6 @@ private:
|
|||
bool _wontBeOver = false;
|
||||
bool _volumeHidden = false;
|
||||
|
||||
class SpeedButton;
|
||||
class OrderController;
|
||||
class SpeedController;
|
||||
object_ptr<Ui::FlatLabel> _nameLabel;
|
||||
object_ptr<Ui::FadeWrap<Ui::RpWidget>> _rightControls;
|
||||
object_ptr<Ui::LabelSimple> _timeLabel;
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/streaming/media_streaming_audio_track.h"
|
||||
#include "media/streaming/media_streaming_video_track.h"
|
||||
#include "media/audio/media_audio.h" // for SupportsSpeedControl()
|
||||
#include "media/media_common.h"
|
||||
#include "data/data_document.h" // for DocumentData::duration()
|
||||
|
||||
namespace Media {
|
||||
|
@ -528,8 +529,7 @@ void Player::fail(Error error) {
|
|||
}
|
||||
|
||||
void Player::play(const PlaybackOptions &options) {
|
||||
Expects(options.speed >= Audio::kSpeedMin
|
||||
&& options.speed <= Audio::kSpeedMax);
|
||||
Expects(options.speed >= kSpeedMin && options.speed <= kSpeedMax);
|
||||
|
||||
// Looping video with audio is not supported for now.
|
||||
Expects(!options.loop || (options.mode != Mode::Both));
|
||||
|
@ -829,7 +829,7 @@ float64 Player::speed() const {
|
|||
}
|
||||
|
||||
void Player::setSpeed(float64 speed) {
|
||||
Expects(speed >= Audio::kSpeedMin && speed <= Audio::kSpeedMax);
|
||||
Expects(speed >= kSpeedMin && speed <= kSpeedMax);
|
||||
|
||||
if (!Media::Audio::SupportsSpeedControl()) {
|
||||
speed = 1.;
|
||||
|
|
|
@ -26,8 +26,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Media {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] auto RepeatModeToLoopStatus(Media::Player::RepeatMode mode) {
|
||||
using Mode = Media::Player::RepeatMode;
|
||||
[[nodiscard]] auto RepeatModeToLoopStatus(Media::RepeatMode mode) {
|
||||
using Mode = Media::RepeatMode;
|
||||
using Status = base::Platform::SystemMediaControls::LoopStatus;
|
||||
switch (mode) {
|
||||
case Mode::None: return Status::None;
|
||||
|
@ -199,8 +199,8 @@ SystemMediaControlsManager::SystemMediaControlsManager()
|
|||
_controls->setIsPreviousEnabled(mediaPlayer->previousAvailable(type));
|
||||
}, _lifetime);
|
||||
|
||||
using Media::Player::RepeatMode;
|
||||
using Media::Player::OrderMode;
|
||||
using Media::RepeatMode;
|
||||
using Media::OrderMode;
|
||||
|
||||
Core::App().settings().playerRepeatModeValue(
|
||||
) | rpl::start_with_next([=](RepeatMode mode) {
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "data/data_audio_msg_id.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "media/media_common.h"
|
||||
|
||||
namespace base::Platform {
|
||||
class SystemMediaControls;
|
||||
|
@ -39,9 +40,9 @@ private:
|
|||
const std::unique_ptr<base::Platform::SystemMediaControls> _controls;
|
||||
|
||||
std::vector<std::shared_ptr<Data::DocumentMedia>> _cachedMediaView;
|
||||
std::unique_ptr<Media::Streaming::Instance> _streamed;
|
||||
std::unique_ptr<Streaming::Instance> _streamed;
|
||||
AudioMsgId _lastAudioMsgId;
|
||||
Media::Player::OrderMode _lastOrderMode;
|
||||
OrderMode _lastOrderMode = OrderMode::Default;
|
||||
|
||||
rpl::lifetime _lifetimeDownload;
|
||||
rpl::lifetime _lifetime;
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
using "ui/basic.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
using "ui/menu_icons.style";
|
||||
using "media/player/media_player.style";
|
||||
|
||||
mediaviewOverDuration: 150;
|
||||
|
||||
|
@ -24,7 +25,8 @@ mediaviewPlayback: MediaSlider {
|
|||
seekSize: size(12px, 12px);
|
||||
duration: mediaviewOverDuration;
|
||||
}
|
||||
mediaviewPlaybackTop: 52px;
|
||||
mediaviewPlaybackTop: 49px;
|
||||
mediaviewPlayProgressTop: 46px;
|
||||
|
||||
mediaviewControlsButton: IconButton {
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
|
@ -34,46 +36,41 @@ mediaviewControlsButton: IconButton {
|
|||
duration: mediaviewOverDuration;
|
||||
}
|
||||
|
||||
mediaviewControllerSize: size(481px, 75px);
|
||||
mediaviewControllerSize: size(480px, 72px);
|
||||
mediaviewPlayProgressLabel: LabelSimple(defaultLabelSimple) {
|
||||
font: semiboldFont;
|
||||
font: font(12px semibold);
|
||||
textFg: mediaviewPlaybackProgressFg;
|
||||
}
|
||||
mediaviewPlayProgressSkip: 8px;
|
||||
mediaviewPlayProgressLeft: 8px;
|
||||
mediaviewPlayButtonTop: 5px;
|
||||
mediaviewPlayProgressSkip: 10px;
|
||||
mediaviewPlayProgressLeft: 4px;
|
||||
mediaviewPlayButtonTop: 2px;
|
||||
mediaviewPlayButton: IconButton(mediaviewControlsButton) {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
rippleAreaSize: 42px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
rippleAreaSize: 40px;
|
||||
|
||||
icon: icon {{ "player/player_play", mediaviewPlaybackIconFg }};
|
||||
iconOver: icon {{ "player/player_play", mediaviewPlaybackIconFgOver }};
|
||||
iconPosition: point(9px, 9px);
|
||||
iconPosition: point(8px, 8px);
|
||||
}
|
||||
mediaviewPauseIcon: icon {{ "player/player_pause", mediaviewPlaybackIconFg }};
|
||||
mediaviewPauseIconOver: icon {{ "player/player_pause", mediaviewPlaybackIconFgOver }};
|
||||
|
||||
mediaviewButtonsTop: 7px;
|
||||
mediaviewButtonsTop: 6px;
|
||||
mediaviewButtonsRight: 8px;
|
||||
|
||||
mediaviewMenuToggleSkip: 4px;
|
||||
mediaviewMenuToggle: IconButton(mediaviewControlsButton) {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
rippleAreaSize: 34px;
|
||||
icon: icon {{ "player/player_more", mediaviewPlaybackIconFg }};
|
||||
iconOver: icon {{ "player/player_more", mediaviewPlaybackIconFgOver }};
|
||||
iconPosition: point(5px, 5px);
|
||||
}
|
||||
|
||||
mediaviewPipButtonSkip: 5px;
|
||||
mediaviewPipButton: IconButton(mediaviewMenuToggle) {
|
||||
mediaviewPipButtonSkip: 4px;
|
||||
mediaviewPipButton: IconButton(mediaviewControlsButton) {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
rippleAreaSize: 32px;
|
||||
icon: icon {{ "player/player_pip", mediaviewPlaybackIconFg }};
|
||||
iconOver: icon {{ "player/player_pip", mediaviewPlaybackIconFgOver }};
|
||||
iconPosition: point(4px, 4px);
|
||||
}
|
||||
|
||||
mediaviewFullScreenButtonSkip: 8px;
|
||||
mediaviewFullScreenButton: IconButton(mediaviewMenuToggle) {
|
||||
mediaviewFullScreenButtonSkip: 4px;
|
||||
mediaviewFullScreenButton: IconButton(mediaviewPipButton) {
|
||||
icon: icon {{ "player/player_fullscreen", mediaviewPlaybackIconFg }};
|
||||
iconOver: icon {{ "player/player_fullscreen", mediaviewPlaybackIconFgOver }};
|
||||
}
|
||||
|
@ -89,17 +86,17 @@ mediaviewVolumeIcon1: icon {{ "player/player_volume_small", mediaviewPlaybackIco
|
|||
mediaviewVolumeIcon1Over: icon {{ "player/player_volume_small", mediaviewPlaybackIconFgOver }};
|
||||
mediaviewVolumeIcon2: icon {{ "player/player_volume_on", mediaviewPlaybackIconFg }};
|
||||
mediaviewVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPlaybackIconFgOver }};
|
||||
mediaviewVolumeTop: 10px;
|
||||
mediaviewVolumeToggleSkip: 11px;
|
||||
mediaviewVolumeTop: 6px;
|
||||
mediaviewVolumeToggleLeft: 6px;
|
||||
mediaviewVolumeToggle: IconButton(mediaviewControlsButton) {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
rippleAreaSize: 30px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
rippleAreaSize: 32px;
|
||||
icon: mediaviewVolumeIcon0;
|
||||
iconOver: mediaviewVolumeIcon0Over;
|
||||
iconPosition: point(3px, 3px);
|
||||
iconPosition: point(4px, 4px);
|
||||
}
|
||||
mediaviewVolumeSkip: 4px;
|
||||
mediaviewVolumeSkip: 3px;
|
||||
|
||||
mediaviewLeft: icon {
|
||||
{ "mediaview/next_shadow-flip_horizontal", windowShadowFg }
|
||||
|
@ -143,6 +140,9 @@ mediaviewFileIconSize: 80px;
|
|||
|
||||
mediaviewFileLink: defaultLinkButton;
|
||||
|
||||
mediaviewMenuSeparator: MenuSeparator(defaultMenuSeparator) {
|
||||
fg: mediaviewMenuFg;
|
||||
}
|
||||
mediaviewMenu: Menu(menuWithIcons) {
|
||||
itemBg: mediaviewMenuBg;
|
||||
itemBgOver: mediaviewMenuBgOver;
|
||||
|
@ -153,9 +153,7 @@ mediaviewMenu: Menu(menuWithIcons) {
|
|||
itemFgShortcutOver: mediaviewMenuFg;
|
||||
itemFgShortcutDisabled: mediaviewMenuFg;
|
||||
|
||||
separator: MenuSeparator(defaultMenuSeparator) {
|
||||
fg: mediaviewMenuFg;
|
||||
}
|
||||
separator: mediaviewMenuSeparator;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: mediaviewMenuBgRipple;
|
||||
|
@ -183,39 +181,6 @@ mediaviewDropdownMenu: DropdownMenu(defaultDropdownMenu) {
|
|||
}
|
||||
}
|
||||
|
||||
mediaviewControlsMenu: Menu(defaultMenu) {
|
||||
itemBg: mediaviewSaveMsgBg;
|
||||
itemBgOver: mediaviewPlaybackIconRipple;
|
||||
itemFg: mediaviewPlaybackProgressFg;
|
||||
itemFgOver: mediaviewPlaybackProgressFg;
|
||||
itemFgDisabled: mediaviewPlaybackProgressFg;
|
||||
itemFgShortcut: mediaviewPlaybackProgressFg;
|
||||
itemFgShortcutOver: mediaviewPlaybackProgressFg;
|
||||
itemFgShortcutDisabled: mediaviewPlaybackProgressFg;
|
||||
|
||||
separator: MenuSeparator(defaultMenuSeparator) {
|
||||
fg: mediaviewPlaybackIconRipple;
|
||||
}
|
||||
|
||||
arrow: icon {{ "menu/submenu_arrow", mediaviewPlaybackProgressFg }};
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: mediaviewPlaybackIconRipple;
|
||||
}
|
||||
}
|
||||
mediaviewControlsMenuShadow: Shadow(defaultEmptyShadow) {
|
||||
fallback: mediaviewSaveMsgBg;
|
||||
}
|
||||
mediaviewControlsPanelAnimation: PanelAnimation(defaultPanelAnimation) {
|
||||
fadeBg: mediaviewSaveMsgBg;
|
||||
shadow: mediaviewControlsMenuShadow;
|
||||
}
|
||||
mediaviewControlsPopupMenu: PopupMenu(defaultPopupMenu) {
|
||||
shadow: mediaviewControlsMenuShadow;
|
||||
menu: mediaviewControlsMenu;
|
||||
animation: mediaviewControlsPanelAnimation;
|
||||
}
|
||||
|
||||
mediaviewSaveMsgCheck: icon {{ "mediaview/save_check", mediaviewSaveMsgFg }};
|
||||
mediaviewSaveMsgPadding: margins(55px, 19px, 29px, 20px);
|
||||
mediaviewSaveMsgCheckPos: point(23px, 21px);
|
||||
|
@ -338,6 +303,59 @@ mediaviewTitleMaximizeMacPadding: margins(0px, 4px, 8px, 4px);
|
|||
mediaviewShadowTop: icon{{ "mediaview/shadow_top", windowShadowFg }};
|
||||
mediaviewShadowBottom: icon{{ "mediaview/shadow_bottom", windowShadowFg }};
|
||||
|
||||
mediaviewSpeedMenu: MediaSpeedMenu(mediaPlayerSpeedMenu) {
|
||||
dropdown: DropdownMenu(mediaviewDropdownMenu) {
|
||||
menu: Menu(mediaviewMenu) {
|
||||
separator: MenuSeparator(mediaviewMenuSeparator) {
|
||||
fg: mediaviewMenuBgOver;
|
||||
padding: margins(0px, 4px, 0px, 4px);
|
||||
width: 6px;
|
||||
}
|
||||
itemPadding: margins(54px, 7px, 54px, 9px);
|
||||
itemFgDisabled: mediaviewTextLinkFg;
|
||||
}
|
||||
}
|
||||
activeCheck: icon {{ "player/player_check", mediaviewTextLinkFg }};
|
||||
slider: MediaSlider(defaultContinuousSlider) {
|
||||
activeFg: mediaviewTextLinkFg;
|
||||
inactiveFg: mediaviewMenuBgOver;
|
||||
activeFgOver: mediaviewTextLinkFg;
|
||||
inactiveFgOver: mediaviewMenuBgOver;
|
||||
activeFgDisabled: mediaviewMenuBgOver;
|
||||
receivedTillFg: mediaviewMenuBgOver;
|
||||
width: 6px;
|
||||
seekSize: size(6px, 6px);
|
||||
}
|
||||
|
||||
slow: mediaSpeedSlow;
|
||||
slowActive: mediaSpeedSlowActive;
|
||||
normal: mediaSpeedNormal;
|
||||
normalActive: mediaSpeedNormalActive;
|
||||
medium: mediaSpeedMedium;
|
||||
mediumActive: mediaSpeedMediumActive;
|
||||
fast: mediaSpeedFast;
|
||||
fastActive: mediaSpeedFastActive;
|
||||
veryFast: mediaSpeedVeryFast;
|
||||
veryFastActive: mediaSpeedVeryFastActive;
|
||||
superFast: mediaSpeedSuperFast;
|
||||
superFastActive: mediaSpeedSuperFastActive;
|
||||
}
|
||||
mediaviewSpeedButton: MediaSpeedButton(mediaPlayerSpeedButton) {
|
||||
size: size(32px, 32px);
|
||||
padding: margins(0px, 0px, 0px, 0px);
|
||||
fg: mediaviewPlaybackIconFg;
|
||||
overFg: mediaviewPlaybackIconFgOver;
|
||||
activeFg: mediaviewTextLinkFg;
|
||||
icon: icon{{ "player/player_speed", mediaviewPlaybackIconFg }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: mediaviewPlaybackIconRipple;
|
||||
}
|
||||
rippleActiveColor: mediaviewPlaybackIconRipple;
|
||||
rippleRadius: 16px;
|
||||
menu: mediaviewSpeedMenu;
|
||||
menuAlign: align(bottomright);
|
||||
}
|
||||
|
||||
themePreviewSize: size(903px, 584px);
|
||||
themePreviewBg: windowBg;
|
||||
themePreviewOverlayOpacity: 0.8;
|
||||
|
|
|
@ -3743,10 +3743,8 @@ void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
|
|||
}
|
||||
}
|
||||
|
||||
float64 OverlayWidget::playbackControlsCurrentSpeed() {
|
||||
const auto result = Core::App().settings().videoPlaybackSpeed();
|
||||
DEBUG_LOG(("Media playback speed: now %1.").arg(result));
|
||||
return result;
|
||||
float64 OverlayWidget::playbackControlsCurrentSpeed(bool lastNonDefault) {
|
||||
return Core::App().settings().videoPlaybackSpeed(lastNonDefault);
|
||||
}
|
||||
|
||||
void OverlayWidget::switchToPip() {
|
||||
|
|
|
@ -204,7 +204,7 @@ private:
|
|||
void playbackControlsVolumeToggled() override;
|
||||
void playbackControlsVolumeChangeFinished() override;
|
||||
void playbackControlsSpeedChanged(float64 speed) override;
|
||||
float64 playbackControlsCurrentSpeed() override;
|
||||
float64 playbackControlsCurrentSpeed(bool lastNonDefault) override;
|
||||
void playbackControlsToFullScreen() override;
|
||||
void playbackControlsFromFullScreen() override;
|
||||
void playbackControlsToPictureInPicture() override;
|
||||
|
|
|
@ -8,13 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/view/media_view_playback_controls.h"
|
||||
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "media/player/media_player_button.h"
|
||||
#include "media/player/media_player_dropdown.h"
|
||||
#include "media/view/media_view_playback_progress.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#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"
|
||||
#include "lang/lang_keys.h"
|
||||
|
@ -22,184 +23,6 @@ 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<std::pair<float64, float64>, 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<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
float64 startSpeed);
|
||||
|
||||
not_null<QAction*> action() const override;
|
||||
bool isEnabled() const override;
|
||||
|
||||
[[nodiscard]] rpl::producer<float64> changeSpeedRequests() const;
|
||||
|
||||
protected:
|
||||
int contentHeight() const override;
|
||||
|
||||
private:
|
||||
float64 computeSpeed(float64 value) const;
|
||||
QString speedString(float64 value) const;
|
||||
|
||||
QRect _itemRect;
|
||||
QRect _textRect;
|
||||
|
||||
const style::MediaSlider &_sliderSt;
|
||||
const base::unique_qptr<Ui::MediaSlider> _slider;
|
||||
const not_null<QAction*> _dummyAction;
|
||||
const int _lineCount;
|
||||
const int _height;
|
||||
|
||||
rpl::event_stream<float64> _changeSpeedRequests;
|
||||
|
||||
};
|
||||
|
||||
MenuSpeedItem::MenuSpeedItem(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
float64 startSpeed)
|
||||
: Ui::Menu::ItemBase(parent, st)
|
||||
, _sliderSt(st::mediaviewPlayback)
|
||||
, _slider(base::make_unique_q<Ui::MediaSlider>(
|
||||
this,
|
||||
_sliderSt))
|
||||
, _dummyAction(new QAction(parent))
|
||||
, _lineCount(std::ceil(st.itemStyle.font->width(speedString(0.9))
|
||||
/ float(st.widthMax)))
|
||||
, _height(st.itemPadding.top() * 2
|
||||
+ st.itemStyle.font->height * _lineCount
|
||||
+ _sliderSt.seekSize.height()
|
||||
+ st.itemPadding.bottom() * 2) {
|
||||
|
||||
initResizeHook(parent->sizeValue());
|
||||
enableMouseSelecting();
|
||||
enableMouseSelecting(_slider.get());
|
||||
|
||||
_slider->setAlwaysDisplayMarker(true);
|
||||
_slider->setValue((base::SafeRound(startSpeed * 100.) - kMinSpeed)
|
||||
/ (kMaxSpeed - kMinSpeed));
|
||||
|
||||
for (const auto &sticked : kSpeedStickedValues) {
|
||||
_slider->addDivider(sticked.first, st::speedSliderDividerSize);
|
||||
}
|
||||
//_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();
|
||||
const auto penWidth = QPen(st.itemBgOver).width();
|
||||
_textRect = _itemRect
|
||||
- style::margins(
|
||||
-penWidth,
|
||||
0,
|
||||
-penWidth,
|
||||
height - st.itemStyle.font->height * _lineCount);
|
||||
|
||||
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) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
const auto selected = isSelected();
|
||||
p.fillRect(clip, selected ? st.itemBgOver : st.itemBg);
|
||||
|
||||
const auto value = _slider->value();
|
||||
|
||||
p.setPen(selected ? st.itemFgOver : st.itemFg);
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
float64 MenuSpeedItem::computeSpeed(float64 value) const {
|
||||
return anim::interpolate(kMinSpeed, kMaxSpeed, std::clamp(value, 0., 1.))
|
||||
/ 100.;
|
||||
}
|
||||
|
||||
QString MenuSpeedItem::speedString(float64 value) const {
|
||||
return tr::lng_mediaview_playback_speed(
|
||||
tr::now,
|
||||
lt_speed,
|
||||
QString::number(computeSpeed(value), 'f', 2) + 'x');
|
||||
}
|
||||
|
||||
not_null<QAction*> MenuSpeedItem::action() const {
|
||||
return _dummyAction;
|
||||
}
|
||||
|
||||
bool MenuSpeedItem::isEnabled() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
int MenuSpeedItem::contentHeight() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
rpl::producer<float64> MenuSpeedItem::changeSpeedRequests() const {
|
||||
return _changeSpeedRequests.events();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PlaybackControls::PlaybackControls(
|
||||
QWidget *parent,
|
||||
|
@ -211,12 +34,18 @@ PlaybackControls::PlaybackControls(
|
|||
, _playbackProgress(std::make_unique<PlaybackProgress>())
|
||||
, _volumeToggle(this, st::mediaviewVolumeToggle)
|
||||
, _volumeController(this, st::mediaviewPlayback)
|
||||
, _menuToggle(this, st::mediaviewMenuToggle)
|
||||
, _speedToggle(this, st::mediaviewSpeedButton)
|
||||
, _fullScreenToggle(this, st::mediaviewFullScreenButton)
|
||||
, _pictureInPicture(this, st::mediaviewPipButton)
|
||||
, _playedAlready(this, st::mediaviewPlayProgressLabel)
|
||||
, _toPlayLeft(this, st::mediaviewPlayProgressLabel)
|
||||
, _menuStyle(st::mediaviewControlsPopupMenu)
|
||||
, _speedController(
|
||||
std::make_unique<Player::SpeedController>(
|
||||
_speedToggle.data(),
|
||||
parent,
|
||||
[=](bool) {},
|
||||
[=](bool lastNonDefault) { return speedLookup(lastNonDefault); },
|
||||
[=](float64 speed) { saveSpeed(speed); }))
|
||||
, _fadeAnimation(std::make_unique<Ui::FadeAnimation>(this)) {
|
||||
_fadeAnimation->show();
|
||||
_fadeAnimation->setFinishedCallback([=] {
|
||||
|
@ -229,9 +58,6 @@ PlaybackControls::PlaybackControls(
|
|||
_pictureInPicture->addClickHandler([=] {
|
||||
_delegate->playbackControlsToPictureInPicture();
|
||||
});
|
||||
_menuToggle->addClickHandler([=] {
|
||||
showMenu();
|
||||
});
|
||||
|
||||
_volumeController->setValue(_delegate->playbackControlsCurrentVolume());
|
||||
_volumeController->setChangeProgressCallback([=](float64 value) {
|
||||
|
@ -354,33 +180,13 @@ void PlaybackControls::fadeUpdated(float64 opacity) {
|
|||
_volumeController->setFadeOpacity(opacity);
|
||||
}
|
||||
|
||||
void PlaybackControls::showMenu() {
|
||||
if (_menu) {
|
||||
_menu = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
_menu.emplace(this, _menuStyle);
|
||||
float64 PlaybackControls::speedLookup(bool lastNonDefault) const {
|
||||
return _delegate->playbackControlsCurrentSpeed(lastNonDefault);
|
||||
}
|
||||
|
||||
if (Media::Audio::SupportsSpeedControl()) {
|
||||
auto speedItem = base::make_unique_q<MenuSpeedItem>(
|
||||
_menu,
|
||||
_menuStyle.menu,
|
||||
_delegate->playbackControlsCurrentSpeed());
|
||||
speedItem->changeSpeedRequests(
|
||||
) | rpl::start_with_next([=](float64 speed) {
|
||||
updatePlaybackSpeed(speed);
|
||||
}, speedItem->lifetime());
|
||||
_menu->addAction(std::move(speedItem));
|
||||
_menu->addSeparator();
|
||||
}
|
||||
|
||||
_menu->addAction(tr::lng_mediaview_rotate_video(tr::now), [=] {
|
||||
_delegate->playbackControlsRotate();
|
||||
});
|
||||
|
||||
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::BottomLeft);
|
||||
_menu->popup(mapToGlobal(_menuToggle->geometry().topLeft()));
|
||||
void PlaybackControls::saveSpeed(float64 speed) {
|
||||
_delegate->playbackControlsSpeedChanged(speed);
|
||||
}
|
||||
|
||||
void PlaybackControls::updatePlaybackSpeed(float64 speed) {
|
||||
|
@ -524,7 +330,7 @@ void PlaybackControls::setInFullScreen(bool inFullScreen) {
|
|||
void PlaybackControls::resizeEvent(QResizeEvent *e) {
|
||||
const auto textSkip = st::mediaviewPlayProgressSkip;
|
||||
const auto textLeft = st::mediaviewPlayProgressLeft;
|
||||
const auto textTop = st::mediaviewPlaybackTop + (_playbackSlider->height() - _playedAlready->height()) / 2;
|
||||
const auto textTop = st::mediaviewPlayProgressTop;
|
||||
_playedAlready->moveToLeft(textLeft + textSkip, textTop);
|
||||
_toPlayLeft->moveToRight(textLeft + textSkip, textTop);
|
||||
const auto remove = 2 * textLeft + 4 * textSkip + _playedAlready->width() + _toPlayLeft->width();
|
||||
|
@ -536,16 +342,16 @@ void PlaybackControls::resizeEvent(QResizeEvent *e) {
|
|||
(width() - _playPauseResume->width()) / 2,
|
||||
st::mediaviewPlayButtonTop);
|
||||
|
||||
auto right = st::mediaviewMenuToggleSkip;
|
||||
_menuToggle->moveToRight(right, st::mediaviewButtonsTop);
|
||||
right += _menuToggle->width() + st::mediaviewPipButtonSkip;
|
||||
auto right = st::mediaviewButtonsRight;
|
||||
_speedToggle->moveToRight(right, st::mediaviewButtonsTop);
|
||||
right += _speedToggle->width() + st::mediaviewPipButtonSkip;
|
||||
_pictureInPicture->moveToRight(right, st::mediaviewButtonsTop);
|
||||
right += _pictureInPicture->width() + st::mediaviewFullScreenButtonSkip;
|
||||
_fullScreenToggle->moveToRight(right, st::mediaviewButtonsTop);
|
||||
|
||||
updateDownloadProgressPosition();
|
||||
|
||||
auto left = st::mediaviewVolumeToggleSkip;
|
||||
auto left = st::mediaviewVolumeToggleLeft;
|
||||
_volumeToggle->moveToLeft(left, st::mediaviewVolumeTop);
|
||||
left += _volumeToggle->width() + st::mediaviewVolumeSkip;
|
||||
_volumeController->resize(
|
||||
|
@ -586,7 +392,7 @@ void PlaybackControls::mousePressEvent(QMouseEvent *e) {
|
|||
}
|
||||
|
||||
bool PlaybackControls::hasMenu() const {
|
||||
return _menu != nullptr;
|
||||
return _speedController->menu() != nullptr;
|
||||
}
|
||||
|
||||
bool PlaybackControls::dragging() const {
|
||||
|
@ -594,7 +400,7 @@ bool PlaybackControls::dragging() const {
|
|||
|| _playbackSlider->isChanging()
|
||||
|| _playPauseResume->isOver()
|
||||
|| _volumeToggle->isOver()
|
||||
|| _menuToggle->isOver()
|
||||
|| _speedToggle->isOver()
|
||||
|| _fullScreenToggle->isOver()
|
||||
|| _pictureInPicture->isOver();
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ class PopupMenu;
|
|||
namespace Media {
|
||||
namespace Player {
|
||||
struct TrackState;
|
||||
class SpeedButton;
|
||||
class SpeedController;
|
||||
} // namespace Player
|
||||
|
||||
namespace View {
|
||||
|
@ -42,7 +44,8 @@ public:
|
|||
virtual void playbackControlsVolumeToggled() = 0;
|
||||
virtual void playbackControlsVolumeChangeFinished() = 0;
|
||||
virtual void playbackControlsSpeedChanged(float64 speed) = 0;
|
||||
[[nodiscard]] virtual float64 playbackControlsCurrentSpeed() = 0;
|
||||
[[nodiscard]] virtual float64 playbackControlsCurrentSpeed(
|
||||
bool lastNonDefault) = 0;
|
||||
virtual void playbackControlsToFullScreen() = 0;
|
||||
virtual void playbackControlsFromFullScreen() = 0;
|
||||
virtual void playbackControlsToPictureInPicture() = 0;
|
||||
|
@ -50,6 +53,7 @@ public:
|
|||
};
|
||||
|
||||
PlaybackControls(QWidget *parent, not_null<Delegate*> delegate);
|
||||
~PlaybackControls();
|
||||
|
||||
void showAnimated();
|
||||
void hideAnimated();
|
||||
|
@ -60,8 +64,6 @@ public:
|
|||
[[nodiscard]] bool hasMenu() const;
|
||||
[[nodiscard]] bool dragging() const;
|
||||
|
||||
~PlaybackControls();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
@ -86,9 +88,11 @@ private:
|
|||
void updatePlayPauseResumeState(const Player::TrackState &state);
|
||||
void updateTimeTexts(const Player::TrackState &state);
|
||||
void refreshTimeTexts();
|
||||
void showMenu();
|
||||
|
||||
not_null<Delegate*> _delegate;
|
||||
[[nodiscard]] float64 speedLookup(bool lastNonDefault) const;
|
||||
void saveSpeed(float64 speed);
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
|
||||
bool _inFullScreen = false;
|
||||
bool _showPause = false;
|
||||
|
@ -106,15 +110,13 @@ private:
|
|||
std::unique_ptr<PlaybackProgress> _receivedTillProgress;
|
||||
object_ptr<Ui::IconButton> _volumeToggle;
|
||||
object_ptr<Ui::MediaSlider> _volumeController;
|
||||
object_ptr<Ui::IconButton> _menuToggle;
|
||||
object_ptr<Player::SpeedButton> _speedToggle;
|
||||
object_ptr<Ui::IconButton> _fullScreenToggle;
|
||||
object_ptr<Ui::IconButton> _pictureInPicture;
|
||||
object_ptr<Ui::LabelSimple> _playedAlready;
|
||||
object_ptr<Ui::LabelSimple> _toPlayLeft;
|
||||
object_ptr<Ui::LabelSimple> _downloadProgress = { nullptr };
|
||||
|
||||
const style::PopupMenu &_menuStyle;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
std::unique_ptr<Player::SpeedController> _speedController;
|
||||
std::unique_ptr<Ui::FadeAnimation> _fadeAnimation;
|
||||
|
||||
};
|
||||
|
|
|
@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
using "ui/basic.style";
|
||||
using "ui/chat/chat.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
using "media/view/media_view.style";
|
||||
using "boxes/boxes.style";
|
||||
|
||||
OverviewFileLayout {
|
||||
|
|
|
@ -131,3 +131,29 @@ menuIconDisableAttention: icon {{ "menu/disable", menuIconAttentionColor }};
|
|||
menuIconReportAttention: icon {{ "menu/report", menuIconAttentionColor }};
|
||||
|
||||
menuIconBlockSettings: icon {{ "menu/block", windowBgActive }};
|
||||
|
||||
playerSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }};
|
||||
playerSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }};
|
||||
playerSpeedNormal: icon {{ "player/speed/audiospeed_menu_1.0", menuIconColor }};
|
||||
playerSpeedNormalActive: icon {{ "player/speed/audiospeed_menu_1.0", mediaPlayerActiveFg }};
|
||||
playerSpeedMedium: icon {{ "player/speed/audiospeed_menu_1.2", menuIconColor }};
|
||||
playerSpeedMediumActive: icon {{ "player/speed/audiospeed_menu_1.2", mediaPlayerActiveFg }};
|
||||
playerSpeedFast: icon {{ "player/speed/audiospeed_menu_1.5", menuIconColor }};
|
||||
playerSpeedFastActive: icon {{ "player/speed/audiospeed_menu_1.5", mediaPlayerActiveFg }};
|
||||
playerSpeedVeryFast: icon {{ "player/speed/audiospeed_menu_1.7", menuIconColor }};
|
||||
playerSpeedVeryFastActive: icon {{ "player/speed/audiospeed_menu_1.7", mediaPlayerActiveFg }};
|
||||
playerSpeedSuperFast: icon {{ "player/speed/audiospeed_menu_2.0", menuIconColor }};
|
||||
playerSpeedSuperFastActive: icon {{ "player/speed/audiospeed_menu_2.0", mediaPlayerActiveFg }};
|
||||
|
||||
mediaSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", mediaviewMenuFg }};
|
||||
mediaSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaviewTextLinkFg }};
|
||||
mediaSpeedNormal: icon {{ "player/speed/audiospeed_menu_1.0", mediaviewMenuFg }};
|
||||
mediaSpeedNormalActive: icon {{ "player/speed/audiospeed_menu_1.0", mediaviewTextLinkFg }};
|
||||
mediaSpeedMedium: icon {{ "player/speed/audiospeed_menu_1.2", mediaviewMenuFg }};
|
||||
mediaSpeedMediumActive: icon {{ "player/speed/audiospeed_menu_1.2", mediaviewTextLinkFg }};
|
||||
mediaSpeedFast: icon {{ "player/speed/audiospeed_menu_1.5", mediaviewMenuFg }};
|
||||
mediaSpeedFastActive: icon {{ "player/speed/audiospeed_menu_1.5", mediaviewTextLinkFg }};
|
||||
mediaSpeedVeryFast: icon {{ "player/speed/audiospeed_menu_1.7", mediaviewMenuFg }};
|
||||
mediaSpeedVeryFastActive: icon {{ "player/speed/audiospeed_menu_1.7", mediaviewTextLinkFg }};
|
||||
mediaSpeedSuperFast: icon {{ "player/speed/audiospeed_menu_2.0", mediaviewMenuFg }};
|
||||
mediaSpeedSuperFastActive: icon {{ "player/speed/audiospeed_menu_2.0", mediaviewTextLinkFg }};
|
||||
|
|
|
@ -125,6 +125,8 @@ PRIVATE
|
|||
media/player/media_player_dropdown.cpp
|
||||
media/player/media_player_dropdown.h
|
||||
|
||||
media/media_common.h
|
||||
|
||||
menu/menu_check_item.cpp
|
||||
menu/menu_check_item.h
|
||||
menu/menu_ttl.cpp
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 62a62d1fb5abe9dc1e6b9f89af1ccef6fef33c52
|
||||
Subproject commit dec1cd8cea24e396c37c327929c0135d46541626
|