more node UI stuff (sample selector!)

portability/boost
zetaPRIME 2019-06-22 23:18:18 -04:00
parent ff0e9c3e56
commit 807c923543
13 changed files with 278 additions and 18 deletions

View File

@ -21,6 +21,7 @@ namespace Xybrid::Config {
QColor patternFgParamAmt = QColor(191,222,255);
QColor waveformBg = QColor(23, 23, 23);
QColor waveformBgHighlight = QColor(31, 31, 47);
QColor waveformFgPrimary = QColor(191, 163, 255);
};
extern ColorScheme colorScheme;

View File

@ -33,7 +33,7 @@
<enum>Qt::NoFocus</enum>
</property>
<property name="currentIndex">
<number>3</number>
<number>2</number>
</property>
<property name="documentMode">
<bool>true</bool>
@ -672,7 +672,7 @@
<property name="minimumSize">
<size>
<width>0</width>
<height>64</height>
<height>128</height>
</size>
</property>
</widget>

View File

@ -21,6 +21,7 @@ using namespace Xybrid::Audio;
#include "ui/patchboard/nodeobject.h"
#include "ui/gadgets/knobgadget.h"
#include "ui/gadgets/selectorgadget.h"
#include "ui/gadgets/sampleselectorgadget.h"
using namespace Xybrid::UI;
#include "ui/patchboard/nodeuiscene.h"
@ -169,7 +170,7 @@ void BeatPad::initUI(NodeUIScene* scene) {
{
auto s = new SelectorGadget();
s->setPos(0, 0);
//s->setPos(0, 0);
s->setWidth(320);
s->fGetList = [this] {
std::vector<SelectorGadget::Entry> v;
@ -206,13 +207,23 @@ void BeatPad::initUI(NodeUIScene* scene) {
s->setEntry({note, Util::noteName(note)});
});
auto wfp = new WaveformPreviewWidget();
/*auto wfp = new WaveformPreviewWidget();
scene->addWidget(wfp);
wfp->move(0, 28);
wfp->resize(320, 96);
//pw->setCacheMode(QGraphicsProxyWidget::CacheMode::NoCache);
//wfp->resize(320, 96);
wfp->setSample(project->samples.begin().value());
wfp->setSample(project->samples.begin().value());*/
auto sampleSelector = new SampleSelectorGadget(project);
scene->addItem(sampleSelector);
sampleSelector->setPos(0, 28);
sampleSelector->setSize(320, 96);
// TODO hook things up to actually *make* entries
auto c = cfg.at(60);
sampleSelector->setSample(c->smp.lock());
QObject::connect(sampleSelector, &SampleSelectorGadget::sampleSelected, [c](auto s) {c->smp = s;});
}
}

View File

@ -0,0 +1,11 @@
#include "gadget.h"
using Xybrid::UI::Gadget;
Gadget::Gadget(QGraphicsItem* parent) : QGraphicsObject(parent) { }
void Gadget::centerOn(const QPointF& newPos) {
auto c = boundingRect().center();
setPos(newPos - c);
// check and compensate
if (auto nc = pos() + c; nc != newPos) setPos(newPos - (nc - newPos));
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <QGraphicsObject>
namespace Xybrid::UI {
class Gadget : public QGraphicsObject {
Q_OBJECT
public:
Gadget(QGraphicsItem* parent = nullptr);
~Gadget() override = default;
void centerOn(const QPointF&);
inline void centerOn(qreal x, qreal y) { centerOn(QPointF(x, y)); }
};
}

View File

@ -35,7 +35,7 @@ double KnobGadget::get() {
return v;
}
KnobGadget::KnobGadget(QGraphicsItem* parent) : QGraphicsObject(parent) {
KnobGadget::KnobGadget(QGraphicsItem* parent) : Gadget(parent) {
static QFont f("Arcon Rounded", 7);
label = new QGraphicsSimpleTextItem(this);

View File

@ -1,13 +1,13 @@
#pragma once
#include "ui/gadgets/gadget.h"
#include <memory>
#include <functional>
#include <atomic>
#include <QGraphicsObject>
namespace Xybrid::UI {
class KnobGadget : public QGraphicsObject {
class KnobGadget : public Gadget {
Q_OBJECT
bool highlighted = false;

View File

@ -0,0 +1,142 @@
#include "sampleselectorgadget.h"
using Xybrid::UI::SampleSelectorGadget;
#include "data/project.h"
#include "data/sample.h"
using namespace Xybrid::Data;
#include "ui/waveformpreviewwidget.h"
#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->setRenderHint(QPainter::HighQualityAntialiasing, true);
p->setPen(QPen(outline, 2));
p->setBrush(QBrush(Qt::NoBrush));
p->drawRoundedRect(br, corner, corner);
// draw label
QFont font("Arcon Rounded", 8);
QFontMetrics fm(font);
name = fm.elidedText(name, 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::mousePressEvent(QGraphicsSceneMouseEvent* e) {
if (!project) return;
std::vector<std::shared_ptr<Sample>> displayOrder;
displayOrder.reserve(static_cast<size_t>(project->samples.size()));
for (auto s : project->samples) displayOrder.push_back(s);
std::sort(displayOrder.begin(), displayOrder.end(), [](std::shared_ptr<Sample> a, std::shared_ptr<Sample> b) {
if (a->name == b->name) return a->uuid < b->uuid;
return a->name < b->name;
});
if (e->button() == Qt::LeftButton) {
auto smp = currentSample.lock();
auto* m = new QMenu();
for (auto s : displayOrder) {
auto wa = new QWidgetAction(m);
auto wfp = new WaveformPreviewWidget(m);
wa->setDefaultWidget(wfp);
wfp->showName = true;
wfp->highlightable = true;
wfp->setSample(s);
wfp->setMinimumSize(192, 48);
m->addAction(wa);
if (s == smp) wa->setDisabled(true);
else connect(wa, &QWidgetAction::triggered, [this, s] { setSample(s); });
}
if (displayOrder.empty()) m->addAction("(no samples in project)")->setDisabled(true);
m->setAttribute(Qt::WA_DeleteOnClose);
m->popup(e->screenPos());
}
}
void SampleSelectorGadget::hoverEnterEvent(QGraphicsSceneHoverEvent*) { update(); }
void SampleSelectorGadget::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { update(); }
void SampleSelectorGadget::setSample(std::shared_ptr<Sample> smp, bool signal) {
currentSample = smp;
update();
if (signal) emit sampleSelected(smp);
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "ui/gadgets/gadget.h"
#include <memory>
namespace Xybrid::Data { class Project; class Sample; }
namespace Xybrid::UI {
class SampleSelectorGadget : public Gadget {
Q_OBJECT
QSizeF size = {128, 32};
Data::Project* project;
std::weak_ptr<Data::Sample> currentSample;
public:
SampleSelectorGadget(Data::Project* project = nullptr, QGraphicsItem* parent = nullptr);
~SampleSelectorGadget() override = default;
QRectF boundingRect() const override;
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
void mousePressEvent(QGraphicsSceneMouseEvent*) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent*) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;
inline void setSize(const QSizeF& s) { size = s; update(); }
inline void setSize(qreal w, qreal h) { setSize(QSizeF(w, h)); }
void setSample(std::shared_ptr<Data::Sample>, bool signal = true);
signals:
void sampleSelected(std::shared_ptr<Data::Sample>);
};
}

View File

@ -8,14 +8,14 @@ using Xybrid::UI::SelectorGadget;
#include <QMenu>
SelectorGadget::SelectorGadget(QGraphicsItem *parent) : QGraphicsObject(parent) {
SelectorGadget::SelectorGadget(QGraphicsItem *parent) : Gadget(parent) {
setAcceptHoverEvents(true);
font = QFont("Arcon Rounded", 8);
}
QRectF SelectorGadget::boundingRect() const {
return QRectF(pos(), QSizeF(width, 24));
return QRectF(QPointF(0, 0), QSizeF(width, 24));
}
void SelectorGadget::paint(QPainter* p, const QStyleOptionGraphicsItem* opt, QWidget*) {

View File

@ -1,13 +1,14 @@
#pragma once
#include "ui/gadgets/gadget.h"
#include <memory>
#include <functional>
#include <QGraphicsObject>
#include <QFont>
namespace Xybrid::UI {
class SelectorGadget : public QGraphicsObject {
class SelectorGadget : public Gadget {
Q_OBJECT
public:
@ -25,7 +26,6 @@ namespace Xybrid::UI {
~SelectorGadget() override = default;
QRectF boundingRect() const override;
//QPainterPath shape() const override;
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
void mousePressEvent(QGraphicsSceneMouseEvent*) override;

View File

@ -6,6 +6,8 @@ using Xybrid::Data::Sample;
#include "config/colorscheme.h"
#include "util/strings.h"
#include <cmath>
#include <QDebug>
@ -20,9 +22,9 @@ WaveformPreviewWidget::WaveformPreviewWidget(QWidget* parent) : QFrame(parent) {
void WaveformPreviewWidget::setSample(std::shared_ptr<Sample> smp) {
currentSample = smp;
int ch = 1;
//int ch = 1;
//if (smp) ch = std::max(1, smp->numChannels());
setMinimumHeight(128 * ch);
//setMinimumHeight(128 * ch);
repaint(); // invalidate paint state
}
@ -44,15 +46,23 @@ void WaveformPreviewWidget::paintChannel(QPainter& p, std::shared_ptr<Sample> sm
}
void WaveformPreviewWidget::paintEvent(QPaintEvent* event [[maybe_unused]]) {
auto disabled = !isEnabled();
QPainter 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);
p.fillRect(rect, Config::colorScheme.waveformBg);
p.setPen(QPen(Config::colorScheme.waveformFgPrimary));
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) {
@ -63,5 +73,34 @@ void WaveformPreviewWidget::paintEvent(QPaintEvent* event [[maybe_unused]]) {
}
}
if (showName) {
// draw label
QFont font("Arcon Rounded", 8);
QFontMetrics fm(font);
name = fm.elidedText(name, Qt::ElideRight, rect.width() - 4);
QPainterPath path;
path.addText(rect.bottomLeft() + QPointF(2, -fm.descent()), font, name);
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::HighQualityAntialiasing, true);
p.fillPath(path, QColor(0, 0, 0));
p.strokePath(path, QPen(QColor(0, 0, 0), 3));
p.fillPath(path, QColor(255, 255, 255));
}
QFrame::paintEvent(event);
}
void WaveformPreviewWidget::enterEvent(QEvent*) {
if (highlightable) {
highlighted = true;
repaint();
}
}
void WaveformPreviewWidget::leaveEvent(QEvent*) {
if (highlightable) {
highlighted = false;
repaint();
}
}

View File

@ -10,7 +10,11 @@ namespace Xybrid::UI {
Q_OBJECT
std::weak_ptr<Data::Sample> currentSample;
bool highlighted = false;
public:
bool showName = false;
bool highlightable = false;
explicit WaveformPreviewWidget(QWidget* parent = nullptr);
void setSample(std::shared_ptr<Data::Sample>);
@ -18,5 +22,8 @@ namespace Xybrid::UI {
protected:
void paintEvent(QPaintEvent* event) override;
void paintChannel(QPainter&, std::shared_ptr<Data::Sample>, size_t channel, QRect);
void enterEvent(QEvent*) override;
void leaveEvent(QEvent*) override;
};
}