chatgui: плеер — фикс drag, центр/fullscreen при открытии, resize за края с сохранением пропорций

- m_render WA_TransparentForMouseEvents — события мыши напрямую в окно (drag работает)
- открытие: видео влезает — натуральный размер по центру; больше экрана — fullscreen
- resize мышью за 4 края и 4 угла, пропорции сохраняются (якорь = противоположный край)
- нижний край доступен через eventFilter на панели контролов (пустые поля пропагируют)
- сброс fullscreen при повторном playFile
This commit is contained in:
evgeny committed 2026-09-08 01:31:12 +03:00
1 parent 76182d018f
commit d09a6af52a
7 files changed
+513 -17

No files matched your search

+142 -12
View File
@@ -70,9 +70,11 @@ VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
m_engine = new VideoPlayerEngine(this);
m_render = new VideoRenderWidget(this);
m_render->setAttribute(Qt::WA_TransparentForMouseEvents, true);
m_controls = new QWidget(this);
m_controls->setObjectName("videoControls");
m_controls->installEventFilter(this);
m_controls->setStyleSheet(
"#videoControls { background-color: rgba(0,0,0,0.65); }"
"QPushButton { background: transparent; color: white; border: none; font-size: 16px; }"
@@ -91,7 +93,7 @@ VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
m_timeLabel = new QLabel(QStringLiteral("0:00 / 0:00"), m_controls);
auto* lay = new QHBoxLayout(m_controls);
lay->setContentsMargins(8, 4, 8, 4);
lay->setContentsMargins(8, 3, 8, 6);
lay->setSpacing(6);
lay->addWidget(m_playBtn);
lay->addWidget(m_slider, 1);
@@ -123,6 +125,7 @@ void VideoPlayerWindow::playFile(const QString& path) {
m_render->clearFrame();
m_slider->setRange(0, 0);
m_timeLabel->setText(QStringLiteral("0:00 / 0:00"));
if (m_fullscreen) { showNormal(); m_fullscreen = false; setControlsVisible(true); unsetCursor(); }
show();
raise();
activateWindow();
@@ -133,16 +136,25 @@ void VideoPlayerWindow::onEngineOpened(int width, int height, qint64 durationMs,
Q_UNUSED(hasAudio);
m_slider->setRange(0, (int)durationMs);
updateTimeLabels(0);
QScreen* scr = QApplication::primaryScreen();
QRect ag = scr ? scr->availableGeometry() : QRect(0, 0, 1280, 720);
QSize s = fitWindowSize(width, height);
resize(s);
if (!m_fullscreen) {
QScreen* scr = QApplication::primaryScreen();
if (scr) {
QRect ag = scr->availableGeometry();
move(ag.center().x() - s.width() / 2, ag.center().y() - s.height() / 2);
}
if (width <= ag.width() && height <= ag.height()) {
/* видео помещается — натуральный размер по центру */
resize(s);
move(ag.center().x() - s.width() / 2, ag.center().y() - s.height() / 2);
} else {
/* видео больше экрана — fullscreen (нормальная геометрия для выхода уже задана) */
resize(s);
move(ag.center().x() - s.width() / 2, ag.center().y() - s.height() / 2);
showFullScreen();
m_fullscreen = true;
m_hideTimer->start();
}
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "VideoWindow: %dx%d dur=%lldms", width, height, (long long)durationMs);
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "VideoWindow: %dx%d dur=%lldms size=%dx%d fullscreen=%d",
width, height, (long long)durationMs, s.width(), s.height(), m_fullscreen ? 1 : 0);
}
void VideoPlayerWindow::onPositionChanged(qint64 ms) {
@@ -239,9 +251,10 @@ QSize VideoPlayerWindow::fitWindowSize(int vw, int vh) const {
if (vw <= 0 || vh <= 0) return QSize(640, 360);
QScreen* scr = QApplication::primaryScreen();
QSize avail(1280, 720);
if (scr) avail = scr->availableGeometry().size() * 0.85;
if (scr) avail = scr->availableGeometry().size();
QSize s(vw, vh);
s.scale(avail, Qt::KeepAspectRatio);
if (s.width() > avail.width() || s.height() > avail.height())
s.scale(avail, Qt::KeepAspectRatio);
return s;
}
@@ -268,6 +281,11 @@ void VideoPlayerWindow::mousePressEvent(QMouseEvent* e) {
/* клик в fullscreen — показать/скрыть контролы */
if (m_controls->isVisible()) hideControls();
else showControls();
return;
}
ResizeMode m = hitTestResize(e->pos());
if (m != ResizeNone) {
beginResize(m, mouseGlobalPos(e));
} else {
m_dragging = true;
m_dragStart = mouseGlobalPos(e);
@@ -281,19 +299,58 @@ void VideoPlayerWindow::mouseMoveEvent(QMouseEvent* e) {
showControls();
return;
}
if (m_resizeMode != ResizeNone && (e->buttons() & Qt::LeftButton)) {
applyResize(mouseGlobalPos(e));
return;
}
if (m_dragging && (e->buttons() & Qt::LeftButton)) {
move(m_windowStart + (mouseGlobalPos(e) - m_dragStart));
return;
}
if (!(e->buttons() & Qt::LeftButton))
updateResizeCursor(e->pos());
}
void VideoPlayerWindow::mouseReleaseEvent(QMouseEvent* e) {
if (e->button() == Qt::LeftButton) m_dragging = false;
if (e->button() == Qt::LeftButton) {
m_dragging = false;
m_resizeMode = ResizeNone;
}
}
void VideoPlayerWindow::mouseDoubleClickEvent(QMouseEvent* e) {
if (e->button() == Qt::LeftButton) toggleFullscreen();
}
bool VideoPlayerWindow::eventFilter(QObject* obj, QEvent* ev) {
/* Резрез за нижний край: панель контролов перекрывает нижнюю кромку окна,
* поэтому маршрутизируем события из её пустых полей в логику 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) 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;
}
}
}
return QWidget::eventFilter(obj, ev);
}
void VideoPlayerWindow::closeEvent(QCloseEvent* e) {
m_engine->close();
emit closed();
@@ -307,3 +364,76 @@ void VideoPlayerWindow::resizeEvent(QResizeEvent* e) {
m_controls->setGeometry(0, height() - ch, width(), ch);
m_controls->raise();
}
VideoPlayerWindow::ResizeMode VideoPlayerWindow::hitTestResize(const QPoint& pos) const {
const int M = 6;
int w = width(), h = height();
bool l = pos.x() <= M;
bool r = pos.x() >= w - M - 1;
bool t = pos.y() <= M;
bool b = pos.y() >= h - M - 1;
if (t && l) return ResizeTopLeft;
if (t && r) return ResizeTopRight;
if (b && l) return ResizeBottomLeft;
if (b && r) return ResizeBottomRight;
if (l) return ResizeLeft;
if (r) return ResizeRight;
if (t) return ResizeTop;
if (b) return ResizeBottom;
return ResizeNone;
}
void VideoPlayerWindow::updateResizeCursor(const QPoint& pos) {
switch (hitTestResize(pos)) {
case ResizeTopLeft: case ResizeBottomRight: setCursor(Qt::SizeFDiagCursor); break;
case ResizeTopRight: case ResizeBottomLeft: setCursor(Qt::SizeBDiagCursor); break;
case ResizeLeft: case ResizeRight: setCursor(Qt::SizeHorCursor); break;
case ResizeTop: case ResizeBottom: setCursor(Qt::SizeVerCursor); break;
default: unsetCursor(); break;
}
}
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;
}
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);
}