mirror of https://github.com/procxx/kepka.git
				
				
				
			
		
			
				
	
	
		
			394 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			394 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop version of Telegram messaging app, see https://telegram.org
 | |
| 
 | |
| Telegram Desktop is free software: you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation, either version 3 of the License, or
 | |
| (at your option) any later version.
 | |
| 
 | |
| It is distributed in the hope that it will be useful,
 | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | |
| GNU General Public License for more details.
 | |
| 
 | |
| In addition, as a special exception, the copyright holders give permission
 | |
| to link the code of portions of this program with the OpenSSL library.
 | |
| 
 | |
| Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 | |
| Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 | |
| */
 | |
| #include "media/player/media_player_cover.h"
 | |
| 
 | |
| #include "ui/widgets/labels.h"
 | |
| #include "ui/widgets/continuous_sliders.h"
 | |
| #include "ui/widgets/buttons.h"
 | |
| #include "media/media_audio.h"
 | |
| #include "media/view/media_clip_playback.h"
 | |
| #include "media/player/media_player_button.h"
 | |
| #include "media/player/media_player_instance.h"
 | |
| #include "media/player/media_player_volume_controller.h"
 | |
| #include "styles/style_media_player.h"
 | |
| #include "styles/style_mediaview.h"
 | |
| 
 | |
| namespace Media {
 | |
| namespace Player {
 | |
| 
 | |
| using ButtonState = PlayButtonLayout::State;
 | |
| 
 | |
| class CoverWidget::PlayButton : public Ui::AbstractButton {
 | |
| public:
 | |
| 	PlayButton(QWidget *parent);
 | |
| 
 | |
| 	void setState(ButtonState state) {
 | |
| 		_layout.setState(state);
 | |
| 	}
 | |
| 	void finishTransform() {
 | |
| 		_layout.finishTransform();
 | |
| 	}
 | |
| 
 | |
| protected:
 | |
| 	void paintEvent(QPaintEvent *e) override;
 | |
| 
 | |
| private:
 | |
| 	PlayButtonLayout _layout;
 | |
| 
 | |
| };
 | |
| 
 | |
| CoverWidget::PlayButton::PlayButton(QWidget *parent) : Ui::AbstractButton(parent)
 | |
| , _layout(st::mediaPlayerPanelButton, [this] { update(); }) {
 | |
| 	resize(st::mediaPlayerPanelButtonSize);
 | |
| 	setCursor(style::cur_pointer);
 | |
| }
 | |
| 
 | |
| void CoverWidget::PlayButton::paintEvent(QPaintEvent *e) {
 | |
| 	Painter p(this);
 | |
| 
 | |
| 	p.translate(st::mediaPlayerPanelButtonPosition.x(), st::mediaPlayerPanelButtonPosition.y());
 | |
| 	_layout.paint(p, st::mediaPlayerActiveFg);
 | |
| }
 | |
| 
 | |
| CoverWidget::CoverWidget(QWidget *parent) : TWidget(parent)
 | |
| , _nameLabel(this, st::mediaPlayerName)
 | |
| , _timeLabel(this, st::mediaPlayerTime)
 | |
| , _close(this, st::mediaPlayerPanelClose)
 | |
| , _playbackSlider(this, st::mediaPlayerPanelPlayback)
 | |
| , _playback(std::make_unique<Clip::Playback>())
 | |
| , _playPause(this)
 | |
| , _volumeToggle(this, st::mediaPlayerVolumeToggle)
 | |
| , _volumeController(this)
 | |
| , _pinPlayer(this, st::mediaPlayerPanelPinButton)
 | |
| , _repeatTrack(this, st::mediaPlayerRepeatButton) {
 | |
| 	setAttribute(Qt::WA_OpaquePaintEvent);
 | |
| 	resize(width(), st::mediaPlayerCoverHeight);
 | |
| 
 | |
| 	_close->hide();
 | |
| 	_nameLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
 | |
| 	_timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
 | |
| 	setMouseTracking(true);
 | |
| 
 | |
| 	_playback->setInLoadingStateChangedCallback([this](bool loading) {
 | |
| 		_playbackSlider->setDisabled(loading);
 | |
| 	});
 | |
| 	_playback->setValueChangedCallback([this](float64 value) {
 | |
| 		_playbackSlider->setValue(value);
 | |
| 	});
 | |
| 	_playbackSlider->setChangeProgressCallback([this](float64 value) {
 | |
| 		_playback->setValue(value, false);
 | |
| 		handleSeekProgress(value);
 | |
| 	});
 | |
| 	_playbackSlider->setChangeFinishedCallback([this](float64 value) {
 | |
| 		_playback->setValue(value, false);
 | |
| 		handleSeekFinished(value);
 | |
| 	});
 | |
| 	_playPause->setClickedCallback([this] {
 | |
| 		instance()->playPauseCancelClicked(AudioMsgId::Type::Song);
 | |
| 	});
 | |
| 
 | |
| 	updateRepeatTrackIcon();
 | |
| 	_repeatTrack->setClickedCallback([this] {
 | |
| 		instance()->toggleRepeat(AudioMsgId::Type::Song);
 | |
| 	});
 | |
| 
 | |
| 	updateVolumeToggleIcon();
 | |
| 	_volumeToggle->setClickedCallback([this]() {
 | |
| 		Global::SetSongVolume((Global::SongVolume() > 0) ? 0. : Global::RememberedSongVolume());
 | |
| 		mixer()->setSongVolume(Global::SongVolume());
 | |
| 		Global::RefSongVolumeChanged().notify();
 | |
| 	});
 | |
| 	subscribe(Global::RefSongVolumeChanged(), [this] { updateVolumeToggleIcon(); });
 | |
| 	subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
 | |
| 		if (type == AudioMsgId::Type::Song) {
 | |
| 			updateRepeatTrackIcon();
 | |
| 		}
 | |
| 	});
 | |
| 	subscribe(instance()->playlistChangedNotifier(), [this](AudioMsgId::Type type) {
 | |
| 		if (type == AudioMsgId::Type::Song) {
 | |
| 			handlePlaylistUpdate();
 | |
| 		}
 | |
| 	});
 | |
| 	subscribe(instance()->updatedNotifier(), [this](const TrackState &state) {
 | |
| 		if (state.id.type() == AudioMsgId::Type::Song) {
 | |
| 			handleSongUpdate(state);
 | |
| 		}
 | |
| 	});
 | |
| 	subscribe(instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) {
 | |
| 		if (type == AudioMsgId::Type::Song) {
 | |
| 			handleSongChange();
 | |
| 		}
 | |
| 	});
 | |
| 	handleSongChange();
 | |
| 
 | |
| 	handleSongUpdate(mixer()->currentState(AudioMsgId::Type::Song));
 | |
| 	_playPause->finishTransform();
 | |
| }
 | |
| 
 | |
| void CoverWidget::setPinCallback(ButtonCallback &&callback) {
 | |
| 	_pinPlayer->setClickedCallback(std::move(callback));
 | |
| }
 | |
| 
 | |
| void CoverWidget::setCloseCallback(ButtonCallback &&callback) {
 | |
| 	_close->setClickedCallback(std::move(callback));
 | |
| }
 | |
| 
 | |
| void CoverWidget::handleSeekProgress(float64 progress) {
 | |
| 	if (!_lastDurationMs) return;
 | |
| 
 | |
| 	auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs);
 | |
| 	if (_seekPositionMs != positionMs) {
 | |
| 		_seekPositionMs = positionMs;
 | |
| 		updateTimeLabel();
 | |
| 		instance()->startSeeking(AudioMsgId::Type::Song);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CoverWidget::handleSeekFinished(float64 progress) {
 | |
| 	if (!_lastDurationMs) return;
 | |
| 
 | |
| 	auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs);
 | |
| 	_seekPositionMs = -1;
 | |
| 
 | |
| 	auto type = AudioMsgId::Type::Song;
 | |
| 	auto state = Media::Player::mixer()->currentState(type);
 | |
| 	if (state.id && state.length) {
 | |
| 		Media::Player::mixer()->seek(type, qRound(progress * state.length));
 | |
| 	}
 | |
| 
 | |
| 	instance()->stopSeeking(type);
 | |
| }
 | |
| 
 | |
| void CoverWidget::resizeEvent(QResizeEvent *e) {
 | |
| 	auto widthForName = width() - 2 * (st::mediaPlayerPanelPadding);
 | |
| 	widthForName -= _timeLabel->width() + 2 * st::normalFont->spacew;
 | |
| 	_nameLabel->resizeToWidth(widthForName);
 | |
| 	updateLabelPositions();
 | |
| 	_close->moveToRight(0, 0);
 | |
| 
 | |
| 	int skip = (st::mediaPlayerPanelPlayback.seekSize.width() / 2);
 | |
| 	int length = (width() - 2 * st::mediaPlayerPanelPadding + st::mediaPlayerPanelPlayback.seekSize.width());
 | |
| 	_playbackSlider->setGeometry(st::mediaPlayerPanelPadding - skip, st::mediaPlayerPanelPlaybackTop, length, 2 * st::mediaPlayerPanelPlaybackPadding + st::mediaPlayerPanelPlayback.width);
 | |
| 
 | |
| 	auto top = st::mediaPlayerPanelVolumeToggleTop;
 | |
| 	auto right = st::mediaPlayerPanelPlayLeft;
 | |
| 	_repeatTrack->moveToRight(right, top); right += _repeatTrack->width();
 | |
| 	_pinPlayer->moveToRight(right, top); right += _pinPlayer->width() + st::mediaPlayerPanelVolumeSkip;
 | |
| 	_volumeController->moveToRight(right, st::mediaPlayerPanelVolumeTop); right += _volumeController->width() + st::mediaPlayerPanelVolumeToggleSkip;
 | |
| 	_volumeToggle->moveToRight(right, top);
 | |
| 
 | |
| 	updatePlayPrevNextPositions();
 | |
| }
 | |
| 
 | |
| void CoverWidget::paintEvent(QPaintEvent *e) {
 | |
| 	Painter p(this);
 | |
| 	p.fillRect(e->rect(), st::windowBg);
 | |
| }
 | |
| 
 | |
| void CoverWidget::mouseMoveEvent(QMouseEvent *e) {
 | |
| 	auto closeAreaLeft = st::mediaPlayerPanelPadding + _nameLabel->width();
 | |
| 	auto closeAreaHeight = _nameLabel->y() + _nameLabel->height();
 | |
| 	auto closeArea = myrtlrect(closeAreaLeft, 0, width() - closeAreaLeft, closeAreaHeight);
 | |
| 	auto closeVisible = closeArea.contains(e->pos());
 | |
| 	setCloseVisible(closeVisible);
 | |
| }
 | |
| 
 | |
| void CoverWidget::leaveEventHook(QEvent *e) {
 | |
| 	setCloseVisible(false);
 | |
| }
 | |
| 
 | |
| void CoverWidget::setCloseVisible(bool visible) {
 | |
| 	if (visible == _close->isHidden()) {
 | |
| 		_close->setVisible(visible);
 | |
| 		_timeLabel->setVisible(!visible);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CoverWidget::updatePlayPrevNextPositions() {
 | |
| 	auto left = st::mediaPlayerPanelPlayLeft;
 | |
| 	auto top = st::mediaPlayerPanelPlayTop;
 | |
| 	if (_previousTrack) {
 | |
| 		_previousTrack->moveToLeft(left, top); left += _previousTrack->width() + st::mediaPlayerPanelPlaySkip;
 | |
| 		_playPause->moveToLeft(left, top); left += _playPause->width() + st::mediaPlayerPanelPlaySkip;
 | |
| 		_nextTrack->moveToLeft(left, top);
 | |
| 	} else {
 | |
| 		_playPause->moveToLeft(left, top);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CoverWidget::updateLabelPositions() {
 | |
| 	_nameLabel->moveToLeft(st::mediaPlayerPanelPadding, st::mediaPlayerPanelNameTop - st::mediaPlayerName.style.font->ascent);
 | |
| 	_timeLabel->moveToRight(st::mediaPlayerPanelPadding, st::mediaPlayerPanelNameTop - st::mediaPlayerTime.font->ascent);
 | |
| }
 | |
| 
 | |
| void CoverWidget::updateRepeatTrackIcon() {
 | |
| 	_repeatTrack->setIconOverride(instance()->repeatEnabled(AudioMsgId::Type::Song) ? nullptr : &st::mediaPlayerRepeatInactiveIcon);
 | |
| }
 | |
| 
 | |
| void CoverWidget::handleSongUpdate(const TrackState &state) {
 | |
| 	if (!state.id.audio() || !state.id.audio()->song()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (state.id.audio()->loading()) {
 | |
| 		_playback->updateLoadingState(state.id.audio()->progress());
 | |
| 	} else {
 | |
| 		_playback->updateState(state);
 | |
| 	}
 | |
| 
 | |
| 	auto stopped = IsStoppedOrStopping(state.state);
 | |
| 	auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
 | |
| 	if (instance()->isSeeking(AudioMsgId::Type::Song)) {
 | |
| 		showPause = true;
 | |
| 	}
 | |
| 	auto buttonState = [audio = state.id.audio(), showPause] {
 | |
| 		if (audio->loading()) {
 | |
| 			return ButtonState::Cancel;
 | |
| 		} else if (showPause) {
 | |
| 			return ButtonState::Pause;
 | |
| 		}
 | |
| 		return ButtonState::Play;
 | |
| 	};
 | |
| 	_playPause->setState(buttonState());
 | |
| 
 | |
| 	updateTimeText(state);
 | |
| }
 | |
| 
 | |
| void CoverWidget::updateTimeText(const TrackState &state) {
 | |
| 	QString time;
 | |
| 	qint64 position = 0, length = 0, display = 0;
 | |
| 	auto frequency = state.frequency;
 | |
| 	if (!IsStoppedOrStopping(state.state)) {
 | |
| 		display = position = state.position;
 | |
| 		length = state.length;
 | |
| 	} else {
 | |
| 		length = state.length ? state.length : (state.id.audio()->song()->duration * frequency);
 | |
| 	}
 | |
| 
 | |
| 	_lastDurationMs = (state.length * 1000LL) / frequency;
 | |
| 
 | |
| 	if (state.id.audio()->loading()) {
 | |
| 		_time = QString::number(qRound(state.id.audio()->progress() * 100)) + '%';
 | |
| 		_playbackSlider->setDisabled(true);
 | |
| 	} else {
 | |
| 		display = display / frequency;
 | |
| 		_time = formatDurationText(display);
 | |
| 		_playbackSlider->setDisabled(false);
 | |
| 	}
 | |
| 	if (_seekPositionMs < 0) {
 | |
| 		updateTimeLabel();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CoverWidget::updateTimeLabel() {
 | |
| 	auto timeLabelWidth = _timeLabel->width();
 | |
| 	if (_seekPositionMs >= 0) {
 | |
| 		auto playAlready = _seekPositionMs / 1000LL;
 | |
| 		_timeLabel->setText(formatDurationText(playAlready));
 | |
| 	} else {
 | |
| 		_timeLabel->setText(_time);
 | |
| 	}
 | |
| 	if (timeLabelWidth != _timeLabel->width()) {
 | |
| 		_nameLabel->resizeToWidth(width() - 2 * (st::mediaPlayerPanelPadding) - _timeLabel->width() - st::normalFont->spacew);
 | |
| 		updateLabelPositions();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CoverWidget::handleSongChange() {
 | |
| 	auto current = instance()->current(AudioMsgId::Type::Song);
 | |
| 	auto song = current.audio()->song();
 | |
| 	if (!song) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	TextWithEntities textWithEntities;
 | |
| 	if (song->performer.isEmpty()) {
 | |
| 		textWithEntities.text = song->title.isEmpty() ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
 | |
| 	} else {
 | |
| 		auto title = song->title.isEmpty() ? qsl("Unknown Track") : TextUtilities::Clean(song->title);
 | |
| 		textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title;
 | |
| 		textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
 | |
| 	}
 | |
| 	_nameLabel->setMarkedText(textWithEntities);
 | |
| 
 | |
| 	handlePlaylistUpdate();
 | |
| }
 | |
| 
 | |
| void CoverWidget::handlePlaylistUpdate() {
 | |
| 	auto current = instance()->current(AudioMsgId::Type::Song);
 | |
| 	auto playlist = instance()->playlist(AudioMsgId::Type::Song);
 | |
| 	auto index = playlist.indexOf(current.contextId());
 | |
| 	if (!current || index < 0) {
 | |
| 		destroyPrevNextButtons();
 | |
| 	} else {
 | |
| 		createPrevNextButtons();
 | |
| 		auto previousEnabled = (index > 0);
 | |
| 		auto nextEnabled = (index + 1 < playlist.size());
 | |
| 		_previousTrack->setIconOverride(previousEnabled ? nullptr : &st::mediaPlayerPanelPreviousDisabledIcon);
 | |
| 		_previousTrack->setCursor(previousEnabled ? style::cur_pointer : style::cur_default);
 | |
| 		_nextTrack->setIconOverride(nextEnabled ? nullptr : &st::mediaPlayerPanelNextDisabledIcon);
 | |
| 		_nextTrack->setCursor(nextEnabled ? style::cur_pointer : style::cur_default);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CoverWidget::createPrevNextButtons() {
 | |
| 	if (!_previousTrack) {
 | |
| 		_previousTrack.create(this, st::mediaPlayerPanelPreviousButton);
 | |
| 		_previousTrack->show();
 | |
| 		_previousTrack->setClickedCallback([this]() {
 | |
| 			instance()->previous();
 | |
| 		});
 | |
| 		_nextTrack.create(this, st::mediaPlayerPanelNextButton);
 | |
| 		_nextTrack->show();
 | |
| 		_nextTrack->setClickedCallback([this]() {
 | |
| 			instance()->next();
 | |
| 		});
 | |
| 		updatePlayPrevNextPositions();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CoverWidget::destroyPrevNextButtons() {
 | |
| 	if (_previousTrack) {
 | |
| 		_previousTrack.destroy();
 | |
| 		_nextTrack.destroy();
 | |
| 		updatePlayPrevNextPositions();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CoverWidget::updateVolumeToggleIcon() {
 | |
| 	auto icon = []() -> const style::icon * {
 | |
| 		auto volume = Global::SongVolume();
 | |
| 		if (volume > 0) {
 | |
| 			if (volume < 1 / 3.) {
 | |
| 				return &st::mediaPlayerVolumeIcon1;
 | |
| 			} else if (volume < 2 / 3.) {
 | |
| 				return &st::mediaPlayerVolumeIcon2;
 | |
| 			}
 | |
| 			return &st::mediaPlayerVolumeIcon3;
 | |
| 		}
 | |
| 		return nullptr;
 | |
| 	};
 | |
| 	_volumeToggle->setIconOverride(icon());
 | |
| }
 | |
| 
 | |
| } // namespace Player
 | |
| } // namespace Media
 |