Allow resizing PiP.

This commit is contained in:
John Preston 2020-01-06 22:50:03 +03:00
parent 55b63cd2e3
commit a73520c9d8
3 changed files with 213 additions and 26 deletions

View File

@ -61,17 +61,125 @@ constexpr auto kPipLoaderPriority = 2;
return inner.topLeft() + QPoint(shiftx, shifty); return inner.topLeft() + QPoint(shiftx, shifty);
} }
[[nodiscard]] QRect Transformed(QRect original, QPoint delta, RectPart by) {
const auto min = st::pipMinimalSize;
const auto width = original.width();
const auto height = original.height();
const auto maxx = width - min;
const auto maxy = height - min;
switch (by) {
case RectPart::Center: return original.translated(delta);
case RectPart::TopLeft:
original.setTop(original.y() + std::min(delta.y(), maxy));
original.setLeft(original.x() + std::min(delta.x(), maxx));
return original;
case RectPart::TopRight:
original.setTop(original.y() + std::min(delta.y(), maxy));
original.setWidth(original.width() + std::max(delta.x(), -maxx));
return original;
case RectPart::BottomRight:
original.setHeight(original.height() + std::max(delta.y(), -maxy));
original.setWidth(original.width() + std::max(delta.x(), -maxx));
return original;
case RectPart::BottomLeft:
original.setHeight(original.height() + std::max(delta.y(), -maxy));
original.setLeft(original.x() + std::min(delta.x(), maxx));
return original;
case RectPart::Left:
original.setLeft(original.x() + std::min(delta.x(), maxx));
return original;
case RectPart::Top:
original.setTop(original.y() + std::min(delta.y(), maxy));
return original;
case RectPart::Right:
original.setWidth(original.width() + std::max(delta.x(), -maxx));
return original;
case RectPart::Bottom:
original.setHeight(original.height() + std::max(delta.y(), -maxy));
return original;
}
return original;
Unexpected("RectPart in PiP Transformed.");
}
[[nodiscard]] QRect Constrained(QRect original, QSize ratio, RectPart by) {
if (by == RectPart::Center) {
return original;
} else if (!original.width() && !original.height()) {
return QRect(original.topLeft(), ratio);
}
const auto widthLarger = (original.width() * ratio.height())
> (original.height() * ratio.width());
const auto newSize = ratio.scaled(
original.size(),
(((RectParts(by) & RectPart::AllCorners)
|| ((by == RectPart::Top || by == RectPart::Bottom)
&& widthLarger)
|| ((by == RectPart::Left || by == RectPart::Right)
&& !widthLarger))
? Qt::KeepAspectRatio
: Qt::KeepAspectRatioByExpanding));
switch (by) {
case RectPart::TopLeft:
return QRect(
original.topLeft() + QPoint(
original.width() - newSize.width(),
original.height() - newSize.height()),
newSize);
case RectPart::TopRight:
return QRect(
original.topLeft() + QPoint(
0,
original.height() - newSize.height()),
newSize);
case RectPart::BottomRight:
return QRect(original.topLeft(), newSize);
case RectPart::BottomLeft:
return QRect(
original.topLeft() + QPoint(
original.width() - newSize.width(),
0),
newSize);
case RectPart::Left:
return QRect(
original.topLeft() + QPoint(
(original.width() - newSize.width()),
(original.height() - newSize.height()) / 2),
newSize);
case RectPart::Top:
return QRect(
original.topLeft() + QPoint(
(original.width() - newSize.width()) / 2,
0),
newSize);
case RectPart::Right:
return QRect(
original.topLeft() + QPoint(
0,
(original.height() - newSize.height()) / 2),
newSize);
case RectPart::Bottom:
return QRect(
original.topLeft() + QPoint(
(original.width() - newSize.width()) / 2,
(original.height() - newSize.height())),
newSize);
}
Unexpected("RectPart in PiP Constrained.");
}
} // namespace } // namespace
PipPanel::PipPanel( PipPanel::PipPanel(
QWidget *parent, QWidget *parent,
Fn<void(QPainter&, const FrameRequest&)> paint) Fn<void(QPainter&, FrameRequest)> paint)
: _parent(parent) : _parent(parent)
, _paint(std::move(paint)) { , _paint(std::move(paint)) {
setWindowFlags(Qt::Tool setWindowFlags(Qt::Tool
| Qt::WindowStaysOnTopHint | Qt::WindowStaysOnTopHint
| Qt::FramelessWindowHint); | Qt::FramelessWindowHint);
setAttribute(Qt::WA_ShowWithoutActivating); setAttribute(Qt::WA_ShowWithoutActivating);
setMouseTracking(true);
resize(0, 0); resize(0, 0);
} }
@ -100,8 +208,12 @@ void PipPanel::setPosition(Position position) {
setPositionDefault(); setPositionDefault();
} }
QScreen *PipPanel::myScreen() const {
return windowHandle() ? windowHandle()->screen() : nullptr;
}
PipPanel::Position PipPanel::countPosition() const { PipPanel::Position PipPanel::countPosition() const {
const auto screen = windowHandle() ? windowHandle()->screen() : nullptr; const auto screen = myScreen();
if (!screen) { if (!screen) {
return Position(); return Position();
} }
@ -180,9 +292,13 @@ void PipPanel::setPositionOnScreen(Position position, QRect available) {
: scaled; : scaled;
// Apply minimal size. // Apply minimal size.
const auto min = st::pipMinimalSize;
const auto minimalSize = (_ratio.width() > _ratio.height())
? QSize(min * _ratio.width() / _ratio.height(), min)
: QSize(min, min * _ratio.height() / _ratio.width());
const auto size = QSize( const auto size = QSize(
std::max(normalized.width(), st::pipMinimalSize), std::max(normalized.width(), minimalSize.width()),
std::max(normalized.height(), st::pipMinimalSize)); std::max(normalized.height(), minimalSize.height()));
// Apply left-right screen borders. // Apply left-right screen borders.
const auto skip = st::pipBorderSkip; const auto skip = st::pipBorderSkip;
@ -224,6 +340,7 @@ void PipPanel::paintEvent(QPaintEvent *e) {
auto request = FrameRequest(); auto request = FrameRequest();
request.outer = size(); request.outer = size();
request.resize = _ratio.scaled(request.outer, Qt::KeepAspectRatio);
request.corners = RectPart(0) request.corners = RectPart(0)
| ((_attached & (RectPart::Left | RectPart::Top)) | ((_attached & (RectPart::Left | RectPart::Top))
? RectPart(0) ? RectPart(0)
@ -244,46 +361,111 @@ void PipPanel::mousePressEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton) { if (e->button() != Qt::LeftButton) {
return; return;
} }
_pressState = _overState;
_pressPoint = e->globalPos(); _pressPoint = e->globalPos();
} }
void PipPanel::mouseReleaseEvent(QMouseEvent *e) { void PipPanel::mouseReleaseEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton || !base::take(_pressPoint)) { if (e->button() != Qt::LeftButton || !base::take(_pressState)) {
return; return;
} else if (!base::take(_dragStartPosition)) { } else if (!base::take(_dragStartGeometry)) {
//playbackPauseResume(); //playbackPauseResume();
} else { } else {
finishDrag(e->globalPos()); finishDrag(e->globalPos());
} }
} }
void PipPanel::updateOverState(QPoint point) {
const auto size = st::pipResizeArea;
const auto overState = [&] {
if (point.x() < size) {
if (point.y() < size) {
return RectPart::TopLeft;
} else if (point.y() >= height() - size) {
return RectPart::BottomLeft;
} else {
return RectPart::Left;
}
} else if (point.x() >= width() - size) {
if (point.y() < size) {
return RectPart::TopRight;
} else if (point.y() >= height() - size) {
return RectPart::BottomRight;
} else {
return RectPart::Right;
}
} else if (point.y() < size) {
return RectPart::Top;
} else if (point.y() >= height() - size) {
return RectPart::Bottom;
} else {
return RectPart::Center;
}
}();
if (_overState != overState) {
_overState = overState;
setCursor([&] {
switch (_overState) {
case RectPart::Center:
return style::cur_default;
case RectPart::TopLeft:
case RectPart::BottomRight:
return style::cur_sizefdiag;
case RectPart::TopRight:
case RectPart::BottomLeft:
return style::cur_sizebdiag;
case RectPart::Left:
case RectPart::Right:
return style::cur_sizehor;
case RectPart::Top:
case RectPart::Bottom:
return style::cur_sizever;
}
Unexpected("State in PipPanel::updateOverState.");
}());
}
}
void PipPanel::mouseMoveEvent(QMouseEvent *e) { void PipPanel::mouseMoveEvent(QMouseEvent *e) {
if (!_pressPoint) { if (!_pressState) {
updateOverState(e->pos());
return; return;
} }
const auto point = e->globalPos(); const auto point = e->globalPos();
const auto distance = QApplication::startDragDistance(); const auto distance = QApplication::startDragDistance();
if (!_dragStartPosition if (!_dragStartGeometry
&& (point - *_pressPoint).manhattanLength() > distance) { && (point - _pressPoint).manhattanLength() > distance) {
_dragStartPosition = pos(); _dragStartGeometry = geometry();
} }
if (_dragStartPosition) { if (_dragStartGeometry) {
updatePosition(point); updatePosition(point);
} }
} }
void PipPanel::updatePosition(QPoint point) { void PipPanel::updatePosition(QPoint point) {
Expects(_dragStartPosition.has_value()); Expects(_dragStartGeometry.has_value());
Expects(_pressState.has_value());
const auto position = *_dragStartPosition + (point - *_pressPoint); const auto screen = (*_pressState == RectPart::Center)
const auto screen = ScreenFromPosition(point); ? ScreenFromPosition(point)
const auto clamped = ClampToEdges(screen, QRect(position, size())); : myScreen()
if (clamped != position) { ? myScreen()->availableGeometry()
moveAnimated(clamped); : QRect();
} else { if (screen.isEmpty()) {
_positionAnimation.stop(); return;
move(position);
} }
const auto geometry = Transformed(
*_dragStartGeometry,
point - _pressPoint,
*_pressState);
const auto valid = Constrained(geometry, _ratio, *_pressState);
//const auto clamped = ClampToEdges(screen, valid);
//if (clamped != position) {
// moveAnimated(clamped);
//} else {
_positionAnimation.stop();
setGeometry(valid);
//}
} }
void PipPanel::finishDrag(QPoint point) { void PipPanel::finishDrag(QPoint point) {
@ -392,7 +574,7 @@ void Pip::setupStreaming() {
}, _instance.lifetime()); }, _instance.lifetime());
} }
void Pip::paint(QPainter &p, const FrameRequest &request) { void Pip::paint(QPainter &p, FrameRequest request) {
const auto image = videoFrameForDirectPaint(request); const auto image = videoFrameForDirectPaint(request);
p.drawImage(0, 0, image); p.drawImage(0, 0, image);
if (_instance.player().ready()) { if (_instance.player().ready()) {

View File

@ -38,7 +38,7 @@ public:
PipPanel( PipPanel(
QWidget *parent, QWidget *parent,
Fn<void(QPainter&, const FrameRequest&)> paint); Fn<void(QPainter&, FrameRequest)> paint);
void setAspectRatio(QSize ratio); void setAspectRatio(QSize ratio);
[[nodiscard]] Position countPosition() const; [[nodiscard]] Position countPosition() const;
@ -55,18 +55,22 @@ private:
void setPositionDefault(); void setPositionDefault();
void setPositionOnScreen(Position position, QRect available); void setPositionOnScreen(Position position, QRect available);
QScreen *myScreen() const;
void finishDrag(QPoint point); void finishDrag(QPoint point);
void updatePosition(QPoint point); void updatePosition(QPoint point);
void updatePositionAnimated(); void updatePositionAnimated();
void updateOverState(QPoint point);
void moveAnimated(QPoint to); void moveAnimated(QPoint to);
QPointer<QWidget> _parent; QPointer<QWidget> _parent;
Fn<void(QPainter&, const FrameRequest&)> _paint; Fn<void(QPainter&, FrameRequest)> _paint;
RectParts _attached = RectParts(); RectParts _attached = RectParts();
QSize _ratio; QSize _ratio;
std::optional<QPoint> _pressPoint; RectPart _overState = RectPart();
std::optional<QPoint> _dragStartPosition; std::optional<RectPart> _pressState;
QPoint _pressPoint;
std::optional<QRect> _dragStartGeometry;
QPoint _positionAnimationFrom; QPoint _positionAnimationFrom;
QPoint _positionAnimationTo; QPoint _positionAnimationTo;
@ -87,7 +91,7 @@ private:
void setupPanel(); void setupPanel();
void setupStreaming(); void setupStreaming();
void paint(QPainter &p, const FrameRequest &request); void paint(QPainter &p, FrameRequest request);
void playbackPauseResume(); void playbackPauseResume();
void waitingAnimationCallback(); void waitingAnimationCallback();
void handleStreamingUpdate(Streaming::Update &&update); void handleStreamingUpdate(Streaming::Update &&update);

View File

@ -212,3 +212,4 @@ pipDefaultSize: 320px;
pipMinimalSize: 100px; pipMinimalSize: 100px;
pipBorderSkip: 20px; pipBorderSkip: 20px;
pipBorderSnapArea: 16px; pipBorderSnapArea: 16px;
pipResizeArea: 10px;