diff options
| author | Anton <anton@picapica.im> | 2026-10-03 20:33:04 +0400 |
|---|---|---|
| committer | Anton <anton@picapica.im> | 2026-10-03 20:33:04 +0400 |
| commit | 835b124bd871afecd88c1e5e9105e3af4d4c5b27 (patch) | |
| tree | 42ee00cb6f37472fe36c802530cdc9a288ba87bd /pica-client/callwindow.cpp | |
| parent | 76225a538e1e960eda9e31a34846f4bc62adef87 (diff) | |
| download | pica-pica-835b124bd871afecd88c1e5e9105e3af4d4c5b27.tar.gz pica-pica-835b124bd871afecd88c1e5e9105e3af4d4c5b27.zip | |
pica-client: callwindow layout, video resize
Diffstat (limited to 'pica-client/callwindow.cpp')
| -rw-r--r-- | pica-client/callwindow.cpp | 151 |
1 files changed, 137 insertions, 14 deletions
diff --git a/pica-client/callwindow.cpp b/pica-client/callwindow.cpp index 81959e3..0b7d309 100644 --- a/pica-client/callwindow.cpp +++ b/pica-client/callwindow.cpp @@ -19,10 +19,19 @@ #include <QHBoxLayout> #include <QVBoxLayout> #include <QCloseEvent> +#include <QEvent> +#include <QGuiApplication> +#include <QScreen> #include <QDebug> +// Smallest the video area is allowed to become. Low enough that the window +// stays freely resizable - the video area is the part that gives, so a floor +// here is a floor under the whole window - while still leaving something +// recognizable on screen. +static const QSize kMinVideoSize(160, 120); + CallWindow::CallWindow(QByteArray peer_id, bool incoming) - : m_peer_id(peer_id), is_incoming(incoming) + : m_peer_id(peer_id), is_incoming(incoming), videoAreaOpened(false) { setAttribute(Qt::WA_DeleteOnClose); @@ -40,30 +49,55 @@ CallWindow::CallWindow(QByteArray peer_id, bool incoming) lbTransport->hide(); lbVideo = new QLabel(this); lbVideo->setAlignment(Qt::AlignCenter); - lbVideo->setMinimumSize(320, 240); + // A label holding a pixmap reports that pixmap's size as both its + // preferred and its minimum size, which would make the video area's size + // depend on the picture currently in it: the window could not be made + // smaller than the last frame drawn, and the layout would be recomputed on + // every frame. Ignored breaks that - the label is given whatever space the + // stretch factors below say and never asks for any. + lbVideo->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + // Small enough to let the window be resized freely; the size the video + // area actually opens at is set once by openVideoArea(). + lbVideo->setMinimumSize(kMinVideoSize); lbVideo->hide(); #ifdef HAVE_VAAPI videoGpu = VaapiRenderWidget::create(this); - videoGpu->widget()->setMinimumSize(320, 240); + videoGpu->widget()->setMinimumSize(kMinVideoSize); videoGpu->widget()->hide(); connect(videoGpu->widget(), SIGNAL(renderingFailed(QString)), this, SLOT(gpu_rendering_failed(QString))); #endif callTimer = new QTimer(this); callElapsedSeconds = 0; - lv->addWidget(lbTimer); - lv->addWidget(lbTransport); - lv->addWidget(lbVideo); + // Only the video gets a stretch factor, so it takes all the height the + // rows around it do not need. Left at the default of zero, every item + // whose size policy allows it to grow gets an equal share of the spare + // height instead - which for two QLabels and the video meant the video + // got about a third of it and the timer and the transport line sat in + // tall empty bands. + // + // Only one of the two video widgets is ever visible and a hidden widget is + // given no space, so both can be stretched. + lv->addWidget(lbTimer, 0); + lv->addWidget(lbTransport, 0); + lv->addWidget(lbVideo, 1); #ifdef HAVE_VAAPI - lv->addWidget(videoGpu->widget()); + lv->addWidget(videoGpu->widget(), 1); #endif - lh->addWidget(pbAccept, Qt::AlignLeft); - lh->addWidget(pbCall, Qt::AlignLeft); - lh->addWidget(pbHang, Qt::AlignRight); - lv->addLayout(lh); + // Note that the second argument of addWidget() is a stretch factor and not + // an alignment - passing Qt::AlignLeft here, as this did, compiles, since + // Qt::Alignment converts to int, and quietly means a stretch of 1. A + // spacer does what the alignment was meant to. + lh->addWidget(pbAccept); + lh->addWidget(pbCall); + lh->addStretch(1); + lh->addWidget(pbHang); + lv->addLayout(lh, 0); setLayout(lv); + lbVideo->installEventFilter(this); + connect(pbCall, SIGNAL(clicked()), this, SLOT(call())); connect(pbHang, SIGNAL(clicked()), this, SLOT(hang())); connect(pbAccept, SIGNAL(clicked()), this, SLOT(accept())); @@ -112,12 +146,78 @@ void CallWindow::showRemoteFrame(QImage frame) if (frame.isNull()) return; + lastRemoteFrame = frame; + if (lbVideo->isHidden()) + { + openVideoArea(frame.size()); lbVideo->show(); + reportVideoArea(); + } + + updateRemoteFrame(); +} + +void CallWindow::updateRemoteFrame() +{ + if (lastRemoteFrame.isNull() || lbVideo->isHidden()) + return; + + // Normally a no-op: the decoder was told the size of this area and the + // frame already arrived at it, and scale() returns the frame untouched + // when there is nothing to do. It earns its place across a resize, when + // the frames already decoded at the old size have still to be drawn at + // the new one. + QImage scaled = videoScaler.scale(lastRemoteFrame, lbVideo->size()); + + if (scaled.isNull()) + return; + + lbVideo->setPixmap(QPixmap::fromImage(scaled)); +} + +void CallWindow::reportVideoArea() +{ + // While the label is hidden there is nothing to size frames for, and its + // geometry is meaningless anyway. isHidden() rather than isVisible(): what + // matters is whether this label is the renderer in use, not whether the + // window happens to be on screen right now. When the GPU widget has the + // video area it scales frames itself, on the GPU, and wants them at their + // own resolution - see showRemoteHwFrame(). + if (lbVideo->isHidden()) + return; - lbVideo->setPixmap(QPixmap::fromImage(frame).scaled(lbVideo->size(), - Qt::KeepAspectRatio, - Qt::SmoothTransformation)); + emit video_area_changed(lbVideo->size()); +} + +void CallWindow::openVideoArea(QSize frame) +{ + if (videoAreaOpened || frame.isEmpty()) + return; + + videoAreaOpened = true; + + // The window has been sized for a call with no video in it, so the video + // area is about to appear into whatever the layout can spare - which is + // its minimum. Grow the window by what the video needs on top of that. + // + // Only the first frame does this. After it the window's size is the user's + // business, and a later frame must not undo a deliberate resize. + QSize wanted = frame; + QScreen *screen = QGuiApplication::primaryScreen(); + + if (screen) + { + // Leave the rows above and below the video the height they have + // now - a peer sending 1080p to a 1080 high screen should not open + // a window taller than the desktop. + QSize available = screen->availableGeometry().size() - QSize(0, height()); + + wanted = VideoScaler::fitted(frame, available.expandedTo(kMinVideoSize)); + } + + resize(qMax(width(), wanted.width()), + height() + qMax(0, wanted.height() - lbVideo->height())); } #ifdef HAVE_VAAPI @@ -138,8 +238,14 @@ void CallWindow::showRemoteHwFrame(AVFramePtr frame) // show them - the GL widget takes over the video area instead. if (videoGpu->widget()->isHidden()) { + openVideoArea(QSize(frame->width, frame->height)); lbVideo->hide(); videoGpu->widget()->show(); + + // The widget scales the surface itself, on the GPU, so the decoder + // has no reason to produce frames at any particular size - and it + // is not producing these ones at all. + emit video_area_changed(QSize()); } videoGpu->setFrame(frame); @@ -155,6 +261,12 @@ void CallWindow::gpu_rendering_failed(QString message) videoGpu->widget()->hide(); lbVideo->show(); + // Frames are about to start arriving in system memory, so the decoder + // needs the size to produce them at. Sent explicitly rather than left to + // the label's resize, which does not fire if showing it did not change its + // geometry. + reportVideoArea(); + emit video_rendering_failed(); } #endif @@ -198,6 +310,17 @@ void CallWindow::accept() emit accept_call_pressed(); } +bool CallWindow::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == lbVideo && event->type() == QEvent::Resize) + { + reportVideoArea(); + updateRemoteFrame(); + } + + return QWidget::eventFilter(watched, event); +} + void CallWindow::closeEvent(QCloseEvent *e) { emit callwindow_closed(this); |
