more node UI stuff (sample selector!)
parent
ff0e9c3e56
commit
807c923543
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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)); }
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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>);
|
||||
};
|
||||
}
|
|
@ -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*) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue