|
|
|
|
@ -15,6 +15,7 @@
|
|
|
|
|
#include <QApplication> |
|
|
|
|
#include <QScreen> |
|
|
|
|
#include <QStyle> |
|
|
|
|
#include <QProxyStyle> |
|
|
|
|
#include <QWindow> |
|
|
|
|
|
|
|
|
|
/* ─── VideoRenderWidget ─── */ |
|
|
|
|
@ -52,23 +53,24 @@ static QString fmtTime(qint64 ms) {
|
|
|
|
|
return QString("%1:%2").arg(s / 60).arg(s % 60, 2, 10, QChar('0')); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Слайдер перемотки: клик по любому месту шкалы = прыжок к позиции (драг кружка сохраняется). */ |
|
|
|
|
class SeekSlider : public QSlider { |
|
|
|
|
static QPoint mouseGlobalPos(const QMouseEvent* e) { |
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
|
|
|
|
return e->globalPosition().toPoint(); |
|
|
|
|
#else |
|
|
|
|
return e->globalPos(); |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Слайдер перемотки: клик по любому месту шкалы = прыжок к позиции (нативно через стиль). */ |
|
|
|
|
class SeekSliderStyle : public QProxyStyle { |
|
|
|
|
public: |
|
|
|
|
using QSlider::QSlider; |
|
|
|
|
|
|
|
|
|
protected: |
|
|
|
|
void mousePressEvent(QMouseEvent* e) override { |
|
|
|
|
if (e->button() == Qt::LeftButton) { |
|
|
|
|
setSliderDown(true); |
|
|
|
|
int v = QStyle::sliderValueFromPosition(minimum(), maximum(), |
|
|
|
|
(int)e->position().x(), width()); |
|
|
|
|
setValue(v); |
|
|
|
|
emit sliderPressed(); |
|
|
|
|
e->accept(); |
|
|
|
|
} else { |
|
|
|
|
QSlider::mousePressEvent(e); |
|
|
|
|
} |
|
|
|
|
using QProxyStyle::QProxyStyle; |
|
|
|
|
|
|
|
|
|
int styleHint(StyleHint hint, const QStyleOption* opt, const QWidget* w, |
|
|
|
|
QStyleHintReturn* ret) const override { |
|
|
|
|
if (hint == QStyle::SH_Slider_AbsoluteSetButtons) |
|
|
|
|
return (int)Qt::LeftButton; |
|
|
|
|
return QProxyStyle::styleHint(hint, opt, w, ret); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
@ -100,7 +102,8 @@ VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
|
|
|
|
|
|
|
|
|
|
m_playBtn = new QPushButton(QStringLiteral("⏸"), m_controls); |
|
|
|
|
m_playBtn->setToolTip(QStringLiteral("Пауза")); |
|
|
|
|
m_slider = new SeekSlider(Qt::Horizontal, m_controls); |
|
|
|
|
m_slider = new QSlider(Qt::Horizontal, m_controls); |
|
|
|
|
m_slider->setStyle(new SeekSliderStyle()); |
|
|
|
|
m_slider->setRange(0, 0); |
|
|
|
|
m_timeLabel = new QLabel(QStringLiteral("0:00 / 0:00"), m_controls); |
|
|
|
|
|
|
|
|
|
@ -125,11 +128,6 @@ VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
|
|
|
|
|
m_hideTimer->setInterval(2500); |
|
|
|
|
connect(m_hideTimer, &QTimer::timeout, this, &VideoPlayerWindow::hideControls); |
|
|
|
|
|
|
|
|
|
m_snapTimer = new QTimer(this); |
|
|
|
|
m_snapTimer->setSingleShot(true); |
|
|
|
|
m_snapTimer->setInterval(150); |
|
|
|
|
connect(m_snapTimer, &QTimer::timeout, this, &VideoPlayerWindow::snapToAspect); |
|
|
|
|
|
|
|
|
|
connect(m_playBtn, &QPushButton::clicked, this, &VideoPlayerWindow::onPlayPause); |
|
|
|
|
connect(m_closeBtn, &QPushButton::clicked, this, &VideoPlayerWindow::onClose); |
|
|
|
|
connect(m_slider, &QSlider::sliderPressed, this, &VideoPlayerWindow::onSliderPressed); |
|
|
|
|
@ -161,7 +159,6 @@ void VideoPlayerWindow::onEngineOpened(int width, int height, qint64 durationMs,
|
|
|
|
|
Q_UNUSED(hasAudio); |
|
|
|
|
m_slider->setRange(0, (int)durationMs); |
|
|
|
|
updateTimeLabels(0); |
|
|
|
|
m_videoAspect = (height > 0) ? (double)width / height : 0.0; |
|
|
|
|
|
|
|
|
|
QScreen* scr = QApplication::primaryScreen(); |
|
|
|
|
QRect ag = scr ? scr->availableGeometry() : QRect(0, 0, 1280, 720); |
|
|
|
|
@ -312,8 +309,7 @@ void VideoPlayerWindow::mousePressEvent(QMouseEvent* e) {
|
|
|
|
|
} |
|
|
|
|
ResizeMode m = hitTestResize(e->pos()); |
|
|
|
|
if (m != ResizeNone) { |
|
|
|
|
m_resizeEdges = edgesForMode(m); |
|
|
|
|
if (windowHandle()) windowHandle()->startSystemResize(m_resizeEdges); |
|
|
|
|
beginResize(m, mouseGlobalPos(e)); |
|
|
|
|
} else { |
|
|
|
|
if (windowHandle()) windowHandle()->startSystemMove(); |
|
|
|
|
} |
|
|
|
|
@ -325,25 +321,44 @@ void VideoPlayerWindow::mouseMoveEvent(QMouseEvent* e) {
|
|
|
|
|
showControls(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (m_resizeMode != ResizeNone && (e->buttons() & Qt::LeftButton)) { |
|
|
|
|
applyResize(mouseGlobalPos(e)); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (!(e->buttons() & Qt::LeftButton)) |
|
|
|
|
updateResizeCursor(e->pos()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void VideoPlayerWindow::mouseReleaseEvent(QMouseEvent* e) { |
|
|
|
|
if (e->button() == Qt::LeftButton) m_resizeMode = ResizeNone; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void VideoPlayerWindow::mouseDoubleClickEvent(QMouseEvent* e) { |
|
|
|
|
if (e->button() == Qt::LeftButton) toggleFullscreen(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool VideoPlayerWindow::eventFilter(QObject* obj, QEvent* ev) { |
|
|
|
|
/* Резрез за нижний край: панель контролов перекрывает нижнюю кромку окна,
|
|
|
|
|
* поэтому маршрутизируем press из её пустых полей в нативный resize. */ |
|
|
|
|
if (obj == m_controls && !m_fullscreen && ev->type() == QEvent::MouseButtonPress) { |
|
|
|
|
QMouseEvent* me = static_cast<QMouseEvent*>(ev); |
|
|
|
|
if (me->button() == Qt::LeftButton) { |
|
|
|
|
* поэтому маршрутизируем события из её пустых полей в логику resize. */ |
|
|
|
|
if (obj == m_controls && !m_fullscreen) { |
|
|
|
|
QEvent::Type t = ev->type(); |
|
|
|
|
if (t == QEvent::MouseButtonPress || t == QEvent::MouseMove || t == QEvent::MouseButtonRelease) { |
|
|
|
|
QMouseEvent* me = static_cast<QMouseEvent*>(ev); |
|
|
|
|
QPoint winPos = m_controls->mapTo(this, me->pos()); |
|
|
|
|
ResizeMode m = hitTestResize(winPos); |
|
|
|
|
if (m != ResizeNone) { |
|
|
|
|
m_resizeEdges = edgesForMode(m); |
|
|
|
|
if (windowHandle()) windowHandle()->startSystemResize(m_resizeEdges); |
|
|
|
|
if (m == ResizeNone) return QWidget::eventFilter(obj, ev); |
|
|
|
|
|
|
|
|
|
QPoint gp = mouseGlobalPos(me); |
|
|
|
|
if (t == QEvent::MouseButtonPress && me->button() == Qt::LeftButton) { |
|
|
|
|
beginResize(m, gp); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
if (t == QEvent::MouseMove && m_resizeMode != ResizeNone && (me->buttons() & Qt::LeftButton)) { |
|
|
|
|
applyResize(gp); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
if (t == QEvent::MouseButtonRelease && me->button() == Qt::LeftButton) { |
|
|
|
|
m_resizeMode = ResizeNone; |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -365,8 +380,6 @@ void VideoPlayerWindow::resizeEvent(QResizeEvent* e) {
|
|
|
|
|
m_controls->raise(); |
|
|
|
|
m_closeBtn->setGeometry(width() - 40, 8, 32, 32); |
|
|
|
|
m_closeBtn->raise(); |
|
|
|
|
/* во время нативного resize снэпаем пропорцию после окончания жеста */ |
|
|
|
|
if (m_resizeEdges != 0) m_snapTimer->start(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
VideoPlayerWindow::ResizeMode VideoPlayerWindow::hitTestResize(const QPoint& pos) const { |
|
|
|
|
@ -397,33 +410,47 @@ void VideoPlayerWindow::updateResizeCursor(const QPoint& pos) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Qt::Edges VideoPlayerWindow::edgesForMode(ResizeMode m) { |
|
|
|
|
switch (m) { |
|
|
|
|
case ResizeLeft: return Qt::LeftEdge; |
|
|
|
|
case ResizeRight: return Qt::RightEdge; |
|
|
|
|
case ResizeTop: return Qt::TopEdge; |
|
|
|
|
case ResizeBottom: return Qt::BottomEdge; |
|
|
|
|
case ResizeTopLeft: return Qt::TopEdge | Qt::LeftEdge; |
|
|
|
|
case ResizeTopRight: return Qt::TopEdge | Qt::RightEdge; |
|
|
|
|
case ResizeBottomLeft: return Qt::BottomEdge | Qt::LeftEdge; |
|
|
|
|
case ResizeBottomRight: return Qt::BottomEdge | Qt::RightEdge; |
|
|
|
|
default: return {}; |
|
|
|
|
void VideoPlayerWindow::beginResize(ResizeMode m, const QPoint& globalPos) { |
|
|
|
|
m_resizeMode = m; |
|
|
|
|
m_resizeStartGeom = geometry(); |
|
|
|
|
m_resizeStartGlobal = globalPos; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void VideoPlayerWindow::applyResize(const QPoint& globalPos) { |
|
|
|
|
const int kMinW = 160; |
|
|
|
|
QRect g = m_resizeStartGeom; |
|
|
|
|
double aspect = (g.height() > 0) ? (double)g.width() / g.height() : 1.0; |
|
|
|
|
int minH = qMax(1, qRound(kMinW / aspect)); |
|
|
|
|
int dx = globalPos.x() - m_resizeStartGlobal.x(); |
|
|
|
|
int dy = globalPos.y() - m_resizeStartGlobal.y(); |
|
|
|
|
|
|
|
|
|
int newW = g.width(), newH = g.height(); |
|
|
|
|
bool widthDriven = true; |
|
|
|
|
switch (m_resizeMode) { |
|
|
|
|
case ResizeRight: case ResizeTopRight: case ResizeBottomRight: newW = g.width() + dx; break; |
|
|
|
|
case ResizeLeft: case ResizeTopLeft: case ResizeBottomLeft: newW = g.width() - dx; break; |
|
|
|
|
case ResizeBottom: newH = g.height() + dy; widthDriven = false; break; |
|
|
|
|
case ResizeTop: newH = g.height() - dy; widthDriven = false; break; |
|
|
|
|
default: return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void VideoPlayerWindow::snapToAspect() { |
|
|
|
|
if (m_videoAspect <= 0.0 || m_resizeEdges == 0) return; |
|
|
|
|
Qt::Edges edges = m_resizeEdges; |
|
|
|
|
m_resizeEdges = {}; /* сброс до resize(), чтобы снэп не ре-триггерил сам себя */ |
|
|
|
|
int w = width(), h = height(); |
|
|
|
|
if (w <= 0 || h <= 0) return; |
|
|
|
|
|
|
|
|
|
bool verticalOnly = !(edges & (Qt::LeftEdge | Qt::RightEdge)); |
|
|
|
|
if (verticalOnly) { |
|
|
|
|
int newW = qRound(h * m_videoAspect); |
|
|
|
|
if (qAbs(newW - w) > 1) resize(newW, h); |
|
|
|
|
} else { |
|
|
|
|
int newH = qRound(w / m_videoAspect); |
|
|
|
|
if (qAbs(newH - h) > 1) resize(w, newH); |
|
|
|
|
if (widthDriven) newH = qRound(newW / aspect); |
|
|
|
|
else newW = qRound(newH * aspect); |
|
|
|
|
|
|
|
|
|
if (newW < kMinW) { newW = kMinW; newH = minH; } |
|
|
|
|
if (newH < minH) { newH = minH; newW = kMinW; } |
|
|
|
|
|
|
|
|
|
int cx = g.center().x(), cy = g.center().y(); |
|
|
|
|
int left = g.left(), top = g.top(); |
|
|
|
|
switch (m_resizeMode) { |
|
|
|
|
case ResizeRight: left = g.left(); top = cy - newH / 2; break; |
|
|
|
|
case ResizeLeft: left = g.right() - newW + 1; top = cy - newH / 2; break; |
|
|
|
|
case ResizeBottom: left = cx - newW / 2; top = g.top(); break; |
|
|
|
|
case ResizeTop: left = cx - newW / 2; top = g.bottom() - newH + 1; break; |
|
|
|
|
case ResizeTopLeft: left = g.right() - newW + 1; top = g.bottom() - newH + 1; break; |
|
|
|
|
case ResizeTopRight: left = g.left(); top = g.bottom() - newH + 1; break; |
|
|
|
|
case ResizeBottomLeft: left = g.right() - newW + 1; top = g.top(); break; |
|
|
|
|
case ResizeBottomRight: left = g.left(); top = g.top(); break; |
|
|
|
|
default: return; |
|
|
|
|
} |
|
|
|
|
setGeometry(left, top, newW, newH); |
|
|
|
|
} |
|
|
|
|
|