ButtonGadget, beginnings of mixer board, UI fixes

portability/boost
zetaPRIME 2019-06-29 20:22:01 -04:00
parent 5dc9befec8
commit 0081b1de1e
10 changed files with 344 additions and 9 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -133,5 +133,6 @@ namespace Xybrid::UI {
signals:
void finalized();
void postGeometryUpdate();
};
}