From c7c328dc83c054cc8b3a64fba6b72f6ba3c63ee9 Mon Sep 17 00:00:00 2001 From: Shaolin Date: Fri, 27 Apr 2018 23:49:48 -0300 Subject: [PATCH] UI: Add Vertical Mixer option --- UI/CMakeLists.txt | 2 + UI/data/locale/en-US.ini | 1 + UI/data/themes/Acri.qss | 33 +- UI/data/themes/Dark.qss | 44 +-- UI/data/themes/Rachni.qss | 50 ++- UI/forms/OBSBasic.ui | 163 ++++++--- .../frontend-tools/CMakeLists.txt | 2 + UI/horizontal-scroll-area.cpp | 10 + UI/horizontal-scroll-area.hpp | 19 + UI/obs-app.cpp | 3 + UI/volume-control.cpp | 333 ++++++++++++++---- UI/volume-control.hpp | 23 +- UI/window-basic-main.cpp | 71 +++- UI/window-basic-main.hpp | 6 +- 14 files changed, 588 insertions(+), 172 deletions(-) create mode 100644 UI/horizontal-scroll-area.cpp create mode 100644 UI/horizontal-scroll-area.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 880869c2c..d33e931b3 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -161,6 +161,7 @@ set(obs_SOURCES item-widget-helpers.cpp visibility-checkbox.cpp locked-checkbox.cpp + horizontal-scroll-area.cpp vertical-scroll-area.cpp visibility-item-widget.cpp slider-absoluteset-style.cpp @@ -209,6 +210,7 @@ set(obs_HEADERS item-widget-helpers.hpp visibility-checkbox.hpp locked-checkbox.hpp + horizontal-scroll-area.hpp vertical-scroll-area.hpp visibility-item-widget.hpp slider-absoluteset-style.hpp diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 48b540fbc..5c0941479 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -83,6 +83,7 @@ None="None" StudioMode.Preview="Preview" StudioMode.Program="Program" ShowInMultiview="Show in Multiview" +VerticalLayout="Vertical Layout" # warning if program already open AlreadyRunning.Title="OBS is already running" diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index af3eb9070..f4b767e48 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -505,16 +505,20 @@ QSlider::handle:horizontal { } QSlider::handle:horizontal:pressed { - background-color: QLinearGradient(x1: 0, y1: 1, x2: 0, y2: 0, + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 rgb(240,239,240), stop: 0.25 rgb(200,199,200), stop: 1 rgb(162,161,162)); } +QSlider::sub-page:horizontal { + background-color: #2a3a75; +} + QSlider::sub-page:horizontal:disabled { - background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 rgb(31,30,31), - stop: 0.75 rgb(50, 49, 50)); + background-color: QLinearGradient(x1: 0, y1: 1, x2: 0, y2: 0, + stop: 0 rgb(26,25,26), + stop: 0.75 rgb(10, 10, 10)); border-radius: 2px; } @@ -533,23 +537,27 @@ QSlider::handle:vertical { stop: 0.25 rgb(200,199,200), stop: 1 rgb(162,161,162)); border: 1px solid rgb(24,24,25); - border-radius: 4px; + border-radius: 3px; width: 10px; height: 18px; - margin: -3px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ + margin: 0 -3px; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ } QSlider::handle:vertical:pressed { - background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, + background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 rgb(240,239,240), stop: 0.25 rgb(200,199,200), stop: 1 rgb(162,161,162)); } -QSlider::sub-page:vertical:disabled { +QSlider::add-page:vertical { + background-color: #2a3a75; +} + +QSlider::add-page:vertical:disabled { background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, - stop: 0 rgb(31,30,31), - stop: 0.75 rgb(50, 49, 50)); + stop: 0 rgb(26,25,26), + stop: 0.75 rgb(10, 10, 10)); border-radius: 2px; } @@ -557,15 +565,10 @@ QSlider::handle:hover { background-color: rgb(200,199,200); } -QSlider::sub-page { - background-color: #2a3a75; -} - QSlider::handle:disabled { background-color: rgb(15,15,16); } - /* Volume Control */ /* Old Meters */ diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index 4a9326c6a..78c343dbf 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -423,9 +423,9 @@ QPushButton::menu-indicator { /* Sliders */ QSlider::groove:horizontal { - background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 rgb(31,30,31), /* veryDark */ - stop: 0.75 rgb(50, 49, 50)); + background-color: QLinearGradient(x1: 0, y1: 1, x2: 0, y2: 0, + stop: 0 rgb(50, 49, 50), /* dark */ + stop: 0.75 rgb(88,87,88)); /* kindaDark */ height: 4px; border: none; border-radius: 2px; @@ -450,32 +450,37 @@ QSlider::handle:horizontal:pressed { stop: 1 rgb(162,161,162)); /* light */ } +QSlider::sub-page:horizontal { + background-color: rgb(42,130,218); /* blue */ + border-radius: 2px; +} + QSlider::sub-page:horizontal:disabled { - background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + background-color: QLinearGradient(x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 rgb(31,30,31), /* veryDark */ - stop: 0.75 rgb(50, 49, 50)); + stop: 0.75 rgb(50, 49, 50)); /* dark */ border-radius: 2px; } QSlider::groove:vertical { - background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, - stop: 0 rgb(31,30,31), /* veryDark */ - stop: 0.75 rgb(50, 49, 50)); + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, + stop: 0 rgb(50, 49, 50), /* dark */ + stop: 0.75 rgb(88,87,88)); /* kindaDark */ width: 4px; border: none; border-radius: 2px; } QSlider::handle:vertical { - background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, stop: 0 rgb(240,239,240), /* lighter */ stop: 0.25 rgb(200,199,200), stop: 1 rgb(162,161,162)); /* light */ border: 1px solid rgb(58,57,58); /* dark */ - border-radius: 4px; + border-radius: 3px; width: 10px; height: 18px; - margin: -3px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ + margin: 0 -3px; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ } QSlider::handle:vertical:pressed { @@ -485,10 +490,15 @@ QSlider::handle:vertical:pressed { stop: 1 rgb(162,161,162)); /* light */ } -QSlider::sub-page:vertical:disabled { - background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, +QSlider::add-page:vertical { + background-color: rgb(42,130,218); /* blue */ + border-radius: 2px; +} + +QSlider::add-page:vertical:disabled { + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, stop: 0 rgb(31,30,31), /* veryDark */ - stop: 0.75 rgb(50, 49, 50)); + stop: 0.75 rgb(50, 49, 50)); /* dark */ border-radius: 2px; } @@ -496,16 +506,10 @@ QSlider::handle:hover { background-color: rgb(200,199,200); /* veryLight */ } -QSlider::sub-page { - background-color: rgb(42,130,218); /* blue */ - border-radius: 2px; -} - QSlider::handle:disabled { background-color: rgb(122,121,122); /* light */ } - /* Volume Control */ VolumeMeter { diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index 5c7887cdb..5249acc91 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -1022,10 +1022,55 @@ QSlider::handle:horizontal:pressed { stop: 1 rgb(162, 161, 162)); } +QSlider::sub-page:horizontal { + background-color: rgb(0, 188, 212); /* Cyan (Primary) */ + border-radius: 2px; +} + QSlider::sub-page:horizontal:disabled { background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 rgb(35, 38, 41), /* Dark Gray */ + stop: 0.75 rgb(35, 38, 41)); /* Dark Gray */ + border-radius: 2px; +} + +QSlider::groove:vertical { + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, + stop: 0 rgb(35, 38, 41), /* Dark Gray */ stop: 0.75 rgb(50, 49, 50)); + width: 4px; + border: none; + border-radius: 2px; +} + +QSlider::handle:vertical { + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, + stop: 0 rgb(240, 239, 240), + stop: 0.25 rgb(200, 199, 200), + stop: 1 rgb(162, 161, 162)); + border: 1px solid rgb(58, 57, 58); + border-radius: 3px; + width: 10px; + height: 18px; + margin: 0 -3px; +} + +QSlider::handle:vertical:pressed { + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, + stop: 0 rgb(240, 239, 240), + stop: 0.25 rgb(200, 199, 200), + stop: 1 rgb(162, 161, 162)); +} + +QSlider::add-page:vertical { + background-color: rgb(0, 188, 212); /* Cyan (Primary) */ + border-radius: 2px; +} + +QSlider::add-page:vertical:disabled { + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, + stop: 0 rgb(35, 38, 41), /* Dark Gray */ + stop: 0.75 rgb(35, 38, 41)); /* Dark Gray */ border-radius: 2px; } @@ -1033,11 +1078,6 @@ QSlider::handle:hover { background-color: rgb(200, 199, 200); } -QSlider::sub-page { - background-color: rgb(0, 188, 212); /* Cyan (Primary) */ - border-radius: 2px; -} - QSlider::handle:disabled { background-color: rgb(122, 121, 122); } diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index 55e66c4da..5f06b0ce3 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -615,63 +615,118 @@ 4 - - - - 220 - 0 - - - - Qt::CustomContextMenu - - - QFrame::StyledPanel - - - QFrame::Sunken - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 230 - 16 - + + + + + 175 + 220 + - - - 0 - 0 - + + Qt::CustomContextMenu - - - 0 + + QFrame::StyledPanel + + + QFrame::Sunken + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 230 + 16 + - - 0 + + + 0 + 0 + - - 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Qt::CustomContextMenu + + + QFrame::StyledPanel + + + QFrame::Sunken + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOn + + + true + + + + + 0 + 0 + 16 + 230 + - - 0 + + + 0 + 0 + - - 0 - - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + @@ -1614,6 +1669,12 @@ QStatusBar
window-basic-status-bar.hpp
+ + HScrollArea + QScrollArea +
horizontal-scroll-area.hpp
+ 1 +
VScrollArea QScrollArea diff --git a/UI/frontend-plugins/frontend-tools/CMakeLists.txt b/UI/frontend-plugins/frontend-tools/CMakeLists.txt index 37a022a88..ca29cad87 100644 --- a/UI/frontend-plugins/frontend-tools/CMakeLists.txt +++ b/UI/frontend-plugins/frontend-tools/CMakeLists.txt @@ -25,6 +25,7 @@ set(frontend-tools_HEADERS tool-helpers.hpp ../../properties-view.hpp ../../properties-view.moc.hpp + ../../horizontal-scroll-area.hpp ../../vertical-scroll-area.hpp ../../double-slider.hpp ) @@ -34,6 +35,7 @@ set(frontend-tools_SOURCES frontend-tools.c output-timer.cpp ../../properties-view.cpp + ../../horizontal-scroll-area.cpp ../../vertical-scroll-area.cpp ../../double-slider.cpp ) diff --git a/UI/horizontal-scroll-area.cpp b/UI/horizontal-scroll-area.cpp new file mode 100644 index 000000000..8f927fc59 --- /dev/null +++ b/UI/horizontal-scroll-area.cpp @@ -0,0 +1,10 @@ +#include +#include "horizontal-scroll-area.hpp" + +void HScrollArea::resizeEvent(QResizeEvent *event) +{ + if (!!widget()) + widget()->setMaximumHeight(event->size().height()); + + QScrollArea::resizeEvent(event); +} diff --git a/UI/horizontal-scroll-area.hpp b/UI/horizontal-scroll-area.hpp new file mode 100644 index 000000000..8a64c3ea8 --- /dev/null +++ b/UI/horizontal-scroll-area.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +class QResizeEvent; + +class HScrollArea : public QScrollArea { + Q_OBJECT + +public: + inline HScrollArea(QWidget *parent = nullptr) + : QScrollArea(parent) + { + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + +protected: + virtual void resizeEvent(QResizeEvent *event) override; +}; diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 752337fe5..f8f76755c 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -418,6 +418,9 @@ bool OBSApp::InitGlobalConfigDefaults() "CurrentTheme", "Dark"); } + config_set_default_bool(globalConfig, "BasicWindow", + "VerticalVolControl", false); + #ifdef _WIN32 config_set_default_bool(globalConfig, "Audio", "DisableAudioDucking", true); diff --git a/UI/volume-control.cpp b/UI/volume-control.cpp index 62b4a7900..532fa8728 100644 --- a/UI/volume-control.cpp +++ b/UI/volume-control.cpp @@ -111,57 +111,20 @@ void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType) volMeter->setPeakMeterType(peakMeterType); } -VolControl::VolControl(OBSSource source_, bool showConfig) +VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) : source (std::move(source_)), levelTotal (0.0f), levelCount (0.0f), obs_fader (obs_fader_create(OBS_FADER_CUBIC)), - obs_volmeter (obs_volmeter_create(OBS_FADER_LOG)) + obs_volmeter (obs_volmeter_create(OBS_FADER_LOG)), + vertical (vertical) { - QHBoxLayout *volLayout = new QHBoxLayout(); - QVBoxLayout *mainLayout = new QVBoxLayout(); - QHBoxLayout *textLayout = new QHBoxLayout(); - QHBoxLayout *botLayout = new QHBoxLayout(); - nameLabel = new QLabel(); volLabel = new QLabel(); - volMeter = new VolumeMeter(nullptr, obs_volmeter); mute = new MuteCheckBox(); - slider = new QSlider(Qt::Horizontal); - - QFont font = nameLabel->font(); - font.setPointSize(font.pointSize()-1); - QString sourceName = obs_source_get_name(source); setObjectName(sourceName); - nameLabel->setText(sourceName); - nameLabel->setFont(font); - volLabel->setFont(font); - slider->setMinimum(0); - slider->setMaximum(100); - -// slider->setMaximumHeight(13); - - textLayout->setContentsMargins(0, 0, 0, 0); - textLayout->addWidget(nameLabel); - textLayout->addWidget(volLabel); - textLayout->setAlignment(nameLabel, Qt::AlignLeft); - textLayout->setAlignment(volLabel, Qt::AlignRight); - - bool muted = obs_source_muted(source); - mute->setChecked(muted); - mute->setAccessibleName( - QTStr("VolControl.Mute").arg(sourceName)); - - volLayout->addWidget(slider); - volLayout->addWidget(mute); - volLayout->setSpacing(5); - - botLayout->setContentsMargins(0, 0, 0, 0); - botLayout->setSpacing(0); - botLayout->addLayout(volLayout); - if (showConfig) { config = new QPushButton(this); config->setProperty("themeID", "configIconSmall"); @@ -176,18 +139,99 @@ VolControl::VolControl(OBSSource source_, bool showConfig) connect(config, &QAbstractButton::clicked, this, &VolControl::EmitConfigClicked); - - botLayout->addWidget(config); } + QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->setContentsMargins(4, 4, 4, 4); mainLayout->setSpacing(2); - mainLayout->addItem(textLayout); - mainLayout->addWidget(volMeter); - mainLayout->addItem(botLayout); + + if (vertical) { + QHBoxLayout *nameLayout = new QHBoxLayout; + QHBoxLayout *controlLayout = new QHBoxLayout; + QHBoxLayout *volLayout = new QHBoxLayout; + QHBoxLayout *meterLayout = new QHBoxLayout; + + volMeter = new VolumeMeter(nullptr, obs_volmeter, true); + slider = new QSlider(Qt::Vertical); + + nameLayout->setAlignment(Qt::AlignCenter); + meterLayout->setAlignment(Qt::AlignCenter); + controlLayout->setAlignment(Qt::AlignCenter); + volLayout->setAlignment(Qt::AlignCenter); + + nameLayout->setContentsMargins(0, 0, 0, 0); + nameLayout->setSpacing(0); + nameLayout->addWidget(nameLabel); + + controlLayout->setContentsMargins(0, 0, 0, 0); + controlLayout->setSpacing(0); + + if (showConfig) + controlLayout->addWidget(config); + + controlLayout->addItem(new QSpacerItem(3, 0)); + // Add Headphone (audio monitoring) widget here + controlLayout->addWidget(mute); + + meterLayout->setContentsMargins(0, 0, 0, 0); + meterLayout->setSpacing(0); + meterLayout->addWidget(volMeter); + meterLayout->addWidget(slider); + + volLayout->setContentsMargins(0, 0, 0, 0); + volLayout->setSpacing(0); + volLayout->addWidget(volLabel); + + mainLayout->addItem(nameLayout); + mainLayout->addItem(volLayout); + mainLayout->addItem(meterLayout); + mainLayout->addItem(controlLayout); + + setMaximumWidth(110); + } else { + QHBoxLayout *volLayout = new QHBoxLayout; + QHBoxLayout *textLayout = new QHBoxLayout; + QHBoxLayout *botLayout = new QHBoxLayout; + + volMeter = new VolumeMeter(nullptr, obs_volmeter, false); + slider = new QSlider(Qt::Horizontal); + + textLayout->setContentsMargins(0, 0, 0, 0); + textLayout->addWidget(nameLabel); + textLayout->addWidget(volLabel); + textLayout->setAlignment(nameLabel, Qt::AlignLeft); + textLayout->setAlignment(volLabel, Qt::AlignRight); + + volLayout->addWidget(slider); + volLayout->addWidget(mute); + volLayout->setSpacing(5); + + botLayout->setContentsMargins(0, 0, 0, 0); + botLayout->setSpacing(0); + botLayout->addLayout(volLayout); + + if (showConfig) + botLayout->addWidget(config); + + mainLayout->addItem(textLayout); + mainLayout->addWidget(volMeter); + mainLayout->addItem(botLayout); + } setLayout(mainLayout); + QFont font = nameLabel->font(); + font.setPointSize(font.pointSize()-1); + + nameLabel->setText(sourceName); + nameLabel->setFont(font); + volLabel->setFont(font); + slider->setMinimum(0); + slider->setMaximum(100); + + bool muted = obs_source_muted(source); + mute->setChecked(muted); + mute->setAccessibleName(QTStr("VolControl.Mute").arg(sourceName)); obs_fader_add_callback(obs_fader, OBSVolumeChanged, this); obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this); @@ -443,8 +487,10 @@ void VolumeMeter::setPeakMeterType(enum obs_peak_meter_type peakMeterType) } } -VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter) - : QWidget(parent), obs_volmeter(obs_volmeter) +VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter, + bool vertical) + : QWidget(parent), obs_volmeter(obs_volmeter), + vertical(vertical) { // Use a font that can be rendered small. tickFont = QFont("Arial"); @@ -532,9 +578,12 @@ inline void VolumeMeter::handleChannelCofigurationChange() if (displayNrAudioChannels != currentNrAudioChannels) { displayNrAudioChannels = currentNrAudioChannels; - // Make room for 3 pixels high meter, with one pixel between - // each. Then 9 pixels below it for ticks and numbers. - setMinimumSize(130, displayNrAudioChannels * 4 + 8); + // Make room for 3 pixels meter, with one pixel between each. + // Then 9/13 pixels for ticks and numbers. + if (vertical) + setMinimumSize(displayNrAudioChannels * 4 + 14, 130); + else + setMinimumSize(130, displayNrAudioChannels * 4 + 8); resetLevels(); } @@ -650,7 +699,7 @@ void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width, painter.fillRect(x, y, width, height, color); } -void VolumeMeter::paintTicks(QPainter &painter, int x, int y, int width, +void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width, int height) { qreal scale = width / minimumLevel; @@ -679,6 +728,36 @@ void VolumeMeter::paintTicks(QPainter &painter, int x, int y, int width, } } +void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height) +{ + qreal scale = height / minimumLevel; + + painter.setFont(tickFont); + painter.setPen(majorTickColor); + + // Draw major tick lines and numeric indicators. + for (int i = 0; i >= minimumLevel; i-= 5) { + int position = y + int((i * scale) - 1); + QString str = QString::number(i); + + if (i == 0) + painter.drawText(x + 5, position + 4, str); + else if (i == -60) + painter.drawText(x + 4, position, str); + else + painter.drawText(x + 4, position + 2, str); + painter.drawLine(x, position, x + 2, position); + } + + // Draw minor tick lines. + painter.setPen(minorTickColor); + for (int i = 0; i >= minimumLevel; i--) { + int position = y + int((i * scale) - 1); + if (i % 5 != 0) + painter.drawLine(x, position, x + 1, position); + } +} + #define CLIP_FLASH_DURATION_MS 1000 void VolumeMeter::ClipEnding() @@ -686,7 +765,7 @@ void VolumeMeter::ClipEnding() clipping = false; } -void VolumeMeter::paintMeter(QPainter &painter, int x, int y, int width, +void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, float peakHold) { qreal scale = width / minimumLevel; @@ -775,22 +854,111 @@ void VolumeMeter::paintMeter(QPainter &painter, int x, int y, int width, magnitudeColor); } +void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width, + int height, float magnitude, float peak, float peakHold) +{ + qreal scale = height / minimumLevel; + + QMutexLocker locker(&dataMutex); + int minimumPosition = y + 0; + int maximumPosition = y + height; + int magnitudePosition = int(y + height - (magnitude * scale)); + int peakPosition = int(y + height - (peak * scale)); + int peakHoldPosition = int(y + height - (peakHold * scale)); + int warningPosition = int(y + height - (warningLevel * scale)); + int errorPosition = int(y + height - (errorLevel * scale)); + + int nominalLength = warningPosition - minimumPosition; + int warningLength = errorPosition - warningPosition; + int errorLength = maximumPosition - errorPosition; + locker.unlock(); + + if (clipping) { + peakPosition = maximumPosition; + } + + if (peakPosition < minimumPosition) { + painter.fillRect(x, minimumPosition, width, nominalLength, + backgroundNominalColor); + painter.fillRect(x, warningPosition, width, warningLength, + backgroundWarningColor); + painter.fillRect(x, errorPosition, width, errorLength, + backgroundErrorColor); + } else if (peakPosition < warningPosition) { + painter.fillRect(x, minimumPosition, width, peakPosition - + minimumPosition, foregroundNominalColor); + painter.fillRect(x, peakPosition, width, warningPosition - + peakPosition, backgroundNominalColor); + painter.fillRect(x, warningPosition, width, warningLength, + backgroundWarningColor); + painter.fillRect(x, errorPosition, width, errorLength, + backgroundErrorColor); + } else if (peakPosition < errorPosition) { + painter.fillRect(x,minimumPosition, width, nominalLength, + foregroundNominalColor); + painter.fillRect(x, warningPosition, width, peakPosition - + warningPosition, foregroundWarningColor); + painter.fillRect(x, peakPosition, width, errorPosition - + peakPosition, backgroundWarningColor); + painter.fillRect(x, errorPosition, width, errorLength, + backgroundErrorColor); + } else if (peakPosition < maximumPosition) { + painter.fillRect(x, minimumPosition, width, nominalLength, + foregroundNominalColor); + painter.fillRect(x, warningPosition, width, warningLength, + foregroundWarningColor); + painter.fillRect(x, errorPosition, width, peakPosition - + errorPosition, foregroundErrorColor); + painter.fillRect(x, peakPosition, width, maximumPosition - + peakPosition, backgroundErrorColor); + } else { + if (!clipping) { + QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, + SLOT(ClipEnding())); + clipping = true; + } + + int end = errorLength + warningLength + nominalLength; + painter.fillRect(x, minimumPosition, width, end, + QBrush(foregroundErrorColor)); + } + + if (peakHoldPosition - 3 < minimumPosition) + ;// Peak-hold below minimum, no drawing. + else if (peakHoldPosition < warningPosition) + painter.fillRect(x, peakHoldPosition - 3, width, 3, + foregroundNominalColor); + else if (peakHoldPosition < errorPosition) + painter.fillRect(x, peakHoldPosition - 3, width, 3, + foregroundWarningColor); + else + painter.fillRect(x, peakHoldPosition - 3, width, 3, + foregroundErrorColor); + + if (magnitudePosition - 3 >= minimumPosition) + painter.fillRect(x, magnitudePosition - 3, width, 3, + magnitudeColor); +} + void VolumeMeter::paintEvent(QPaintEvent *event) { - UNUSED_PARAMETER(event); - uint64_t ts = os_gettime_ns(); qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001; - int width = size().width(); - int height = size().height(); + const QRect rect = event->region().boundingRect(); + int width = rect.width(); + int height = rect.height(); handleChannelCofigurationChange(); calculateBallistics(ts, timeSinceLastRedraw); bool idle = detectIdle(ts); // Draw the ticks in a off-screen buffer when the widget changes size. - QSize tickPaintCacheSize = QSize(width, 9); + QSize tickPaintCacheSize; + if (vertical) + tickPaintCacheSize = QSize(14, height); + else + tickPaintCacheSize = QSize(width, 9); if (tickPaintCache == nullptr || tickPaintCache->size() != tickPaintCacheSize) { delete tickPaintCache; @@ -800,29 +968,56 @@ void VolumeMeter::paintEvent(QPaintEvent *event) tickPaintCache->fill(clearColor); QPainter tickPainter(tickPaintCache); - paintTicks(tickPainter, 6, 0, tickPaintCacheSize.width() - 6, - tickPaintCacheSize.height()); + if (vertical) { + tickPainter.translate(0, height); + tickPainter.scale(1, -1); + paintVTicks(tickPainter, 0, 11, + tickPaintCacheSize.height() - 11); + } else { + paintHTicks(tickPainter, 6, 0, + tickPaintCacheSize.width() - 6, + tickPaintCacheSize.height()); + } tickPainter.end(); } // Actual painting of the widget starts here. QPainter painter(this); - painter.drawPixmap(0, height - 9, *tickPaintCache); + if (vertical) { + // Invert the Y axis to ease the math + painter.translate(0, height); + painter.scale(1, -1); + painter.drawPixmap(displayNrAudioChannels * 4 - 1, 7, + *tickPaintCache); + } else { + painter.drawPixmap(0, height - 9, *tickPaintCache); + } for (int channelNr = 0; channelNr < displayNrAudioChannels; channelNr++) { - paintMeter(painter, 5, channelNr * 4, width - 5, 3, - displayMagnitude[channelNr], - displayPeak[channelNr], - displayPeakHold[channelNr]); + if (vertical) + paintVMeter(painter, channelNr * 4, 8, 3, height - 10, + displayMagnitude[channelNr], + displayPeak[channelNr], + displayPeakHold[channelNr]); + else + paintHMeter(painter, 5, channelNr * 4, width - 5, 3, + displayMagnitude[channelNr], + displayPeak[channelNr], + displayPeakHold[channelNr]); - if (!idle) { - // By not drawing the input meter boxes the user can - // see that the audio stream has been stopped, without - // having too much visual impact. + if (idle) + continue; + + // By not drawing the input meter boxes the user can + // see that the audio stream has been stopped, without + // having too much visual impact. + if (vertical) + paintInputMeter(painter, channelNr * 4, 3, 3, 3, + displayInputPeakHold[channelNr]); + else paintInputMeter(painter, 0, channelNr * 4, 3, 3, displayInputPeakHold[channelNr]); - } } lastRedrawTime = ts; diff --git a/UI/volume-control.hpp b/UI/volume-control.hpp index 60e88ffd9..fde0d12f4 100644 --- a/UI/volume-control.hpp +++ b/UI/volume-control.hpp @@ -96,12 +96,15 @@ private: inline void calculateBallisticsForChannel(int channelNr, uint64_t ts, qreal timeSinceLastRedraw); - void paintInputMeter(QPainter &painter, int x, int y, - int width, int height, float peakHold); - void paintMeter(QPainter &painter, int x, int y, - int width, int height, - float magnitude, float peak, float peakHold); - void paintTicks(QPainter &painter, int x, int y, int width, int height); + void paintInputMeter(QPainter &painter, int x, int y, int width, + int height, float peakHold); + void paintHMeter(QPainter &painter, int x, int y, int width, int height, + float magnitude, float peak, float peakHold); + void paintHTicks(QPainter &painter, int x, int y, int width, + int height); + void paintVMeter(QPainter &painter, int x, int y, int width, int height, + float magnitude, float peak, float peakHold); + void paintVTicks(QPainter &painter, int x, int y, int height); QMutex dataMutex; @@ -142,10 +145,12 @@ private: uint64_t lastRedrawTime = 0; bool clipping = false; + bool vertical; public: explicit VolumeMeter(QWidget *parent = nullptr, - obs_volmeter_t *obs_volmeter = nullptr); + obs_volmeter_t *obs_volmeter = nullptr, + bool vertical = false); ~VolumeMeter(); void setLevels( @@ -230,6 +235,7 @@ private: float levelCount; obs_fader_t *obs_fader; obs_volmeter_t *obs_volmeter; + bool vertical; static void OBSVolumeChanged(void *param, float db); static void OBSVolumeLevel(void *data, @@ -252,7 +258,8 @@ signals: void ConfigClicked(); public: - explicit VolControl(OBSSource source, bool showConfig = false); + explicit VolControl(OBSSource source, bool showConfig = false, + bool vertical = false); ~VolControl(); inline obs_source_t *GetSource() const {return source;} diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 18eac1bac..50c1ecb9b 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1572,6 +1572,10 @@ void OBSBasic::OBSInit() } } + bool vertical = config_get_bool(App()->GlobalConfig(), "BasicWindow", + "VerticalVolControl"); + ui->stackedMixerArea->setCurrentIndex(vertical); + if (config_get_bool(basicConfig, "General", "OpenStatsOnStartup")) on_stats_triggered(); @@ -2471,6 +2475,11 @@ void OBSBasic::VolControlContextMenu() QAction propertiesAction(QTStr("Properties"), this); QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); + QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); + toggleControlLayoutAction.setCheckable(true); + toggleControlLayoutAction.setChecked(config_get_bool(GetGlobalConfig(), + "BasicWindow", "VerticalVolControl")); + /* ------------------- */ connect(&hideAction, &QAction::triggered, @@ -2495,6 +2504,12 @@ void OBSBasic::VolControlContextMenu() /* ------------------- */ + connect(&toggleControlLayoutAction, &QAction::changed, this, + &OBSBasic::ToggleVolControlLayout, + Qt::DirectConnection); + + /* ------------------- */ + hideAction.setProperty("volControl", QVariant::fromValue(vol)); mixerRenameAction.setProperty("volControl", @@ -2512,18 +2527,35 @@ void OBSBasic::VolControlContextMenu() popup.addAction(&hideAction); popup.addAction(&mixerRenameAction); popup.addSeparator(); + popup.addAction(&toggleControlLayoutAction); + popup.addSeparator(); popup.addAction(&filtersAction); popup.addAction(&propertiesAction); popup.addAction(&advPropAction); popup.exec(QCursor::pos()); } -void OBSBasic::on_mixerScrollArea_customContextMenuRequested() +void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() +{ + StackedMixerAreaContextMenuRequested(); +} + +void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() +{ + StackedMixerAreaContextMenuRequested(); +} + +void OBSBasic::StackedMixerAreaContextMenuRequested() { QAction unhideAllAction(QTStr("UnhideAll"), this); QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); + QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); + toggleControlLayoutAction.setCheckable(true); + toggleControlLayoutAction.setChecked(config_get_bool(GetGlobalConfig(), + "BasicWindow", "VerticalVolControl")); + /* ------------------- */ connect(&unhideAllAction, &QAction::triggered, @@ -2536,19 +2568,49 @@ void OBSBasic::on_mixerScrollArea_customContextMenuRequested() /* ------------------- */ + connect(&toggleControlLayoutAction, &QAction::changed, this, + &OBSBasic::ToggleVolControlLayout, + Qt::DirectConnection); + + /* ------------------- */ + QMenu popup(this); popup.addAction(&unhideAllAction); popup.addSeparator(); + popup.addAction(&toggleControlLayoutAction); + popup.addSeparator(); popup.addAction(&advPropAction); popup.exec(QCursor::pos()); } +void OBSBasic::ToggleVolControlLayout() +{ + bool vertical = !config_get_bool(GetGlobalConfig(), "BasicWindow", + "VerticalVolControl"); + config_set_bool(GetGlobalConfig(), "BasicWindow", "VerticalVolControl", + vertical); + ui->stackedMixerArea->setCurrentIndex(vertical); + + // We need to store it so we can delete current and then add + // at the right order + vector sources; + for (size_t i = 0; i != volumes.size(); i++) + sources.emplace_back(volumes[i]->GetSource()); + + ClearVolumeControls(); + + for (const auto &source : sources) + ActivateAudioSource(source); +} + void OBSBasic::ActivateAudioSource(OBSSource source) { if (SourceMixerHidden(source)) return; - VolControl *vol = new VolControl(source, true); + bool vertical = config_get_bool(GetGlobalConfig(), "BasicWindow", + "VerticalVolControl"); + VolControl *vol = new VolControl(source, true, vertical); double meterDecayRate = config_get_double(basicConfig, "Audio", "MeterDecayRate"); @@ -2582,7 +2644,10 @@ void OBSBasic::ActivateAudioSource(OBSSource source) InsertQObjectByName(volumes, vol); for (auto volume : volumes) { - ui->volumeWidgets->layout()->addWidget(volume); + if (vertical) + ui->vVolControlLayout->addWidget(volume); + else + ui->hVolControlLayout->addWidget(volume); } } diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 2951438fa..277d6f6e2 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -258,6 +258,7 @@ private: void GetAudioSourceFilters(); void GetAudioSourceProperties(); void VolControlContextMenu(); + void ToggleVolControlLayout(); void RefreshSceneCollections(); void ChangeSceneCollection(); @@ -455,7 +456,8 @@ private slots: void MixerRenameSource(); - void on_mixerScrollArea_customContextMenuRequested(); + void on_vMixerScrollArea_customContextMenuRequested(); + void on_hMixerScrollArea_customContextMenuRequested(); void on_actionCopySource_triggered(); void on_actionPasteRef_triggered(); @@ -715,6 +717,8 @@ private slots: void DeferredLoad(const QString &file, int requeueCount); + void StackedMixerAreaContextMenuRequested(); + public slots: void on_actionResetTransform_triggered();