UI: Make volume meter tweakable by stylesheet

Replace fixed Arial, 7 px meter scale font with the font used
for VolumeMeter/QWidget. Add qproperties for meter bar thickness and
a scaling factor for the meter scale numbers. If not specified in a
QSS, defaults are 3 pixel bar width and 80% of base font size.
master
OldBaldGeek 2021-08-30 16:27:36 -05:00 committed by Matt Gajownik
parent c8a0dbff1e
commit 6a5a5b4538
6 changed files with 197 additions and 59 deletions

View File

@ -707,6 +707,13 @@ VolumeMeter {
qproperty-majorTickColor: rgb(239,240,241); /* White */ qproperty-majorTickColor: rgb(239,240,241); /* White */
qproperty-minorTickColor: rgb(118,121,124); /* Light Gray */ qproperty-minorTickColor: rgb(118,121,124); /* Light Gray */
qproperty-peakDecayRate: 23.4; /* Override of the standard PPM Type I rate. */ qproperty-peakDecayRate: 23.4; /* Override of the standard PPM Type I rate. */
qproperty-meterThickness: 3;
/* The meter scale numbers normally use your QWidget font, with size */
/* multiplied by meterFontScaling to get a proportionally smaller font. */
/* To use a unique font for the numbers, specify font-family and/or */
/* font-size here, and set meterFontScaling to 1.0. */
qproperty-meterFontScaling: 0.7;
} }
@ -1260,7 +1267,7 @@ QCalendarWidget QToolButton:pressed {
/* Month Dropdown Menu */ /* Month Dropdown Menu */
QCalendarWidget QMenu { QCalendarWidget QMenu {
} }
/* Year spinbox */ /* Year spinbox */
QCalendarWidget QSpinBox { QCalendarWidget QSpinBox {

View File

@ -583,6 +583,13 @@ VolumeMeter {
qproperty-magnitudeColor: rgb(0,0,0); qproperty-magnitudeColor: rgb(0,0,0);
qproperty-majorTickColor: palette(window-text); qproperty-majorTickColor: palette(window-text);
qproperty-minorTickColor: rgb(122,121,122); /* light */ qproperty-minorTickColor: rgb(122,121,122); /* light */
qproperty-meterThickness: 3;
/* The meter scale numbers normally use your QWidget font, with size */
/* multiplied by meterFontScaling to get a proportionally smaller font. */
/* To use a unique font for the numbers, specify font-family and/or */
/* font-size here, and set meterFontScaling to 1.0. */
qproperty-meterFontScaling: 0.7;
} }
@ -978,7 +985,7 @@ QCalendarWidget QToolButton:pressed {
/* Month Dropdown Menu */ /* Month Dropdown Menu */
QCalendarWidget QMenu { QCalendarWidget QMenu {
} }
/* Year spinbox */ /* Year spinbox */
QCalendarWidget QSpinBox { QCalendarWidget QSpinBox {

View File

@ -831,6 +831,13 @@ VolumeMeter {
qproperty-magnitudeColor: palette(window); qproperty-magnitudeColor: palette(window);
qproperty-majorTickColor: palette(window-text); qproperty-majorTickColor: palette(window-text);
qproperty-minorTickColor: palette(mid); qproperty-minorTickColor: palette(mid);
qproperty-meterThickness: 3;
/* The meter scale numbers normally use your QWidget font, with size */
/* multiplied by meterFontScaling to get a proportionally smaller font. */
/* To use a unique font for the numbers, specify font-family and/or */
/* font-size here, and set meterFontScaling to 1.0. */
qproperty-meterFontScaling: 0.7;
} }
/*******************/ /*******************/

View File

@ -90,6 +90,13 @@ VolumeMeter {
qproperty-magnitudeColor: rgb(0, 0, 0); qproperty-magnitudeColor: rgb(0, 0, 0);
qproperty-majorTickColor: rgb(0, 0, 0); qproperty-majorTickColor: rgb(0, 0, 0);
qproperty-minorTickColor: rgb(50, 50, 50); qproperty-minorTickColor: rgb(50, 50, 50);
qproperty-meterThickness: 3;
/* The meter scale numbers normally use your QWidget font, with size */
/* multiplied by meterFontScaling to get a proportionally smaller font. */
/* To use a unique font for the numbers, specify font-family and/or */
/* font-size here, and set meterFontScaling to 1.0. */
qproperty-meterFontScaling: 0.7;
} }

View File

@ -17,6 +17,9 @@ using namespace std;
#define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) #define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))
#define FADER_PRECISION 4096.0 #define FADER_PRECISION 4096.0
// Size of the audio indicator in pixels
#define INDICATOR_THICKNESS 3
QWeakPointer<VolumeMeterTimer> VolumeMeter::updateTimer; QWeakPointer<VolumeMeterTimer> VolumeMeter::updateTimer;
void VolControl::OBSVolumeChanged(void *data, float db) void VolControl::OBSVolumeChanged(void *data, float db)
@ -235,6 +238,12 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
volMeter->setFocusProxy(slider); 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); setMaximumWidth(110);
} else { } else {
QHBoxLayout *volLayout = new QHBoxLayout; QHBoxLayout *volLayout = new QHBoxLayout;
@ -270,12 +279,7 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
setLayout(mainLayout); setLayout(mainLayout);
QFont font = nameLabel->font();
font.setPointSize(font.pointSize() - 1);
nameLabel->setText(sourceName); nameLabel->setText(sourceName);
nameLabel->setFont(font);
volLabel->setFont(font);
slider->setMinimum(0); slider->setMinimum(0);
slider->setMaximum(int(FADER_PRECISION)); slider->setMaximum(int(FADER_PRECISION));
@ -493,6 +497,28 @@ void VolumeMeter::setMinorTickColor(QColor c)
minorTickColor = std::move(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;
}
qreal VolumeMeter::getMinimumLevel() const qreal VolumeMeter::getMinimumLevel() const
{ {
return minimumLevel; return minimumLevel;
@ -633,10 +659,7 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
{ {
setAttribute(Qt::WA_OpaquePaintEvent, true); setAttribute(Qt::WA_OpaquePaintEvent, true);
// Use a font that can be rendered small. // Default meter settings, they only show if
tickFont = QFont("Arial");
tickFont.setPixelSize(7);
// Default meter color settings, they only show if
// there is no stylesheet, do not remove. // there is no stylesheet, do not remove.
backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green
backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow
@ -665,10 +688,12 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
magnitudeIntegrationTime = 0.3; // 99% in 300 ms magnitudeIntegrationTime = 0.3; // 99% in 300 ms
peakHoldDuration = 20.0; // 20 seconds peakHoldDuration = 20.0; // 20 seconds
inputPeakHoldDuration = 1.0; // 1 second 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()); channels = (int)audio_output_get_channels(obs_get_audio());
handleChannelCofigurationChange(); doLayout();
updateTimerRef = updateTimer.toStrongRef(); updateTimerRef = updateTimer.toStrongRef();
if (!updateTimerRef) { if (!updateTimerRef) {
updateTimerRef = QSharedPointer<VolumeMeterTimer>::create(); updateTimerRef = QSharedPointer<VolumeMeterTimer>::create();
@ -722,23 +747,49 @@ inline void VolumeMeter::resetLevels()
} }
} }
inline void VolumeMeter::handleChannelCofigurationChange() bool VolumeMeter::needLayoutChange()
{ {
QMutexLocker locker(&dataMutex);
int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter);
if (displayNrAudioChannels != currentNrAudioChannels) { if (displayNrAudioChannels != currentNrAudioChannels) {
displayNrAudioChannels = currentNrAudioChannels; displayNrAudioChannels = currentNrAudioChannels;
recalculateLayout = true;
// 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();
} }
return recalculateLayout;
}
// When this is called from the constructor, obs_volmeter_get_nr_channels returns 1
// and Q_PROPERTY settings have not yet been read from the stylesheet.
inline void VolumeMeter::doLayout()
{
QMutexLocker locker(&dataMutex);
recalculateLayout = false;
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) inline bool VolumeMeter::detectIdle(uint64_t ts)
@ -856,12 +907,12 @@ void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width,
painter.fillRect(x, y, width, height, color); painter.fillRect(x, y, width, height, color);
} }
void VolumeMeter::paintHTicks(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; qreal scale = width / minimumLevel;
painter.setFont(tickFont); painter.setFont(tickFont);
QFontMetrics metrics(tickFont);
painter.setPen(majorTickColor); painter.setPen(majorTickColor);
// Draw major tick lines and numeric indicators. // Draw major tick lines and numeric indicators.
@ -869,10 +920,18 @@ void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width,
int position = int(x + width - (i * scale) - 1); int position = int(x + width - (i * scale) - 1);
QString str = QString::number(i); QString str = QString::number(i);
if (i == 0 || i == -5) // Center the number on the tick, but don't overflow
painter.drawText(position - 3, height, str); QRect textBounds = metrics.boundingRect(str);
else int pos;
painter.drawText(position - 5, height, str); 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); painter.drawLine(position, y, position, y + 2);
} }
@ -890,26 +949,31 @@ void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height)
qreal scale = height / minimumLevel; qreal scale = height / minimumLevel;
painter.setFont(tickFont); painter.setFont(tickFont);
QFontMetrics metrics(tickFont);
painter.setPen(majorTickColor); painter.setPen(majorTickColor);
// Draw major tick lines and numeric indicators. // Draw major tick lines and numeric indicators.
for (int i = 0; i >= minimumLevel; i -= 5) { for (int i = 0; i >= minimumLevel; i -= 5) {
int position = y + int((i * scale) - 1); int position = y + int(i * scale);
QString str = QString::number(i); QString str = QString::number(i);
if (i == 0) // Center the number on the tick, but don't overflow
painter.drawText(x + 5, position + 5, str); if (i == 0) {
else if (i == -60) painter.drawText(x + 6, position + metrics.capHeight(),
painter.drawText(x + 4, position + 1, str); str);
else } else {
painter.drawText(x + 4, position + 3, str); painter.drawText(x + 4,
position + (metrics.capHeight() / 2),
str);
}
painter.drawLine(x, position, x + 2, position); painter.drawLine(x, position, x + 2, position);
} }
// Draw minor tick lines. // Draw minor tick lines.
painter.setPen(minorTickColor); painter.setPen(minorTickColor);
for (int i = 0; i >= minimumLevel; i--) { for (int i = 0; i >= minimumLevel; i--) {
int position = y + int((i * scale) - 1); int position = y + int(i * scale);
if (i % 5 != 0) if (i % 5 != 0)
painter.drawLine(x, position, x + 1, position); painter.drawLine(x, position, x + 1, position);
} }
@ -1160,9 +1224,10 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
QPainter painter(this); QPainter painter(this);
// timerEvent requests update of the bar(s) only, so we can avoid the // timerEvent requests update of the bar(s) only, so we can avoid the
// overhead of repainting the scale and labels // overhead of repainting the scale and labels.
if (event->region().boundingRect() != getBarRect()) { if (event->region().boundingRect() != getBarRect()) {
handleChannelCofigurationChange(); if (needLayoutChange())
doLayout();
// Paint window background color (as widget is opaque) // Paint window background color (as widget is opaque)
QColor background = QColor background =
@ -1170,11 +1235,17 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
painter.fillRect(widgetRect, background); painter.fillRect(widgetRect, background);
if (vertical) { if (vertical) {
paintVTicks(painter, displayNrAudioChannels * 4 - 1, 1, paintVTicks(painter,
height - 6); displayNrAudioChannels *
(meterThickness + 1) -
1,
0, height - (INDICATOR_THICKNESS + 3));
} else { } else {
paintHTicks(painter, 6, displayNrAudioChannels * 4 - 1, paintHTicks(painter, INDICATOR_THICKNESS + 3,
width - 6, height); displayNrAudioChannels *
(meterThickness + 1) -
1,
width - (INDICATOR_THICKNESS + 3));
} }
} }
@ -1193,12 +1264,17 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
: channelNr; : channelNr;
if (vertical) if (vertical)
paintVMeter(painter, channelNr * 4, 5, 3, height - 5, paintVMeter(painter, channelNr * (meterThickness + 1),
INDICATOR_THICKNESS + 2, meterThickness,
height - (INDICATOR_THICKNESS + 2),
displayMagnitude[channelNrFixed], displayMagnitude[channelNrFixed],
displayPeak[channelNrFixed], displayPeak[channelNrFixed],
displayPeakHold[channelNrFixed]); displayPeakHold[channelNrFixed]);
else else
paintHMeter(painter, 5, channelNr * 4, width - 5, 3, paintHMeter(painter, INDICATOR_THICKNESS + 2,
channelNr * (meterThickness + 1),
width - (INDICATOR_THICKNESS + 2),
meterThickness,
displayMagnitude[channelNrFixed], displayMagnitude[channelNrFixed],
displayPeak[channelNrFixed], displayPeak[channelNrFixed],
displayPeakHold[channelNrFixed]); displayPeakHold[channelNrFixed]);
@ -1210,26 +1286,40 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
// see that the audio stream has been stopped, without // see that the audio stream has been stopped, without
// having too much visual impact. // having too much visual impact.
if (vertical) if (vertical)
paintInputMeter(painter, channelNr * 4, 0, 3, 3, paintInputMeter(painter,
channelNr * (meterThickness + 1), 0,
meterThickness, INDICATOR_THICKNESS,
displayInputPeakHold[channelNrFixed]); displayInputPeakHold[channelNrFixed]);
else else
paintInputMeter(painter, 0, channelNr * 4, 3, 3, paintInputMeter(painter, 0,
channelNr * (meterThickness + 1),
INDICATOR_THICKNESS, meterThickness,
displayInputPeakHold[channelNrFixed]); displayInputPeakHold[channelNrFixed]);
} }
lastRedrawTime = ts; lastRedrawTime = ts;
} }
QRect VolumeMeter::getBarRect() QRect VolumeMeter::getBarRect() const
{ {
QRect rec = rect(); QRect rec = rect();
if (vertical) if (vertical)
rec.setWidth(displayNrAudioChannels * 4); rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1);
else else
rec.setHeight(displayNrAudioChannels * 4); rec.setHeight(displayNrAudioChannels * (meterThickness + 1) -
1);
return rec; return rec;
} }
void VolumeMeter::changeEvent(QEvent *e)
{
if (e->type() == QEvent::StyleChange)
recalculateLayout = true;
QWidget::changeEvent(e);
}
void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) void VolumeMeterTimer::AddVolControl(VolumeMeter *meter)
{ {
volumeMeters.push_back(meter); volumeMeters.push_back(meter);
@ -1242,7 +1332,13 @@ void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter)
void VolumeMeterTimer::timerEvent(QTimerEvent *) void VolumeMeterTimer::timerEvent(QTimerEvent *)
{ {
// Tell paintEvent to paint only the bars, leaving the scale alone. for (VolumeMeter *meter : volumeMeters) {
for (VolumeMeter *meter : volumeMeters) if (meter->needLayoutChange()) {
meter->update(meter->getBarRect()); // Tell paintEvent to update layout and paint everything
meter->update();
} else {
// Tell paintEvent to paint only the bars
meter->update(meter->getBarRect());
}
}
} }

View File

@ -60,6 +60,10 @@ class VolumeMeter : public QWidget {
setMajorTickColor DESIGNABLE true) setMajorTickColor DESIGNABLE true)
Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE
setMinorTickColor DESIGNABLE true) 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. // Levels are denoted in dBFS.
Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel
@ -99,7 +103,7 @@ private:
QSharedPointer<VolumeMeterTimer> updateTimerRef; QSharedPointer<VolumeMeterTimer> updateTimerRef;
inline void resetLevels(); inline void resetLevels();
inline void handleChannelCofigurationChange(); inline void doLayout();
inline bool detectIdle(uint64_t ts); inline bool detectIdle(uint64_t ts);
inline void calculateBallistics(uint64_t ts, inline void calculateBallistics(uint64_t ts,
qreal timeSinceLastRedraw = 0.0); qreal timeSinceLastRedraw = 0.0);
@ -110,14 +114,14 @@ private:
int height, float peakHold); int height, float peakHold);
void paintHMeter(QPainter &painter, int x, int y, int width, int height, void paintHMeter(QPainter &painter, int x, int y, int width, int height,
float magnitude, float peak, float peakHold); float magnitude, float peak, float peakHold);
void paintHTicks(QPainter &painter, int x, int y, int width, void paintHTicks(QPainter &painter, int x, int y, int width);
int height);
void paintVMeter(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); float magnitude, float peak, float peakHold);
void paintVTicks(QPainter &painter, int x, int y, int height); void paintVTicks(QPainter &painter, int x, int y, int height);
QMutex dataMutex; QMutex dataMutex;
bool recalculateLayout = true;
uint64_t currentLastUpdateTime = 0; uint64_t currentLastUpdateTime = 0;
float currentMagnitude[MAX_AUDIO_CHANNELS]; float currentMagnitude[MAX_AUDIO_CHANNELS];
float currentPeak[MAX_AUDIO_CHANNELS]; float currentPeak[MAX_AUDIO_CHANNELS];
@ -150,6 +154,10 @@ private:
QColor magnitudeColor; QColor magnitudeColor;
QColor majorTickColor; QColor majorTickColor;
QColor minorTickColor; QColor minorTickColor;
int meterThickness;
qreal meterFontScaling;
qreal minimumLevel; qreal minimumLevel;
qreal warningLevel; qreal warningLevel;
qreal errorLevel; qreal errorLevel;
@ -175,7 +183,8 @@ public:
void setLevels(const float magnitude[MAX_AUDIO_CHANNELS], void setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS]); const float inputPeak[MAX_AUDIO_CHANNELS]);
QRect getBarRect(); QRect getBarRect() const;
bool needLayoutChange();
QColor getBackgroundNominalColor() const; QColor getBackgroundNominalColor() const;
void setBackgroundNominalColor(QColor c); void setBackgroundNominalColor(QColor c);
@ -211,6 +220,10 @@ public:
void setMajorTickColor(QColor c); void setMajorTickColor(QColor c);
QColor getMinorTickColor() const; QColor getMinorTickColor() const;
void setMinorTickColor(QColor c); void setMinorTickColor(QColor c);
int getMeterThickness() const;
void setMeterThickness(int v);
qreal getMeterFontScaling() const;
void setMeterFontScaling(qreal v);
qreal getMinimumLevel() const; qreal getMinimumLevel() const;
void setMinimumLevel(qreal v); void setMinimumLevel(qreal v);
qreal getWarningLevel() const; qreal getWarningLevel() const;
@ -235,6 +248,7 @@ public:
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void changeEvent(QEvent *e) override;
}; };
class VolumeMeterTimer : public QTimer { class VolumeMeterTimer : public QTimer {