diff --git a/src/utils/volume-control.cpp b/src/utils/volume-control.cpp index 2f130787..e0a93f03 100644 --- a/src/utils/volume-control.cpp +++ b/src/utils/volume-control.cpp @@ -5,23 +5,24 @@ #include #include #include +#include #include -#include - #include using namespace std; #define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) -#define FADER_PRECISION 4096.0 +#define FADER_PRECISION 100 + +// Size of the audio indicator in pixels +#define INDICATOR_THICKNESS 3 + +// Padding on top and bottom of vertical meters +#define METER_PADDING 1 QWeakPointer VolumeMeter::updateTimer; -void VolControl::OBSVolumeChanged(void *data, float db) -{ - Q_UNUSED(db); - Q_UNUSED(data); -} +void VolControl::OBSVolumeChanged(void *, float) {} void VolControl::OBSVolumeLevel(void *data, const float magnitude[MAX_AUDIO_CHANNELS], @@ -33,9 +34,19 @@ void VolControl::OBSVolumeLevel(void *data, volControl->volMeter->setLevels(magnitude, peak, inputPeak); } -void VolControl::SliderChanged(int vol) +void VolControl::VolumeChanged() { - Q_UNUSED(vol); + slider->blockSignals(true); + slider->setValue( + (int)(obs_fader_get_deflection(obs_fader) * FADER_PRECISION)); + slider->blockSignals(false); + + updateText(); +} + +void VolControl::SliderChanged(int) +{ + float prev = obs_source_get_volume(source); updateText(); } @@ -61,6 +72,12 @@ void VolControl::SetName(const QString &newName) { nameLabel->setText(newName); } + +void VolControl::EmitConfigClicked() +{ + emit ConfigClicked(); +} + void VolControl::SetMeterDecayRate(qreal q) { volMeter->setPeakDecayRate(q); @@ -71,23 +88,46 @@ void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType) volMeter->setPeakMeterType(peakMeterType); } -VolControl::VolControl(OBSSource source_, bool vertical) +VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent) + : QSlider(parent) +{ + fad = fader; +} + +VolumeSlider::VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, + QWidget *parent) + : QSlider(orientation, parent) +{ + fad = fader; +} + +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_LOG)), obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)), - vertical(vertical) + vertical(vertical), + contextMenu(nullptr) { nameLabel = new QLabel(); volLabel = new QLabel(); - QString sourceName = ""; - if (source) { - obs_source_get_name(source); - } + QString sourceName = obs_source_get_name(source); setObjectName(sourceName); + if (showConfig) { + config = new QPushButton(this); + config->setProperty("themeID", "menuIconSmall"); + config->setSizePolicy(QSizePolicy::Maximum, + QSizePolicy::Maximum); + config->setMaximumSize(22, 22); + config->setAutoDefault(false); + + connect(config, &QAbstractButton::clicked, this, + &VolControl::EmitConfigClicked); + } + QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->setContentsMargins(4, 4, 4, 4); mainLayout->setSpacing(2); @@ -99,7 +139,7 @@ VolControl::VolControl(OBSSource source_, bool vertical) QHBoxLayout *meterLayout = new QHBoxLayout; volMeter = new VolumeMeter(nullptr, obs_volmeter, true); - slider = new QSlider(Qt::Vertical); + slider = new VolumeSlider(obs_fader, Qt::Vertical); nameLayout->setAlignment(Qt::AlignCenter); meterLayout->setAlignment(Qt::AlignCenter); @@ -113,7 +153,11 @@ VolControl::VolControl(OBSSource source_, bool vertical) 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 meterLayout->setContentsMargins(0, 0, 0, 0); meterLayout->setSpacing(0); @@ -131,6 +175,12 @@ VolControl::VolControl(OBSSource source_, bool vertical) volMeter->setFocusProxy(slider); + // Default size can cause clipping of long names in vertical layout. + QFont font = nameLabel->font(); + QFontInfo info(font); + font.setPointSizeF(0.8 * info.pointSizeF()); + nameLabel->setFont(font); + setMaximumWidth(110); } else { QHBoxLayout *volLayout = new QHBoxLayout; @@ -138,7 +188,7 @@ VolControl::VolControl(OBSSource source_, bool vertical) QHBoxLayout *botLayout = new QHBoxLayout; volMeter = new VolumeMeter(nullptr, obs_volmeter, false); - slider = new QSlider(Qt::Horizontal); + slider = new VolumeSlider(obs_fader, Qt::Horizontal); textLayout->setContentsMargins(0, 0, 0, 0); textLayout->addWidget(nameLabel); @@ -153,6 +203,9 @@ VolControl::VolControl(OBSSource source_, bool vertical) botLayout->setSpacing(0); botLayout->addLayout(volLayout); + if (showConfig) + botLayout->addWidget(config); + mainLayout->addItem(textLayout); mainLayout->addWidget(volMeter); mainLayout->addItem(botLayout); @@ -162,15 +215,10 @@ VolControl::VolControl(OBSSource source_, bool vertical) 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); + slider->setMaximum(int(FADER_PRECISION)); obs_fader_add_callback(obs_fader, OBSVolumeChanged, this); obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this); @@ -180,6 +228,9 @@ VolControl::VolControl(OBSSource source_, bool vertical) obs_fader_attach_source(obs_fader, source); obs_volmeter_attach_source(obs_volmeter, source); + + /* Call volume changed once to init the slider position and label */ + VolumeChanged(); } void VolControl::EnableSlider(bool enable) @@ -187,11 +238,6 @@ void VolControl::EnableSlider(bool enable) slider->setEnabled(enable); } -QSlider *VolControl::GetSlider() const -{ - return slider; -} - VolControl::~VolControl() { obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this); @@ -199,66 +245,143 @@ VolControl::~VolControl() obs_fader_destroy(obs_fader); obs_volmeter_destroy(obs_volmeter); + if (contextMenu) + contextMenu->close(); +} + +static inline QColor color_from_int(long long val) +{ + QColor color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, + (val >> 24) & 0xff); + color.setAlpha(255); + + return color; } QColor VolumeMeter::getBackgroundNominalColor() const { - return backgroundNominalColor; + return p_backgroundNominalColor; +} + +QColor VolumeMeter::getBackgroundNominalColorDisabled() const +{ + return backgroundNominalColorDisabled; } void VolumeMeter::setBackgroundNominalColor(QColor c) { - backgroundNominalColor = std::move(c); + p_backgroundNominalColor = std::move(c); + backgroundNominalColor = p_backgroundNominalColor; +} + +void VolumeMeter::setBackgroundNominalColorDisabled(QColor c) +{ + backgroundNominalColorDisabled = std::move(c); } QColor VolumeMeter::getBackgroundWarningColor() const { - return backgroundWarningColor; + return p_backgroundWarningColor; +} + +QColor VolumeMeter::getBackgroundWarningColorDisabled() const +{ + return backgroundWarningColorDisabled; } void VolumeMeter::setBackgroundWarningColor(QColor c) { - backgroundWarningColor = std::move(c); + p_backgroundWarningColor = std::move(c); + backgroundWarningColor = p_backgroundWarningColor; +} + +void VolumeMeter::setBackgroundWarningColorDisabled(QColor c) +{ + backgroundWarningColorDisabled = std::move(c); } QColor VolumeMeter::getBackgroundErrorColor() const { - return backgroundErrorColor; + return p_backgroundErrorColor; +} + +QColor VolumeMeter::getBackgroundErrorColorDisabled() const +{ + return backgroundErrorColorDisabled; } void VolumeMeter::setBackgroundErrorColor(QColor c) { - backgroundErrorColor = std::move(c); + p_backgroundErrorColor = std::move(c); + backgroundErrorColor = p_backgroundErrorColor; +} + +void VolumeMeter::setBackgroundErrorColorDisabled(QColor c) +{ + backgroundErrorColorDisabled = std::move(c); } QColor VolumeMeter::getForegroundNominalColor() const { - return foregroundNominalColor; + return p_foregroundNominalColor; +} + +QColor VolumeMeter::getForegroundNominalColorDisabled() const +{ + return foregroundNominalColorDisabled; } void VolumeMeter::setForegroundNominalColor(QColor c) { - foregroundNominalColor = std::move(c); + p_foregroundNominalColor = std::move(c); + foregroundNominalColor = p_foregroundNominalColor; +} + +void VolumeMeter::setForegroundNominalColorDisabled(QColor c) +{ + foregroundNominalColorDisabled = std::move(c); } QColor VolumeMeter::getForegroundWarningColor() const { - return foregroundWarningColor; + return p_foregroundWarningColor; +} + +QColor VolumeMeter::getForegroundWarningColorDisabled() const +{ + return foregroundWarningColorDisabled; } void VolumeMeter::setForegroundWarningColor(QColor c) { - foregroundWarningColor = std::move(c); + p_foregroundWarningColor = std::move(c); + foregroundWarningColor = p_foregroundWarningColor; +} + +void VolumeMeter::setForegroundWarningColorDisabled(QColor c) +{ + foregroundWarningColorDisabled = std::move(c); } QColor VolumeMeter::getForegroundErrorColor() const { - return foregroundErrorColor; + return p_foregroundErrorColor; +} + +QColor VolumeMeter::getForegroundErrorColorDisabled() const +{ + return foregroundErrorColorDisabled; } void VolumeMeter::setForegroundErrorColor(QColor c) { - foregroundErrorColor = std::move(c); + p_foregroundErrorColor = std::move(c); + foregroundErrorColor = p_foregroundErrorColor; +} + +void VolumeMeter::setForegroundErrorColorDisabled(QColor c) +{ + foregroundErrorColorDisabled = std::move(c); } QColor VolumeMeter::getClipColor() const @@ -301,6 +424,42 @@ void VolumeMeter::setMinorTickColor(QColor c) minorTickColor = std::move(c); } +int VolumeMeter::getMeterThickness() const +{ + return meterThickness; +} + +void VolumeMeter::setMeterThickness(int v) +{ + meterThickness = v; + recalculateLayout = true; +} + +qreal VolumeMeter::getMeterFontScaling() const +{ + return meterFontScaling; +} + +void VolumeMeter::setMeterFontScaling(qreal v) +{ + meterFontScaling = v; + recalculateLayout = true; +} + +void VolControl::refreshColors() +{ + volMeter->setBackgroundNominalColor( + volMeter->getBackgroundNominalColor()); + volMeter->setBackgroundWarningColor( + volMeter->getBackgroundWarningColor()); + volMeter->setBackgroundErrorColor(volMeter->getBackgroundErrorColor()); + volMeter->setForegroundNominalColor( + volMeter->getForegroundNominalColor()); + volMeter->setForegroundWarningColor( + volMeter->getForegroundWarningColor()); + volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor()); +} + qreal VolumeMeter::getMinimumLevel() const { return minimumLevel; @@ -434,10 +593,9 @@ 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"); - tickFont.setPixelSize(7); - // Default meter color settings, they only show if + setAttribute(Qt::WA_OpaquePaintEvent, true); + + // Default meter settings, they only show if // there is no stylesheet, do not remove. backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow @@ -445,27 +603,38 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter, foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red - clipColor.setRgb(0xff, 0xff, 0xff); // Bright white - magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black - majorTickColor.setRgb(0xff, 0xff, 0xff); // Black - minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black - minimumLevel = -60.0; // -60 dB - warningLevel = -20.0; // -20 dB - errorLevel = -9.0; // -9 dB - clipLevel = -0.5; // -0.5 dB - minimumInputLevel = -50.0; // -50 dB - peakDecayRate = 11.76; // 20 dB / 1.7 sec - magnitudeIntegrationTime = 0.3; // 99% in 300 ms - peakHoldDuration = 20.0; // 20 seconds - inputPeakHoldDuration = 1.0; // 1 second + backgroundNominalColorDisabled.setRgb(90, 90, 90); + backgroundWarningColorDisabled.setRgb(117, 117, 117); + backgroundErrorColorDisabled.setRgb(65, 65, 65); + foregroundNominalColorDisabled.setRgb(163, 163, 163); + foregroundWarningColorDisabled.setRgb(217, 217, 217); + foregroundErrorColorDisabled.setRgb(113, 113, 113); + + clipColor.setRgb(0xff, 0xff, 0xff); // Bright white + magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black + majorTickColor.setRgb(0xff, 0xff, 0xff); // Black + minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black + minimumLevel = -60.0; // -60 dB + warningLevel = -20.0; // -20 dB + errorLevel = -9.0; // -9 dB + clipLevel = -0.5; // -0.5 dB + minimumInputLevel = -50.0; // -50 dB + peakDecayRate = 11.76; // 20 dB / 1.7 sec + magnitudeIntegrationTime = 0.3; // 99% in 300 ms + peakHoldDuration = 20.0; // 20 seconds + inputPeakHoldDuration = 1.0; // 1 second + meterThickness = 3; // Bar thickness in pixels + meterFontScaling = + 0.7; // Font size for numbers is 70% of Widget's font size channels = (int)audio_output_get_channels(obs_get_audio()); - handleChannelCofigurationChange(); + doLayout(); updateTimerRef = updateTimer.toStrongRef(); if (!updateTimerRef) { updateTimerRef = QSharedPointer::create(); - updateTimerRef->start(34); + updateTimerRef->setTimerType(Qt::PreciseTimer); + updateTimerRef->start(16); updateTimer = updateTimerRef; } @@ -475,7 +644,6 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter, VolumeMeter::~VolumeMeter() { updateTimerRef->RemoveVolControl(this); - delete tickPaintCache; } void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS], @@ -515,23 +683,58 @@ inline void VolumeMeter::resetLevels() } } -inline void VolumeMeter::handleChannelCofigurationChange() +bool VolumeMeter::needLayoutChange() +{ + int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); + + if (!currentNrAudioChannels) { + struct obs_audio_info oai; + obs_get_audio_info(&oai); + currentNrAudioChannels = (oai.speakers == SPEAKERS_MONO) ? 1 + : 2; + } + + if (displayNrAudioChannels != currentNrAudioChannels) { + displayNrAudioChannels = currentNrAudioChannels; + recalculateLayout = true; + } + + return recalculateLayout; +} + +// When this is called from the constructor, obs_volmeter_get_nr_channels has not +// yet been called and Q_PROPERTY settings have not yet been read from the +// stylesheet. +inline void VolumeMeter::doLayout() { QMutexLocker locker(&dataMutex); - int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); - if (displayNrAudioChannels != currentNrAudioChannels) { - displayNrAudioChannels = currentNrAudioChannels; + recalculateLayout = false; - // 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(); + tickFont = font(); + QFontInfo info(tickFont); + tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling); + QFontMetrics metrics(tickFont); + if (vertical) { + // Each meter channel is meterThickness pixels wide, plus one pixel + // between channels, but not after the last. + // Add 4 pixels for ticks, space to hold our longest label in this font, + // and a few pixels before the fader. + QRect scaleBounds = metrics.boundingRect("-88"); + setMinimumSize(displayNrAudioChannels * (meterThickness + 1) - + 1 + 4 + scaleBounds.width() + 2, + 130); + } else { + // Each meter channel is meterThickness pixels high, plus one pixel + // between channels, but not after the last. + // Add 4 pixels for ticks, and space high enough to hold our label in + // this font, presuming that digits don't have descenders. + setMinimumSize(130, + displayNrAudioChannels * (meterThickness + 1) - + 1 + 4 + metrics.capHeight()); } + + resetLevels(); } inline bool VolumeMeter::detectIdle(uint64_t ts) @@ -649,12 +852,12 @@ void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width, painter.fillRect(x, y, width, height, color); } -void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width, - int height) +void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width) { qreal scale = width / minimumLevel; painter.setFont(tickFont); + QFontMetrics metrics(tickFont); painter.setPen(majorTickColor); // Draw major tick lines and numeric indicators. @@ -662,10 +865,18 @@ void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width, int position = int(x + width - (i * scale) - 1); QString str = QString::number(i); - if (i == 0 || i == -5) - painter.drawText(position - 3, height, str); - else - painter.drawText(position - 5, height, str); + // Center the number on the tick, but don't overflow + QRect textBounds = metrics.boundingRect(str); + int pos; + if (i == 0) { + pos = position - textBounds.width(); + } else { + pos = position - (textBounds.width() / 2); + if (pos < 0) + pos = 0; + } + painter.drawText(pos, y + 4 + metrics.capHeight(), str); + painter.drawLine(position, y, position, y + 2); } @@ -683,26 +894,31 @@ void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height) qreal scale = height / minimumLevel; painter.setFont(tickFont); + QFontMetrics metrics(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); + int position = y + int(i * scale) + METER_PADDING; 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); + // Center the number on the tick, but don't overflow + if (i == 0) { + painter.drawText(x + 6, position + metrics.capHeight(), + str); + } else { + painter.drawText(x + 4, + position + (metrics.capHeight() / 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); + int position = y + int(i * scale) + METER_PADDING; if (i % 5 != 0) painter.drawLine(x, position, x + 1, position); } @@ -777,7 +993,7 @@ void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width, painter.fillRect(peakPosition, y, maximumPosition - peakPosition, height, backgroundErrorColor); - } else { + } else if (int(magnitude) != 0) { if (!clipping) { QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, SLOT(ClipEnding())); @@ -879,7 +1095,7 @@ void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width, int end = errorLength + warningLength + nominalLength; painter.fillRect(x, minimumPosition, width, end, - QBrush(foregroundErrorColor)); + foregroundErrorColor); } if (peakHoldPosition - 3 < minimumPosition) @@ -903,53 +1119,48 @@ void VolumeMeter::paintEvent(QPaintEvent *event) { uint64_t ts = os_gettime_ns(); qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001; - - 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; + QRect widgetRect = rect(); + int width = widgetRect.width(); + int height = widgetRect.height(); + + QPainter painter(this); + if (vertical) - tickPaintCacheSize = QSize(14, height); - else - tickPaintCacheSize = QSize(width, 9); - if (tickPaintCache == nullptr || - tickPaintCache->size() != tickPaintCacheSize) { - delete tickPaintCache; - tickPaintCache = new QPixmap(tickPaintCacheSize); + height -= METER_PADDING * 2; - QColor clearColor(0, 0, 0, 0); - tickPaintCache->fill(clearColor); + // timerEvent requests update of the bar(s) only, so we can avoid the + // overhead of repainting the scale and labels. + if (event->region().boundingRect() != getBarRect()) { + if (needLayoutChange()) + doLayout(); + + // Paint window background color (as widget is opaque) + QColor background = + palette().color(QPalette::ColorRole::Window); + painter.fillRect(widgetRect, background); - QPainter tickPainter(tickPaintCache); if (vertical) { - tickPainter.translate(0, height); - tickPainter.scale(1, -1); - paintVTicks(tickPainter, 0, 11, - tickPaintCacheSize.height() - 11); + paintVTicks(painter, + displayNrAudioChannels * + (meterThickness + 1) - + 1, + 0, height - (INDICATOR_THICKNESS + 3)); } else { - paintHTicks(tickPainter, 6, 0, - tickPaintCacheSize.width() - 6, - tickPaintCacheSize.height()); + paintHTicks(painter, INDICATOR_THICKNESS + 3, + displayNrAudioChannels * + (meterThickness + 1) - + 1, + width - (INDICATOR_THICKNESS + 3)); } - tickPainter.end(); } - // Actual painting of the widget starts here. - QPainter painter(this); if (vertical) { // Invert the Y axis to ease the math - painter.translate(0, height); + painter.translate(0, height + METER_PADDING); painter.scale(1, -1); - painter.drawPixmap(displayNrAudioChannels * 4 - 1, 7, - *tickPaintCache); - } else { - painter.drawPixmap(0, height - 9, *tickPaintCache); } for (int channelNr = 0; channelNr < displayNrAudioChannels; @@ -961,12 +1172,17 @@ void VolumeMeter::paintEvent(QPaintEvent *event) : channelNr; if (vertical) - paintVMeter(painter, channelNr * 4, 8, 3, height - 10, + paintVMeter(painter, channelNr * (meterThickness + 1), + INDICATOR_THICKNESS + 2, meterThickness, + height - (INDICATOR_THICKNESS + 2), displayMagnitude[channelNrFixed], displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); else - paintHMeter(painter, 5, channelNr * 4, width - 5, 3, + paintHMeter(painter, INDICATOR_THICKNESS + 2, + channelNr * (meterThickness + 1), + width - (INDICATOR_THICKNESS + 2), + meterThickness, displayMagnitude[channelNrFixed], displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); @@ -978,16 +1194,40 @@ void VolumeMeter::paintEvent(QPaintEvent *event) // see that the audio stream has been stopped, without // having too much visual impact. if (vertical) - paintInputMeter(painter, channelNr * 4, 3, 3, 3, + paintInputMeter(painter, + channelNr * (meterThickness + 1), 0, + meterThickness, INDICATOR_THICKNESS, displayInputPeakHold[channelNrFixed]); else - paintInputMeter(painter, 0, channelNr * 4, 3, 3, + paintInputMeter(painter, 0, + channelNr * (meterThickness + 1), + INDICATOR_THICKNESS, meterThickness, displayInputPeakHold[channelNrFixed]); } lastRedrawTime = ts; } +QRect VolumeMeter::getBarRect() const +{ + QRect rec = rect(); + if (vertical) + rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1); + else + rec.setHeight(displayNrAudioChannels * (meterThickness + 1) - + 1); + + return rec; +} + +void VolumeMeter::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::StyleChange) + recalculateLayout = true; + + QWidget::changeEvent(e); +} + void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) { volumeMeters.push_back(meter); @@ -1000,6 +1240,13 @@ void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter) void VolumeMeterTimer::timerEvent(QTimerEvent *) { - for (VolumeMeter *meter : volumeMeters) - meter->update(); + for (VolumeMeter *meter : volumeMeters) { + if (meter->needLayoutChange()) { + // Tell paintEvent to update layout and paint everything + meter->update(); + } else { + // Tell paintEvent to paint only the bars + meter->update(meter->getBarRect()); + } + } } diff --git a/src/utils/volume-control.hpp b/src/utils/volume-control.hpp index 7ef783d1..a7d409e1 100644 --- a/src/utils/volume-control.hpp +++ b/src/utils/volume-control.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include class QPushButton; class VolumeMeterTimer; @@ -25,6 +27,32 @@ class VolumeMeter : public QWidget { WRITE setForegroundWarningColor DESIGNABLE true) Q_PROPERTY(QColor foregroundErrorColor READ getForegroundErrorColor WRITE setForegroundErrorColor DESIGNABLE true) + + Q_PROPERTY(QColor backgroundNominalColorDisabled READ + getBackgroundNominalColorDisabled WRITE + setBackgroundNominalColorDisabled + DESIGNABLE true) + Q_PROPERTY(QColor backgroundWarningColorDisabled READ + getBackgroundWarningColorDisabled WRITE + setBackgroundWarningColorDisabled + DESIGNABLE true) + Q_PROPERTY( + QColor backgroundErrorColorDisabled READ + getBackgroundErrorColorDisabled WRITE + setBackgroundErrorColorDisabled DESIGNABLE true) + Q_PROPERTY(QColor foregroundNominalColorDisabled READ + getForegroundNominalColorDisabled WRITE + setForegroundNominalColorDisabled + DESIGNABLE true) + Q_PROPERTY(QColor foregroundWarningColorDisabled READ + getForegroundWarningColorDisabled WRITE + setForegroundWarningColorDisabled + DESIGNABLE true) + Q_PROPERTY( + QColor foregroundErrorColorDisabled READ + getForegroundErrorColorDisabled WRITE + setForegroundErrorColorDisabled DESIGNABLE true) + Q_PROPERTY(QColor clipColor READ getClipColor WRITE setClipColor DESIGNABLE true) Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE @@ -33,6 +61,10 @@ class VolumeMeter : public QWidget { setMajorTickColor DESIGNABLE true) Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE setMinorTickColor DESIGNABLE true) + Q_PROPERTY(int meterThickness READ getMeterThickness WRITE + setMeterThickness DESIGNABLE true) + Q_PROPERTY(qreal meterFontScaling READ getMeterFontScaling WRITE + setMeterFontScaling DESIGNABLE true) // Levels are denoted in dBFS. Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel @@ -61,6 +93,8 @@ class VolumeMeter : public QWidget { Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration WRITE setInputPeakHoldDuration DESIGNABLE true) + friend class VolControl; + private slots: void ClipEnding(); @@ -70,7 +104,7 @@ private: QSharedPointer updateTimerRef; inline void resetLevels(); - inline void handleChannelCofigurationChange(); + inline void doLayout(); inline bool detectIdle(uint64_t ts); inline void calculateBallistics(uint64_t ts, qreal timeSinceLastRedraw = 0.0); @@ -81,20 +115,19 @@ private: 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 paintHTicks(QPainter &painter, int x, int y, int width); 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; + bool recalculateLayout = true; uint64_t currentLastUpdateTime = 0; float currentMagnitude[MAX_AUDIO_CHANNELS]; float currentPeak[MAX_AUDIO_CHANNELS]; float currentInputPeak[MAX_AUDIO_CHANNELS]; - QPixmap *tickPaintCache = nullptr; int displayNrAudioChannels = 0; float displayMagnitude[MAX_AUDIO_CHANNELS]; float displayPeak[MAX_AUDIO_CHANNELS]; @@ -110,10 +143,22 @@ private: QColor foregroundNominalColor; QColor foregroundWarningColor; QColor foregroundErrorColor; + + QColor backgroundNominalColorDisabled; + QColor backgroundWarningColorDisabled; + QColor backgroundErrorColorDisabled; + QColor foregroundNominalColorDisabled; + QColor foregroundWarningColorDisabled; + QColor foregroundErrorColorDisabled; + QColor clipColor; QColor magnitudeColor; QColor majorTickColor; QColor minorTickColor; + + int meterThickness; + qreal meterFontScaling; + qreal minimumLevel; qreal warningLevel; qreal errorLevel; @@ -124,6 +169,13 @@ private: qreal peakHoldDuration; qreal inputPeakHoldDuration; + QColor p_backgroundNominalColor; + QColor p_backgroundWarningColor; + QColor p_backgroundErrorColor; + QColor p_foregroundNominalColor; + QColor p_foregroundWarningColor; + QColor p_foregroundErrorColor; + uint64_t lastRedrawTime = 0; int channels = 0; bool clipping = false; @@ -138,6 +190,8 @@ public: void setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]); + QRect getBarRect() const; + bool needLayoutChange(); QColor getBackgroundNominalColor() const; void setBackgroundNominalColor(QColor c); @@ -151,6 +205,20 @@ public: void setForegroundWarningColor(QColor c); QColor getForegroundErrorColor() const; void setForegroundErrorColor(QColor c); + + QColor getBackgroundNominalColorDisabled() const; + void setBackgroundNominalColorDisabled(QColor c); + QColor getBackgroundWarningColorDisabled() const; + void setBackgroundWarningColorDisabled(QColor c); + QColor getBackgroundErrorColorDisabled() const; + void setBackgroundErrorColorDisabled(QColor c); + QColor getForegroundNominalColorDisabled() const; + void setForegroundNominalColorDisabled(QColor c); + QColor getForegroundWarningColorDisabled() const; + void setForegroundWarningColorDisabled(QColor c); + QColor getForegroundErrorColorDisabled() const; + void setForegroundErrorColorDisabled(QColor c); + QColor getClipColor() const; void setClipColor(QColor c); QColor getMagnitudeColor() const; @@ -159,6 +227,10 @@ public: void setMajorTickColor(QColor c); QColor getMinorTickColor() const; void setMinorTickColor(QColor c); + int getMeterThickness() const; + void setMeterThickness(int v); + qreal getMeterFontScaling() const; + void setMeterFontScaling(qreal v); qreal getMinimumLevel() const; void setMinimumLevel(qreal v); qreal getWarningLevel() const; @@ -182,6 +254,7 @@ public: protected: void paintEvent(QPaintEvent *event) override; + void changeEvent(QEvent *e) override; }; class VolumeMeterTimer : public QTimer { @@ -200,7 +273,6 @@ protected: class QLabel; class QSlider; -class MuteCheckBox; class VolControl : public QWidget { Q_OBJECT @@ -211,11 +283,13 @@ private: QLabel *volLabel; VolumeMeter *volMeter; QSlider *slider; + QPushButton *config = nullptr; float levelTotal; float levelCount; obs_fader_t *obs_fader; obs_volmeter_t *obs_volmeter; bool vertical; + QMenu *contextMenu; static void OBSVolumeChanged(void *param, float db); static void OBSVolumeLevel(void *data, @@ -223,12 +297,20 @@ private: const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]); + void EmitConfigClicked(); + private slots: + void VolumeChanged(); + void SliderChanged(int vol); void updateText(); +signals: + void ConfigClicked(); + public: - explicit VolControl(OBSSource source, bool vertical = false); + explicit VolControl(OBSSource source, bool showConfig = false, + bool vertical = false); ~VolControl(); inline obs_source_t *GetSource() const { return source; } @@ -240,6 +322,19 @@ public: void setPeakMeterType(enum obs_peak_meter_type peakMeterType); void EnableSlider(bool enable); + inline void SetContextMenu(QMenu *cm) { contextMenu = cm; } - QSlider *GetSlider() const; + void refreshColors(); + QSlider *GetSlider() const { return slider; } +}; + +class VolumeSlider : public QSlider { + Q_OBJECT + +public: + obs_fader_t *fad; + + VolumeSlider(obs_fader_t *fader, QWidget *parent = nullptr); + VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, + QWidget *parent = nullptr); };