xybrid/xybrid/ui/waveformpreviewwidget.cpp

127 lines
4.3 KiB
C++

#include "waveformpreviewwidget.h"
using Xybrid::UI::WaveformPreviewWidget;
#include "data/sample.h"
using Xybrid::Data::Sample;
#include "config/colorscheme.h"
#include "util/strings.h"
#include <cmath>
#include <QDebug>
#include <QPaintEvent>
#include <QPainter>
#include <QPainterPath>
#include <QStylePainter>
#include <QStyle>
#include <QStyleOptionFrame>
#include <QFocusFrame>
WaveformPreviewWidget::WaveformPreviewWidget(QWidget* parent) : QFrame(parent) {
setFrameStyle(Shape::StyledPanel | Shadow::Sunken);
setLineWidth(2);
}
void WaveformPreviewWidget::setSample(std::shared_ptr<Sample> smp) {
currentSample = smp;
//int ch = 1;
//if (smp) ch = std::max(1, smp->numChannels());
//setMinimumHeight(128 * ch);
repaint(); // invalidate paint state
}
void WaveformPreviewWidget::paintChannel(QPainter& p, std::shared_ptr<Sample> smp, size_t ch, QRect rect) {
auto x = rect.x();
auto y = rect.y();
auto h = rect.height();
auto c = h / 2;
double scale = static_cast<double>(smp->length()) / static_cast<double>(rect.width());
for (int i = 0; i < rect.width(); i++) {
size_t sp1 = static_cast<size_t>(std::floor(static_cast<double>(i) * scale));
size_t sp2 = static_cast<size_t>(std::floor(static_cast<double>(i+1) * scale)); // would be - 1, but this makes sure adjacent pixels always share at least one value
auto v = smp->plotBetween(ch, sp1, sp2);
v[0] = std::clamp(v[0], -1.0f, 1.0f);
v[1] = std::clamp(v[1], -1.0f, 1.0f);
//qDebug() << "x" << i << "v" << v[1] << v[2];
p.drawLine(QPoint(x+i, static_cast<int>(y+c - (c * v[0]))), QPoint(x+i, static_cast<int>(y+c - (c * v[1]))));
}
}
void WaveformPreviewWidget::paintEvent(QPaintEvent* event [[maybe_unused]]) {
auto disabled = !isEnabled();
QStylePainter p(this);
p.setRenderHint(QPainter::RenderHint::Antialiasing, false); // ensure sharpness even when rendered in a QGraphicsScene
QRect rect(QPoint(), size());
auto lw = lineWidth();
rect.adjust(lw, lw, -lw, -lw);
if (highlighted && !disabled) p.fillRect(rect, Config::colorScheme.waveformBgHighlight);
else p.fillRect(rect, Config::colorScheme.waveformBg);
p.setPen(QPen(Config::colorScheme.waveformFgPrimary));
if (disabled) p.setPen(Config::colorScheme.waveformFgPrimary.darker());
QString name = qs("(no data)");
if (auto smp = currentSample.lock(); smp) {
name = smp->name;
auto chs = smp->numChannels();
if (chs == 1) paintChannel(p, smp, 0, rect);
else if (chs == 2) {
auto cs = rect.height() / 2;
auto r = rect.adjusted(0, 0, 0, -cs);
paintChannel(p, smp, 0, r);
paintChannel(p, smp, 1, r.translated(0, cs));
}
if (showLoopPoints && smp->loopStart >= 0) { // draw loop points if able
double scale = static_cast<double>(smp->length()) / static_cast<double>(rect.width());
p.setPen(Config::colorScheme.waveformLoopPoints);
auto x = rect.x();
auto y = rect.y();
auto h = rect.height();
auto xs = x + static_cast<int>(static_cast<double>(smp->loopStart)/scale);
auto xe = x + static_cast<int>(static_cast<double>(smp->loopEnd)/scale);
p.drawLine(QPoint(xs, y), QPoint(xs, y+h));
p.drawLine(QPoint(xe, y), QPoint(xe, y+h));
}
}
if (showName) {
// draw label
QFont font("Arcon Rounded", 8);
QFontMetrics fm(font);
name = fm.elidedText(name.section('/', -1, -1), Qt::ElideRight, rect.width() - 4);
QPainterPath path;
path.addText(rect.bottomLeft() + QPointF(2, -fm.descent()), font, name);
p.setRenderHint(QPainter::Antialiasing, true);
p.fillPath(path, QColor(0, 0, 0));
p.strokePath(path, QPen(QColor(0, 0, 0), 3));
p.fillPath(path, QColor(255, 255, 255));
}
setAttribute(Qt::WA_Hover, highlighted);
QStyleOptionFrame optF;
optF.initFrom(this);
optF.lineWidth = 2;
optF.state |= QStyle::State_Sunken;
style()->drawPrimitive(QStyle::PE_Frame, &optF, &p, this);
}
void WaveformPreviewWidget::enterEvent(QEvent*) {
if (highlightable) {
highlighted = true;
repaint();
}
}
void WaveformPreviewWidget::leaveEvent(QEvent*) {
if (highlightable) {
highlighted = false;
repaint();
}
}