xybrid/xybrid/ui/gadgets/sampleselectorgadget.cpp

153 lines
5.3 KiB
C++

#include "sampleselectorgadget.h"
using Xybrid::UI::SampleSelectorGadget;
#include "data/project.h"
#include "data/sample.h"
using namespace Xybrid::Data;
#include "ui/directorynode.h"
#include "ui/waveformpreviewwidget.h"
using namespace Xybrid::UI;
#include "config/colorscheme.h"
#include "util/strings.h"
#include <cmath>
#include <vector>
#include <QDebug>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
#include <QStyleOptionGraphicsItem>
#include <QMenu>
#include <QWidgetAction>
SampleSelectorGadget::SampleSelectorGadget(Data::Project* project, QGraphicsItem* parent) : Gadget(parent), project(project) {
setAcceptHoverEvents(true);
}
QRectF SampleSelectorGadget::boundingRect() const {
return {QPointF(0, 0), size};
}
namespace {
void 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 SampleSelectorGadget::paint(QPainter* p, const QStyleOptionGraphicsItem* opt, QWidget*) {
const qreal corner = 5;
auto r = boundingRect();
auto br = r; // border rect
bool hover = opt->state & QStyle::State_MouseOver;
QColor outline = QColor(31, 31, 31);
if (hover) outline = QColor(127, 127, 255);
p->setPen(QPen(Qt::NoPen));
p->setBrush(QBrush(Config::colorScheme.waveformBg));
p->drawRoundedRect(br, corner, corner);
r.adjust(2, 2, -2, -2);
//p->fillRect(r, Config::colorScheme.waveformBg);
QString name = qs("(no sample selected)");
// draw waveforms
//auto aa = p->renderHints() & QPainter::Antialiasing;
p->setRenderHint(QPainter::Antialiasing, false);
p->setPen(QPen(Config::colorScheme.waveformFgPrimary));
if (auto smp = currentSample.lock(); smp) {
name = smp->name;
auto chs = smp->numChannels();
if (chs == 1) paintChannel(p, smp, 0, r.toAlignedRect());
else if (chs == 2) {
auto cs = r.height() / 2;
auto cr = r.adjusted(0, 0, 0, -cs);
paintChannel(p, smp, 0, cr.toAlignedRect());
paintChannel(p, smp, 1, cr.translated(0, cs).toAlignedRect());
}
}
p->setRenderHint(QPainter::Antialiasing, true);
p->setPen(QPen(outline, 2));
p->setBrush(QBrush(Qt::NoBrush));
p->drawRoundedRect(br, corner, corner);
// draw label
QFont font("Arcon Rounded", 8);
QFontMetricsF fm(font);
name = fm.elidedText(name.section('/', -1, -1), Qt::ElideRight, static_cast<int>(r.width() - 4));
QPainterPath path;
path.addText(r.bottomLeft() + QPointF(2, -fm.descent()), font, name);
p->fillPath(path, QColor(0, 0, 0));
p->strokePath(path, QPen(QColor(0, 0, 0), 3));
p->fillPath(path, QColor(255, 255, 255));
}
void SampleSelectorGadget::buildSubmenu(DirectoryNode* dir, QMenu* menu) {
auto smp = currentSample.lock();
bool needSeparator = false;
for (auto c : qAsConst(dir->children)) {
if (c->isDirectory()) {
needSeparator = true;
buildSubmenu(c, menu->addMenu(c->name));
} else {
if (needSeparator) { menu->addSeparator(); needSeparator = false; }
auto s = project->samples[c->data.toUuid()];
auto wa = new QWidgetAction(menu);
auto wfp = new WaveformPreviewWidget(menu);
wa->setDefaultWidget(wfp);
wfp->showName = true;
wfp->highlightable = true;
wfp->showLoopPoints = false;
wfp->setSample(s);
wfp->setMinimumSize(192, 48);
menu->addAction(wa);
if (s == smp) wa->setDisabled(true);
else connect(wa, &QWidgetAction::triggered, this, [this, s] { setSample(s); });
}
}
}
void SampleSelectorGadget::mousePressEvent(QGraphicsSceneMouseEvent* e) {
if (!project) return;
if (e->button() == Qt::LeftButton) {
auto smp = currentSample.lock();
auto* m = new QMenu();
if (project->samples.empty()) m->addAction("(no samples in project)")->setDisabled(true);
else {
m->addAction("(no sample)", this, [this] { setSample(nullptr); });
m->addSeparator();
DirectoryNode root;
for (auto& s : qAsConst(project->samples)) root.placeData(s->name, s->uuid);
root.sortTree();
buildSubmenu(&root, m);
}
m->setAttribute(Qt::WA_DeleteOnClose);
m->popup(e->screenPos());
}
}
void SampleSelectorGadget::hoverEnterEvent(QGraphicsSceneHoverEvent*) { update(); }
void SampleSelectorGadget::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { update(); }