596 lines
17 KiB
C++
596 lines
17 KiB
C++
/*
|
|
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
|
|
*/
|
|
#include "media/view/media_view_playback_controls.h"
|
|
|
|
#include "media/audio/media_audio.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"
|
|
#include "styles/style_media_view.h"
|
|
|
|
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((std::round(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) {
|
|
Painter p(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, value) / 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,
|
|
not_null<Delegate*> delegate)
|
|
: RpWidget(parent)
|
|
, _delegate(delegate)
|
|
, _playPauseResume(this, st::mediaviewPlayButton)
|
|
, _playbackSlider(this, st::mediaviewPlayback)
|
|
, _playbackProgress(std::make_unique<PlaybackProgress>())
|
|
, _volumeToggle(this, st::mediaviewVolumeToggle)
|
|
, _volumeController(this, st::mediaviewPlayback)
|
|
, _menuToggle(this, st::mediaviewMenuToggle)
|
|
, _fullScreenToggle(this, st::mediaviewFullScreenButton)
|
|
, _pictureInPicture(this, st::mediaviewPipButton)
|
|
, _playedAlready(this, st::mediaviewPlayProgressLabel)
|
|
, _toPlayLeft(this, st::mediaviewPlayProgressLabel)
|
|
, _menuStyle(st::mediaviewControlsPopupMenu)
|
|
, _fadeAnimation(std::make_unique<Ui::FadeAnimation>(this)) {
|
|
_fadeAnimation->show();
|
|
_fadeAnimation->setFinishedCallback([=] {
|
|
fadeFinished();
|
|
});
|
|
_fadeAnimation->setUpdatedCallback([=](float64 opacity) {
|
|
fadeUpdated(opacity);
|
|
});
|
|
|
|
_pictureInPicture->addClickHandler([=] {
|
|
_delegate->playbackControlsToPictureInPicture();
|
|
});
|
|
_menuToggle->addClickHandler([=] {
|
|
showMenu();
|
|
});
|
|
|
|
_volumeController->setValue(_delegate->playbackControlsCurrentVolume());
|
|
_volumeController->setChangeProgressCallback([=](float64 value) {
|
|
_delegate->playbackControlsVolumeChanged(value);
|
|
updateVolumeToggleIcon();
|
|
});
|
|
_volumeController->setChangeFinishedCallback([=](float64) {
|
|
_delegate->playbackControlsVolumeChangeFinished();
|
|
});
|
|
updateVolumeToggleIcon();
|
|
_volumeToggle->setClickedCallback([=] {
|
|
_delegate->playbackControlsVolumeToggled();
|
|
_volumeController->setValue(_delegate->playbackControlsCurrentVolume());
|
|
updateVolumeToggleIcon();
|
|
});
|
|
|
|
_playPauseResume->addClickHandler([=] {
|
|
if (_showPause) {
|
|
_delegate->playbackControlsPause();
|
|
} else {
|
|
_delegate->playbackControlsPlay();
|
|
}
|
|
});
|
|
_fullScreenToggle->addClickHandler([=] {
|
|
if (_inFullScreen) {
|
|
_delegate->playbackControlsFromFullScreen();
|
|
} else {
|
|
_delegate->playbackControlsToFullScreen();
|
|
}
|
|
});
|
|
|
|
_playbackProgress->setValueChangedCallback([=](
|
|
float64 value,
|
|
float64 receivedTill) {
|
|
_playbackSlider->setValue(value, receivedTill);
|
|
});
|
|
_playbackSlider->setChangeProgressCallback([=](float64 value) {
|
|
_playbackProgress->setValue(value, false);
|
|
|
|
// This may destroy PlaybackControls.
|
|
handleSeekProgress(value);
|
|
});
|
|
_playbackSlider->setChangeFinishedCallback([=](float64 value) {
|
|
_playbackProgress->setValue(value, false);
|
|
handleSeekFinished(value);
|
|
});
|
|
}
|
|
|
|
void PlaybackControls::handleSeekProgress(float64 progress) {
|
|
if (!_lastDurationMs) return;
|
|
|
|
const auto positionMs = std::clamp(
|
|
static_cast<crl::time>(progress * _lastDurationMs),
|
|
crl::time(0),
|
|
_lastDurationMs);
|
|
if (_seekPositionMs != positionMs) {
|
|
_seekPositionMs = positionMs;
|
|
refreshTimeTexts();
|
|
|
|
// This may destroy PlaybackControls.
|
|
_delegate->playbackControlsSeekProgress(positionMs);
|
|
}
|
|
}
|
|
|
|
void PlaybackControls::handleSeekFinished(float64 progress) {
|
|
if (!_lastDurationMs) return;
|
|
|
|
const auto positionMs = std::clamp(
|
|
static_cast<crl::time>(progress * _lastDurationMs),
|
|
crl::time(0),
|
|
_lastDurationMs);
|
|
_seekPositionMs = -1;
|
|
_delegate->playbackControlsSeekFinished(positionMs);
|
|
refreshTimeTexts();
|
|
}
|
|
|
|
template <typename Callback>
|
|
void PlaybackControls::startFading(Callback start) {
|
|
if (!_fadeAnimation->animating()) {
|
|
showChildren();
|
|
_playbackSlider->disablePaint(true);
|
|
_volumeController->disablePaint(true);
|
|
_childrenHidden = false;
|
|
}
|
|
start();
|
|
if (_fadeAnimation->animating()) {
|
|
for (const auto child : children()) {
|
|
if (child->isWidgetType()
|
|
&& child != _playbackSlider
|
|
&& child != _volumeController) {
|
|
static_cast<QWidget*>(child)->hide();
|
|
}
|
|
}
|
|
_childrenHidden = true;
|
|
} else {
|
|
fadeFinished();
|
|
}
|
|
_playbackSlider->disablePaint(false);
|
|
_volumeController->disablePaint(false);
|
|
}
|
|
|
|
void PlaybackControls::showAnimated() {
|
|
startFading([this]() {
|
|
_fadeAnimation->fadeIn(st::mediaviewShowDuration);
|
|
});
|
|
}
|
|
|
|
void PlaybackControls::hideAnimated() {
|
|
startFading([this]() {
|
|
_fadeAnimation->fadeOut(st::mediaviewHideDuration);
|
|
});
|
|
}
|
|
|
|
void PlaybackControls::fadeFinished() {
|
|
fadeUpdated(_fadeAnimation->visible() ? 1. : 0.);
|
|
}
|
|
|
|
void PlaybackControls::fadeUpdated(float64 opacity) {
|
|
_playbackSlider->setFadeOpacity(opacity);
|
|
_volumeController->setFadeOpacity(opacity);
|
|
}
|
|
|
|
void PlaybackControls::showMenu() {
|
|
if (_menu) {
|
|
_menu = nullptr;
|
|
return;
|
|
}
|
|
|
|
_menu.emplace(this, _menuStyle);
|
|
|
|
{
|
|
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::updatePlaybackSpeed(float64 speed) {
|
|
DEBUG_LOG(("Media playback speed: update to %1.").arg(speed));
|
|
_delegate->playbackControlsSpeedChanged(speed);
|
|
resizeEvent(nullptr);
|
|
}
|
|
|
|
void PlaybackControls::updatePlayback(const Player::TrackState &state) {
|
|
updatePlayPauseResumeState(state);
|
|
_playbackProgress->updateState(state, countDownloadedTillPercent(state));
|
|
updateTimeTexts(state);
|
|
}
|
|
|
|
void PlaybackControls::updateVolumeToggleIcon() {
|
|
const auto volume = _delegate->playbackControlsCurrentVolume();
|
|
_volumeToggle->setIconOverride([&] {
|
|
return (volume <= 0.)
|
|
? nullptr
|
|
: (volume < 1 / 2.)
|
|
? &st::mediaviewVolumeIcon1
|
|
: &st::mediaviewVolumeIcon2;
|
|
}(), [&] {
|
|
return (volume <= 0.)
|
|
? nullptr
|
|
: (volume < 1 / 2.)
|
|
? &st::mediaviewVolumeIcon1Over
|
|
: &st::mediaviewVolumeIcon2Over;
|
|
}());
|
|
}
|
|
|
|
float64 PlaybackControls::countDownloadedTillPercent(
|
|
const Player::TrackState &state) const {
|
|
if (_loadingReady > 0 && _loadingReady == _loadingTotal) {
|
|
return 1.;
|
|
}
|
|
const auto header = state.fileHeaderSize;
|
|
if (!header || _loadingReady <= header || _loadingTotal <= header) {
|
|
return 0.;
|
|
}
|
|
return (_loadingReady - header) / float64(_loadingTotal - header);
|
|
}
|
|
|
|
void PlaybackControls::setLoadingProgress(int ready, int total) {
|
|
if (_loadingReady == ready && _loadingTotal == total) {
|
|
return;
|
|
}
|
|
_loadingReady = ready;
|
|
_loadingTotal = total;
|
|
if (_loadingReady != 0 && _loadingReady != _loadingTotal) {
|
|
if (!_downloadProgress) {
|
|
_downloadProgress.create(this, st::mediaviewPlayProgressLabel);
|
|
_downloadProgress->setVisible(!_fadeAnimation->animating());
|
|
_loadingPercent = -1;
|
|
}
|
|
const auto progress = total ? (ready / float64(total)) : 0.;
|
|
const auto percent = int(std::round(progress * 100));
|
|
if (_loadingPercent != percent) {
|
|
_loadingPercent = percent;
|
|
_downloadProgress->setText(QString::number(percent) + '%');
|
|
updateDownloadProgressPosition();
|
|
refreshFadeCache();
|
|
}
|
|
} else {
|
|
_downloadProgress.destroy();
|
|
}
|
|
}
|
|
|
|
void PlaybackControls::refreshFadeCache() {
|
|
if (!_fadeAnimation->animating()) {
|
|
return;
|
|
}
|
|
startFading([&] {
|
|
_fadeAnimation->refreshCache();
|
|
});
|
|
}
|
|
|
|
void PlaybackControls::updatePlayPauseResumeState(const Player::TrackState &state) {
|
|
auto showPause = ShowPauseIcon(state.state) || (_seekPositionMs >= 0);
|
|
if (showPause != _showPause) {
|
|
_showPause = showPause;
|
|
_playPauseResume->setIconOverride(_showPause ? &st::mediaviewPauseIcon : nullptr, _showPause ? &st::mediaviewPauseIconOver : nullptr);
|
|
}
|
|
}
|
|
|
|
void PlaybackControls::updateTimeTexts(const Player::TrackState &state) {
|
|
qint64 position = 0, length = state.length;
|
|
|
|
if (Player::IsStoppedAtEnd(state.state)) {
|
|
position = state.length;
|
|
} else if (!Player::IsStoppedOrStopping(state.state)) {
|
|
position = state.position;
|
|
} else {
|
|
position = 0;
|
|
}
|
|
auto playFrequency = state.frequency;
|
|
auto playAlready = position / playFrequency;
|
|
auto playLeft = (state.length / playFrequency) - playAlready;
|
|
|
|
_lastDurationMs = (state.length * crl::time(1000)) / playFrequency;
|
|
|
|
_timeAlready = Ui::FormatDurationText(playAlready);
|
|
auto minus = QChar(8722);
|
|
_timeLeft = minus + Ui::FormatDurationText(playLeft);
|
|
|
|
if (_seekPositionMs < 0) {
|
|
refreshTimeTexts();
|
|
}
|
|
}
|
|
|
|
void PlaybackControls::refreshTimeTexts() {
|
|
auto alreadyChanged = false, leftChanged = false;
|
|
auto timeAlready = _timeAlready;
|
|
auto timeLeft = _timeLeft;
|
|
if (_seekPositionMs >= 0) {
|
|
auto playAlready = _seekPositionMs / crl::time(1000);
|
|
auto playLeft = (_lastDurationMs / crl::time(1000)) - playAlready;
|
|
|
|
timeAlready = Ui::FormatDurationText(playAlready);
|
|
auto minus = QChar(8722);
|
|
timeLeft = minus + Ui::FormatDurationText(playLeft);
|
|
}
|
|
|
|
_playedAlready->setText(timeAlready, &alreadyChanged);
|
|
_toPlayLeft->setText(timeLeft, &leftChanged);
|
|
if (alreadyChanged || leftChanged) {
|
|
resizeEvent(nullptr);
|
|
refreshFadeCache();
|
|
}
|
|
}
|
|
|
|
void PlaybackControls::setInFullScreen(bool inFullScreen) {
|
|
if (_inFullScreen != inFullScreen) {
|
|
_inFullScreen = inFullScreen;
|
|
_fullScreenToggle->setIconOverride(
|
|
_inFullScreen ? &st::mediaviewFullScreenOutIcon : nullptr,
|
|
_inFullScreen ? &st::mediaviewFullScreenOutIconOver : nullptr);
|
|
}
|
|
}
|
|
|
|
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;
|
|
_playedAlready->moveToLeft(textLeft + textSkip, textTop);
|
|
_toPlayLeft->moveToRight(textLeft + textSkip, textTop);
|
|
const auto remove = 2 * textLeft + 4 * textSkip + _playedAlready->width() + _toPlayLeft->width();
|
|
auto playbackWidth = width() - remove;
|
|
_playbackSlider->resize(playbackWidth, st::mediaviewPlayback.seekSize.height());
|
|
_playbackSlider->moveToLeft(textLeft + 2 * textSkip + _playedAlready->width(), st::mediaviewPlaybackTop);
|
|
|
|
_playPauseResume->moveToLeft(
|
|
(width() - _playPauseResume->width()) / 2,
|
|
st::mediaviewPlayButtonTop);
|
|
|
|
auto right = st::mediaviewMenuToggleSkip;
|
|
_menuToggle->moveToRight(right, st::mediaviewButtonsTop);
|
|
right += _menuToggle->width() + st::mediaviewPipButtonSkip;
|
|
_pictureInPicture->moveToRight(right, st::mediaviewButtonsTop);
|
|
right += _pictureInPicture->width() + st::mediaviewFullScreenButtonSkip;
|
|
_fullScreenToggle->moveToRight(right, st::mediaviewButtonsTop);
|
|
|
|
updateDownloadProgressPosition();
|
|
|
|
auto left = st::mediaviewVolumeToggleSkip;
|
|
_volumeToggle->moveToLeft(left, st::mediaviewVolumeTop);
|
|
left += _volumeToggle->width() + st::mediaviewVolumeSkip;
|
|
_volumeController->resize(
|
|
st::mediaviewVolumeWidth,
|
|
st::mediaviewPlayback.seekSize.height());
|
|
_volumeController->moveToLeft(left, st::mediaviewVolumeTop + (_volumeToggle->height() - _volumeController->height()) / 2);
|
|
}
|
|
|
|
void PlaybackControls::updateDownloadProgressPosition() {
|
|
if (!_downloadProgress) {
|
|
return;
|
|
}
|
|
const auto left = _playPauseResume->x() + _playPauseResume->width();
|
|
const auto right = _fullScreenToggle->x();
|
|
const auto available = right - left;
|
|
const auto x = left + (available - _downloadProgress->width()) / 2;
|
|
const auto y = _playPauseResume->y() + (_playPauseResume->height() - _downloadProgress->height()) / 2;
|
|
_downloadProgress->move(x, y);
|
|
}
|
|
|
|
void PlaybackControls::paintEvent(QPaintEvent *e) {
|
|
Painter p(this);
|
|
|
|
if (_fadeAnimation->paint(p)) {
|
|
return;
|
|
}
|
|
if (_childrenHidden) {
|
|
showChildren();
|
|
_playbackSlider->setFadeOpacity(1.);
|
|
_volumeController->setFadeOpacity(1.);
|
|
_childrenHidden = false;
|
|
}
|
|
Ui::FillRoundRect(p, rect(), st::mediaviewSaveMsgBg, Ui::MediaviewSaveCorners);
|
|
}
|
|
|
|
void PlaybackControls::mousePressEvent(QMouseEvent *e) {
|
|
e->accept(); // Don't pass event to the Media::View::OverlayWidget.
|
|
}
|
|
|
|
bool PlaybackControls::hasMenu() const {
|
|
return _menu != nullptr;
|
|
}
|
|
|
|
PlaybackControls::~PlaybackControls() = default;
|
|
|
|
} // namespace View
|
|
} // namespace Media
|