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; auto gifs = ::gifItems;
for_const (auto item, gifs) { for_const (auto item, gifs) {
if (auto media = item->getMedia()) { if (auto media = item->getMedia()) {
media->stopInline(); if (!media->isRoundVideoPlaying()) {
}
}
}
}
void stopRoundVideoPlayback() {
if (!::gifItems.isEmpty()) {
auto gifs = ::gifItems;
for_const (auto item, gifs) {
if (auto media = item->getMedia()) {
if (media->isRoundVideoPlaying()) {
media->stopInline(); media->stopInline();
} }
} }

View File

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

View File

@ -98,9 +98,11 @@ StackItemSection::StackItemSection(std::unique_ptr<Window::SectionMemento> &&mem
StackItemSection::~StackItemSection() { StackItemSection::~StackItemSection() {
} }
template <typename ToggleCallback> template <typename ToggleCallback, typename DraggedCallback>
MainWidget::Float::Float(QWidget *parent, HistoryItem *item, ToggleCallback callback) : widget(parent, item, [this, toggle = std::move(callback)](bool visible) { MainWidget::Float::Float(QWidget *parent, HistoryItem *item, ToggleCallback toggle, DraggedCallback dragged) : widget(parent, item, [this, toggle = std::move(toggle)](bool visible) {
toggle(this, 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) { _playerFloats.push_back(std::make_unique<Float>(this, item, [this](Float *instance, bool visible) {
instance->hiddenByWidget = !visible; instance->hiddenByWidget = !visible;
toggleFloatPlayer(instance); toggleFloatPlayer(instance);
}, [this](Float *instance, bool closed) {
finishFloatPlayerDrag(instance, closed);
})); }));
currentFloatPlayer()->corner = _playerFloatCorner;
currentFloatPlayer()->column = _playerFloatColumn;
checkFloatPlayerVisibility(); checkFloatPlayerVisibility();
} }
} }
@ -313,8 +319,15 @@ void MainWidget::updateFloatPlayerPosition(Float *instance) {
position = mapFromGlobal(position); position = mapFromGlobal(position);
auto hiddenTop = Window::IsTopCorner(instance->corner) ? -instance->widget->height() : height(); auto hiddenTop = Window::IsTopCorner(instance->corner) ? -instance->widget->height() : height();
auto visibleTop = position.y(); position.setY(anim::interpolate(hiddenTop, position.y(), visible));
instance->widget->move(position.x(), anim::interpolate(hiddenTop, visibleTop, 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) { void MainWidget::removeFloatPlayer(Float *instance) {
@ -358,6 +371,77 @@ Window::AbstractSectionWidget *MainWidget::getFloatPlayerSection(gsl::not_null<W
return _history; 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) { bool MainWidget::onForward(const PeerId &peer, ForwardWhatMessages what) {
PeerData *p = App::peer(peer); 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())) { 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(); destroyCallTopBar();
} }
}); });
App::stopRoundVideoPlayback();
} else { } else {
destroyCallTopBar(); destroyCallTopBar();
} }

View File

@ -463,8 +463,8 @@ protected:
private: private:
struct Float { struct Float {
template <typename ToggleCallback> template <typename ToggleCallback, typename DraggedCallback>
Float(QWidget *parent, HistoryItem *item, ToggleCallback callback); Float(QWidget *parent, HistoryItem *item, ToggleCallback callback, DraggedCallback dragged);
bool hiddenByWidget = false; bool hiddenByWidget = false;
bool hiddenByHistory = false; bool hiddenByHistory = false;
@ -472,7 +472,8 @@ private:
Animation visibleAnimation; Animation visibleAnimation;
Window::Corner corner = Window::Corner::TopRight; Window::Corner corner = Window::Corner::TopRight;
Window::Column column = Window::Column::Second; Window::Column column = Window::Column::Second;
QPoint position; QPoint dragFrom;
Animation draggedAnimation;
object_ptr<Media::Player::Float> widget; object_ptr<Media::Player::Float> widget;
}; };
@ -584,6 +585,8 @@ private:
return _playerFloats.empty() ? nullptr : _playerFloats.back().get(); return _playerFloats.empty() ? nullptr : _playerFloats.back().get();
} }
Window::AbstractSectionWidget *getFloatPlayerSection(gsl::not_null<Window::Column*> column); 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);
bool ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdates &updates); bool ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdates &updates);
@ -637,6 +640,8 @@ private:
object_ptr<Media::Player::Panel> _playerPanel; object_ptr<Media::Player::Panel> _playerPanel;
bool _playerUsingPanel = false; bool _playerUsingPanel = false;
std::vector<std::unique_ptr<Float>> _playerFloats; 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 QPointer<ConfirmBox> _forwardConfirm; // for single column layout
object_ptr<HistoryHider> _hider = { nullptr }; 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)); 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)); alSourcef(stream.source, AL_GAIN, ComputeVolume(type));
alSourcePlay(stream.source); alSourcePlay(stream.source);
if (IsPaused(state.state)) { if (IsPaused(state.state)) {
@ -781,6 +781,7 @@ void Mixer::pause(const AudioMsgId &audio, bool fast) {
} }
} break; } break;
case State::Pausing:
case State::Stopping: { case State::Stopping: {
track->state.state = fast ? State::Paused : State::Pausing; track->state.state = fast ? State::Paused : State::Pausing;
} break; } break;

View File

@ -29,9 +29,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Media { namespace Media {
namespace Player { 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) , _item(item)
, _toggleCallback(std::move(toggleCallback)) { , _toggleCallback(std::move(toggleCallback))
, _draggedCallback(std::move(draggedCallback)) {
auto media = _item->getMedia(); auto media = _item->getMedia();
t_assert(media != nullptr); t_assert(media != nullptr);
@ -56,14 +57,56 @@ Float::Float(QWidget *parent, HistoryItem *item, base::lambda<void(bool visible)
void Float::mousePressEvent(QMouseEvent *e) { void Float::mousePressEvent(QMouseEvent *e) {
_down = true; _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) { void Float::mouseReleaseEvent(QMouseEvent *e) {
if (_down && _item) { if (_down) {
if (auto media = _item->getMedia()) { _down = false;
if (auto media = _item ? _item->getMedia() : nullptr) {
media->playInline(); media->playInline();
} }
} }
if (_drag) {
finishDrag(outRatio() < 0.5);
}
}
void Float::finishDrag(bool closed) {
_drag = false;
if (_draggedCallback) {
_draggedCallback(closed);
}
} }
void Float::mouseDoubleClickEvent(QMouseEvent *e) { void Float::mouseDoubleClickEvent(QMouseEvent *e) {

View File

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

View File

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

View File

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