diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 689a9babd..2dbf79ec4 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -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(); } } diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index d41e7dada..8fe32c02a 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -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()); } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index e3a771fbe..a5d23d6c5 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -98,9 +98,11 @@ StackItemSection::StackItemSection(std::unique_ptr &&mem StackItemSection::~StackItemSection() { } -template -MainWidget::Float::Float(QWidget *parent, HistoryItem *item, ToggleCallback callback) : widget(parent, item, [this, toggle = std::move(callback)](bool visible) { +template +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(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_nullwidget->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(); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 4ae470d65..5a0c7a611 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -463,8 +463,8 @@ protected: private: struct Float { - template - Float(QWidget *parent, HistoryItem *item, ToggleCallback callback); + template + 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 widget; }; @@ -584,6 +585,8 @@ private: return _playerFloats.empty() ? nullptr : _playerFloats.back().get(); } Window::AbstractSectionWidget *getFloatPlayerSection(gsl::not_null 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 _playerPanel; bool _playerUsingPanel = false; std::vector> _playerFloats; + Window::Corner _playerFloatCorner = Window::Corner::TopRight; + Window::Column _playerFloatColumn = Window::Column::Second; QPointer _forwardConfirm; // for single column layout object_ptr _hider = { nullptr }; diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 5e5d6bc65..c0e1e9778 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -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; diff --git a/Telegram/SourceFiles/media/player/media_player_float.cpp b/Telegram/SourceFiles/media/player/media_player_float.cpp index 0c4bc4dd7..4f61129d6 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.cpp +++ b/Telegram/SourceFiles/media/player/media_player_float.cpp @@ -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 toggleCallback) : TWidget(parent) +Float::Float(QWidget *parent, HistoryItem *item, base::lambda toggleCallback, base::lambda 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::lambdapos(); +} + +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) { diff --git a/Telegram/SourceFiles/media/player/media_player_float.h b/Telegram/SourceFiles/media/player/media_player_float.h index 675df3f15..c7ae731dd 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.h +++ b/Telegram/SourceFiles/media/player/media_player_float.h @@ -29,21 +29,29 @@ namespace Player { class Float : public TWidget, private base::Subscriber { public: - Float(QWidget *parent, HistoryItem *item, base::lambda toggleCallback); + Float(QWidget *parent, HistoryItem *item, base::lambda toggleCallback, base::lambda 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 _toggleCallback; @@ -74,6 +85,11 @@ private: QPixmap _shadow; QImage _frame; bool _down = false; + QPoint _downPoint; + + bool _drag = false; + QPoint _dragLocalPoint; + base::lambda _draggedCallback; std::unique_ptr _roundPlayback; diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index a45a924ea..6d882541c 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -187,18 +187,23 @@ void Widget::updateVolumeToggleIcon() { } void Widget::setCloseCallback(base::lambda 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) { diff --git a/Telegram/SourceFiles/media/player/media_player_widget.h b/Telegram/SourceFiles/media/player/media_player_widget.h index 6b370cd0b..75a93fd29 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.h +++ b/Telegram/SourceFiles/media/player/media_player_widget.h @@ -46,7 +46,7 @@ public: Widget(QWidget *parent); void setCloseCallback(base::lambda 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 _closeCallback; bool _labelsOver = false; bool _labelsDown = false;