Added context menu to voice playback speed button.
Fixed #16868. Fixed #16138.
Before Width: | Height: | Size: 479 B |
Before Width: | Height: | Size: 1,015 B |
Before Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/voice_speed/voice_speed0.5.png
Normal file
After Width: | Height: | Size: 637 B |
BIN
Telegram/Resources/icons/voice_speed/voice_speed0.5@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/voice_speed/voice_speed0.5@3x.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/voice_speed/voice_speed1.5.png
Normal file
After Width: | Height: | Size: 603 B |
BIN
Telegram/Resources/icons/voice_speed/voice_speed1.5@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/voice_speed/voice_speed1.5@3x.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/voice_speed/voice_speed2.png
Normal file
After Width: | Height: | Size: 581 B |
BIN
Telegram/Resources/icons/voice_speed/voice_speed2@2x.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
Telegram/Resources/icons/voice_speed/voice_speed2@3x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
|
@ -2855,6 +2855,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_photo_editor_menu_flip" = "Flip";
|
"lng_photo_editor_menu_flip" = "Flip";
|
||||||
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
||||||
|
|
||||||
|
"lng_voice_speed_slow" = "Slow";
|
||||||
|
"lng_voice_speed_normal" = "Normal";
|
||||||
|
"lng_voice_speed_fast" = "Fast";
|
||||||
|
"lng_voice_speed_very_fast" = "Very fast";
|
||||||
|
|
||||||
// Wnd specific
|
// Wnd specific
|
||||||
|
|
||||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||||
|
|
|
@ -77,9 +77,9 @@ mediaPlayerSpeedButton: IconButton {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
|
||||||
icon: icon {
|
icon: icon {
|
||||||
{ "voice2x", mediaPlayerActiveFg, point(8px, 11px) }
|
{ "voice_speed/voice_speed2", mediaPlayerActiveFg }
|
||||||
};
|
};
|
||||||
iconPosition: point(0px, 0px);
|
iconPosition: point(3px, 10px);
|
||||||
|
|
||||||
rippleAreaPosition: point(3px, 5px);
|
rippleAreaPosition: point(3px, 5px);
|
||||||
rippleAreaSize: 25px;
|
rippleAreaSize: 25px;
|
||||||
|
@ -88,15 +88,38 @@ mediaPlayerSpeedButton: IconButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mediaPlayerSpeedDisabledIcon: icon {
|
mediaPlayerSpeedDisabledIcon: icon {
|
||||||
{ "voice2x", menuIconFg, point(8px, 11px)}
|
{ "voice_speed/voice_speed2", menuIconFg }
|
||||||
};
|
};
|
||||||
mediaPlayerSpeedDisabledIconOver: icon {
|
mediaPlayerSpeedDisabledIconOver: icon {
|
||||||
{ "voice2x", menuIconFgOver, point(8px, 11px)}
|
{ "voice_speed/voice_speed2", menuIconFgOver }
|
||||||
|
};
|
||||||
|
mediaPlayerSpeedSlowIcon: icon {
|
||||||
|
{ "voice_speed/voice_speed0.5", mediaPlayerActiveFg }
|
||||||
|
};
|
||||||
|
mediaPlayerSpeedSlowDisabledIcon: icon {
|
||||||
|
{ "voice_speed/voice_speed0.5", menuIconFg }
|
||||||
|
};
|
||||||
|
mediaPlayerSpeedSlowDisabledIconOver: icon {
|
||||||
|
{ "voice_speed/voice_speed0.5", menuIconFgOver }
|
||||||
|
};
|
||||||
|
mediaPlayerSpeedFastIcon: icon {
|
||||||
|
{ "voice_speed/voice_speed1.5", mediaPlayerActiveFg }
|
||||||
|
};
|
||||||
|
mediaPlayerSpeedFastDisabledIcon: icon {
|
||||||
|
{ "voice_speed/voice_speed1.5", menuIconFg }
|
||||||
|
};
|
||||||
|
mediaPlayerSpeedFastDisabledIconOver: icon {
|
||||||
|
{ "voice_speed/voice_speed1.5", menuIconFgOver }
|
||||||
};
|
};
|
||||||
mediaPlayerSpeedDisabledRippleBg: windowBgOver;
|
mediaPlayerSpeedDisabledRippleBg: windowBgOver;
|
||||||
mediaPlayerSpeedInactiveIcon: icon {
|
|
||||||
{ "voice2x", mediaPlayerInactiveFg, point(8px, 11px)}
|
mediaPlayerPopupMenu: PopupMenu(defaultPopupMenu) {
|
||||||
};
|
menu: Menu(defaultMenu) {
|
||||||
|
itemIconPosition: point(6px, 5px);
|
||||||
|
itemPadding: margins(34px, 8px, 17px, 7px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mediaPlayerMenuCheck: icon {{ "player_check", mediaPlayerActiveFg }};
|
||||||
|
|
||||||
mediaPlayerVolumeIcon0: icon {
|
mediaPlayerVolumeIcon0: icon {
|
||||||
{ "player_volume0", mediaPlayerActiveFg },
|
{ "player_volume0", mediaPlayerActiveFg },
|
||||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/continuous_sliders.h"
|
#include "ui/widgets/continuous_sliders.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
#include "ui/text/format_song_document_name.h"
|
#include "ui/text/format_song_document_name.h"
|
||||||
|
@ -60,6 +61,153 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Widget::SpeedButton : public Ui::IconButton {
|
||||||
|
public:
|
||||||
|
SpeedButton(QWidget *parent, const style::IconButton &st);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<> saved() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class SpeedController final {
|
||||||
|
public:
|
||||||
|
SpeedController() {
|
||||||
|
setSpeed(Core::App().settings().voicePlaybackSpeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<float64> speedValue() const {
|
||||||
|
return _speedChanged.events_starting_with(speed());
|
||||||
|
}
|
||||||
|
[[nodiscard]] rpl::producer<> saved() const {
|
||||||
|
return _saved.events();
|
||||||
|
}
|
||||||
|
[[nodiscard]] float64 speed() const {
|
||||||
|
return _isDefault ? 1. : _speed;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool isDefault() const {
|
||||||
|
return _isDefault;
|
||||||
|
}
|
||||||
|
[[nodiscard]] float64 lastNonDefaultSpeed() const {
|
||||||
|
return _speed;
|
||||||
|
}
|
||||||
|
void toggleDefault() {
|
||||||
|
_isDefault = !_isDefault;
|
||||||
|
_speedChanged.fire(speed());
|
||||||
|
}
|
||||||
|
void setSpeed(float64 newSpeed) {
|
||||||
|
if (!(_isDefault = (newSpeed == 1.))) {
|
||||||
|
_speed = newSpeed;
|
||||||
|
}
|
||||||
|
_speedChanged.fire(speed());
|
||||||
|
}
|
||||||
|
void save() {
|
||||||
|
Core::App().settings().setVoicePlaybackSpeed(speed());
|
||||||
|
Core::App().saveSettingsDelayed();
|
||||||
|
_saved.fire({});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float64 _speed = 2.;
|
||||||
|
bool _isDefault = true;
|
||||||
|
rpl::event_stream<float64> _speedChanged;
|
||||||
|
rpl::event_stream<> _saved;
|
||||||
|
};
|
||||||
|
|
||||||
|
SpeedController _speed;
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Widget::SpeedButton::SpeedButton(QWidget *parent, const style::IconButton &st)
|
||||||
|
: IconButton(parent, st) {
|
||||||
|
setClickedCallback([=] {
|
||||||
|
_speed.toggleDefault();
|
||||||
|
_speed.save();
|
||||||
|
});
|
||||||
|
|
||||||
|
struct Icons {
|
||||||
|
const style::icon *icon = nullptr;
|
||||||
|
const style::icon *over = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
_speed.speedValue(
|
||||||
|
) | rpl::start_with_next([=](float64 speed) {
|
||||||
|
const auto isDefaultSpeed = _speed.isDefault();
|
||||||
|
const auto nonDefaultSpeed = _speed.lastNonDefaultSpeed();
|
||||||
|
|
||||||
|
const auto icons = [&]() -> Icons {
|
||||||
|
if (nonDefaultSpeed == .5) {
|
||||||
|
return {
|
||||||
|
.icon = isDefaultSpeed
|
||||||
|
? &st::mediaPlayerSpeedSlowDisabledIcon
|
||||||
|
: &st::mediaPlayerSpeedSlowIcon,
|
||||||
|
.over = isDefaultSpeed
|
||||||
|
? &st::mediaPlayerSpeedSlowDisabledIconOver
|
||||||
|
: &st::mediaPlayerSpeedSlowIcon,
|
||||||
|
};
|
||||||
|
} else if (nonDefaultSpeed == 1.5) {
|
||||||
|
return {
|
||||||
|
.icon = isDefaultSpeed
|
||||||
|
? &st::mediaPlayerSpeedFastDisabledIcon
|
||||||
|
: &st::mediaPlayerSpeedFastIcon,
|
||||||
|
.over = isDefaultSpeed
|
||||||
|
? &st::mediaPlayerSpeedFastDisabledIconOver
|
||||||
|
: &st::mediaPlayerSpeedFastIcon,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
.icon = isDefaultSpeed
|
||||||
|
? &st::mediaPlayerSpeedDisabledIcon
|
||||||
|
: nullptr, // 2x icon.
|
||||||
|
.over = isDefaultSpeed
|
||||||
|
? &st::mediaPlayerSpeedDisabledIconOver
|
||||||
|
: nullptr, // 2x icon.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
setIconOverride(icons.icon, icons.over);
|
||||||
|
setRippleColorOverride(isDefaultSpeed
|
||||||
|
? &st::mediaPlayerSpeedDisabledRippleBg
|
||||||
|
: nullptr);
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::SpeedButton::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
|
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||||
|
this,
|
||||||
|
st::mediaPlayerPopupMenu);
|
||||||
|
|
||||||
|
const auto setPlaybackSpeed = [=](float64 speed) {
|
||||||
|
_speed.setSpeed(speed);
|
||||||
|
_speed.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto currentSpeed = _speed.speed();
|
||||||
|
const auto addSpeed = [&](float64 speed, QString text = QString()) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
text = QString::number(speed);
|
||||||
|
}
|
||||||
|
const auto action = _menu->addAction(
|
||||||
|
text,
|
||||||
|
[=] { setPlaybackSpeed(speed); },
|
||||||
|
(speed == currentSpeed) ? &st::mediaPlayerMenuCheck : nullptr);
|
||||||
|
};
|
||||||
|
addSpeed(0.5, tr::lng_voice_speed_slow(tr::now));
|
||||||
|
addSpeed(1., tr::lng_voice_speed_normal(tr::now));
|
||||||
|
addSpeed(1.5, tr::lng_voice_speed_fast(tr::now));
|
||||||
|
addSpeed(2., tr::lng_voice_speed_very_fast(tr::now));
|
||||||
|
|
||||||
|
_menu->popup(e->globalPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> Widget::SpeedButton::saved() const {
|
||||||
|
return _speed.saved();
|
||||||
|
}
|
||||||
|
|
||||||
Widget::PlayButton::PlayButton(QWidget *parent) : Ui::RippleButton(parent, st::mediaPlayerButton.ripple)
|
Widget::PlayButton::PlayButton(QWidget *parent) : Ui::RippleButton(parent, st::mediaPlayerButton.ripple)
|
||||||
, _layout(st::mediaPlayerButton, [this] { update(); }) {
|
, _layout(st::mediaPlayerButton, [this] { update(); }) {
|
||||||
resize(st::mediaPlayerButtonSize);
|
resize(st::mediaPlayerButtonSize);
|
||||||
|
@ -146,15 +294,10 @@ Widget::Widget(QWidget *parent, not_null<Main::Session*> session)
|
||||||
instance()->toggleRepeat(AudioMsgId::Type::Song);
|
instance()->toggleRepeat(AudioMsgId::Type::Song);
|
||||||
});
|
});
|
||||||
|
|
||||||
updatePlaybackSpeedIcon();
|
_playbackSpeed->saved(
|
||||||
_playbackSpeed->setClickedCallback([=] {
|
) | rpl::start_with_next([=] {
|
||||||
const auto doubled = (Core::App().settings().voicePlaybackSpeed()
|
|
||||||
== 2.);
|
|
||||||
Core::App().settings().setVoicePlaybackSpeed(doubled ? 1. : 2.);
|
|
||||||
instance()->updateVoicePlaybackSpeed();
|
instance()->updateVoicePlaybackSpeed();
|
||||||
updatePlaybackSpeedIcon();
|
}, lifetime());
|
||||||
Core::App().saveSettingsDelayed();
|
|
||||||
});
|
|
||||||
|
|
||||||
subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
|
subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
|
||||||
if (type == _type) {
|
if (type == _type) {
|
||||||
|
@ -403,16 +546,6 @@ void Widget::updateRepeatTrackIcon() {
|
||||||
_repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
|
_repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::updatePlaybackSpeedIcon() {
|
|
||||||
const auto speed = Core::App().settings().voicePlaybackSpeed();
|
|
||||||
const auto isDefaultSpeed = (speed == 1.);
|
|
||||||
_playbackSpeed->setIconOverride(
|
|
||||||
isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIcon : nullptr,
|
|
||||||
isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIconOver : nullptr);
|
|
||||||
_playbackSpeed->setRippleColorOverride(
|
|
||||||
isDefaultSpeed ? &st::mediaPlayerSpeedDisabledRippleBg : nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::checkForTypeChange() {
|
void Widget::checkForTypeChange() {
|
||||||
auto hasActiveType = [](AudioMsgId::Type type) {
|
auto hasActiveType = [](AudioMsgId::Type type) {
|
||||||
const auto current = instance()->current(type);
|
const auto current = instance()->current(type);
|
||||||
|
|
|
@ -35,6 +35,7 @@ namespace Media {
|
||||||
namespace Player {
|
namespace Player {
|
||||||
|
|
||||||
class PlayButton;
|
class PlayButton;
|
||||||
|
class SpeedButton;
|
||||||
class VolumeWidget;
|
class VolumeWidget;
|
||||||
struct TrackState;
|
struct TrackState;
|
||||||
|
|
||||||
|
@ -77,7 +78,6 @@ private:
|
||||||
void updateRepeatTrackIcon();
|
void updateRepeatTrackIcon();
|
||||||
void updateControlsVisibility();
|
void updateControlsVisibility();
|
||||||
void updateControlsGeometry();
|
void updateControlsGeometry();
|
||||||
void updatePlaybackSpeedIcon();
|
|
||||||
void createPrevNextButtons();
|
void createPrevNextButtons();
|
||||||
void destroyPrevNextButtons();
|
void destroyPrevNextButtons();
|
||||||
|
|
||||||
|
@ -113,6 +113,7 @@ private:
|
||||||
bool _labelsDown = false;
|
bool _labelsDown = false;
|
||||||
|
|
||||||
class PlayButton;
|
class PlayButton;
|
||||||
|
class SpeedButton;
|
||||||
object_ptr<Ui::FlatLabel> _nameLabel;
|
object_ptr<Ui::FlatLabel> _nameLabel;
|
||||||
object_ptr<Ui::LabelSimple> _timeLabel;
|
object_ptr<Ui::LabelSimple> _timeLabel;
|
||||||
object_ptr<Ui::IconButton> _previousTrack = { nullptr };
|
object_ptr<Ui::IconButton> _previousTrack = { nullptr };
|
||||||
|
@ -120,7 +121,7 @@ private:
|
||||||
object_ptr<Ui::IconButton> _nextTrack = { nullptr };
|
object_ptr<Ui::IconButton> _nextTrack = { nullptr };
|
||||||
object_ptr<Ui::IconButton> _volumeToggle;
|
object_ptr<Ui::IconButton> _volumeToggle;
|
||||||
object_ptr<Ui::IconButton> _repeatTrack;
|
object_ptr<Ui::IconButton> _repeatTrack;
|
||||||
object_ptr<Ui::IconButton> _playbackSpeed;
|
object_ptr<SpeedButton> _playbackSpeed;
|
||||||
object_ptr<Ui::IconButton> _close;
|
object_ptr<Ui::IconButton> _close;
|
||||||
object_ptr<Ui::PlainShadow> _shadow = { nullptr };
|
object_ptr<Ui::PlainShadow> _shadow = { nullptr };
|
||||||
object_ptr<Ui::FilledSlider> _playbackSlider;
|
object_ptr<Ui::FilledSlider> _playbackSlider;
|
||||||
|
|