1
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-03-25 04:38:23 +00:00

Snap floating player to column and corner.

This commit is contained in:
John Preston 2017-05-23 21:00:57 +03:00
parent 8f290451b6
commit ee6d80673a
9 changed files with 185 additions and 42 deletions

View File

@ -2648,18 +2648,7 @@ namespace {
auto gifs = ::gifItems;
for_const (auto item, gifs) {
if (auto media = item->getMedia()) {
media->stopInline();
}
}
}
}
void stopRoundVideoPlayback() {
if (!::gifItems.isEmpty()) {
auto gifs = ::gifItems;
for_const (auto item, gifs) {
if (auto media = item->getMedia()) {
if (media->isRoundVideoPlaying()) {
if (!media->isRoundVideoPlaying()) {
media->stopInline();
}
}

View File

@ -2631,7 +2631,7 @@ void HistoryWidget::onScroll() {
}
bool HistoryWidget::isItemCompletelyHidden(HistoryItem *item) const {
auto top = _list->itemTop(item);
auto top = _list ? _list->itemTop(item) : -2;
if (top < 0) {
return true;
}
@ -3428,7 +3428,7 @@ bool HistoryWidget::wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn
QRect HistoryWidget::rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) {
if (playerColumn == Window::Column::Third && _tabbedSection) {
auto tabbedColumn = (myColumn == Window::Column::First) ? Window::Column::Second : Window::Column::Third;
return mapToGlobal(_tabbedSection->rectForFloatPlayer(tabbedColumn, playerColumn));
return _tabbedSection->rectForFloatPlayer(tabbedColumn, playerColumn);
}
return mapToGlobal(_scroll->geometry());
}

View File

@ -98,9 +98,11 @@ StackItemSection::StackItemSection(std::unique_ptr<Window::SectionMemento> &&mem
StackItemSection::~StackItemSection() {
}
template <typename ToggleCallback>
MainWidget::Float::Float(QWidget *parent, HistoryItem *item, ToggleCallback callback) : widget(parent, item, [this, toggle = std::move(callback)](bool visible) {
template <typename ToggleCallback, typename DraggedCallback>
MainWidget::Float::Float(QWidget *parent, HistoryItem *item, ToggleCallback toggle, DraggedCallback dragged) : widget(parent, item, [this, toggle = std::move(toggle)](bool visible) {
toggle(this, visible);
}, [this, dragged = std::move(dragged)](bool closed) {
dragged(this, closed);
}) {
}
@ -248,7 +250,11 @@ void MainWidget::checkCurrentFloatPlayer() {
_playerFloats.push_back(std::make_unique<Float>(this, item, [this](Float *instance, bool visible) {
instance->hiddenByWidget = !visible;
toggleFloatPlayer(instance);
}, [this](Float *instance, bool closed) {
finishFloatPlayerDrag(instance, closed);
}));
currentFloatPlayer()->corner = _playerFloatCorner;
currentFloatPlayer()->column = _playerFloatColumn;
checkFloatPlayerVisibility();
}
}
@ -313,8 +319,15 @@ void MainWidget::updateFloatPlayerPosition(Float *instance) {
position = mapFromGlobal(position);
auto hiddenTop = Window::IsTopCorner(instance->corner) ? -instance->widget->height() : height();
auto visibleTop = position.y();
instance->widget->move(position.x(), anim::interpolate(hiddenTop, visibleTop, visible));
position.setY(anim::interpolate(hiddenTop, position.y(), visible));
if (!instance->widget->dragged()) {
auto dragged = instance->draggedAnimation.current(1.);
if (dragged < 1.) {
position.setX(anim::interpolate(instance->dragFrom.x(), position.x(), dragged));
position.setY(anim::interpolate(instance->dragFrom.y(), position.y(), dragged));
}
instance->widget->move(position);
}
}
void MainWidget::removeFloatPlayer(Float *instance) {
@ -358,6 +371,77 @@ Window::AbstractSectionWidget *MainWidget::getFloatPlayerSection(gsl::not_null<W
return _history;
}
void MainWidget::updateFloatPlayerColumnCorner(QPoint center) {
Expects(!_playerFloats.empty());
auto size = _playerFloats.back()->widget->size();
auto min = INT_MAX;
auto checkSection = [this, center, size, &min](Window::AbstractSectionWidget *widget, Window::Column myColumn, Window::Column playerColumn) {
auto rect = mapFromGlobal(widget->rectForFloatPlayer(myColumn, playerColumn));
auto left = rect.x() + (size.width() / 2);
auto right = rect.x() + rect.width() - (size.width() / 2);
auto top = rect.y() + (size.height() / 2);
auto bottom = rect.y() + rect.height() - (size.height() / 2);
auto checkCorner = [this, playerColumn, &min](int distance, Window::Corner corner) {
if (min > distance) {
min = distance;
_playerFloatColumn = playerColumn;
_playerFloatCorner = corner;
}
};
checkCorner((QPoint(left, top) - center).manhattanLength(), Window::Corner::TopLeft);
checkCorner((QPoint(right, top) - center).manhattanLength(), Window::Corner::TopRight);
checkCorner((QPoint(left, bottom) - center).manhattanLength(), Window::Corner::BottomLeft);
checkCorner((QPoint(right, bottom) - center).manhattanLength(), Window::Corner::BottomRight);
};
if (!Adaptive::Normal()) {
if (Adaptive::OneColumn() && selectingPeer()) {
checkSection(_dialogs, Window::Column::First, Window::Column::First);
} else if (_overview) {
checkSection(_overview, Window::Column::Second, Window::Column::Second);
} else if (_wideSection) {
checkSection(_wideSection, Window::Column::Second, Window::Column::Second);
} else if (!Adaptive::OneColumn() || _history->peer()) {
checkSection(_history, Window::Column::Second, Window::Column::Second);
checkSection(_history, Window::Column::Second, Window::Column::Third);
} else {
checkSection(_dialogs, Window::Column::First, Window::Column::First);
}
} else {
checkSection(_dialogs, Window::Column::First, Window::Column::First);
if (_overview) {
checkSection(_overview, Window::Column::Second, Window::Column::Second);
} else if (_wideSection) {
checkSection(_wideSection, Window::Column::Second, Window::Column::Second);
} else {
checkSection(_history, Window::Column::Second, Window::Column::Second);
checkSection(_history, Window::Column::Second, Window::Column::Third);
}
}
}
void MainWidget::finishFloatPlayerDrag(Float *instance, bool closed) {
instance->dragFrom = instance->widget->pos();
updateFloatPlayerColumnCorner(instance->widget->geometry().center());
instance->column = _playerFloatColumn;
instance->corner = _playerFloatCorner;
instance->draggedAnimation.finish();
instance->draggedAnimation.start([this, instance] { updateFloatPlayerPosition(instance); }, 0., 1., st::slideDuration, anim::sineInOut);
updateFloatPlayerPosition(instance);
if (closed) {
if (auto item = instance->widget->item()) {
auto voiceData = Media::Player::instance()->current(AudioMsgId::Type::Voice);
if (_player && voiceData.contextId() == item->fullId()) {
_player->entity()->stopAndClose();
}
}
instance->widget->detach();
}
}
bool MainWidget::onForward(const PeerId &peer, ForwardWhatMessages what) {
PeerData *p = App::peer(peer);
if (!peer || (p->isChannel() && !p->asChannel()->canPublish() && p->asChannel()->isBroadcast()) || (p->isChat() && !p->asChat()->canWrite()) || (p->isUser() && p->asUser()->isInaccessible())) {
@ -1816,7 +1900,6 @@ void MainWidget::setCurrentCall(Calls::Call *call) {
destroyCallTopBar();
}
});
App::stopRoundVideoPlayback();
} else {
destroyCallTopBar();
}

View File

@ -463,8 +463,8 @@ protected:
private:
struct Float {
template <typename ToggleCallback>
Float(QWidget *parent, HistoryItem *item, ToggleCallback callback);
template <typename ToggleCallback, typename DraggedCallback>
Float(QWidget *parent, HistoryItem *item, ToggleCallback callback, DraggedCallback dragged);
bool hiddenByWidget = false;
bool hiddenByHistory = false;
@ -472,7 +472,8 @@ private:
Animation visibleAnimation;
Window::Corner corner = Window::Corner::TopRight;
Window::Column column = Window::Column::Second;
QPoint position;
QPoint dragFrom;
Animation draggedAnimation;
object_ptr<Media::Player::Float> widget;
};
@ -584,6 +585,8 @@ private:
return _playerFloats.empty() ? nullptr : _playerFloats.back().get();
}
Window::AbstractSectionWidget *getFloatPlayerSection(gsl::not_null<Window::Column*> column);
void finishFloatPlayerDrag(Float *instance, bool closed);
void updateFloatPlayerColumnCorner(QPoint center);
bool ptsUpdated(int32 pts, int32 ptsCount);
bool ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdates &updates);
@ -637,6 +640,8 @@ private:
object_ptr<Media::Player::Panel> _playerPanel;
bool _playerUsingPanel = false;
std::vector<std::unique_ptr<Float>> _playerFloats;
Window::Corner _playerFloatCorner = Window::Corner::TopRight;
Window::Column _playerFloatColumn = Window::Column::Second;
QPointer<ConfirmBox> _forwardConfirm; // for single column layout
object_ptr<HistoryHider> _hider = { nullptr };

View File

@ -316,7 +316,7 @@ void Mixer::Track::reattach(AudioMsgId::Type type) {
}
alSourcei(stream.source, AL_SAMPLE_OFFSET, qMax(state.position - bufferedPosition, 0LL));
if (!IsStopped(state.state)) {
if (!IsStopped(state.state) && state.state != State::PausedAtEnd) {
alSourcef(stream.source, AL_GAIN, ComputeVolume(type));
alSourcePlay(stream.source);
if (IsPaused(state.state)) {
@ -781,6 +781,7 @@ void Mixer::pause(const AudioMsgId &audio, bool fast) {
}
} break;
case State::Pausing:
case State::Stopping: {
track->state.state = fast ? State::Paused : State::Pausing;
} break;

View File

@ -29,9 +29,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Media {
namespace Player {
Float::Float(QWidget *parent, HistoryItem *item, base::lambda<void(bool visible)> toggleCallback) : TWidget(parent)
Float::Float(QWidget *parent, HistoryItem *item, base::lambda<void(bool visible)> toggleCallback, base::lambda<void(bool closed)> draggedCallback) : TWidget(parent)
, _item(item)
, _toggleCallback(std::move(toggleCallback)) {
, _toggleCallback(std::move(toggleCallback))
, _draggedCallback(std::move(draggedCallback)) {
auto media = _item->getMedia();
t_assert(media != nullptr);
@ -56,14 +57,56 @@ Float::Float(QWidget *parent, HistoryItem *item, base::lambda<void(bool visible)
void Float::mousePressEvent(QMouseEvent *e) {
_down = true;
_downPoint = e->pos();
}
void Float::mouseMoveEvent(QMouseEvent *e) {
if (_down && (e->pos() - _downPoint).manhattanLength() > QApplication::startDragDistance()) {
_down = false;
_drag = true;
_dragLocalPoint = e->pos();
} else if (_drag) {
auto delta = (e->pos() - _dragLocalPoint);
move(pos() + delta);
setOpacity(outRatio());
}
}
float64 Float::outRatio() const {
auto parent = parentWidget()->rect();
auto min = 1.;
if (x() < parent.x()) {
accumulate_min(min, 1. - (parent.x() - x()) / float64(width()));
}
if (y() < parent.y()) {
accumulate_min(min, 1. - (parent.y() - y()) / float64(height()));
}
if (x() + width() > parent.x() + parent.width()) {
accumulate_min(min, 1. - (x() + width() - parent.x() - parent.width()) / float64(width()));
}
if (y() + height() > parent.y() + parent.height()) {
accumulate_min(min, 1. - (y() + height() - parent.y() - parent.height()) / float64(height()));
}
return snap(min, 0., 1.);
}
void Float::mouseReleaseEvent(QMouseEvent *e) {
if (_down && _item) {
if (auto media = _item->getMedia()) {
if (_down) {
_down = false;
if (auto media = _item ? _item->getMedia() : nullptr) {
media->playInline();
}
}
if (_drag) {
finishDrag(outRatio() < 0.5);
}
}
void Float::finishDrag(bool closed) {
_drag = false;
if (_draggedCallback) {
_draggedCallback(closed);
}
}
void Float::mouseDoubleClickEvent(QMouseEvent *e) {

View File

@ -29,21 +29,29 @@ namespace Player {
class Float : public TWidget, private base::Subscriber {
public:
Float(QWidget *parent, HistoryItem *item, base::lambda<void(bool visible)> toggleCallback);
Float(QWidget *parent, HistoryItem *item, base::lambda<void(bool visible)> toggleCallback, base::lambda<void(bool closed)> draggedCallback);
HistoryItem *item() const {
return _item;
}
void setOpacity(float64 opacity) {
_opacity = opacity;
update();
if (_opacity != opacity) {
_opacity = opacity;
update();
}
}
void detach();
bool detached() const {
return !_item;
}
bool dragged() const {
return _drag;
}
void resetMouseState() {
_down = false;
if (_drag) {
finishDrag(false);
}
}
void ui_repaintHistoryItem(const HistoryItem *item) {
if (item == _item) {
@ -53,11 +61,13 @@ public:
protected:
void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseDoubleClickEvent(QMouseEvent *e) override;
private:
float64 outRatio() const;
Clip::Reader *getReader() const;
void repaintItem();
void prepareShadow();
@ -65,6 +75,7 @@ private:
bool fillFrame();
QRect getInnerRect() const;
void updatePlayback();
void finishDrag(bool closed);
HistoryItem *_item = nullptr;
base::lambda<void(bool visible)> _toggleCallback;
@ -74,6 +85,11 @@ private:
QPixmap _shadow;
QImage _frame;
bool _down = false;
QPoint _downPoint;
bool _drag = false;
QPoint _dragLocalPoint;
base::lambda<void(bool closed)> _draggedCallback;
std::unique_ptr<Clip::Playback> _roundPlayback;

View File

@ -187,18 +187,23 @@ void Widget::updateVolumeToggleIcon() {
}
void Widget::setCloseCallback(base::lambda<void()> callback) {
_close->setClickedCallback([this, callback = std::move(callback)] {
_voiceIsActive = false;
if (_type == AudioMsgId::Type::Voice) {
auto songData = instance()->current(AudioMsgId::Type::Song);
auto songState = mixer()->currentState(AudioMsgId::Type::Song);
if (songData == songState.id && !IsStoppedOrStopping(songState.state)) {
instance()->stop(AudioMsgId::Type::Voice);
return;
}
_closeCallback = std::move(callback);
_close->setClickedCallback([this] { stopAndClose(); });
}
void Widget::stopAndClose() {
_voiceIsActive = false;
if (_type == AudioMsgId::Type::Voice) {
auto songData = instance()->current(AudioMsgId::Type::Song);
auto songState = mixer()->currentState(AudioMsgId::Type::Song);
if (songData == songState.id && !IsStoppedOrStopping(songState.state)) {
instance()->stop(AudioMsgId::Type::Voice);
return;
}
callback();
});
}
if (_closeCallback) {
_closeCallback();
}
}
void Widget::setShadowGeometryToLeft(int x, int y, int w, int h) {

View File

@ -46,7 +46,7 @@ public:
Widget(QWidget *parent);
void setCloseCallback(base::lambda<void()> callback);
void stopAndClose();
void setShadowGeometryToLeft(int x, int y, int w, int h);
void showShadow();
void hideShadow();
@ -101,6 +101,7 @@ private:
// We change _voiceIsActive to false only manually or from tracksFinished().
AudioMsgId::Type _type = AudioMsgId::Type::Unknown;
bool _voiceIsActive = false;
base::lambda<void()> _closeCallback;
bool _labelsOver = false;
bool _labelsDown = false;