node UI stuff; beatpad load/save
parent
d8c345f7e3
commit
ff0e9c3e56
|
@ -16,13 +16,18 @@ using namespace Xybrid::Config;
|
|||
#include "audio/audioengine.h"
|
||||
using namespace Xybrid::Audio;
|
||||
|
||||
#include "ui/waveformpreviewwidget.h"
|
||||
|
||||
#include "ui/patchboard/nodeobject.h"
|
||||
#include "ui/gadgets/knobgadget.h"
|
||||
#include "ui/gadgets/selectorgadget.h"
|
||||
using namespace Xybrid::UI;
|
||||
|
||||
#include "ui/patchboard/nodeuiscene.h"
|
||||
#include "uisocket.h"
|
||||
|
||||
#include "util/strings.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QDebug>
|
||||
|
@ -30,6 +35,9 @@ using namespace Xybrid::UI;
|
|||
#include <QCborValue>
|
||||
#include <QCborArray>
|
||||
|
||||
#include <QMenu>
|
||||
#include <QGraphicsProxyWidget>
|
||||
|
||||
#define qs QStringLiteral
|
||||
|
||||
namespace {
|
||||
|
@ -121,14 +129,32 @@ void BeatPad::release() { core.release(); }
|
|||
void BeatPad::process() { core.process(this); }
|
||||
|
||||
void BeatPad::saveData(QCborMap& m) const {
|
||||
//
|
||||
// TODO: mark samples for inclusion in export
|
||||
QCborMap cm;
|
||||
for (auto c : cfg) {
|
||||
if (auto smp = c.second->smp.lock(); smp) {
|
||||
QCborMap e;
|
||||
e[qs("sample")] = QCborValue(smp->uuid);
|
||||
e[qs("start")] = static_cast<qint64>(c.second->start);
|
||||
e[qs("end")] = static_cast<qint64>(c.second->end);
|
||||
cm[c.first] = e;
|
||||
}
|
||||
}
|
||||
m[qs("notecfg")] = cm;
|
||||
}
|
||||
|
||||
void BeatPad::loadData(const QCborMap& m) {
|
||||
// TODO: testing
|
||||
auto c = std::make_shared<NoteConfig>();
|
||||
cfg.insert_or_assign(60, c);
|
||||
if (!project->samples.empty()) c->smp = (project->samples.begin()).value();
|
||||
for (auto ce : m.value("notecfg").toMap()) {
|
||||
auto cm = ce.second.toMap();
|
||||
auto id = cm.value("sample").toUuid();
|
||||
if (auto f = project->samples.find(id); f != project->samples.end()) {
|
||||
auto c = std::make_shared<NoteConfig>();
|
||||
c->smp = f.value();
|
||||
c->start = cm.value("start").toInteger(-1);
|
||||
c->end = cm.value("end").toInteger(-1);
|
||||
cfg[static_cast<int16_t>(ce.first.toInteger())] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BeatPad::onGadgetCreated() {
|
||||
|
@ -142,19 +168,51 @@ void BeatPad::initUI(NodeUIScene* scene) {
|
|||
//scene->addItem()
|
||||
|
||||
{
|
||||
KnobGadget* k;
|
||||
auto s = new SelectorGadget();
|
||||
s->setPos(0, 0);
|
||||
s->setWidth(320);
|
||||
s->fGetList = [this] {
|
||||
std::vector<SelectorGadget::Entry> v;
|
||||
v.reserve(cfg.size());
|
||||
for (int16_t i = 0; i < 12*8; i++) {
|
||||
if (auto f = cfg.find(i); f != cfg.end()) {
|
||||
auto c = f->second;
|
||||
QString n;
|
||||
if (auto smp = c->smp.lock(); smp) n = smp->name;
|
||||
v.push_back({ f->first, qs("%1 %2").arg(Util::noteName(i)).arg(n) });
|
||||
}
|
||||
}
|
||||
return v;
|
||||
};
|
||||
s->fEditMenu = [this](QMenu* m) {
|
||||
auto first = m->actions().at(0);
|
||||
auto mAdd = new QMenu("Add...", m);
|
||||
m->insertMenu(first, mAdd);
|
||||
m->insertSeparator(first);
|
||||
for (int16_t oct = 0; oct < 8; oct++) {
|
||||
auto mo = mAdd->addMenu(qs("Octave %1").arg(oct));
|
||||
for (int16_t i = 0; i < 12; i++) {
|
||||
int16_t n = oct*12+i;
|
||||
if (auto f = cfg.find(n); f != cfg.end()) mo->addAction(Util::noteName(n))->setDisabled(true);
|
||||
else mo->addAction(Util::noteName(n));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
k = new KnobGadget(nullptr);
|
||||
k->setPos(32, 32);
|
||||
k->min = 0;
|
||||
k->max = 99999;
|
||||
k->step = 1;
|
||||
k->stepPx = KnobGadget::BigStep;
|
||||
k->bind(x);
|
||||
//k->fText = wavetxt;
|
||||
k->setLabel("Bananagram.");
|
||||
scene->addItem(s);
|
||||
|
||||
scene->addItem(k);
|
||||
QObject::connect(scene, &NodeUIScene::notePreview, s, [s](int16_t note) {
|
||||
//k->setLabel(Util::noteName(note));
|
||||
s->setEntry({note, Util::noteName(note)});
|
||||
});
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ namespace Xybrid::Instruments {
|
|||
|
||||
struct NoteConfig {
|
||||
std::weak_ptr<Data::Sample> smp = std::weak_ptr<Data::Sample>();
|
||||
double start = -1;
|
||||
double end = -1;
|
||||
ptrdiff_t start = -1;
|
||||
ptrdiff_t end = -1;
|
||||
};
|
||||
|
||||
struct NoteData {
|
||||
|
|
|
@ -36,13 +36,7 @@ double KnobGadget::get() {
|
|||
}
|
||||
|
||||
KnobGadget::KnobGadget(QGraphicsItem* parent) : QGraphicsObject(parent) {
|
||||
static QFont f = [] {
|
||||
QFont f("Arcon Rounded", 7);
|
||||
//f.setPointSizeF(7.5);
|
||||
//f.setStretch(QFont::SemiCondensed);
|
||||
//f.setStretch(90);
|
||||
return f;
|
||||
}();
|
||||
static QFont f("Arcon Rounded", 7);
|
||||
|
||||
label = new QGraphicsSimpleTextItem(this);
|
||||
valLabel = new QGraphicsSimpleTextItem(this);
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
#include "selectorgadget.h"
|
||||
using Xybrid::UI::SelectorGadget;
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QCursor>
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
SelectorGadget::SelectorGadget(QGraphicsItem *parent) : QGraphicsObject(parent) {
|
||||
setAcceptHoverEvents(true);
|
||||
|
||||
font = QFont("Arcon Rounded", 8);
|
||||
}
|
||||
|
||||
QRectF SelectorGadget::boundingRect() const {
|
||||
return QRectF(pos(), QSizeF(width, 24));
|
||||
}
|
||||
|
||||
void SelectorGadget::paint(QPainter* p, const QStyleOptionGraphicsItem* opt, QWidget*) {
|
||||
auto r = boundingRect();
|
||||
//p->fillRect(rect, QColor(255, 0, 0));
|
||||
|
||||
bool hover = opt->state & QStyle::State_MouseOver;
|
||||
|
||||
QColor outline = QColor(31, 31, 31);
|
||||
if (hover) outline = QColor(127, 127, 255);
|
||||
|
||||
QLinearGradient fill(r.topLeft(), r.bottomLeft());
|
||||
fill.setColorAt(0.0, QColor(95, 95, 95));
|
||||
fill.setColorAt(0.32, QColor(63, 63, 63));
|
||||
//fill.setColorAt(1.0 - (1.0 - 8.0/r.height()) / 2, QColor(55, 55, 55));
|
||||
fill.setColorAt(1.0, QColor(35, 35, 35));
|
||||
|
||||
p->setRenderHint(QPainter::RenderHint::Antialiasing);
|
||||
p->setBrush(QBrush(fill));
|
||||
p->setPen(QPen(QBrush(outline), 2));
|
||||
p->drawRoundedRect(r, 8, 8);
|
||||
|
||||
p->setPen(QPen(Qt::NoPen));
|
||||
p->setBrush(outline);
|
||||
qreal tw = 6;
|
||||
QPointF tc = {r.right() - 4 - tw/2, r.center().y()};
|
||||
p->drawPolygon(QPolygonF({tc + QPointF(-tw/2, -tw/2), tc + QPointF(tw/2, -tw/2), tc + QPointF(0, tw/2)}));
|
||||
|
||||
p->setFont(font);
|
||||
p->setBrush(QBrush(Qt::NoBrush));
|
||||
p->setPen(QColor(255, 255, 255));
|
||||
QFontMetrics f(font);
|
||||
p->drawText(QPointF(r.left() + 4, r.center().y() + f.ascent()/2), f.elidedText(_entry.second, Qt::ElideRight, static_cast<int>(r.width() - 8-2-tw)));
|
||||
}
|
||||
|
||||
void SelectorGadget::mousePressEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
auto* m = new QMenu();
|
||||
std::vector<Entry> v = fGetList ? fGetList() : std::vector<Entry>();
|
||||
if (v.empty()) m->addAction("(no entries)")->setDisabled(true);
|
||||
else {
|
||||
for (auto e : v) {
|
||||
if (_entry.first == e.first) {
|
||||
m->addAction(e.second)->setDisabled(true);
|
||||
} else m->addAction(e.second, [this, e] {
|
||||
setEntry(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (fEditMenu) fEditMenu(m);
|
||||
m->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m->popup(e->screenPos());
|
||||
}
|
||||
}
|
||||
|
||||
void SelectorGadget::hoverEnterEvent(QGraphicsSceneHoverEvent*) { update(); }
|
||||
void SelectorGadget::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { update(); }
|
||||
|
||||
void SelectorGadget::setWidth(qreal w) {
|
||||
width = w;
|
||||
update();
|
||||
}
|
||||
|
||||
void SelectorGadget::setEntry(const SelectorGadget::Entry& e, bool signal) {
|
||||
_entry = e;
|
||||
update();
|
||||
if (signal) emit onSelect(_entry);
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include <QGraphicsObject>
|
||||
#include <QFont>
|
||||
|
||||
namespace Xybrid::UI {
|
||||
class SelectorGadget : public QGraphicsObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef std::pair<int, QString> Entry;
|
||||
private:
|
||||
QFont font;
|
||||
qreal width = 128;
|
||||
Entry _entry = {-1, ""};
|
||||
|
||||
public:
|
||||
std::function<std::vector<Entry>()> fGetList;
|
||||
std::function<void(QMenu*)> fEditMenu;
|
||||
|
||||
SelectorGadget(QGraphicsItem* parent = nullptr);
|
||||
~SelectorGadget() override = default;
|
||||
|
||||
QRectF boundingRect() const override;
|
||||
//QPainterPath shape() const override;
|
||||
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
|
||||
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent*) override;
|
||||
|
||||
void hoverEnterEvent(QGraphicsSceneHoverEvent*) override;
|
||||
void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;
|
||||
|
||||
void setWidth(qreal);
|
||||
|
||||
const Entry& entry() const { return _entry; }
|
||||
void setEntry(const Entry&, bool signal = true);
|
||||
|
||||
signals:
|
||||
void onSelect(const Entry&);
|
||||
};
|
||||
}
|
|
@ -12,11 +12,41 @@ using namespace Xybrid::Audio;
|
|||
|
||||
#include "config/colorscheme.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <QTimer>
|
||||
#include <QPainter>
|
||||
#include <QKeyEvent>
|
||||
#include <QScrollBar>
|
||||
|
||||
NodeUIScene::NodeUIScene(QGraphicsView* view, const std::shared_ptr<Xybrid::Data::Node>& node) : QGraphicsScene(view), node(node) {
|
||||
NodeUIScene::NodeUIScene(QGraphicsView* view, const std::shared_ptr<Xybrid::Data::Node>& node) : QGraphicsScene(view), node(node), view(view) {
|
||||
setSceneRect(QRectF(QPointF(-9999,-9999), QPointF(9999, 9999))); // keep view anchored
|
||||
node->initUI(this);
|
||||
view->centerOn(itemsBoundingRect().center());
|
||||
|
||||
connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, this, &NodeUIScene::queueResize);
|
||||
connect(view->verticalScrollBar(), &QScrollBar::valueChanged, this, &NodeUIScene::queueResize);
|
||||
connect(view->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &NodeUIScene::queueResize);
|
||||
connect(view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeUIScene::queueResize);
|
||||
|
||||
queueResize();
|
||||
}
|
||||
|
||||
void NodeUIScene::queueResize() {
|
||||
if (!resizeQueued) {
|
||||
resizeQueued = true;
|
||||
QTimer::singleShot(1, this, &NodeUIScene::autoResize);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeUIScene::autoResize() {
|
||||
resizeQueued = false;
|
||||
if (!view || view->scene() != this) return;
|
||||
if (!autoCenter) return;
|
||||
auto vrect = view->mapToScene(view->viewport()->visibleRegion().boundingRect()).boundingRect();
|
||||
auto brect = itemsBoundingRect();
|
||||
vrect.moveCenter(brect.center());
|
||||
setSceneRect(vrect);
|
||||
}
|
||||
|
||||
void NodeUIScene::drawBackground(QPainter* p, const QRectF& rect) {
|
||||
|
@ -44,6 +74,7 @@ void NodeUIScene::startPreview(int key, int16_t note) {
|
|||
auto p = node->project->shared_from_this();
|
||||
previewKey[key] = {audioEngine->previewPort(), audioEngine->preview(p, 0, note, 0, node.get())};
|
||||
|
||||
emit notePreview(note);
|
||||
}
|
||||
|
||||
void NodeUIScene::stopPreview(int key) {
|
||||
|
|
|
@ -12,9 +12,13 @@ namespace Xybrid::Data { class Node; }
|
|||
|
||||
namespace Xybrid::UI {
|
||||
class NodeUIScene : public QGraphicsScene {
|
||||
Q_OBJECT
|
||||
|
||||
std::shared_ptr<Data::Node> node;
|
||||
QGraphicsView* view;
|
||||
|
||||
bool resizeQueued = false;
|
||||
|
||||
std::unordered_map<int, std::pair<int16_t, uint16_t>> previewKey;
|
||||
|
||||
void startPreview(int, int16_t);
|
||||
|
@ -23,13 +27,22 @@ namespace Xybrid::UI {
|
|||
//QShortcut* shortcut(QKeySequence);
|
||||
|
||||
public:
|
||||
bool autoCenter = true;
|
||||
|
||||
NodeUIScene(QGraphicsView* view, const std::shared_ptr<Data::Node>& node);
|
||||
~NodeUIScene() override = default;
|
||||
|
||||
void queueResize();
|
||||
void autoResize();
|
||||
|
||||
void drawBackground(QPainter*, const QRectF&) override;
|
||||
|
||||
//void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;
|
||||
void keyPressEvent(QKeyEvent*) override;
|
||||
void keyReleaseEvent(QKeyEvent*) override;
|
||||
|
||||
signals:
|
||||
void notePreview(int16_t);
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ void WaveformPreviewWidget::paintChannel(QPainter& p, std::shared_ptr<Sample> sm
|
|||
|
||||
void WaveformPreviewWidget::paintEvent(QPaintEvent* event [[maybe_unused]]) {
|
||||
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);
|
||||
|
|
|
@ -27,4 +27,18 @@ namespace Xybrid::Util {
|
|||
QChar zero('0');
|
||||
return qs("%1:%2.%3").arg(min, 2, 10, zero).arg(sec, 2, 10, zero).arg(sub, 2, 10, zero);
|
||||
}
|
||||
|
||||
constexpr char notemap[] = "C-C#D-D#E-F-F#G-G#A-A#B-";
|
||||
inline QString noteName(int16_t note) {
|
||||
if (note == -1) return " - ";
|
||||
if (note == -2) return " ^ ";
|
||||
if (note == -3) return " x ";
|
||||
QString s(3, ' ');
|
||||
auto nn = note % 12;
|
||||
auto oc = (note - nn) / 12;
|
||||
s[2] = '0' + static_cast<char>(oc);
|
||||
s[0] = notemap[nn*2];
|
||||
s[1] = notemap[nn*2+1];
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue