Pin call actions to the bottom and add a separate diagnostics toggle
This commit is contained in:
1 parent
264a13705f
commit
b82e9cc98e
3 files changed
+45
-16
No files matched your search
@@ -404,8 +404,9 @@ void CallVideoEngine::transmit() {
|
||||
}
|
||||
format = next; appliedRevision = revision; forceKey = true; request = 0;
|
||||
if (call_video_port_format(m->id, &format, encoder.bitrate()) < 0) break;
|
||||
m->report(QString("%1 · %2×%3 · %4 fps · %5 kbit/s").arg(format.codec == 2 ? "H.265" : "H.264")
|
||||
.arg(width).arg(height).arg(format.fps).arg(encoder.bitrate() / 1000));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video format applied codec=%u size=%ux%u fps=%u bitrate_bps=%u",
|
||||
format.codec, format.width, format.height, format.fps, encoder.bitrate());
|
||||
m->report({});
|
||||
}
|
||||
if (state.configured_generation != format.generation) { idle(); continue; }
|
||||
if (state.key_serial != keySerial) { keySerial = state.key_serial; forceKey = true; }
|
||||
@@ -428,8 +429,8 @@ void CallVideoEngine::transmit() {
|
||||
encodeMax = std::max(encodeMax, nowUs() - encodeStarted);
|
||||
if (completion.request) {
|
||||
call_video_port_applied(m->id, completion.generation, completion.request, completion.bitrate);
|
||||
m->report(QString("%1 · %2×%3 · %4 fps · %5 kbit/s").arg(format.codec == 2 ? "H.265" : "H.264")
|
||||
.arg(format.width).arg(format.height).arg(format.fps).arg(encoder.bitrate() / 1000));
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call video bitrate applied generation=%u request=%llu bitrate_bps=%u",
|
||||
completion.generation, (unsigned long long)completion.request, encoder.bitrate());
|
||||
}
|
||||
if (!encoded) {
|
||||
m->report(encoder.error());
|
||||
|
||||
@@ -31,7 +31,7 @@ extern "C" {
|
||||
|
||||
/* ── рисование иконок звонка (QPainter, без внешних ассетов) ── */
|
||||
|
||||
enum class Glyph { Phone, PhoneDown, Mic, MicOff, Camera, CameraOff, Settings };
|
||||
enum class Glyph { Phone, PhoneDown, Mic, MicOff, Camera, CameraOff, Settings, Diagnostics };
|
||||
|
||||
static QString callText(const char* text) { return QCoreApplication::translate("ChatGui", text); }
|
||||
using namespace CaptureControls;
|
||||
@@ -239,6 +239,16 @@ static QIcon makeRoundIcon(const QColor& bg, Glyph glyph, int size) {
|
||||
case Glyph::Camera: drawCameraGlyph(&p, glyphRect, false); break;
|
||||
case Glyph::CameraOff: drawCameraGlyph(&p, glyphRect, true); break;
|
||||
case Glyph::Settings: drawSettingsGlyph(&p, glyphRect); break;
|
||||
case Glyph::Diagnostics: {
|
||||
p.setPen(QPen(Qt::white, glyphRect.width() * 0.07, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
|
||||
p.drawLine(glyphRect.bottomLeft(), glyphRect.topLeft()); p.drawLine(glyphRect.bottomLeft(), glyphRect.bottomRight());
|
||||
QPainterPath graph; graph.moveTo(glyphRect.left() + glyphRect.width() * 0.1, glyphRect.center().y());
|
||||
graph.lineTo(glyphRect.left() + glyphRect.width() * 0.35, glyphRect.center().y());
|
||||
graph.lineTo(glyphRect.center().x(), glyphRect.top() + glyphRect.height() * 0.2);
|
||||
graph.lineTo(glyphRect.left() + glyphRect.width() * 0.65, glyphRect.top() + glyphRect.height() * 0.8);
|
||||
graph.lineTo(glyphRect.right() - glyphRect.width() * 0.1, glyphRect.top() + glyphRect.height() * 0.3);
|
||||
p.setBrush(Qt::NoBrush); p.drawPath(graph); break;
|
||||
}
|
||||
}
|
||||
p.end();
|
||||
return QIcon(pm);
|
||||
@@ -278,6 +288,7 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
m_avatarLabel = new QLabel(this);
|
||||
m_avatarLabel->setObjectName("callAvatar");
|
||||
m_avatarLabel->setFixedSize(80, 80);
|
||||
m_avatarLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
@@ -326,8 +337,7 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
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->addStretch(1);
|
||||
// Содержимое сверху, отдельная строка действий всегда у нижнего края окна.
|
||||
root->addWidget(m_panel, 0, Qt::AlignHCenter);
|
||||
root->addStretch(1);
|
||||
QVBoxLayout* lay = new QVBoxLayout(m_panel);
|
||||
@@ -348,14 +358,15 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
SoundManager::instance()->playLoop("call_connecting");
|
||||
});
|
||||
lay->addWidget(m_durationLabel);
|
||||
lay->addWidget(m_connectionLabel);
|
||||
lay->addWidget(m_bgpPathLabel);
|
||||
lay->addWidget(m_statsLabel);
|
||||
m_diagnostics = new QWidget(m_panel); m_diagnostics->setObjectName("callDiagnostics");
|
||||
auto* diagnosticLayout = new QVBoxLayout(m_diagnostics); diagnosticLayout->setContentsMargins(0, 0, 0, 0);
|
||||
diagnosticLayout->addWidget(m_connectionLabel); diagnosticLayout->addWidget(m_bgpPathLabel); diagnosticLayout->addWidget(m_statsLabel);
|
||||
lay->addWidget(m_diagnostics);
|
||||
m_video = new CallVideoEngine(this);
|
||||
m_video->setPresentationDriven(true); m_videoWindow->engine = m_video;
|
||||
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_videoStatus = new QLabel(this); m_videoStatus->setObjectName("callVideoStatus"); 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_videoMenuBar = new QMenuBar(m_videoControls); m_videoMenuBar->setNativeMenuBar(false); m_videoMenuBar->setMouseTracking(true);
|
||||
@@ -381,6 +392,9 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
m_settingsBtn = makeRoundBtn(makeRoundIcon(QColor("#7f8c8d"), Glyph::Settings, 96), callText("Video settings"), this);
|
||||
m_settingsBtn->setObjectName("callVideoSettings");
|
||||
m_settingsBtn->setCheckable(true); m_settingsBtn->setAccessibleName(callText("Video settings"));
|
||||
m_diagnosticsBtn = makeRoundBtn(makeRoundIcon(QColor("#7f8c8d"), Glyph::Diagnostics, 96), callText("Diagnostics"), this);
|
||||
m_diagnosticsBtn->setObjectName("callDiagnosticsButton"); m_diagnosticsBtn->setCheckable(true);
|
||||
m_diagnosticsBtn->setAccessibleName(callText("Diagnostics"));
|
||||
m_showVideoBtn = new QPushButton(callText("Show video"), this); actions->addWidget(m_showVideoBtn);
|
||||
lay->addLayout(actions);
|
||||
connect(m_settingsBtn, &QPushButton::toggled, this, [this](bool checked) {
|
||||
@@ -388,6 +402,11 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "desktop video settings visible=%d", checked);
|
||||
updateVideoPresentation();
|
||||
});
|
||||
connect(m_diagnosticsBtn, &QPushButton::toggled, this, [this](bool checked) {
|
||||
m_diagnosticsBtn->setIcon(makeRoundIcon(checked ? QColor("#2980b9") : QColor("#7f8c8d"), Glyph::Diagnostics, 96));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "desktop call diagnostics visible=%d id=%llu", checked, (unsigned long long)m_callId);
|
||||
updateVideoPresentation();
|
||||
});
|
||||
connect(m_showVideoBtn, &QPushButton::clicked, this, [this] {
|
||||
if (m_videoWindow->isMinimized()) m_videoWindow->showNormal(); else m_videoWindow->show();
|
||||
m_videoWindow->raise(); m_videoWindow->activateWindow();
|
||||
@@ -488,8 +507,10 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
m_muteCell = wrapWithCaption(m_muteBtn, QCoreApplication::translate("ChatGui", "Mute"), &m_muteCaption);
|
||||
m_videoCell = wrapWithCaption(m_videoEnabled, callText("Camera"));
|
||||
m_settingsCell = wrapWithCaption(m_settingsBtn, callText("Video settings"));
|
||||
m_diagnosticsCell = wrapWithCaption(m_diagnosticsBtn, callText("Diagnostics"));
|
||||
|
||||
QHBoxLayout* btn = new QHBoxLayout();
|
||||
auto* footer = new QWidget(this); footer->setObjectName("callActions");
|
||||
QHBoxLayout* btn = new QHBoxLayout(footer); btn->setContentsMargins(0, 0, 0, 0);
|
||||
btn->setSpacing(10);
|
||||
btn->addStretch(1);
|
||||
btn->addWidget(m_acceptCell);
|
||||
@@ -498,8 +519,9 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
btn->addWidget(m_muteCell);
|
||||
btn->addWidget(m_videoCell);
|
||||
btn->addWidget(m_settingsCell);
|
||||
btn->addWidget(m_diagnosticsCell);
|
||||
btn->addStretch(1);
|
||||
lay->addLayout(btn);
|
||||
root->addWidget(footer, 0, Qt::AlignHCenter);
|
||||
|
||||
connect(m_acceptBtn, &QPushButton::clicked, this, &CallWindow::onAccept);
|
||||
connect(m_declineBtn, &QPushButton::clicked, this, &CallWindow::onDecline);
|
||||
@@ -608,15 +630,18 @@ void CallWindow::updateVideoPresentation() {
|
||||
if (receiving) m_videoWindow->show(); else m_videoWindow->hide();
|
||||
}
|
||||
bool active = m_state == Active, settings = active && m_settingsBtn->isChecked();
|
||||
m_avatarLabel->setVisible(!active || (!settings && !receiving && m_localImage.isNull()));
|
||||
m_localPreview->setVisible(active && !receiving && !m_localImage.isNull());
|
||||
m_connectionLabel->setVisible(active); m_bgpPathLabel->setVisible(active && !m_bgpPathLabel->text().isEmpty());
|
||||
m_statsLabel->setVisible(active && !m_statsLabel->text().isEmpty());
|
||||
m_diagnostics->setVisible(active && m_diagnosticsBtn->isChecked()); m_diagnosticsCell->setVisible(active);
|
||||
m_videoCell->setVisible(active); m_settingsCell->setVisible(active);
|
||||
m_videoControls->setVisible(settings); m_showVideoBtn->setVisible(receiving);
|
||||
m_videoStatus->setVisible(active && !m_videoStatus->text().isEmpty());
|
||||
QSize panelMinimum = m_panel->minimumSizeHint();
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call layout active=%d preview=%d settings=%d window=%dx%d panel_min=%dx%d",
|
||||
active, m_localPreview->isVisible(), settings, width(), height(), panelMinimum.width(), panelMinimum.height());
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call layout active=%d preview=%d settings=%d avatar=%d diagnostics=%d window=%dx%d panel_min=%dx%d",
|
||||
active, m_localPreview->isVisible(), settings, !m_avatarLabel->isHidden(), !m_diagnostics->isHidden(),
|
||||
width(), height(), panelMinimum.width(), panelMinimum.height());
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
quint64 callId() const { return m_callId; } // ID для команд моста и сигнала closed.
|
||||
|
||||
void setPeerName(const QString& name); // Имя, буквенная аватарка и заголовок отдельного видеоокна.
|
||||
void setBgpPath(const QString& path); // Текст пути; показывается только при Active и непустой строке.
|
||||
void setBgpPath(const QString& path); // Текст пути; показывается в диагностике Active при непустой строке.
|
||||
void setConnectionType(uint8_t type, quint64 nextHop); // 0/1/2/3 — нет/direct/reverse/indirect; nextHop в tooltip только для 3.
|
||||
// rtt/buffer/minimum/maximum/reserve — мс; tempo=100 для 1x, dropped/underruns — счётчики; min/max за 10с.
|
||||
void setAudioStats(int rtt, int buffer, int tempo, int dropped, int underruns, int minimum, int maximum, int reserve);
|
||||
@@ -75,6 +75,8 @@ private:
|
||||
QWidget *m_videoControls, *m_panel; // Сворачиваемые настройки и центральная панель шириной до 640.
|
||||
CallVideoWindow* m_videoWindow; // Дочернее Qt::Window; закрытие скрывает картинку, звонок продолжает работать.
|
||||
QPushButton *m_settingsBtn, *m_showVideoBtn; // Раскрытие настроек и повторное открытие remote-видео.
|
||||
QPushButton* m_diagnosticsBtn; // Независимый checkable-переключатель блока метрик, по умолчанию выключен.
|
||||
QWidget* m_diagnostics; // Тип соединения, BGP-путь, аудиобуфер/RTT/latency; скрытие не останавливает сбор метрик.
|
||||
QImage m_remoteImage, m_localImage; // Последние shared-кадры; видеодвижок очищает их при stop.
|
||||
bool m_receivingVideo = false; // Active + непустой remote-кадр; не признак видимости отдельного окна.
|
||||
quint64 m_videoCall = 0; // ID, с которым запущен видеодвижок; 0 после Ended/close.
|
||||
@@ -104,5 +106,6 @@ private:
|
||||
QWidget* m_muteCell; // Кнопка mute и подпись.
|
||||
QWidget* m_videoCell; // Кнопка камеры и подпись справа от mute в общей центрированной строке.
|
||||
QWidget* m_settingsCell; // Шестерёнка настроек видео и подпись справа от камеры.
|
||||
QWidget* m_diagnosticsCell; // Кнопка диагностики и подпись справа от настроек в нижней строке действий.
|
||||
QLabel* m_muteCaption; // Меняется между Mute/Unmute.
|
||||
};
|
||||
Reference in new issue
Block a user