diff --git a/UI/cmake/legacy.cmake b/UI/cmake/legacy.cmake index b1145f7253d2a5..ae08191b0c3198 100644 --- a/UI/cmake/legacy.cmake +++ b/UI/cmake/legacy.cmake @@ -116,6 +116,7 @@ target_sources( forms/OBSRemux.ui forms/OBSUpdate.ui forms/OBSYoutubeActions.ui + forms/StatusBarWidget.ui forms/source-toolbar/browser-source-toolbar.ui forms/source-toolbar/color-source-toolbar.ui forms/source-toolbar/device-select-toolbar.ui diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index 3a47002d1827db..e7c70496acd901 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -337,8 +337,20 @@ QScrollArea { border-radius: 4px; } +/* Qt enforces a padding inside its status bar, so we + * oversize it and use margin to crunch it back down + */ OBSBasicStatusBar { - margin-top: 8px; + margin-top: 4px; + border-top: 1px solid #3c404b; + background: palette(dark); +} + +StatusBarWidget > QFrame { + margin-top: 1px; + border: 0px solid #3c404b; + border-left-width: 1px; + padding: 0px 12px 2px; } /* Group Box */ diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index fc4883759288e6..6c96a902cb9e3a 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -644,6 +644,22 @@ QStatusBar::item { border: none; } +/* Qt enforces a padding inside its status bar, so we + * oversize it and use margin to crunch it back down + */ +OBSBasicStatusBar { + margin-top: 4px; + border-top: 1px solid palette(dark); + background: palette(window); +} + +StatusBarWidget > QFrame { + margin-top: 2px; + border: 0px solid palette(dark); + border-left-width: 1px; + padding: 0px 12px 4px; +} + /* Table View */ QTableView { diff --git a/UI/data/themes/Dark/network-disconnected.svg b/UI/data/themes/Dark/network-disconnected.svg new file mode 100644 index 00000000000000..153b57066310b7 --- /dev/null +++ b/UI/data/themes/Dark/network-disconnected.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/UI/data/themes/Dark/network-inactive.svg b/UI/data/themes/Dark/network-inactive.svg new file mode 100644 index 00000000000000..8711d71e26d18d --- /dev/null +++ b/UI/data/themes/Dark/network-inactive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/UI/data/themes/Dark/recording-inactive.svg b/UI/data/themes/Dark/recording-inactive.svg new file mode 100644 index 00000000000000..d9cf620f613ce7 --- /dev/null +++ b/UI/data/themes/Dark/recording-inactive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/UI/data/themes/Dark/recording-pause-inactive.svg b/UI/data/themes/Dark/recording-pause-inactive.svg new file mode 100644 index 00000000000000..800060855e853a --- /dev/null +++ b/UI/data/themes/Dark/recording-pause-inactive.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/UI/data/themes/Dark/streaming-inactive.svg b/UI/data/themes/Dark/streaming-inactive.svg new file mode 100644 index 00000000000000..c5def1d65a6d4c --- /dev/null +++ b/UI/data/themes/Dark/streaming-inactive.svg @@ -0,0 +1,20 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/UI/data/themes/Grey.qss b/UI/data/themes/Grey.qss index 93733dd07c6797..0748deffad638c 100644 --- a/UI/data/themes/Grey.qss +++ b/UI/data/themes/Grey.qss @@ -337,8 +337,20 @@ QScrollArea { border-radius: 4px; } +/* Qt enforces a padding inside its status bar, so we + * oversize it and use margin to crunch it back down + */ OBSBasicStatusBar { - margin-top: 8px; + margin-top: 4px; + border-top: 1px solid #3c404b; + background: palette(dark); +} + +StatusBarWidget > QFrame { + margin-top: 1px; + border: 0px solid #3c404b; + border-left-width: 1px; + padding: 0px 12px 2px; } /* Group Box */ diff --git a/UI/data/themes/Light.qss b/UI/data/themes/Light.qss index 3f8f81781f6e3e..e6c9a6be0b2dc1 100644 --- a/UI/data/themes/Light.qss +++ b/UI/data/themes/Light.qss @@ -337,8 +337,20 @@ QScrollArea { border-radius: 4px; } +/* Qt enforces a padding inside its status bar, so we + * oversize it and use margin to crunch it back down + */ OBSBasicStatusBar { - margin-top: 8px; + margin-top: 4px; + border-top: 1px solid rgb(192,192,192); + background: palette(dark); +} + +StatusBarWidget > QFrame { + margin-top: 1px; + border: 0px solid rgb(192,192,192); + border-left-width: 1px; + padding: 0px 12px 2px; } /* Group Box */ diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index 351e9bef9a24be..92880cfd0cb035 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -339,8 +339,20 @@ QScrollArea { border-radius: 4px; } +/* Qt enforces a padding inside its status bar, so we + * oversize it and use margin to crunch it back down + */ OBSBasicStatusBar { - margin-top: 8px; + margin-top: 4px; + border-top: 1px solid #3c404b; + background: palette(dark); +} + +StatusBarWidget > QFrame { + margin-top: 1px; + border: 0px solid #3c404b; + border-left-width: 1px; + padding: 0px 12px 2px; } /* Group Box */ diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss index 76af193c6b5490..94c4466730c190 100644 --- a/UI/data/themes/System.qss +++ b/UI/data/themes/System.qss @@ -402,3 +402,8 @@ QCalendarWidget #qt_calendar_nextmonth { qproperty-icon: url(./Dark/right.svg); icon-size: 16px, 16px; } + +/* Status Bar */ +StatusBarWidget > QFrame { + padding: 0px 12px 8px; +} diff --git a/UI/data/themes/Yami.qss b/UI/data/themes/Yami.qss index 54b3c2e6a225cf..b32a7b47d2f946 100644 --- a/UI/data/themes/Yami.qss +++ b/UI/data/themes/Yami.qss @@ -341,8 +341,20 @@ QScrollArea { border-radius: 4px; } +/* Qt enforces a padding inside its status bar, so we + * oversize it and use margin to crunch it back down + */ OBSBasicStatusBar { - margin-top: 8px; + margin-top: 4px; + border-top: 1px solid #3c404b; + background: palette(dark); +} + +StatusBarWidget > QFrame { + margin-top: 1px; + border: 0px solid #3c404b; + border-left-width: 1px; + padding: 0px 12px 2px; } /* Group Box */ diff --git a/UI/forms/StatusBarWidget.ui b/UI/forms/StatusBarWidget.ui new file mode 100644 index 00000000000000..0971f7dd9eecf8 --- /dev/null +++ b/UI/forms/StatusBarWidget.ui @@ -0,0 +1,418 @@ + + + StatusBarWidget + + + + 0 + 0 + 714 + 34 + + + + + 0 + 34 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 6 + + + 0 + + + + + Qt::Horizontal + + + + 2 + 2 + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + DelayInfo + + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + DroppedFrames + + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + :/res/images/network-inactive.svg + + + + + + + true + + + + 0 + 0 + + + + 0 kbps + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + :/res/images/streaming-inactive.svg + + + false + + + + + + + true + + + + 0 + 0 + + + + 00:00:00 + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + :/res/images/recording-inactive.svg + + + false + + + + + + + true + + + + 0 + 0 + + + + 00:00:00 + + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + CPU: 0.0% + + + Qt::AlignCenter + + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + 0.00 / 0.00 FPS + + + Qt::AlignCenter + + + + + + + + + + + + + diff --git a/UI/forms/images/network-bad.svg b/UI/forms/images/network-bad.svg new file mode 100644 index 00000000000000..9e3517f32995d6 --- /dev/null +++ b/UI/forms/images/network-bad.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/UI/forms/images/network-disconnected.svg b/UI/forms/images/network-disconnected.svg new file mode 100644 index 00000000000000..df5e20512bcf7a --- /dev/null +++ b/UI/forms/images/network-disconnected.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/UI/forms/images/network-excellent.svg b/UI/forms/images/network-excellent.svg new file mode 100644 index 00000000000000..0e04e7d34d93d9 --- /dev/null +++ b/UI/forms/images/network-excellent.svg @@ -0,0 +1,4 @@ + + + + diff --git a/UI/forms/images/network-good.svg b/UI/forms/images/network-good.svg new file mode 100644 index 00000000000000..0bbe206e345750 --- /dev/null +++ b/UI/forms/images/network-good.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/UI/forms/images/network-inactive.svg b/UI/forms/images/network-inactive.svg new file mode 100644 index 00000000000000..af3c8369af5f90 --- /dev/null +++ b/UI/forms/images/network-inactive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/UI/forms/images/network-mediocre.svg b/UI/forms/images/network-mediocre.svg new file mode 100644 index 00000000000000..283983aa0e40cf --- /dev/null +++ b/UI/forms/images/network-mediocre.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/UI/forms/images/recording-active.svg b/UI/forms/images/recording-active.svg index 7c735a9d31a87b..30b442e91fd179 100644 --- a/UI/forms/images/recording-active.svg +++ b/UI/forms/images/recording-active.svg @@ -1,37 +1,4 @@ - - - - - - - image/svg+xml - - - - - - - - + + + diff --git a/UI/forms/images/recording-inactive.svg b/UI/forms/images/recording-inactive.svg index 6b6a198d5c6806..1c19b22dca34d9 100644 --- a/UI/forms/images/recording-inactive.svg +++ b/UI/forms/images/recording-inactive.svg @@ -1,14 +1,4 @@ - - - - - - - - + + + diff --git a/UI/forms/images/recording-pause-inactive.svg b/UI/forms/images/recording-pause-inactive.svg index 64d9c08de7ccef..1749df086a535a 100644 --- a/UI/forms/images/recording-pause-inactive.svg +++ b/UI/forms/images/recording-pause-inactive.svg @@ -1,45 +1,7 @@ - - - - - - - image/svg+xml - - - - - - - - - + + + + + + diff --git a/UI/forms/images/recording-pause.svg b/UI/forms/images/recording-pause.svg index 1d440f5a1823cf..780138cf724492 100644 --- a/UI/forms/images/recording-pause.svg +++ b/UI/forms/images/recording-pause.svg @@ -1,45 +1,7 @@ - - - - - - - image/svg+xml - - - - - - - - - + + + + + + diff --git a/UI/forms/images/streaming-active.svg b/UI/forms/images/streaming-active.svg index 2d7b7a146c4d2f..d92bca48db504a 100644 --- a/UI/forms/images/streaming-active.svg +++ b/UI/forms/images/streaming-active.svg @@ -1,55 +1,20 @@ - - - - - - - image/svg+xml - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/UI/forms/images/streaming-inactive.svg b/UI/forms/images/streaming-inactive.svg index 94393032b7353c..b7dc06be44b507 100644 --- a/UI/forms/images/streaming-inactive.svg +++ b/UI/forms/images/streaming-inactive.svg @@ -1,21 +1,20 @@ - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/UI/forms/obs.qrc b/UI/forms/obs.qrc index 0b09567a932602..4386adf7a0574f 100644 --- a/UI/forms/obs.qrc +++ b/UI/forms/obs.qrc @@ -64,6 +64,12 @@ images/expand.svg images/collapse.svg images/entry-clear.svg + images/network-excellent.svg + images/network-good.svg + images/network-mediocre.svg + images/network-bad.svg + images/network-disconnected.svg + images/network-inactive.svg images/settings/output.svg diff --git a/UI/window-basic-status-bar.cpp b/UI/window-basic-status-bar.cpp index d136b35b5e9b4d..fa16fd1b068387 100644 --- a/UI/window-basic-status-bar.cpp +++ b/UI/window-basic-status-bar.cpp @@ -1,97 +1,66 @@ -#include -#include #include #include #include "obs-app.hpp" #include "window-basic-main.hpp" #include "window-basic-status-bar.hpp" #include "window-basic-main-outputs.hpp" +#include "qt-wrappers.hpp" +#include "platform.hpp" + +#include "ui_StatusBarWidget.h" + +static constexpr int bitrateUpdateSeconds = 2; +static constexpr int congestionUpdateSeconds = 4; +static constexpr float excellentThreshold = 0.0f; +static constexpr float goodThreshold = 0.3333f; +static constexpr float mediocreThreshold = 0.6667f; +static constexpr float badThreshold = 1.0f; + +StatusBarWidget::StatusBarWidget(QWidget *parent) + : QWidget(parent), + ui(new Ui::StatusBarWidget) +{ + ui->setupUi(this); +} + +StatusBarWidget::~StatusBarWidget() {} OBSBasicStatusBar::OBSBasicStatusBar(QWidget *parent) : QStatusBar(parent), - delayInfo(new QLabel), - droppedFrames(new QLabel), - streamIcon(new QLabel), - streamTime(new QLabel), - recordTime(new QLabel), - recordIcon(new QLabel), - cpuUsage(new QLabel), - transparentPixmap(20, 20), - greenPixmap(20, 20), - grayPixmap(20, 20), - redPixmap(20, 20), + excellentPixmap(QIcon(":/res/images/network-excellent.svg") + .pixmap(QSize(16, 16))), + goodPixmap( + QIcon(":/res/images/network-good.svg").pixmap(QSize(16, 16))), + mediocrePixmap(QIcon(":/res/images/network-mediocre.svg") + .pixmap(QSize(16, 16))), + badPixmap( + QIcon(":/res/images/network-bad.svg").pixmap(QSize(16, 16))), recordingActivePixmap(QIcon(":/res/images/recording-active.svg") - .pixmap(QSize(20, 20))), + .pixmap(QSize(16, 16))), recordingPausePixmap(QIcon(":/res/images/recording-pause.svg") - .pixmap(QSize(20, 20))), - recordingPauseInactivePixmap( - QIcon(":/res/images/recording-pause-inactive.svg") - .pixmap(QSize(20, 20))), - recordingInactivePixmap(QIcon(":/res/images/recording-inactive.svg") - .pixmap(QSize(20, 20))), + .pixmap(QSize(16, 16))), streamingActivePixmap(QIcon(":/res/images/streaming-active.svg") - .pixmap(QSize(20, 20))), - streamingInactivePixmap(QIcon(":/res/images/streaming-inactive.svg") - .pixmap(QSize(20, 20))) + .pixmap(QSize(16, 16))) { - streamTime->setText(QString("LIVE: 00:00:00")); - recordTime->setText(QString("REC: 00:00:00")); - cpuUsage->setText(QString("CPU: 0.0%, 0.00 fps")); - - streamIcon->setPixmap(streamingInactivePixmap); - recordIcon->setPixmap(recordingInactivePixmap); - - QWidget *brWidget = new QWidget(this); - QHBoxLayout *brLayout = new QHBoxLayout(brWidget); - brLayout->setContentsMargins(0, 0, 0, 0); - - statusSquare = new QLabel(brWidget); - brLayout->addWidget(statusSquare); - - kbps = new QLabel(brWidget); - brLayout->addWidget(kbps); - - brWidget->setLayout(brLayout); - - delayInfo->setAlignment(Qt::AlignRight); - delayInfo->setAlignment(Qt::AlignVCenter); - droppedFrames->setAlignment(Qt::AlignRight); - droppedFrames->setAlignment(Qt::AlignVCenter); - streamIcon->setAlignment(Qt::AlignRight); - streamIcon->setAlignment(Qt::AlignVCenter); - streamTime->setAlignment(Qt::AlignRight); - streamTime->setAlignment(Qt::AlignVCenter); - recordIcon->setAlignment(Qt::AlignRight); - recordIcon->setAlignment(Qt::AlignVCenter); - recordTime->setAlignment(Qt::AlignRight); - recordTime->setAlignment(Qt::AlignVCenter); - cpuUsage->setAlignment(Qt::AlignRight); - cpuUsage->setAlignment(Qt::AlignVCenter); - kbps->setAlignment(Qt::AlignRight); - kbps->setAlignment(Qt::AlignVCenter); - - delayInfo->setIndent(20); - droppedFrames->setIndent(20); - streamIcon->setIndent(20); - recordIcon->setIndent(20); - cpuUsage->setIndent(20); - kbps->setIndent(10); - - addPermanentWidget(droppedFrames); - addPermanentWidget(streamIcon); - addPermanentWidget(streamTime); - addPermanentWidget(recordIcon); - addPermanentWidget(recordTime); - addPermanentWidget(cpuUsage); - addPermanentWidget(delayInfo); - addPermanentWidget(brWidget); - - transparentPixmap.fill(QColor(0, 0, 0, 0)); - greenPixmap.fill(QColor(0, 255, 0)); - grayPixmap.fill(QColor(72, 72, 72)); - redPixmap.fill(QColor(255, 0, 0)); - - statusSquare->setPixmap(transparentPixmap); + statusWidget = new StatusBarWidget(this); + statusWidget->ui->delayInfo->setText(""); + statusWidget->ui->droppedFrames->setText( + QTStr("DroppedFrames").arg("0", "0.0")); + statusWidget->ui->statusIcon->setPixmap(inactivePixmap); + statusWidget->ui->streamIcon->setPixmap(streamingInactivePixmap); + statusWidget->ui->streamTime->setDisabled(true); + statusWidget->ui->recordIcon->setPixmap(recordingInactivePixmap); + statusWidget->ui->recordTime->setDisabled(true); + statusWidget->ui->delayFrame->hide(); + statusWidget->ui->issuesFrame->hide(); + statusWidget->ui->kbps->hide(); + + addPermanentWidget(statusWidget); + setMinimumHeight(statusWidget->height()); + + UpdateIcons(); + connect(App(), &OBSApp::StyleChanged, this, + &OBSBasicStatusBar::UpdateIcons); } void OBSBasicStatusBar::Activate() @@ -114,16 +83,21 @@ void OBSBasicStatusBar::Activate() active = true; if (streamOutput) { - statusSquare->setPixmap(grayPixmap); + statusWidget->ui->statusIcon->setPixmap(inactivePixmap); } } if (streamOutput) { - streamIcon->setPixmap(streamingActivePixmap); + statusWidget->ui->streamIcon->setPixmap(streamingActivePixmap); + statusWidget->ui->streamTime->setDisabled(false); + statusWidget->ui->issuesFrame->show(); + statusWidget->ui->kbps->show(); + firstCongestionUpdate = true; } if (recordOutput) { - recordIcon->setPixmap(recordingActivePixmap); + statusWidget->ui->recordIcon->setPixmap(recordingActivePixmap); + statusWidget->ui->recordTime->setDisabled(false); } } @@ -134,23 +108,35 @@ void OBSBasicStatusBar::Deactivate() return; if (!streamOutput) { - streamTime->setText(QString("LIVE: 00:00:00")); - streamIcon->setPixmap(streamingInactivePixmap); + statusWidget->ui->streamTime->setText(QString("00:00:00")); + statusWidget->ui->streamTime->setDisabled(true); + statusWidget->ui->streamIcon->setPixmap( + streamingInactivePixmap); + statusWidget->ui->statusIcon->setPixmap(inactivePixmap); + statusWidget->ui->delayFrame->hide(); + statusWidget->ui->issuesFrame->hide(); + statusWidget->ui->kbps->hide(); totalStreamSeconds = 0; + congestionArray.clear(); + disconnected = false; + firstCongestionUpdate = false; } if (!recordOutput) { - recordTime->setText(QString("REC: 00:00:00")); - recordIcon->setPixmap(recordingInactivePixmap); + statusWidget->ui->recordTime->setText(QString("00:00:00")); + statusWidget->ui->recordTime->setDisabled(true); + statusWidget->ui->recordIcon->setPixmap( + recordingInactivePixmap); totalRecordSeconds = 0; } if (main->outputHandler && !main->outputHandler->Active()) { delete refreshTimer; - delayInfo->setText(""); - droppedFrames->setText(""); - kbps->setText(""); + statusWidget->ui->delayInfo->setText(""); + statusWidget->ui->droppedFrames->setText( + QTStr("DroppedFrames").arg("0", "0.0")); + statusWidget->ui->kbps->setText("0 kbps"); delaySecTotal = 0; delaySecStarting = 0; @@ -159,7 +145,7 @@ void OBSBasicStatusBar::Deactivate() active = false; overloadedNotify = true; - statusSquare->setPixmap(transparentPixmap); + statusWidget->ui->statusIcon->setPixmap(inactivePixmap); } } @@ -184,19 +170,20 @@ void OBSBasicStatusBar::UpdateDelayMsg() msg = QTStr("Basic.StatusBar.Delay"); msg = msg.arg(QString::number(delaySecTotal)); } - } - delayInfo->setText(msg); -} + if (!statusWidget->ui->delayFrame->isVisible()) + statusWidget->ui->delayFrame->show(); -#define BITRATE_UPDATE_SECONDS 2 + statusWidget->ui->delayInfo->setText(msg); + } +} void OBSBasicStatusBar::UpdateBandwidth() { if (!streamOutput) return; - if (++bitrateUpdateSeconds < BITRATE_UPDATE_SECONDS) + if (++seconds < bitrateUpdateSeconds) return; uint64_t bytesSent = obs_output_get_total_bytes(streamOutput); @@ -215,14 +202,18 @@ void OBSBasicStatusBar::UpdateBandwidth() double kbitsPerSec = double(bitsBetween) / timePassed / 1000.0; QString text; - text += QString("kb/s: ") + QString::number(kbitsPerSec, 'f', 0); + text += QString::number(kbitsPerSec, 'f', 0) + QString(" kbps"); + + statusWidget->ui->kbps->setText(text); + statusWidget->ui->kbps->setMinimumWidth( + statusWidget->ui->kbps->width()); - kbps->setText(text); - kbps->setMinimumWidth(kbps->width()); + if (!statusWidget->ui->kbps->isVisible()) + statusWidget->ui->kbps->show(); lastBytesSent = bytesSent; lastBytesSentTime = bytesSentTime; - bitrateUpdateSeconds = 0; + seconds = 0; } void OBSBasicStatusBar::UpdateCPUUsage() @@ -233,11 +224,34 @@ void OBSBasicStatusBar::UpdateCPUUsage() QString text; text += QString("CPU: ") + - QString::number(main->GetCPUUsage(), 'f', 1) + QString("%, ") + - QString::number(obs_get_active_fps(), 'f', 2) + QString(" fps"); + QString::number(main->GetCPUUsage(), 'f', 1) + QString("%"); + + statusWidget->ui->cpuUsage->setText(text); + statusWidget->ui->cpuUsage->setMinimumWidth( + statusWidget->ui->cpuUsage->width()); - cpuUsage->setText(text); - cpuUsage->setMinimumWidth(cpuUsage->width()); + UpdateCurrentFPS(); +} + +void OBSBasicStatusBar::UpdateCurrentFPS() +{ + OBSBasic *main = qobject_cast(parent()); + if (!main) + return; + + struct obs_video_info ovi; + obs_get_video_info(&ovi); + float targetFPS = (float)ovi.fps_num / (float)ovi.fps_den; + + QString text; + text += QString::number(obs_get_active_fps(), 'f', 2); + text += QString(" / "); + text += QString::number(targetFPS, 'f', 2); + text += QString(" FPS"); + + statusWidget->ui->fpsCurrent->setText(text); + statusWidget->ui->fpsCurrent->setMinimumWidth( + statusWidget->ui->fpsCurrent->width()); } void OBSBasicStatusBar::UpdateStreamTime() @@ -249,16 +263,20 @@ void OBSBasicStatusBar::UpdateStreamTime() int minutes = totalMinutes % 60; int hours = totalMinutes / 60; - QString text = QString::asprintf("LIVE: %02d:%02d:%02d", hours, minutes, - seconds); - streamTime->setText(text); - streamTime->setMinimumWidth(streamTime->width()); + QString text = + QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds); + statusWidget->ui->streamTime->setText(text); + if (streamOutput && !statusWidget->ui->streamTime->isEnabled()) + statusWidget->ui->streamTime->setDisabled(false); if (reconnectTimeout > 0) { QString msg = QTStr("Basic.StatusBar.Reconnecting") .arg(QString::number(retries), QString::number(reconnectTimeout)); showMessage(msg); + disconnected = true; + statusWidget->ui->statusIcon->setPixmap(disconnectedPixmap); + congestionArray.clear(); reconnectTimeout--; } else if (retries > 0) { @@ -289,14 +307,15 @@ void OBSBasicStatusBar::UpdateRecordTime() int minutes = totalMinutes % 60; int hours = totalMinutes / 60; - QString text = QString::asprintf("REC: %02d:%02d:%02d", hours, + QString text = QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds); - recordTime->setText(text); - recordTime->setMinimumWidth(recordTime->width()); + statusWidget->ui->recordTime->setText(text); + if (recordOutput && !statusWidget->ui->recordTime->isEnabled()) + statusWidget->ui->recordTime->setDisabled(false); } else { - recordIcon->setPixmap(streamPauseIconToggle - ? recordingPauseInactivePixmap + statusWidget->ui->recordIcon->setPixmap( + streamPauseIconToggle ? recordingPauseInactivePixmap : recordingPausePixmap); streamPauseIconToggle = !streamPauseIconToggle; @@ -318,8 +337,10 @@ void OBSBasicStatusBar::UpdateDroppedFrames() QString text = QTStr("DroppedFrames"); text = text.arg(QString::number(totalDropped), QString::number(percent, 'f', 1)); - droppedFrames->setText(text); - droppedFrames->setMinimumWidth(droppedFrames->width()); + statusWidget->ui->droppedFrames->setText(text); + + if (!statusWidget->ui->issuesFrame->isVisible()) + statusWidget->ui->issuesFrame->show(); /* ----------------------------------- * * calculate congestion color */ @@ -331,28 +352,37 @@ void OBSBasicStatusBar::UpdateDroppedFrames() if (avgCongestion > 1.0f) avgCongestion = 1.0f; - if (avgCongestion < EPSILON) { - statusSquare->setPixmap(greenPixmap); - } else if (fabsf(avgCongestion - 1.0f) < EPSILON) { - statusSquare->setPixmap(redPixmap); - } else { - QPixmap pixmap(20, 20); + lastCongestion = congestion; - float red = avgCongestion * 2.0f; - if (red > 1.0f) - red = 1.0f; - red *= 255.0; + if (disconnected) + return; - float green = (1.0f - avgCongestion) * 2.0f; - if (green > 1.0f) - green = 1.0f; - green *= 255.0; + bool update = firstCongestionUpdate; + float congestionOverTime = avgCongestion; - pixmap.fill(QColor(int(red), int(green), 0)); - statusSquare->setPixmap(pixmap); + if (congestionArray.size() >= congestionUpdateSeconds) { + congestionOverTime = accumulate(congestionArray.begin(), + congestionArray.end(), 0.0f) / + (float)congestionArray.size(); + congestionArray.clear(); + update = true; + } else { + congestionArray.emplace_back(avgCongestion); } - lastCongestion = congestion; + if (update) { + if (congestionOverTime <= excellentThreshold + EPSILON) + statusWidget->ui->statusIcon->setPixmap( + excellentPixmap); + else if (congestionOverTime <= goodThreshold) + statusWidget->ui->statusIcon->setPixmap(goodPixmap); + else if (congestionOverTime <= mediocreThreshold) + statusWidget->ui->statusIcon->setPixmap(mediocrePixmap); + else if (congestionOverTime <= badThreshold) + statusWidget->ui->statusIcon->setPixmap(badPixmap); + + firstCongestionUpdate = false; + } } void OBSBasicStatusBar::OBSOutputReconnect(void *data, calldata_t *params) @@ -395,7 +425,7 @@ void OBSBasicStatusBar::ReconnectClear() { retries = 0; reconnectTimeout = 0; - bitrateUpdateSeconds = -1; + seconds = -1; lastBytesSent = 0; lastBytesSentTime = os_gettime_ns(); delaySecTotal = 0; @@ -414,6 +444,8 @@ void OBSBasicStatusBar::ReconnectSuccess() if (streamOutput) { delaySecTotal = obs_output_get_active_delay(streamOutput); UpdateDelayMsg(); + disconnected = false; + firstCongestionUpdate = true; } } @@ -518,11 +550,12 @@ void OBSBasicStatusBar::RecordingStopped() void OBSBasicStatusBar::RecordingPaused() { - QString text = recordTime->text() + QStringLiteral(" (PAUSED)"); - recordTime->setText(text); + QString text = statusWidget->ui->recordTime->text() + + QStringLiteral(" (PAUSED)"); + statusWidget->ui->recordTime->setText(text); if (recordOutput) { - recordIcon->setPixmap(recordingPausePixmap); + statusWidget->ui->recordIcon->setPixmap(recordingPausePixmap); streamPauseIconToggle = true; } } @@ -530,6 +563,53 @@ void OBSBasicStatusBar::RecordingPaused() void OBSBasicStatusBar::RecordingUnpaused() { if (recordOutput) { - recordIcon->setPixmap(recordingActivePixmap); + statusWidget->ui->recordIcon->setPixmap(recordingActivePixmap); + } +} + +static QPixmap GetPixmap(const QString &filename) +{ + bool darkTheme = obs_frontend_is_theme_dark(); + QString path; + + if (darkTheme) { + std::string darkPath; + QString themePath = QString("themes/Dark/") + filename; + GetDataFilePath(QT_TO_UTF8(themePath), darkPath); + path = QT_UTF8(darkPath.c_str()); + } else { + path = QString(":/res/images/" + filename); + } + + return QIcon(path).pixmap(QSize(16, 16)); +} + +void OBSBasicStatusBar::UpdateIcons() +{ + disconnectedPixmap = GetPixmap("network-disconnected.svg"); + inactivePixmap = GetPixmap("network-inactive.svg"); + + streamingInactivePixmap = GetPixmap("streaming-inactive.svg"); + + recordingInactivePixmap = GetPixmap("recording-inactive.svg"); + recordingPauseInactivePixmap = + GetPixmap("recording-pause-inactive.svg"); + + bool streaming = obs_frontend_streaming_active(); + + if (!streaming) { + statusWidget->ui->streamIcon->setPixmap( + streamingInactivePixmap); + statusWidget->ui->statusIcon->setPixmap(inactivePixmap); + } else { + if (disconnected) + statusWidget->ui->statusIcon->setPixmap( + disconnectedPixmap); } + + bool recording = obs_frontend_recording_active(); + + if (!recording) + statusWidget->ui->recordIcon->setPixmap( + recordingInactivePixmap); } diff --git a/UI/window-basic-status-bar.hpp b/UI/window-basic-status-bar.hpp index 65e3fd40d87532..a37e3313c67f1f 100644 --- a/UI/window-basic-status-bar.hpp +++ b/UI/window-basic-status-bar.hpp @@ -3,30 +3,39 @@ #include #include #include -#include #include +#include -class QLabel; +class Ui_StatusBarWidget; + +class StatusBarWidget : public QWidget { + Q_OBJECT + + friend class OBSBasicStatusBar; + +private: + std::unique_ptr ui; + +public: + StatusBarWidget(QWidget *parent = nullptr); + ~StatusBarWidget(); +}; class OBSBasicStatusBar : public QStatusBar { Q_OBJECT private: - QLabel *delayInfo; - QLabel *droppedFrames; - QLabel *streamIcon; - QLabel *streamTime; - QLabel *recordTime; - QLabel *recordIcon; - QLabel *cpuUsage; - QLabel *kbps; - QLabel *statusSquare; + StatusBarWidget *statusWidget = nullptr; obs_output_t *streamOutput = nullptr; obs_output_t *recordOutput = nullptr; bool active = false; bool overloadedNotify = true; bool streamPauseIconToggle = false; + bool disconnected = false; + bool firstCongestionUpdate = false; + + std::vector congestionArray; int retries = 0; int totalStreamSeconds = 0; @@ -42,14 +51,16 @@ class OBSBasicStatusBar : public QStatusBar { int startTotalFrameCount = 0; int lastSkippedFrameCount = 0; - int bitrateUpdateSeconds = 0; + int seconds = 0; uint64_t lastBytesSent = 0; uint64_t lastBytesSentTime = 0; - QPixmap transparentPixmap; - QPixmap greenPixmap; - QPixmap grayPixmap; - QPixmap redPixmap; + QPixmap excellentPixmap; + QPixmap goodPixmap; + QPixmap mediocrePixmap; + QPixmap badPixmap; + QPixmap disconnectedPixmap; + QPixmap inactivePixmap; QPixmap recordingActivePixmap; QPixmap recordingPausePixmap; @@ -81,6 +92,8 @@ private slots: void ReconnectSuccess(); void UpdateStatusBar(); void UpdateCPUUsage(); + void UpdateCurrentFPS(); + void UpdateIcons(); public: OBSBasicStatusBar(QWidget *parent);