ButtonGadget, beginnings of mixer board, UI fixes
parent
5dc9befec8
commit
0081b1de1e
|
@ -259,7 +259,7 @@ std::shared_ptr<Port> Node::port(Port::Type t, Port::DataType dt, uint8_t idx, b
|
|||
return addIfNeeded ? addPort(t, dt, idx) : nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Port> Node::addPort(Port::Type t, Port::DataType dt, uint8_t idx) {
|
||||
std::shared_ptr<Port> Node::addPort(Port::Type t, Port::DataType dt, uint8_t idx, bool allowUpdate) {
|
||||
auto& m = t == Port::Input ? inputs : outputs;
|
||||
m.try_emplace(dt);
|
||||
auto mdt = m.find(dt);
|
||||
|
@ -269,21 +269,21 @@ std::shared_ptr<Port> Node::addPort(Port::Type t, Port::DataType dt, uint8_t idx
|
|||
p->type = t;
|
||||
mdt->second.insert({idx, p});
|
||||
p->index = idx;
|
||||
if (obj) obj->updatePorts();
|
||||
if (allowUpdate && obj) obj->updatePorts();
|
||||
return p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Node::removePort(Port::Type t, Port::DataType dt, uint8_t idx) {
|
||||
void Node::removePort(Port::Type t, Port::DataType dt, uint8_t idx, bool allowUpdate) {
|
||||
auto& m = t == Port::Input ? inputs : outputs;
|
||||
if (auto mdt = m.find(dt); mdt != m.end()) {
|
||||
mdt->second.erase(idx);
|
||||
if (obj) obj->updatePorts();
|
||||
if (allowUpdate && obj) obj->updatePorts();
|
||||
}
|
||||
}
|
||||
|
||||
bool Node::movePort(Port::Type t, Port::DataType dt, uint8_t idx, uint8_t nidx) {
|
||||
bool Node::movePort(Port::Type t, Port::DataType dt, uint8_t idx, uint8_t nidx, bool allowUpdate) {
|
||||
auto p = port(t, dt, idx);
|
||||
if (!p) return false;
|
||||
auto& m = t == Port::Input ? inputs : outputs;
|
||||
|
@ -292,7 +292,7 @@ bool Node::movePort(Port::Type t, Port::DataType dt, uint8_t idx, uint8_t nidx)
|
|||
mdt->second.erase(idx);
|
||||
mdt->second.insert({nidx, p});
|
||||
p->index = nidx;
|
||||
if (obj) obj->updatePorts();
|
||||
if (allowUpdate && obj) obj->updatePorts();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -112,9 +112,9 @@ namespace Xybrid::Data {
|
|||
void parentTo(std::shared_ptr<Graph>);
|
||||
|
||||
std::shared_ptr<Port> port(Port::Type, Port::DataType, uint8_t, bool addIfNeeded = false);
|
||||
std::shared_ptr<Port> addPort(Port::Type, Port::DataType, uint8_t);
|
||||
void removePort(Port::Type, Port::DataType, uint8_t);
|
||||
bool movePort(Port::Type, Port::DataType, uint8_t, uint8_t);
|
||||
std::shared_ptr<Port> addPort(Port::Type, Port::DataType, uint8_t, bool allowUpdate = true);
|
||||
void removePort(Port::Type, Port::DataType, uint8_t, bool allowUpdate = true);
|
||||
bool movePort(Port::Type, Port::DataType, uint8_t, uint8_t, bool allowUpdate = true);
|
||||
void collapsePorts(Port::Type, Port::DataType);
|
||||
|
||||
std::unordered_set<std::shared_ptr<Node>> dependencies() const;
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
#include "mixboard.h"
|
||||
using Xybrid::Gadgets::MixBoard;
|
||||
using namespace Xybrid::Data;
|
||||
|
||||
#include "nodelib/basics.h"
|
||||
using namespace Xybrid::NodeLib;
|
||||
|
||||
#include "data/audioframe.h"
|
||||
#include "data/porttypes.h"
|
||||
|
||||
#include "config/pluginregistry.h"
|
||||
using namespace Xybrid::Config;
|
||||
|
||||
#include "audio/audioengine.h"
|
||||
using namespace Xybrid::Audio;
|
||||
|
||||
#include "ui/patchboard/nodeobject.h"
|
||||
#include "ui/gadgets/layoutgadget.h"
|
||||
#include "ui/gadgets/buttongadget.h"
|
||||
#include "ui/gadgets/knobgadget.h"
|
||||
using namespace Xybrid::UI;
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QPen>
|
||||
#include <QCborMap>
|
||||
#include <QCborArray>
|
||||
|
||||
#define qs QStringLiteral
|
||||
|
||||
namespace {
|
||||
bool _ = PluginRegistry::enqueueRegistration([] {
|
||||
auto i = std::make_shared<PluginInfo>();
|
||||
i->id = "gadget:mixboard";
|
||||
i->displayName = "Mixer Board";
|
||||
i->category = "Gadget";
|
||||
i->createInstance = []{ return std::make_shared<MixBoard>(); };
|
||||
PluginRegistry::registerPlugin(i);
|
||||
});
|
||||
}
|
||||
|
||||
MixBoard::MixBoard() {
|
||||
|
||||
}
|
||||
|
||||
void MixBoard::init() {
|
||||
addPort(Port::Output, Port::Audio, 0);
|
||||
if (sections.empty()) insertSection(0);
|
||||
}
|
||||
|
||||
void MixBoard::process() { // TODO: lerp from tick to tick?
|
||||
/*auto m = AudioFrame::MixBoardMult(gain.load(), balance.load());
|
||||
|
||||
auto in = std::static_pointer_cast<AudioPort>(port(Port::Input, Port::Audio, 0));
|
||||
auto out = std::static_pointer_cast<AudioPort>(port(Port::Output, Port::Audio, 0));
|
||||
in->pull();
|
||||
out->pull();
|
||||
|
||||
size_t ts = audioEngine->curTickSize();
|
||||
for (size_t s = 0; s < ts; s++) (*out)[s] = (*in)[s] * m;*/
|
||||
}
|
||||
|
||||
void MixBoard::saveData(QCborMap& m) const {
|
||||
QCborArray sa;
|
||||
for (auto& sec : sections) {
|
||||
QCborMap s;
|
||||
s[qs("gain")] = QCborValue(sec.gain);
|
||||
s[qs("mute")] = sec.mute;
|
||||
s[qs("solo")] = sec.solo;
|
||||
sa << s;
|
||||
}
|
||||
|
||||
m[qs("sections")] = sa;
|
||||
}
|
||||
|
||||
void MixBoard::loadData(const QCborMap& m) {
|
||||
auto sa = m.value("sections").toArray();
|
||||
|
||||
sections.clear();
|
||||
sections.resize(static_cast<size_t>(sa.size()));
|
||||
for (size_t i = 0; i < sections.size(); i++) {
|
||||
addPort(Port::Input, Port::Audio, static_cast<uint8_t>(i), false);
|
||||
auto& s = sections[i];
|
||||
auto sm = sa[static_cast<int>(i)].toMap();
|
||||
s.gain = sm.value("gain").toDouble(s.gain);
|
||||
s.mute = sm.value("mute").toBool(s.mute);
|
||||
s.solo = sm.value("solo").toBool(s.solo);
|
||||
}
|
||||
if (obj) {
|
||||
obj->updatePorts();
|
||||
onGadgetCreated();
|
||||
emit obj->finalized();
|
||||
}
|
||||
}
|
||||
|
||||
void MixBoard::insertSection(uint8_t p) {
|
||||
p = std::min(p, static_cast<uint8_t>(sections.size()));
|
||||
sections.emplace(sections.begin()+p);
|
||||
for (int16_t ii = 254; ii >= p; ii--) {
|
||||
auto i = static_cast<uint8_t>(ii);
|
||||
movePort(Port::Input, Port::Audio, i, i+1, false);
|
||||
}
|
||||
addPort(Port::Input, Port::Audio, p, false);
|
||||
if (obj) {
|
||||
obj->updatePorts();
|
||||
onGadgetCreated();
|
||||
emit obj->finalized();
|
||||
}
|
||||
}
|
||||
|
||||
void MixBoard::removeSection(uint8_t p) {
|
||||
if (p >= sections.size()) return;
|
||||
sections.erase(sections.begin()+p);
|
||||
removePort(Port::Input, Port::Audio, p, false);
|
||||
for (int16_t ii = p+1; ii < 256; ii++) {
|
||||
auto i = static_cast<uint8_t>(ii);
|
||||
movePort(Port::Input, Port::Audio, i, i-1, false);
|
||||
}
|
||||
if (obj) {
|
||||
obj->updatePorts();
|
||||
onGadgetCreated();
|
||||
emit obj->finalized();
|
||||
}
|
||||
}
|
||||
|
||||
void MixBoard::onGadgetCreated() {
|
||||
if (!obj) return;
|
||||
{
|
||||
auto k = new Gadget();
|
||||
for (auto c : obj->contents->childItems()) c->setParentItem(k);
|
||||
k->deleteLater();
|
||||
}
|
||||
qDeleteAll(obj->contents->childItems()); // clear out anything already there
|
||||
auto l = new LayoutGadget(obj, true);
|
||||
|
||||
QObject::connect(obj, &NodeObject::postGeometryUpdate, l, [this] {
|
||||
obj->inputPortContainer->setY(obj->contents->y());
|
||||
auto c = static_cast<QGraphicsLineItem*>(obj->inputPortContainer.get());
|
||||
c->setPen(Qt::NoPen); // hide line
|
||||
});
|
||||
|
||||
auto c = obj->inputPortContainer->childItems();
|
||||
auto count = sections.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
auto ln = new LayoutGadget(l);
|
||||
|
||||
auto gain = new KnobGadget(ln);
|
||||
gain->setLabel("Gain");
|
||||
gain->fText = [](double d) { return QString("%1dB").arg(d); };
|
||||
gain->min = -60;
|
||||
gain->max = 6;
|
||||
gain->step = .1;
|
||||
gain->bind(sections[i].gain);
|
||||
|
||||
auto end = new LayoutGadget(ln, true);
|
||||
auto bIns = new ButtonGadget(end);
|
||||
if (count > 1) end->addSpacer();
|
||||
auto bDel = new ButtonGadget(end);
|
||||
bIns->setSize(16, 16);
|
||||
bDel->setSize(16, 16);
|
||||
bIns->text = "+";
|
||||
bDel->text = "-";
|
||||
|
||||
QObject::connect(bIns, &ButtonGadget::clicked, [this, i] { insertSection(static_cast<uint8_t>(i)); });
|
||||
QObject::connect(bDel, &ButtonGadget::clicked, [this, i] { removeSection(static_cast<uint8_t>(i)); });
|
||||
|
||||
if (count <= 1) delete bDel; // no dropping to zero
|
||||
}
|
||||
|
||||
l->updateGeometry();
|
||||
auto lc = l->childItems();
|
||||
for (auto i = 0; i < static_cast<int>(count); i++) c[i]->setY(lc[i]->y() + lc[i]->boundingRect().center().y());
|
||||
|
||||
auto btn = new ButtonGadget(l);
|
||||
btn->text = "+";
|
||||
btn->setSize(16, 16);
|
||||
QObject::connect(btn, &ButtonGadget::clicked, [this, count] {
|
||||
insertSection(static_cast<uint8_t>(count));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "data/node.h"
|
||||
|
||||
namespace Xybrid::Gadgets {
|
||||
class MixBoard : public Data::Node {
|
||||
//
|
||||
public:
|
||||
struct Section {
|
||||
bool mute = false;
|
||||
bool solo = false;
|
||||
double gain = 0;
|
||||
};
|
||||
|
||||
std::vector<Section> sections;
|
||||
|
||||
MixBoard();
|
||||
~MixBoard() override = default;
|
||||
|
||||
void init() override;
|
||||
//void reset() override;
|
||||
void process() override;
|
||||
|
||||
//void onRename() override;
|
||||
|
||||
void saveData(QCborMap&) const override;
|
||||
void loadData(const QCborMap&) override;
|
||||
|
||||
void insertSection(uint8_t);
|
||||
void removeSection(uint8_t);
|
||||
|
||||
//void onUnparent(std::shared_ptr<Data::Graph>) override;
|
||||
//void onParent(std::shared_ptr<Data::Graph>) override;
|
||||
|
||||
void onGadgetCreated() override;
|
||||
|
||||
//void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) override;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
#include "buttongadget.h"
|
||||
using Xybrid::UI::ButtonGadget;
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QCursor>
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
ButtonGadget::ButtonGadget(QGraphicsItem *parent) : Gadget(parent) {
|
||||
setAcceptHoverEvents(true);
|
||||
|
||||
font = QFont("Arcon Rounded", 8);
|
||||
}
|
||||
|
||||
QRectF ButtonGadget::boundingRect() const {
|
||||
return QRectF(QPointF(0, 0), size);
|
||||
}
|
||||
|
||||
void ButtonGadget::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);
|
||||
|
||||
QLinearGradient fill(r.topLeft(), r.bottomLeft());
|
||||
if (pressed) {
|
||||
fill.setColorAt(0.0, QColor(35, 35, 35));
|
||||
fill.setColorAt(1.0, QColor(47, 47, 47));
|
||||
} else {
|
||||
fill.setColorAt(0.0, QColor(63, 63, 63));
|
||||
if (hover) fill.setColorAt(0.32, QColor(71, 71, 71));
|
||||
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, 5, 5);
|
||||
|
||||
if (!text.isEmpty()) {
|
||||
p->setFont(font);
|
||||
p->setBrush(QBrush(Qt::NoBrush));
|
||||
p->setPen(QColor(255, 255, 255));
|
||||
QFontMetrics f(font);
|
||||
auto t = f.elidedText(text, Qt::ElideRight, static_cast<int>(r.width() - 8));
|
||||
p->drawText(QPointF(r.center().x() - f.width(t)/2, r.center().y() + f.ascent()/2), t);
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonGadget::mousePressEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
pressed = true;
|
||||
update();
|
||||
}
|
||||
}
|
||||
void ButtonGadget::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
if (pressed) emit clicked();
|
||||
pressed = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonGadget::hoverEnterEvent(QGraphicsSceneHoverEvent*) { update(); }
|
||||
void ButtonGadget::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { pressed = false; update(); }
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "ui/gadgets/gadget.h"
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include <QFont>
|
||||
|
||||
namespace Xybrid::UI {
|
||||
class ButtonGadget : public Gadget {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QSizeF size = {96, 24};
|
||||
QFont font;
|
||||
bool pressed = false;
|
||||
|
||||
public:
|
||||
QString text;
|
||||
|
||||
ButtonGadget(QGraphicsItem* parent = nullptr);
|
||||
~ButtonGadget() override = default;
|
||||
|
||||
QRectF boundingRect() const override;
|
||||
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
|
||||
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent*) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
|
||||
|
||||
void hoverEnterEvent(QGraphicsSceneHoverEvent*) override;
|
||||
void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;
|
||||
|
||||
inline void setSize(QSizeF s) { size = s; }
|
||||
inline void setSize(qreal w, qreal h = 24) { size = {w, h}; }
|
||||
|
||||
signals:
|
||||
void clicked();
|
||||
};
|
||||
}
|
|
@ -12,6 +12,8 @@ void Gadget::centerOn(const QPointF& newPos) {
|
|||
|
||||
void Gadget::updateGeometry() { }
|
||||
|
||||
QRectF Gadget::boundingRect() const { return { }; }
|
||||
|
||||
QRectF Gadget::layoutBoundingRect() const { return boundingRect(); }
|
||||
|
||||
void Gadget::paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) { }
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Xybrid::UI {
|
|||
|
||||
virtual void updateGeometry();
|
||||
|
||||
QRectF boundingRect() const override;
|
||||
virtual QRectF layoutBoundingRect() const;
|
||||
|
||||
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
|
||||
|
|
|
@ -320,6 +320,7 @@ void NodeObject::updateGeometry() {
|
|||
if (inputPortContainer) inputPortContainer->setPos(QPointF(-pm, PortObject::portSize));
|
||||
if (outputPortContainer) outputPortContainer->setPos(QPointF(boundingRect().width() + pm, PortObject::portSize));
|
||||
}
|
||||
emit postGeometryUpdate();
|
||||
}
|
||||
|
||||
qreal NodeObject::nameSize() const {
|
||||
|
|
|
@ -133,5 +133,6 @@ namespace Xybrid::UI {
|
|||
|
||||
signals:
|
||||
void finalized();
|
||||
void postGeometryUpdate();
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue