Browse Source

Show remote calls full screen with camera picture in picture

master
evgeny 5 days ago
parent
commit
434b59b1c7
  1. 113
      tools/chatgui/src/callwindow.cpp
  2. 15
      tools/chatgui/src/callwindow.h

113
tools/chatgui/src/callwindow.cpp

@ -16,6 +16,8 @@
#include <QTimer>
#include <QDateTime>
#include <QCloseEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <cstring>
@ -142,8 +144,8 @@ static QWidget* wrapWithCaption(QWidget* btn, const QString& caption, QLabel** c
CallWindow::CallWindow(QWidget* parent)
: QWidget(parent, Qt::Window) {
setWindowTitle(QCoreApplication::translate("ChatGui", "Call"));
resize(640, 800);
setMinimumSize(400, 600);
resize(520, 520);
setMinimumSize(420, 360);
setAttribute(Qt::WA_DeleteOnClose, false);
m_avatarLabel = new QLabel(this);
@ -188,7 +190,12 @@ CallWindow::CallWindow(QWidget* parent)
"QComboBox { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
QVBoxLayout* lay = new QVBoxLayout(this);
auto* root = new QVBoxLayout(this);
root->setContentsMargins(12, 12, 12, 12); root->addStretch(1);
m_panel = new QWidget(this); m_panel->setObjectName("callPanel"); m_panel->setMaximumWidth(640);
m_panel->setStyleSheet("QWidget#callPanel { background: palette(window); border-radius: 12px; }");
root->addWidget(m_panel, 0, Qt::AlignHCenter | Qt::AlignBottom);
QVBoxLayout* lay = new QVBoxLayout(m_panel);
lay->setContentsMargins(16, 20, 16, 16);
lay->setSpacing(8);
lay->addWidget(m_avatarLabel, 0, Qt::AlignHCenter);
@ -199,12 +206,19 @@ CallWindow::CallWindow(QWidget* parent)
lay->addWidget(m_bgpPathLabel);
lay->addWidget(m_statsLabel);
m_video = new CallVideoEngine(this);
m_videoImage = new QLabel(this); m_videoImage->setMinimumHeight(180); m_videoImage->setAlignment(Qt::AlignCenter);
lay->addWidget(m_videoImage, 1);
m_localPreview = new QLabel(this); m_localPreview->setAlignment(Qt::AlignCenter); m_localPreview->hide();
lay->addWidget(m_localPreview, 0, Qt::AlignHCenter);
m_videoStatus = new QLabel(this); m_videoStatus->setWordWrap(true); lay->addWidget(m_videoStatus);
m_videoControls = new QWidget(this); auto* videoLayout = new QVBoxLayout(m_videoControls);
videoLayout->setContentsMargins(0, 0, 0, 0);
m_videoEnabled = new QCheckBox(tr("Send video"), this); videoLayout->addWidget(m_videoEnabled);
auto* actions = new QHBoxLayout();
m_videoEnabled = new QCheckBox(tr("Send video"), this); actions->addWidget(m_videoEnabled);
actions->addStretch(1);
m_settingsBtn = new QPushButton(tr("Call settings"), this); m_settingsBtn->setCheckable(true); actions->addWidget(m_settingsBtn);
m_fullScreenBtn = new QPushButton(tr("Full screen"), this); actions->addWidget(m_fullScreenBtn);
lay->addLayout(actions);
connect(m_settingsBtn, &QPushButton::toggled, this, [this] { updateVideoPresentation(); });
connect(m_fullScreenBtn, &QPushButton::clicked, this, &CallWindow::toggleFullScreen);
auto* modes = new QHBoxLayout();
m_camera = new QComboBox(this);
for (const auto& camera : CallVideoEngine::cameras()) m_camera->addItem(camera.first, camera.second);
@ -224,9 +238,22 @@ CallWindow::CallWindow(QWidget* parent)
};
connect(m_videoMin, &QSpinBox::editingFinished, this, updateRange);
connect(m_videoMax, &QSpinBox::editingFinished, this, updateRange);
connect(m_video, &CallVideoEngine::statusChanged, m_videoStatus, &QLabel::setText);
connect(m_video, &CallVideoEngine::statusChanged, this, [this](const QString& text) {
m_videoStatus->setText(text); m_videoStatus->setVisible(m_state == Active && !text.isEmpty());
});
connect(m_video, &CallVideoEngine::imageChanged, this, [this](const QImage& image) {
m_videoImage->setPixmap(image.isNull() ? QPixmap() : QPixmap::fromImage(image).scaled(m_videoImage->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
bool changed = m_remoteImage.isNull() != image.isNull(); m_remoteImage = image;
if (changed) updateVideoPresentation();
update();
});
connect(m_video, &CallVideoEngine::localImageChanged, this, [this](const QImage& image) {
bool changed = m_localImage.isNull() != image.isNull(); m_localImage = image;
if (!image.isNull()) {
QSize size = image.size().scaled(200, 150, Qt::KeepAspectRatio); m_localPreview->setFixedSize(size);
m_localPreview->setPixmap(QPixmap::fromImage(image).scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
} else m_localPreview->clear();
if (changed) updateVideoPresentation();
update();
});
auto* videoTimer = new QTimer(this); videoTimer->setInterval(100);
connect(videoTimer, &QTimer::timeout, this, [this] {
@ -237,7 +264,6 @@ CallWindow::CallWindow(QWidget* parent)
m_videoMin->setValue(status.minimum_bps / 1000); m_videoMax->setValue(status.maximum_bps / 1000); m_videoRangeReady = true;
}
}); videoTimer->start();
lay->addStretch(1);
m_acceptCell = wrapWithCaption(m_acceptBtn, QCoreApplication::translate("ChatGui", "Accept"));
m_declineCell = wrapWithCaption(m_declineBtn, QCoreApplication::translate("ChatGui", "Decline"));
@ -253,8 +279,7 @@ CallWindow::CallWindow(QWidget* parent)
btn->addWidget(m_muteCell);
btn->addStretch(1);
lay->addLayout(btn);
lay->addStretch(1);
lay->addWidget(m_deviceCombo);
videoLayout->addWidget(m_deviceCombo);
connect(m_acceptBtn, &QPushButton::clicked, this, &CallWindow::onAccept);
connect(m_declineBtn, &QPushButton::clicked, this, &CallWindow::onDecline);
@ -278,7 +303,7 @@ void CallWindow::setPeerName(const QString& name) {
void CallWindow::setBgpPath(const QString& path) {
m_bgpPathLabel->setText(path);
m_bgpPathLabel->setVisible(!path.isEmpty());
m_bgpPathLabel->setVisible(!path.isEmpty() && m_settingsBtn->isChecked());
}
void CallWindow::setConnectionType(uint8_t type, quint64 nextHop) {
@ -344,10 +369,66 @@ void CallWindow::updateButtons() {
m_declineCell->setVisible(m_state == Incoming);
m_hangupCell->setVisible(m_state == Outgoing || m_state == Ringing || m_state == Active);
m_muteCell->setVisible(m_state == Active);
m_deviceCombo->setVisible(m_state == Active);
m_videoControls->setVisible(m_state == Active);
m_videoImage->setVisible(m_state == Active);
m_videoStatus->setVisible(m_state == Active);
updateVideoPresentation();
}
void CallWindow::leaveFullScreen() {
if (!isFullScreen()) return;
showNormal();
if (!m_windowGeometry.isEmpty()) restoreGeometry(m_windowGeometry);
}
void CallWindow::toggleFullScreen() {
if (m_remoteImage.isNull()) return;
if (isFullScreen()) leaveFullScreen();
else { m_windowGeometry = saveGeometry(); showFullScreen(); }
m_fullScreenBtn->setText(isFullScreen() ? tr("Exit full screen") : tr("Full screen"));
}
void CallWindow::updateVideoPresentation() {
bool receiving = m_state == Active && !m_remoteImage.isNull();
if (receiving != m_receivingVideo) {
m_receivingVideo = receiving;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video presentation remote=%d local=%d", receiving, !m_localImage.isNull());
if (receiving) {
m_settingsBtn->setChecked(false); m_windowGeometry = saveGeometry(); showFullScreen();
} else leaveFullScreen();
}
bool active = m_state == Active, settings = active && m_settingsBtn->isChecked();
m_localPreview->setVisible(active && !receiving && !m_localImage.isNull());
m_avatarLabel->setVisible(!receiving); m_statusLabel->setVisible(!receiving);
m_connectionLabel->setVisible(settings); m_bgpPathLabel->setVisible(settings && !m_bgpPathLabel->text().isEmpty());
m_statsLabel->setVisible(settings && !m_statsLabel->text().isEmpty());
m_videoEnabled->setVisible(active); m_settingsBtn->setVisible(active);
m_videoControls->setVisible(settings); m_fullScreenBtn->setVisible(receiving);
m_fullScreenBtn->setText(isFullScreen() ? tr("Exit full screen") : tr("Full screen"));
m_videoStatus->setVisible(active && !m_videoStatus->text().isEmpty());
update();
}
void CallWindow::paintEvent(QPaintEvent* event) {
QWidget::paintEvent(event);
if (!m_receivingVideo) return;
QPainter painter(this); painter.fillRect(rect(), Qt::black); painter.setRenderHint(QPainter::SmoothPixmapTransform);
QSize remote = m_remoteImage.size().scaled(size(), Qt::KeepAspectRatio);
painter.drawImage(QRect(QPoint((width() - remote.width()) / 2, (height() - remote.height()) / 2), remote), m_remoteImage);
if (!m_localImage.isNull()) {
QSize preview = m_localImage.size().scaled(qMin(240, width() / 4), qMin(180, height() / 4), Qt::KeepAspectRatio);
QRect target(QPoint(width() - preview.width() - 20, 20), preview);
painter.setPen(QPen(Qt::white, 2)); painter.drawRect(target.adjusted(-1, -1, 1, 1));
painter.drawImage(target, m_localImage);
}
}
void CallWindow::keyPressEvent(QKeyEvent* event) {
if (event->key() == Qt::Key_Escape && isFullScreen()) { toggleFullScreen(); event->accept(); return; }
if (event->key() == Qt::Key_F11 && m_receivingVideo) { toggleFullScreen(); event->accept(); return; }
QWidget::keyPressEvent(event);
}
void CallWindow::mouseDoubleClickEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton && m_receivingVideo) { toggleFullScreen(); event->accept(); return; }
QWidget::mouseDoubleClickEvent(event);
}
void CallWindow::applyVideo() {

15
tools/chatgui/src/callwindow.h

@ -1,6 +1,7 @@
#pragma once
#include <QWidget>
#include <QImage>
#include <cstdint>
class QLabel;
@ -36,6 +37,9 @@ signals:
protected:
void closeEvent(QCloseEvent* event) override;
void paintEvent(QPaintEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
private slots:
void onAccept();
@ -50,12 +54,19 @@ private:
void populateDeviceCombo();
void postCallFn(void (*fn)(void*), uint64_t callId);
void applyVideo();
void updateVideoPresentation();
void toggleFullScreen();
void leaveFullScreen();
CallVideoEngine* m_video;
QCheckBox* m_videoEnabled;
QComboBox *m_camera, *m_resolution, *m_codec;
QSpinBox *m_videoMin, *m_videoMax;
QLabel *m_videoImage, *m_videoStatus;
QWidget* m_videoControls;
QLabel *m_localPreview, *m_videoStatus;
QWidget *m_videoControls, *m_panel;
QPushButton *m_settingsBtn, *m_fullScreenBtn;
QImage m_remoteImage, m_localImage;
QByteArray m_windowGeometry;
bool m_receivingVideo = false;
quint64 m_videoCall = 0;
bool m_videoRangeReady = false;

Loading…
Cancel
Save