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-minorTickColor: rgb(118,121,124); /* Light Gray */
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 */
QCalendarWidget QMenu {
}
/* Year spinbox */
QCalendarWidget QSpinBox {

View File

@ -583,6 +583,13 @@ VolumeMeter {
qproperty-magnitudeColor: rgb(0,0,0);
qproperty-majorTickColor: palette(window-text);
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 */
QCalendarWidget QMenu {
}
/* Year spinbox */
QCalendarWidget QSpinBox {

View File

@ -831,6 +831,13 @@ VolumeMeter {
qproperty-magnitudeColor: palette(window);
qproperty-majorTickColor: palette(window-text);
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-majorTickColor: rgb(0, 0, 0);
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 FADER_PRECISION 4096.0
// Size of the audio indicator in pixels
#define INDICATOR_THICKNESS 3
QWeakPointer<VolumeMeterTimer> VolumeMeter::updateTimer;
void VolControl::OBSVolumeChanged(void *data, float db)
@ -235,6 +238,12 @@ VolControl::VolControl(OBSSource source_, bool showConfig, 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;
@ -270,12 +279,7 @@ VolControl::VolControl(OBSSource source_, bool showConfig, 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(int(FADER_PRECISION));
@ -493,6 +497,28 @@ 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;
}
qreal VolumeMeter::getMinimumLevel() const
{
return minimumLevel;
@ -633,10 +659,7 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
{
setAttribute(Qt::WA_OpaquePaintEvent, true);
// Use a font that can be rendered small.
tickFont = QFont("Arial");
tickFont.setPixelSize(7);
// Default meter color settings, they only show if
// 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
@ -665,10 +688,12 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
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<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);
if (displayNrAudioChannels != currentNrAudioChannels) {
displayNrAudioChannels = currentNrAudioChannels;
// 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();
recalculateLayout = true;
}
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)
@ -856,12 +907,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.
@ -869,10 +920,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);
}
@ -890,26 +949,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);
QString str = QString::number(i);
if (i == 0)
painter.drawText(x + 5, position + 5, str);
else if (i == -60)
painter.drawText(x + 4, position + 1, str);
else
painter.drawText(x + 4, position + 3, 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);
if (i % 5 != 0)
painter.drawLine(x, position, x + 1, position);
}
@ -1160,9 +1224,10 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
QPainter painter(this);
// 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()) {
handleChannelCofigurationChange();
if (needLayoutChange())
doLayout();
// Paint window background color (as widget is opaque)
QColor background =
@ -1170,11 +1235,17 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
painter.fillRect(widgetRect, background);
if (vertical) {
paintVTicks(painter, displayNrAudioChannels * 4 - 1, 1,
height - 6);
paintVTicks(painter,
displayNrAudioChannels *
(meterThickness + 1) -
1,
0, height - (INDICATOR_THICKNESS + 3));
} else {
paintHTicks(painter, 6, displayNrAudioChannels * 4 - 1,
width - 6, height);
paintHTicks(painter, INDICATOR_THICKNESS + 3,
displayNrAudioChannels *
(meterThickness + 1) -
1,
width - (INDICATOR_THICKNESS + 3));
}
}
@ -1193,12 +1264,17 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
: channelNr;
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],
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]);
@ -1210,26 +1286,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, 0, 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()
QRect VolumeMeter::getBarRect() const
{
QRect rec = rect();
if (vertical)
rec.setWidth(displayNrAudioChannels * 4);
rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1);
else
rec.setHeight(displayNrAudioChannels * 4);
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);
@ -1242,7 +1332,13 @@ void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter)
void VolumeMeterTimer::timerEvent(QTimerEvent *)
{
// Tell paintEvent to paint only the bars, leaving the scale alone.
for (VolumeMeter *meter : volumeMeters)
meter->update(meter->getBarRect());
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());
}
}
}

View File

@ -60,6 +60,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
@ -99,7 +103,7 @@ private:
QSharedPointer<VolumeMeterTimer> 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);
@ -110,14 +114,14 @@ 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];
@ -150,6 +154,10 @@ private:
QColor magnitudeColor;
QColor majorTickColor;
QColor minorTickColor;
int meterThickness;
qreal meterFontScaling;
qreal minimumLevel;
qreal warningLevel;
qreal errorLevel;
@ -175,7 +183,8 @@ public:
void setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS]);
QRect getBarRect();
QRect getBarRect() const;
bool needLayoutChange();
QColor getBackgroundNominalColor() const;
void setBackgroundNominalColor(QColor c);
@ -211,6 +220,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;
@ -235,6 +248,7 @@ public:
protected:
void paintEvent(QPaintEvent *event) override;
void changeEvent(QEvent *e) override;
};
class VolumeMeterTimer : public QTimer {