Browse Source

Show call video in a separate resizable desktop window

master
evgeny 4 days ago
parent
commit
9f8fb64e6e
  1. 87
      tools/chatgui/src/callwindow.cpp
  2. 11
      tools/chatgui/src/callwindow.h

87
tools/chatgui/src/callwindow.cpp

@ -35,12 +35,29 @@ enum class Glyph { Phone, PhoneDown, Mic, MicOff };
static QString callText(const char* text) { return QCoreApplication::translate("ChatGui", text); }
// Только изображение и миниатюра; подписи/настройки находятся в отдельной панели окна.
class CallVideoCanvas : public QWidget {
// Отдельное окно показа. Закрытие скрывает только изображение, не завершает звонок.
class CallVideoWindow : public QWidget {
public:
explicit CallVideoCanvas(QWidget* parent) : QWidget(parent) { setMinimumSize(160, 120); }
explicit CallVideoWindow(QWidget* parent) : QWidget(parent, Qt::Window) {
setObjectName("callVideoWindow"); setMinimumSize(320, 240); setAttribute(Qt::WA_QuitOnClose, false);
QSize available = parent->screen() ? parent->screen()->availableGeometry().size() : QSize(1280, 900);
resize(qMin(1100, available.width() * 9 / 10), qMin(800, available.height() * 9 / 10));
}
QImage remote, local;
protected:
void closeEvent(QCloseEvent* event) override {
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video window closed; call continues"); event->accept();
}
void keyPressEvent(QKeyEvent* event) override {
if (event->key() == Qt::Key_F11 || (event->key() == Qt::Key_Escape && isFullScreen())) {
toggleFullScreen(); event->accept(); return;
}
QWidget::keyPressEvent(event);
}
void mouseDoubleClickEvent(QMouseEvent* event) override {
if (event->button() == Qt::LeftButton) { toggleFullScreen(); event->accept(); return; }
QWidget::mouseDoubleClickEvent(event);
}
void paintEvent(QPaintEvent*) override {
QPainter painter(this); painter.fillRect(rect(), Qt::black); painter.setRenderHint(QPainter::SmoothPixmapTransform);
if (remote.isNull()) return;
@ -53,6 +70,12 @@ protected:
painter.setPen(QPen(Qt::white, 2)); painter.drawRect(target.adjusted(-1, -1, 1, 1)); painter.drawImage(target, local);
}
}
private:
void toggleFullScreen() {
if (isFullScreen()) { showNormal(); if (!m_geometry.isEmpty()) restoreGeometry(m_geometry); }
else { m_geometry = saveGeometry(); showFullScreen(); }
}
QByteArray m_geometry;
};
static QColor colorForName(const QString& name) {
@ -215,10 +238,10 @@ CallWindow::CallWindow(QWidget* parent)
auto* root = new QVBoxLayout(this);
root->setContentsMargins(12, 12, 12, 12);
m_videoArea = new CallVideoCanvas(this); m_videoArea->hide(); root->addWidget(m_videoArea, 1);
m_videoWindow = new CallVideoWindow(this);
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);
root->addWidget(m_panel, 0, Qt::AlignCenter);
QVBoxLayout* lay = new QVBoxLayout(m_panel);
lay->setContentsMargins(16, 20, 16, 16);
lay->setSpacing(8);
@ -239,10 +262,14 @@ CallWindow::CallWindow(QWidget* parent)
m_videoEnabled = new QCheckBox(callText("Send video"), this); actions->addWidget(m_videoEnabled);
actions->addStretch(1);
m_settingsBtn = new QPushButton(callText("Call settings"), this); m_settingsBtn->setCheckable(true); actions->addWidget(m_settingsBtn);
m_fullScreenBtn = new QPushButton(callText("Full screen"), this); actions->addWidget(m_fullScreenBtn);
m_showVideoBtn = new QPushButton(callText("Show video"), this); actions->addWidget(m_showVideoBtn);
lay->addLayout(actions);
connect(m_settingsBtn, &QPushButton::toggled, this, [this] { updateVideoPresentation(); });
connect(m_fullScreenBtn, &QPushButton::clicked, this, &CallWindow::toggleFullScreen);
connect(m_showVideoBtn, &QPushButton::clicked, this, [this] {
if (m_videoWindow->isMinimized()) m_videoWindow->showNormal(); else m_videoWindow->show();
m_videoWindow->raise(); m_videoWindow->activateWindow();
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video window reopened");
});
auto* modes = new QHBoxLayout();
m_camera = new QComboBox(this);
for (const auto& camera : CallVideoEngine::cameras()) m_camera->addItem(camera.first, camera.second);
@ -267,13 +294,13 @@ CallWindow::CallWindow(QWidget* parent)
});
connect(m_video, &CallVideoEngine::imageChanged, this, [this](const QImage& image) {
bool changed = m_remoteImage.isNull() != image.isNull(); m_remoteImage = image;
static_cast<CallVideoCanvas*>(m_videoArea)->remote = image; m_videoArea->update();
m_videoWindow->remote = image; m_videoWindow->update();
if (changed) updateVideoPresentation();
update();
});
connect(m_video, &CallVideoEngine::localImageChanged, this, [this](const QImage& image) {
bool changed = m_localImage.isNull() != image.isNull(); m_localImage = image;
static_cast<CallVideoCanvas*>(m_videoArea)->local = image; m_videoArea->update();
m_videoWindow->local = image; m_videoWindow->update();
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));
@ -328,6 +355,7 @@ CallWindow::~CallWindow() {
void CallWindow::setPeerName(const QString& name) {
m_peerLabel->setText(name);
m_avatarLabel->setPixmap(makeAvatar(name, 80));
m_videoWindow->setWindowTitle(callText("Video — %1").arg(name));
}
void CallWindow::setBgpPath(const QString& path) {
@ -401,58 +429,23 @@ void CallWindow::updateButtons() {
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() ? callText("Exit full screen") : callText("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();
QSize available = screen() ? screen()->availableGeometry().size() : QSize(1280, 900);
resize(qMin(1100, available.width() * 9 / 10), qMin(800, available.height() * 9 / 10));
} else {
leaveFullScreen();
if (!m_windowGeometry.isEmpty()) restoreGeometry(m_windowGeometry);
}
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video separate window remote=%d local=%d", receiving, !m_localImage.isNull());
if (receiving) m_videoWindow->show(); else m_videoWindow->hide();
}
bool active = m_state == Active, settings = active && m_settingsBtn->isChecked();
m_videoArea->setVisible(receiving);
layout()->setAlignment(m_panel, receiving ? Qt::AlignHCenter | Qt::AlignBottom : Qt::AlignCenter);
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() ? callText("Exit full screen") : callText("Full screen"));
m_videoControls->setVisible(settings); m_showVideoBtn->setVisible(receiving);
m_videoStatus->setVisible(active && !m_videoStatus->text().isEmpty());
update();
}
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() {
if (!m_videoCall) return;
QSize size = m_resolution->currentData().toSize();

11
tools/chatgui/src/callwindow.h

@ -11,6 +11,7 @@ class QTimer;
class QCheckBox;
class QSpinBox;
class CallVideoEngine;
class CallVideoWindow;
/* Окно звонка: состояния Outgoing (пытаемся связаться), Ringing (звоним),
* Incoming (принять/отклонить), Active (идёт разговор, таймер), Ended (завершён). */
@ -37,8 +38,6 @@ signals:
protected:
void closeEvent(QCloseEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
private slots:
void onAccept();
@ -54,17 +53,15 @@ private:
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_localPreview, *m_videoStatus;
QWidget *m_videoControls, *m_panel, *m_videoArea;
QPushButton *m_settingsBtn, *m_fullScreenBtn;
QWidget *m_videoControls, *m_panel;
CallVideoWindow* m_videoWindow;
QPushButton *m_settingsBtn, *m_showVideoBtn;
QImage m_remoteImage, m_localImage;
QByteArray m_windowGeometry;
bool m_receivingVideo = false;
quint64 m_videoCall = 0;
bool m_videoRangeReady = false;

Loading…
Cancel
Save