node UI stuff; beatpad load/save

portability/boost
zetaPRIME 2019-06-22 05:29:55 -04:00
parent d8c345f7e3
commit ff0e9c3e56
9 changed files with 268 additions and 26 deletions

View File

@ -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());
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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);
}

View File

@ -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&);
};
}

View File

@ -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) {

View File

@ -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);
};
}

View File

@ -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);

View File

@ -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;
}
}