L I C E N S E and SO MUCH PATCHBOARD STUFF
graph i/o ports, breadcrumb nav, graph serialization, etc. etc. etc.portability/boost
parent
f78c3da03d
commit
333a06cac7
68
notes
68
notes
|
@ -45,52 +45,72 @@ project data {
|
||||||
|
|
||||||
TODO {
|
TODO {
|
||||||
immediate frontburner {
|
immediate frontburner {
|
||||||
group 1 {
|
neeeeext {
|
||||||
- make selection follow pattern move where applicable
|
hook the graph up to the audio engine! {
|
||||||
- strut command in pattern editor (mostly selection agnostic)
|
recursive dependency queue resolution
|
||||||
|
done/readiness test (process() wrapper?)
|
||||||
|
}
|
||||||
|
hook up commands to the graph {
|
||||||
|
figure out how to do note numbers
|
||||||
|
^ vector sized by channel count rounded up to next 16
|
||||||
|
^^ on switching to new graph... map of (hashes of) named channels??
|
||||||
|
assemble wire command queues (probaby some minor trickiness) and push into ports
|
||||||
|
NOTE! note number and port have to be combined in tracking (have to know what port to send the note-offs to)
|
||||||
|
}
|
||||||
|
then implement multithreading! :D
|
||||||
}
|
}
|
||||||
group 2 {
|
|
||||||
- skeleton graph and node
|
|
||||||
- skeleton audio engine
|
|
||||||
skeleton plugin registry - stuff into config namespace?
|
|
||||||
}
|
|
||||||
|
|
||||||
for passthrough {
|
|
||||||
passthroughLink attribute to keep track of whether a passthrough exists
|
|
||||||
^ build the logic into output pull()?
|
|
||||||
gadget that links up to parent's things
|
|
||||||
maybe call Graph::process as the end of its own subqueue??
|
|
||||||
}
|
|
||||||
... how to handle graph port naming and numbering?
|
|
||||||
have them fully dependent on the internal gadget??
|
|
||||||
- maybe make inputs/outputs into an (ordered) map<datatype> of map<uchar>s
|
|
||||||
|
|
||||||
audio engine invokes workers, then QThread::wait()s on them
|
audio engine invokes workers, then QThread::wait()s on them
|
||||||
|
|
||||||
# fix how qt5.12 broke header text (removed elide for now)
|
# fix how qt5.12 broke header text (removed elide for now)
|
||||||
|
|
||||||
add metadata and pattern properties (artist, song title, project bpm; pattern name, length etc.)
|
give node helper functions to move ports (index-wise) and collapse them (eliminate numbering holes)
|
||||||
|
node event that fires on port connect/disconnect (to enable, say, automatic collapsing, and changing of behavior by how many connections there are)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
at some point {
|
misc features needed before proper release {
|
||||||
|
at *least* js plugin support, with lua+lv2 highly preferable
|
||||||
|
SAMPLES and SAMPLING
|
||||||
|
|
||||||
|
gadget widgets (w/container) - at least a knob with nice range and such
|
||||||
|
|
||||||
|
add metadata and pattern properties (artist, song title, project bpm; pattern name, length etc.)
|
||||||
pattern cut+copy+paste
|
pattern cut+copy+paste
|
||||||
|
|
||||||
|
different context menu for multiple selected nodes
|
||||||
|
pack/unpack selection to/from subgraph
|
||||||
|
import/export subgraph as file (*.xyg)
|
||||||
|
|
||||||
|
proper playback controls and indicators
|
||||||
|
instrument previewing
|
||||||
|
|
||||||
pattern editor cells can have (dynamic) tool tips; set this up with port names, etc.
|
pattern editor cells can have (dynamic) tool tips; set this up with port names, etc.
|
||||||
? de-hardcode the "» " (probably just make it a static const variable somewhere?)
|
? de-hardcode the "» " (probably just make it a static const variable somewhere?)
|
||||||
make everything relevant check if editing is locked
|
make everything relevant check if editing is locked
|
||||||
|
|
||||||
make the save routine displace the old file and write a new one
|
make the save routine displace the old file and write a new one
|
||||||
|
|
||||||
multi-document, single-instance (QLocalServer etc.)
|
open file from command line argument
|
||||||
|
^ multi-document, single-instance (QLocalServer etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
gadgets and bundled things {
|
||||||
|
(the simple things:)
|
||||||
|
gain and panning gadget
|
||||||
|
volume meter
|
||||||
|
|
||||||
|
Polyplexer (splits a single command input into several monophonic outputs and keeps track of individual notes between them)
|
||||||
|
|
||||||
|
probably three sorts of sampler (quick drum sequencer, quick single-sample "wavetable", then the full-on tracker sampler later on)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dumb per-cycle atomic memory allocator from fixed pool for port buffer allocations
|
- dumb per-cycle atomic memory allocator from fixed pool for port buffer allocations
|
||||||
can also set up a tlsf pool per worker; prefix allocations with single byte identifier indicating which one they came from,
|
? can also set up a tlsf pool per worker; prefix allocations with single byte identifier indicating which one they came from,
|
||||||
and defer freeing operations via message queues
|
? and defer freeing operations via message queues
|
||||||
|
|
||||||
resampler object {
|
resampler object {
|
||||||
one used internally for each note
|
one used internally for each note
|
||||||
|
|
|
@ -4,3 +4,8 @@ something something, actual readme coming later
|
||||||
|
|
||||||
## Build dependencies:
|
## Build dependencies:
|
||||||
- Qt 5.12 or later
|
- Qt 5.12 or later
|
||||||
|
|
||||||
|
## License
|
||||||
|
The Software (all C/C++ code and bundled Lua or JavaScript, excepting where otherwise specified) is made available under the terms of the [GNU Lesser General Public License, v2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) or later.
|
||||||
|
|
||||||
|
The Assets (images and image project files (`*.xcf`), as well as all bundled example projects, excepting where otherwise specified) are licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||||
|
|
|
@ -60,7 +60,8 @@ namespace Xybrid::Audio {
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
void* tickAlloc(size_t size);
|
void* tickAlloc(size_t size);
|
||||||
inline size_t curTickSize() { return buffer[0].size(); }
|
inline size_t curTickId() const { return tickId; }
|
||||||
|
inline size_t curTickSize() const { return buffer[0].size(); }
|
||||||
|
|
||||||
// QIODevice functions
|
// QIODevice functions
|
||||||
qint64 readData(char* data, qint64 maxlen) override;
|
qint64 readData(char* data, qint64 maxlen) override;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "colorscheme.h"
|
#include "colorscheme.h"
|
||||||
using Xybrid::Config::ColorScheme;
|
using Xybrid::Config::ColorScheme;
|
||||||
|
|
||||||
ColorScheme ColorScheme::current;
|
ColorScheme Xybrid::Config::colorScheme;
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
namespace Xybrid::Config {
|
namespace Xybrid::Config {
|
||||||
class ColorScheme {
|
class ColorScheme {
|
||||||
public:
|
public:
|
||||||
static ColorScheme current;
|
|
||||||
ColorScheme() = default;
|
ColorScheme() = default;
|
||||||
|
|
||||||
QColor patternSelection = QColor(127, 63, 255, 63);
|
QColor patternSelection = QColor(127, 63, 255, 63);
|
||||||
|
@ -20,4 +19,5 @@ namespace Xybrid::Config {
|
||||||
QColor patternFgParamCmd = QColor(191,163,255);
|
QColor patternFgParamCmd = QColor(191,163,255);
|
||||||
QColor patternFgParamAmt = QColor(191,222,255);
|
QColor patternFgParamAmt = QColor(191,222,255);
|
||||||
};
|
};
|
||||||
|
extern ColorScheme colorScheme;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,14 @@ using namespace Xybrid::Config;
|
||||||
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
|
||||||
#include "data/node.h"
|
#include "data/graph.h"
|
||||||
using namespace Xybrid::Data;
|
using namespace Xybrid::Data;
|
||||||
|
|
||||||
|
#include "gadgets/ioport.h"
|
||||||
|
using Xybrid::Gadgets::IOPort;
|
||||||
|
|
||||||
|
#include "util/strings.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
typedef std::list<std::function<void()>> fqueue; // typedef so QtCreator's auto indent doesn't completely break :|
|
typedef std::list<std::function<void()>> fqueue; // typedef so QtCreator's auto indent doesn't completely break :|
|
||||||
fqueue& regQueue() {
|
fqueue& regQueue() {
|
||||||
|
@ -19,6 +24,10 @@ namespace {
|
||||||
bool& initialized() { static bool b = false; return b; }
|
bool& initialized() { static bool b = false; return b; }
|
||||||
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<PluginInfo>> plugins;
|
std::unordered_map<std::string, std::shared_ptr<PluginInfo>> plugins;
|
||||||
|
|
||||||
|
std::string priorityCategories[] {
|
||||||
|
"Gadget", "Instrument", "Effect"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PluginRegistry::enqueueRegistration(std::function<void ()> f) {
|
bool PluginRegistry::enqueueRegistration(std::function<void ()> f) {
|
||||||
|
@ -49,8 +58,85 @@ std::shared_ptr<Node> PluginRegistry::createInstance(const std::string& id) {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginRegistry::populatePluginMenu(QMenu* m, std::function<void (std::shared_ptr<Node>)> f) {
|
void PluginRegistry::populatePluginMenu(QMenu* m, std::function<void (std::shared_ptr<Node>)> f, Graph* g) {
|
||||||
for (auto& i : plugins) m->addAction(QString::fromStdString(i.second->displayName), [f, pi = i.second] {
|
std::map<std::string, std::map<std::string, std::shared_ptr<PluginInfo>>> cm; // category map
|
||||||
|
cm.try_emplace(""); // force empty category
|
||||||
|
for (auto& i : plugins) {
|
||||||
|
if (i.second->hidden) continue;
|
||||||
|
cm.try_emplace(i.second->category);
|
||||||
|
cm[i.second->category][i.second->displayName] = i.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// I/O ports
|
||||||
|
if (g) {
|
||||||
|
auto* mio = m->addMenu("I/O Port");
|
||||||
|
//auto* mi = mio->addMenu("Input");
|
||||||
|
//auto* mo = mio->addMenu("Output");
|
||||||
|
Port::DataType d[] {Port::Audio, Port::Command};
|
||||||
|
|
||||||
|
for (auto dt : d) {
|
||||||
|
auto* mi = mio->addMenu(QString("&%1 In").arg(Util::enumName(dt)));
|
||||||
|
auto* mo = mio->addMenu(QString("&%1 Out").arg(Util::enumName(dt)));
|
||||||
|
//mi->setStyleSheet("QMenu { menu-scrollable: 1; }");
|
||||||
|
//mi->setFixedHeight(256);
|
||||||
|
|
||||||
|
for (int ih = 0; ih < 16; ih++) {
|
||||||
|
QString n = QString::number(ih, 16).toUpper();
|
||||||
|
auto* mis = mi->addMenu(QString(u8"%10-%1F").arg(n));
|
||||||
|
auto* mos = mo->addMenu(QString(u8"%10-%1F").arg(n));
|
||||||
|
for (int il = 0; il < 16; il++) {
|
||||||
|
int i = ih*16+il;
|
||||||
|
QString nn = Util::hex(i);
|
||||||
|
mis->addAction(nn, [f, dt, i] {
|
||||||
|
auto n = std::static_pointer_cast<IOPort>(createInstance("ioport"));
|
||||||
|
n->setPort(Port::Input, dt, static_cast<uint8_t>(i));
|
||||||
|
f(n);
|
||||||
|
})->setEnabled(!g->port(Port::Input, dt, static_cast<uint8_t>(i)));
|
||||||
|
mos->addAction(nn, [f, dt, i] {
|
||||||
|
auto n = std::static_pointer_cast<IOPort>(createInstance("ioport"));
|
||||||
|
n->setPort(Port::Output, dt, static_cast<uint8_t>(i));
|
||||||
|
f(n);
|
||||||
|
})->setEnabled(!g->port(Port::Output, dt, static_cast<uint8_t>(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
m->addSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate priorities into menu
|
||||||
|
for (auto& pc : priorityCategories) {
|
||||||
|
if (auto c = cm.find(pc); c != cm.end()) {
|
||||||
|
auto* ccm = m->addMenu(QString::fromStdString(c->first));
|
||||||
|
for (auto& i : c->second) {
|
||||||
|
ccm->addAction(QString::fromStdString(i.second->displayName), [f, pi = i.second] {
|
||||||
|
auto n = pi->createInstance();
|
||||||
|
n->plugin = pi;
|
||||||
|
f(n);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cm.erase(pc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then any other category
|
||||||
|
for (auto& c : cm) {
|
||||||
|
if (c.first.empty() || c.second.empty()) continue;
|
||||||
|
auto* ccm = m->addMenu(QString::fromStdString(c.first));
|
||||||
|
for (auto& i : c.second) {
|
||||||
|
ccm->addAction(QString::fromStdString(i.second->displayName), [f, pi = i.second] {
|
||||||
|
auto n = pi->createInstance();
|
||||||
|
n->plugin = pi;
|
||||||
|
f(n);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m->addSeparator();
|
||||||
|
|
||||||
|
for (auto& i : cm[""]) m->addAction(QString::fromStdString(i.second->displayName), [f, pi = i.second] {
|
||||||
auto n = pi->createInstance();
|
auto n = pi->createInstance();
|
||||||
n->plugin = pi;
|
n->plugin = pi;
|
||||||
f(n);
|
f(n);
|
||||||
|
|
|
@ -8,6 +8,7 @@ class QMenu;
|
||||||
|
|
||||||
namespace Xybrid::Data {
|
namespace Xybrid::Data {
|
||||||
class Node;
|
class Node;
|
||||||
|
class Graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Xybrid::Config {
|
namespace Xybrid::Config {
|
||||||
|
@ -17,6 +18,7 @@ namespace Xybrid::Config {
|
||||||
std::string displayName;
|
std::string displayName;
|
||||||
std::string category;
|
std::string category;
|
||||||
std::function<std::shared_ptr<Data::Node>()> createInstance;
|
std::function<std::shared_ptr<Data::Node>()> createInstance;
|
||||||
|
bool hidden = false;
|
||||||
|
|
||||||
PluginInfo() = default;
|
PluginInfo() = default;
|
||||||
virtual ~PluginInfo() = default;
|
virtual ~PluginInfo() = default;
|
||||||
|
@ -28,6 +30,6 @@ namespace Xybrid::Config {
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
std::shared_ptr<Data::Node> createInstance(const std::string& id);
|
std::shared_ptr<Data::Node> createInstance(const std::string& id);
|
||||||
void populatePluginMenu(QMenu*, std::function<void(std::shared_ptr<Data::Node>)>);
|
void populatePluginMenu(QMenu*, std::function<void(std::shared_ptr<Data::Node>)>, Data::Graph* = nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,139 @@ using namespace Xybrid::Data;
|
||||||
#include "config/pluginregistry.h"
|
#include "config/pluginregistry.h"
|
||||||
using namespace Xybrid::Config;
|
using namespace Xybrid::Config;
|
||||||
|
|
||||||
|
#include "data/project.h"
|
||||||
|
#include "uisocket.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
#include "util/strings.h"
|
||||||
|
|
||||||
|
#include <QCborMap>
|
||||||
|
#include <QCborArray>
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QMetaEnum>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
std::shared_ptr<PluginInfo> inf;
|
||||||
bool c = PluginRegistry::enqueueRegistration([] {
|
bool c = PluginRegistry::enqueueRegistration([] {
|
||||||
auto i = std::make_shared<PluginInfo>();
|
auto i = std::make_shared<PluginInfo>();
|
||||||
i->id = "graph";
|
i->id = "graph";
|
||||||
i->displayName = "Subgraph";
|
i->displayName = "Subgraph";
|
||||||
i->createInstance = []{ return std::make_shared<Graph>(); };
|
i->createInstance = []{ return std::make_shared<Graph>(); };
|
||||||
PluginRegistry::registerPlugin(i);
|
PluginRegistry::registerPlugin(i);
|
||||||
|
inf = i;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//std::string Graph::pluginName() const { return "Subgraph"; }
|
//std::string Graph::pluginName() const { return "Subgraph"; }
|
||||||
|
|
||||||
|
Graph::Graph() {
|
||||||
|
plugin = inf; // harder bind
|
||||||
|
}
|
||||||
|
|
||||||
|
void Graph::saveData(QCborMap& m) {
|
||||||
|
// graph properties
|
||||||
|
// ... maybe there will be some at some point
|
||||||
|
|
||||||
|
std::unordered_map<Node*, int> indices;
|
||||||
|
{ /* children */ } {
|
||||||
|
QCborArray c;
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
for (auto ch : children) {
|
||||||
|
if (!ch->plugin) continue;
|
||||||
|
indices[ch.get()] = idx++;
|
||||||
|
QCborMap chm;
|
||||||
|
|
||||||
|
chm.insert(QString("id"), QString::fromStdString(ch->plugin->id));
|
||||||
|
if (!ch->name.empty()) chm.insert(QString("name"), QString::fromStdString(ch->name));
|
||||||
|
chm.insert(QString("x"), ch->x);
|
||||||
|
chm.insert(QString("y"), ch->y);
|
||||||
|
ch->saveData(chm);
|
||||||
|
|
||||||
|
c << chm;
|
||||||
|
}
|
||||||
|
|
||||||
|
m.insert(QString("children"), c);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ /* connections */ } {
|
||||||
|
// mapped from output to input
|
||||||
|
// array { oIdx, dataType, pIdx, iIdx, dataType, pIdx }
|
||||||
|
QCborArray cn;
|
||||||
|
|
||||||
|
for (auto ch : children) {
|
||||||
|
if (!ch->plugin) continue; // already skipped over
|
||||||
|
int idx = indices[ch.get()];
|
||||||
|
for (auto dt : ch->outputs) {
|
||||||
|
for (auto op : dt.second) {
|
||||||
|
auto o = op.second;
|
||||||
|
o->cleanConnections(); // let's just do some groundskeeping here
|
||||||
|
for (auto iw : o->connections) {
|
||||||
|
auto i = iw.lock();
|
||||||
|
QCborArray c;
|
||||||
|
c << idx;
|
||||||
|
c << Util::enumName(o->dataType());
|
||||||
|
c << o->index;
|
||||||
|
c << indices[i->owner.lock().get()];
|
||||||
|
c << Util::enumName(i->dataType());
|
||||||
|
c << i->index;
|
||||||
|
cn << c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
m.insert(QString("connections"), cn);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Graph::loadData(QCborMap& m) {
|
||||||
|
auto g = std::static_pointer_cast<Graph>(shared_from_this());
|
||||||
|
// graph properties (none)
|
||||||
|
|
||||||
|
{ /* children */ } {
|
||||||
|
QCborArray c = m.value("children").toArray();
|
||||||
|
|
||||||
|
for (auto chmv : c) {
|
||||||
|
auto chm = chmv.toMap();
|
||||||
|
auto ch = PluginRegistry::createInstance(chm.value("id").toString().toStdString());
|
||||||
|
ch->parentTo(g);
|
||||||
|
ch->name = chm.value("name").toString().toStdString();
|
||||||
|
ch->x = static_cast<int>(chm.value("x").toInteger());
|
||||||
|
ch->y = static_cast<int>(chm.value("y").toInteger());
|
||||||
|
ch->loadData(chm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ /* connections */ } {
|
||||||
|
QCborArray cn = m.value("connections").toArray();
|
||||||
|
|
||||||
|
auto pmt = QMetaType::metaObjectForType(QMetaType::type("Xybrid::Data::Port"));
|
||||||
|
auto dtm = pmt->enumerator(pmt->indexOfEnumerator("DataType"));
|
||||||
|
|
||||||
|
for (auto cv : cn) {
|
||||||
|
auto c = cv.toArray();
|
||||||
|
if (c.empty()) continue;
|
||||||
|
auto on = children[static_cast<size_t>(c[0].toInteger())];
|
||||||
|
auto in = children[static_cast<size_t>(c[3].toInteger())];
|
||||||
|
auto op = on->port(Port::Output, static_cast<Port::DataType>(dtm.keyToValue(c[1].toString().toStdString().c_str())), static_cast<uint8_t>(c[2].toInteger()));
|
||||||
|
auto ip = in->port(Port::Input, static_cast<Port::DataType>(dtm.keyToValue(c[4].toString().toStdString().c_str())), static_cast<uint8_t>(c[5].toInteger()));
|
||||||
|
if (op && ip) op->connect(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Graph::onParent(std::shared_ptr<Graph>) {
|
||||||
|
// propagate project pointer
|
||||||
|
for (auto c : children) {
|
||||||
|
c->project = project;
|
||||||
|
// let this handle the recursion for us, since this is all this function does
|
||||||
|
if (c->plugin == inf) c->onParent(c->parent.lock());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Graph::onDoubleClick() {
|
||||||
|
emit project->socket->openGraph(this);
|
||||||
|
}
|
||||||
|
|
|
@ -5,11 +5,19 @@
|
||||||
namespace Xybrid::Data {
|
namespace Xybrid::Data {
|
||||||
class Graph : public Node {
|
class Graph : public Node {
|
||||||
public:
|
public:
|
||||||
|
Graph();
|
||||||
|
~Graph() override = default;
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Node>> children;
|
std::vector<std::shared_ptr<Node>> children;
|
||||||
|
|
||||||
// position of viewport within graph (not serialized)
|
// position of viewport within graph (not serialized)
|
||||||
int viewX{}, viewY{};
|
int viewX{}, viewY{};
|
||||||
|
|
||||||
|
void saveData(QCborMap&) override;
|
||||||
|
void loadData(QCborMap&) override;
|
||||||
|
|
||||||
//std::string pluginName() const override;
|
//std::string pluginName() const override;
|
||||||
|
void onParent(std::shared_ptr<Graph>) override;
|
||||||
|
void onDoubleClick() override;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ bool Port::connect(std::shared_ptr<Port> p) {
|
||||||
if (!canConnectTo(p->dataType())) return false; // can't hook up to an incompatible data type
|
if (!canConnectTo(p->dataType())) return false; // can't hook up to an incompatible data type
|
||||||
for (auto c : connections) if (c.lock() == p) return true; // I guess report success if already connected?
|
for (auto c : connections) if (c.lock() == p) return true; // I guess report success if already connected?
|
||||||
if (singleInput() && connections.size() > 0) return false; // reject multiple connections on single-input ports
|
if (singleInput() && connections.size() > 0) return false; // reject multiple connections on single-input ports
|
||||||
|
if (auto o = owner.lock(), po = p->owner.lock(); !o || !po || po->dependsOn(o)) return false; // no dependency loops!
|
||||||
// actually hook up
|
// actually hook up
|
||||||
connections.emplace_back(p);
|
connections.emplace_back(p);
|
||||||
p->connections.emplace_back(shared_from_this());
|
p->connections.emplace_back(shared_from_this());
|
||||||
|
@ -56,11 +57,14 @@ std::shared_ptr<Port> Port::makePort(DataType dt) {
|
||||||
void Node::parentTo(std::shared_ptr<Graph> graph) {
|
void Node::parentTo(std::shared_ptr<Graph> graph) {
|
||||||
auto t = shared_from_this(); // keep alive during reparenting
|
auto t = shared_from_this(); // keep alive during reparenting
|
||||||
if (auto p = parent.lock(); p) {
|
if (auto p = parent.lock(); p) {
|
||||||
|
onUnparent(p);
|
||||||
p->children.erase(std::remove(p->children.begin(), p->children.end(), t), p->children.end());
|
p->children.erase(std::remove(p->children.begin(), p->children.end(), t), p->children.end());
|
||||||
}
|
}
|
||||||
parent = graph;
|
parent = graph;
|
||||||
if (graph) {
|
if (graph) {
|
||||||
|
project = graph->project;
|
||||||
graph->children.push_back(t);
|
graph->children.push_back(t);
|
||||||
|
onParent(graph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +82,7 @@ std::shared_ptr<Port> Node::addPort(Port::Type t, Port::DataType dt, uint8_t idx
|
||||||
auto mdt = m.find(dt);
|
auto mdt = m.find(dt);
|
||||||
if (mdt->second.find(idx) == mdt->second.end()) {
|
if (mdt->second.find(idx) == mdt->second.end()) {
|
||||||
auto p = Port::makePort(dt);
|
auto p = Port::makePort(dt);
|
||||||
|
p->owner = shared_from_this();
|
||||||
p->type = t;
|
p->type = t;
|
||||||
mdt->second.insert({idx, p});
|
mdt->second.insert({idx, p});
|
||||||
p->index = idx;
|
p->index = idx;
|
||||||
|
@ -93,4 +98,26 @@ void Node::removePort(Port::Type t, Port::DataType dt, uint8_t idx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unordered_set<std::shared_ptr<Node>> Node::dependencies() const {
|
||||||
|
std::unordered_set<std::shared_ptr<Node>> set;
|
||||||
|
for (auto& t : inputs) {
|
||||||
|
for (auto& p : t.second) {
|
||||||
|
for (auto& c : p.second->connections) {
|
||||||
|
// if connection still exists, *and its owner* still exists...
|
||||||
|
if (auto cp = c.lock(); cp) if (auto n = cp->owner.lock(); n) set.insert(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Node::dependsOn(std::shared_ptr<Node> o) {
|
||||||
|
auto deps = dependencies();
|
||||||
|
for (auto d : deps) {
|
||||||
|
if (d == o) return true;
|
||||||
|
if (d->dependsOn(o)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::string Node::pluginName() const { if (!plugin) return "(unknown plugin)"; return plugin->displayName; }
|
std::string Node::pluginName() const { if (!plugin) return "(unknown plugin)"; return plugin->displayName; }
|
||||||
|
|
|
@ -3,11 +3,17 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class QPainter;
|
||||||
|
class QStyleOptionGraphicsItem;
|
||||||
|
|
||||||
namespace Xybrid::UI {
|
namespace Xybrid::UI {
|
||||||
|
class NodeObject;
|
||||||
class PortObject;
|
class PortObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,20 +22,25 @@ namespace Xybrid::Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Xybrid::Data {
|
namespace Xybrid::Data {
|
||||||
|
class Project;
|
||||||
|
|
||||||
class Graph;
|
class Graph;
|
||||||
class Node;
|
class Node;
|
||||||
|
|
||||||
class Port : public std::enable_shared_from_this<Port> {
|
class Port : public std::enable_shared_from_this<Port> {
|
||||||
|
Q_GADGET
|
||||||
public:
|
public:
|
||||||
enum Type : uint8_t {
|
enum Type : uint8_t {
|
||||||
Input, Output
|
Input, Output
|
||||||
};
|
};
|
||||||
|
Q_ENUM(Type)
|
||||||
enum DataType : uint8_t {
|
enum DataType : uint8_t {
|
||||||
Audio, Command, MIDI, Parameter
|
Audio, Command, MIDI, Parameter
|
||||||
};
|
};
|
||||||
|
Q_ENUM(DataType)
|
||||||
std::weak_ptr<Node> owner;
|
std::weak_ptr<Node> owner;
|
||||||
std::vector<std::weak_ptr<Port>> connections;
|
std::vector<std::weak_ptr<Port>> connections;
|
||||||
std::weak_ptr<Port> passthroughFor;
|
std::weak_ptr<Port> passthroughTo;
|
||||||
Type type; // TODO: figure out passthrough?
|
Type type; // TODO: figure out passthrough?
|
||||||
uint8_t index;
|
uint8_t index;
|
||||||
size_t tickUpdatedOn = static_cast<size_t>(-1);
|
size_t tickUpdatedOn = static_cast<size_t>(-1);
|
||||||
|
@ -54,6 +65,7 @@ namespace Xybrid::Data {
|
||||||
|
|
||||||
class Node : public std::enable_shared_from_this<Node> {
|
class Node : public std::enable_shared_from_this<Node> {
|
||||||
public:
|
public:
|
||||||
|
Project* project;
|
||||||
std::weak_ptr<Graph> parent;
|
std::weak_ptr<Graph> parent;
|
||||||
int x{}, y{};
|
int x{}, y{};
|
||||||
std::string name;
|
std::string name;
|
||||||
|
@ -62,6 +74,8 @@ namespace Xybrid::Data {
|
||||||
|
|
||||||
std::shared_ptr<Config::PluginInfo> plugin;
|
std::shared_ptr<Config::PluginInfo> plugin;
|
||||||
|
|
||||||
|
QPointer<UI::NodeObject> obj;
|
||||||
|
|
||||||
virtual ~Node() = default;
|
virtual ~Node() = default;
|
||||||
|
|
||||||
void parentTo(std::shared_ptr<Graph>);
|
void parentTo(std::shared_ptr<Graph>);
|
||||||
|
@ -70,7 +84,25 @@ namespace Xybrid::Data {
|
||||||
std::shared_ptr<Port> addPort(Port::Type, Port::DataType, uint8_t);
|
std::shared_ptr<Port> addPort(Port::Type, Port::DataType, uint8_t);
|
||||||
void removePort(Port::Type, Port::DataType, uint8_t);
|
void removePort(Port::Type, Port::DataType, uint8_t);
|
||||||
|
|
||||||
|
std::unordered_set<std::shared_ptr<Node>> dependencies() const;
|
||||||
|
bool dependsOn(std::shared_ptr<Node>);
|
||||||
|
|
||||||
|
virtual void saveData(QCborMap&) { }
|
||||||
|
virtual void loadData(QCborMap&) { }
|
||||||
|
|
||||||
virtual void process() { }
|
virtual void process() { }
|
||||||
virtual std::string pluginName() const;
|
virtual std::string pluginName() const;
|
||||||
|
|
||||||
|
virtual void onGadgetCreated() { }
|
||||||
|
virtual void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) { }
|
||||||
|
virtual void onRename() { }
|
||||||
|
|
||||||
|
virtual void onUnparent(std::shared_ptr<Graph>) { }
|
||||||
|
virtual void onParent(std::shared_ptr<Graph>) { }
|
||||||
|
|
||||||
|
virtual void onDoubleClick() { }
|
||||||
|
// something something customChrome?
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Q_DECLARE_METATYPE(Xybrid::Data::Port)
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
#include "porttypes.h"
|
||||||
|
using namespace Xybrid::Data;
|
||||||
|
|
||||||
|
#include "audio/audioengine.h"
|
||||||
|
using namespace Xybrid::Audio;
|
||||||
|
|
||||||
|
|
||||||
|
void AudioPort::pull() {
|
||||||
|
auto t = audioEngine->curTickId();
|
||||||
|
if (tickUpdatedOn == t) return;
|
||||||
|
tickUpdatedOn = t;
|
||||||
|
|
||||||
|
size_t ts = audioEngine->curTickSize();
|
||||||
|
size_t s = sizeof(float) * ts;
|
||||||
|
|
||||||
|
if (type == Input) {
|
||||||
|
bufL = static_cast<float*>(audioEngine->tickAlloc(s*2));
|
||||||
|
bufR = bufL + s;
|
||||||
|
memset(bufL, 0, s*2); // clear buffers
|
||||||
|
|
||||||
|
for (auto c : connections) { // mix
|
||||||
|
if (auto p = std::static_pointer_cast<AudioPort>(c.lock()); p && p->dataType() == Audio) {
|
||||||
|
p->pull();
|
||||||
|
for (size_t i = 0; i < ts; i++) {
|
||||||
|
bufL[i] += p->bufL[i];
|
||||||
|
bufR[i] += p->bufR[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (auto pt = std::static_pointer_cast<AudioPort>(passthroughTo.lock()); pt && pt->dataType() == Audio) {
|
||||||
|
// passthrough; ports abound
|
||||||
|
pt->pull();
|
||||||
|
bufL = pt->bufL;
|
||||||
|
bufR = pt->bufR;
|
||||||
|
} else { // output without valid passthrough, just clear and prepare a blank buffer
|
||||||
|
bufL = static_cast<float*>(audioEngine->tickAlloc(s*2));
|
||||||
|
bufR = bufL + s;
|
||||||
|
memset(bufL, 0, s*2); // clear buffers
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,18 +6,27 @@ namespace Xybrid::Data {
|
||||||
class AudioPort : public Port {
|
class AudioPort : public Port {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
float* bufL;
|
||||||
|
float* bufR;
|
||||||
|
|
||||||
AudioPort() = default;
|
AudioPort() = default;
|
||||||
~AudioPort() override = default;
|
~AudioPort() override = default;
|
||||||
|
|
||||||
Port::DataType dataType() const override { return Port::Audio; }
|
Port::DataType dataType() const override { return Port::Audio; }
|
||||||
|
|
||||||
|
void pull() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CommandPort : public Port {
|
class CommandPort : public Port {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
uint8_t* data;
|
||||||
|
size_t dataSize;
|
||||||
|
|
||||||
CommandPort() = default;
|
CommandPort() = default;
|
||||||
~CommandPort() override = default;
|
~CommandPort() override = default;
|
||||||
|
|
||||||
Port::DataType dataType() const override { return Port::Command; }
|
Port::DataType dataType() const override { return Port::Command; }
|
||||||
|
bool singleInput() const override { return true; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ using namespace Xybrid::Audio;
|
||||||
|
|
||||||
Project::Project() {
|
Project::Project() {
|
||||||
rootGraph = std::make_shared<Graph>();
|
rootGraph = std::make_shared<Graph>();
|
||||||
|
rootGraph->project = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Project::~Project() {
|
Project::~Project() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "fileops.h"
|
#include "fileops.h"
|
||||||
|
|
||||||
#include "uisocket.h"
|
#include "uisocket.h"
|
||||||
|
#include "data/graph.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
|
@ -95,6 +96,12 @@ bool Xybrid::FileOps::saveProject(std::shared_ptr<Project> project, QString file
|
||||||
main.insert(QString("patterns"), ptns);
|
main.insert(QString("patterns"), ptns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ /* Graph */ } {
|
||||||
|
QCborMap g;
|
||||||
|
project->rootGraph->saveData(g);
|
||||||
|
main.insert(QString("graph"), g);
|
||||||
|
}
|
||||||
|
|
||||||
root << main;
|
root << main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,5 +194,10 @@ std::shared_ptr<Xybrid::Data::Project> Xybrid::FileOps::loadProject(QString file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ /* Graph */ } {
|
||||||
|
QCborMap g = main.value("graph").toMap();
|
||||||
|
if (!g.isEmpty()) project->rootGraph->loadData(g);
|
||||||
|
}
|
||||||
|
|
||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
#include "ioport.h"
|
||||||
|
using Xybrid::Gadgets::IOPort;
|
||||||
|
using namespace Xybrid::Data;
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QGraphicsScene>
|
||||||
|
#include <QStyleOptionGraphicsItem>
|
||||||
|
|
||||||
|
#include <QCborMap>
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QMetaEnum>
|
||||||
|
|
||||||
|
#include "config/pluginregistry.h"
|
||||||
|
using namespace Xybrid::Config;
|
||||||
|
|
||||||
|
#include "data/graph.h"
|
||||||
|
|
||||||
|
#include "ui/patchboard/nodeobject.h"
|
||||||
|
using namespace Xybrid::UI;
|
||||||
|
|
||||||
|
#include "util/strings.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool _ = PluginRegistry::enqueueRegistration([] {
|
||||||
|
auto i = std::make_shared<PluginInfo>();
|
||||||
|
i->id = "ioport";
|
||||||
|
i->displayName = "I/O Port";
|
||||||
|
i->hidden = true;
|
||||||
|
i->createInstance = []{ return std::make_shared<IOPort>(); };
|
||||||
|
PluginRegistry::registerPlugin(i);
|
||||||
|
//inf = i;
|
||||||
|
});
|
||||||
|
|
||||||
|
Port::Type opposite(Port::Type t) {
|
||||||
|
if (t == Port::Input) return Port::Output;
|
||||||
|
return Port::Input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IOPort::IOPort() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOPort::remove() {
|
||||||
|
auto g = parent.lock();
|
||||||
|
if (!g) return;
|
||||||
|
if (!portSet) return;
|
||||||
|
g->removePort(type, dataType, index);
|
||||||
|
removePort(opposite(type), dataType, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOPort::add() {
|
||||||
|
auto g = parent.lock();
|
||||||
|
if (!g) return;
|
||||||
|
if (!portSet) return;
|
||||||
|
auto gp = g->addPort(type, dataType, index);
|
||||||
|
auto p = addPort(opposite(type), dataType, index);
|
||||||
|
if (gp && p) {
|
||||||
|
p->name = name;
|
||||||
|
gp->name = name;
|
||||||
|
p->passthroughTo = gp;
|
||||||
|
gp->passthroughTo = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOPort::setPort(Port::Type type, Port::DataType dataType, uint8_t index) {
|
||||||
|
remove();
|
||||||
|
this->type = type;
|
||||||
|
this->dataType = dataType;
|
||||||
|
this->index = index;
|
||||||
|
portSet = true;
|
||||||
|
add();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOPort::onRename() {
|
||||||
|
auto g = parent.lock();
|
||||||
|
if (!g) return;
|
||||||
|
if (!portSet) return;
|
||||||
|
auto gp = g->port(type, dataType, index);
|
||||||
|
auto p = port(opposite(type), dataType, index);
|
||||||
|
if (gp && p) {
|
||||||
|
p->name = name;
|
||||||
|
gp->name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOPort::saveData(QCborMap& m) {
|
||||||
|
QCborMap pm;
|
||||||
|
pm.insert(QString("type"), Util::enumName(type));
|
||||||
|
pm.insert(QString("dataType"), Util::enumName(dataType));
|
||||||
|
pm.insert(QString("index"), index);
|
||||||
|
m.insert(QString("port"), pm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOPort::loadData(QCborMap& m) {
|
||||||
|
auto pm = m.value("port").toMap();
|
||||||
|
if (pm.empty()) return;
|
||||||
|
auto pmt = QMetaType::metaObjectForType(QMetaType::type("Xybrid::Data::Port"));
|
||||||
|
auto tm = pmt->enumerator(pmt->indexOfEnumerator("Type"));
|
||||||
|
auto dtm = pmt->enumerator(pmt->indexOfEnumerator("DataType"));
|
||||||
|
std::string st = pm.value("type").toString().toStdString();
|
||||||
|
std::string sdt = pm.value("dataType").toString().toStdString();
|
||||||
|
setPort(
|
||||||
|
static_cast<Port::Type>(tm.keyToValue(st.c_str())),
|
||||||
|
static_cast<Port::DataType>(dtm.keyToValue(sdt.c_str())),
|
||||||
|
static_cast<uint8_t>(pm.value("index").toInteger())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOPort::onUnparent(std::shared_ptr<Graph>) { remove(); }
|
||||||
|
void IOPort::onParent(std::shared_ptr<Graph>) { add(); }
|
||||||
|
|
||||||
|
void IOPort::onGadgetCreated() {
|
||||||
|
if (!obj) return;
|
||||||
|
|
||||||
|
obj->customChrome = true;
|
||||||
|
qreal ps = (PortObject::portSize + PortObject::portSpacing) * 2;
|
||||||
|
obj->setGadgetSize(QPointF(ps, ps));
|
||||||
|
|
||||||
|
// do this after setting size
|
||||||
|
auto r = obj->boundingRect();
|
||||||
|
obj->inputPortContainer->setPos(r.center());
|
||||||
|
obj->outputPortContainer->setPos(r.center());
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOPort::drawCustomChrome(QPainter* painter, const QStyleOptionGraphicsItem* opt) {
|
||||||
|
QColor outline = QColor(31, 31, 31);
|
||||||
|
if (opt->state & QStyle::State_Selected) outline = QColor(127, 127, 255);
|
||||||
|
|
||||||
|
auto r = obj->boundingRect();
|
||||||
|
auto rf = r - QMarginsF(1, 1, 1, 1);
|
||||||
|
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
int ro = 180*16 * (1-type);
|
||||||
|
|
||||||
|
painter->setBrush(QColor(63, 63, 63));
|
||||||
|
painter->drawPie(rf, (90+45)*16 + ro, -90*3*16);
|
||||||
|
painter->setBrush(QColor(95, 95, 95));
|
||||||
|
painter->drawPie(rf, (90+45)*16 + 180*16 + ro, 90*16);
|
||||||
|
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
painter->setPen(QPen(outline, 2));
|
||||||
|
painter->drawPie(r, (90+45)*16 + ro, -90*3*16);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/node.h"
|
||||||
|
|
||||||
|
namespace Xybrid::Gadgets {
|
||||||
|
class IOPort : public Data::Node {
|
||||||
|
bool portSet = false;
|
||||||
|
Data::Port::Type type;
|
||||||
|
Data::Port::DataType dataType;
|
||||||
|
uint8_t index;
|
||||||
|
|
||||||
|
void remove();
|
||||||
|
void add();
|
||||||
|
public:
|
||||||
|
IOPort();
|
||||||
|
~IOPort() override = default;
|
||||||
|
|
||||||
|
void setPort(Data::Port::Type type, Data::Port::DataType dataType, uint8_t index);
|
||||||
|
|
||||||
|
void onRename() override;
|
||||||
|
|
||||||
|
void saveData(QCborMap&) override;
|
||||||
|
void loadData(QCborMap&) override;
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "audio/audioengine.h"
|
#include "audio/audioengine.h"
|
||||||
#include "config/pluginregistry.h"
|
#include "config/pluginregistry.h"
|
||||||
|
#include "data/graph.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -10,6 +11,8 @@
|
||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
qRegisterMetaType<Xybrid::Data::Port>();
|
||||||
|
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
// enable antialiasing on accelerated graphicsview
|
// enable antialiasing on accelerated graphicsview
|
||||||
|
|
|
@ -13,10 +13,13 @@ using Xybrid::MainWindow;
|
||||||
#include <QUndoStack>
|
#include <QUndoStack>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QOpenGLWidget>
|
#include <QOpenGLWidget>
|
||||||
|
#include <QGLWidget>
|
||||||
|
|
||||||
#include <QScroller>
|
#include <QScroller>
|
||||||
#include <QGraphicsTextItem>
|
#include <QGraphicsTextItem>
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "data/graph.h"
|
#include "data/graph.h"
|
||||||
|
|
||||||
#include "util/strings.h"
|
#include "util/strings.h"
|
||||||
|
@ -128,6 +131,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
(new ProjectPatternDeleteCommand(project, p))->commit();
|
(new ProjectPatternDeleteCommand(project, p))->commit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
menu->popup(ui->patternList->mapToGlobal(pt));
|
menu->popup(ui->patternList->mapToGlobal(pt));
|
||||||
});//*/
|
});//*/
|
||||||
}
|
}
|
||||||
|
@ -187,6 +191,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
menu->popup(ui->patternSequencer->mapToGlobal(pt));
|
menu->popup(ui->patternSequencer->mapToGlobal(pt));
|
||||||
});//*/
|
});//*/
|
||||||
}
|
}
|
||||||
|
@ -213,7 +218,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
});
|
});
|
||||||
|
|
||||||
// TEMP - play/stop
|
// TEMP - play/stop
|
||||||
connect(new QShortcut(QKeySequence("Ctrl+P"), ui->pattern), &QShortcut::activated, this, [this]() {
|
connect(new QShortcut(QKeySequence("Ctrl+P"), this), &QShortcut::activated, this, [this]() {
|
||||||
if (audioEngine->playbackMode() == AudioEngine::Playing) audioEngine->stop();
|
if (audioEngine->playbackMode() == AudioEngine::Playing) audioEngine->stop();
|
||||||
else audioEngine->play(project);
|
else audioEngine->play(project);
|
||||||
});
|
});
|
||||||
|
@ -235,8 +240,16 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
//ui->patchboardView->setDragMode(QGraphicsView::DragMode::RubberBandDrag);
|
//ui->patchboardView->setDragMode(QGraphicsView::DragMode::RubberBandDrag);
|
||||||
auto* view = ui->patchboardView;
|
auto* view = ui->patchboardView;
|
||||||
|
|
||||||
view->setViewport(new QOpenGLWidget); // enable hardware acceleration
|
bool enableHWAccel = false; // disabled because QOpenGLWidget has some huge lag issues in this context
|
||||||
|
if (enableHWAccel) {
|
||||||
|
auto* vp = new QOpenGLWidget();
|
||||||
|
view->setViewport(vp); // enable hardware acceleration
|
||||||
|
}
|
||||||
view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing);
|
view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing);
|
||||||
|
glEnable(GL_MULTISAMPLE);
|
||||||
|
glEnable(GL_LINE_SMOOTH);
|
||||||
|
//QGL::FormatOption::Rgba
|
||||||
|
|
||||||
|
|
||||||
view->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
view->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||||
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
@ -258,20 +271,17 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
if (e->type() == QEvent::MouseButtonPress) {
|
if (e->type() == QEvent::MouseButtonPress) {
|
||||||
auto* me = static_cast<QMouseEvent*>(e);
|
auto* me = static_cast<QMouseEvent*>(e);
|
||||||
// initiate drag
|
// initiate drag
|
||||||
if (me->button() == Qt::LeftButton) view->setDragMode(QGraphicsView::RubberBandDrag);
|
if (me->button() == Qt::LeftButton) {
|
||||||
|
view->setDragMode(QGraphicsView::RubberBandDrag);
|
||||||
|
}
|
||||||
} else if (e->type() == QEvent::MouseButtonRelease) { // disable drag after end
|
} else if (e->type() == QEvent::MouseButtonRelease) { // disable drag after end
|
||||||
QTimer::singleShot(1, [view] { view->setDragMode(QGraphicsView::NoDrag); });
|
QTimer::singleShot(1, [view] {
|
||||||
|
view->setDragMode(QGraphicsView::NoDrag);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return w->QObject::eventFilter(w, e);
|
return w->QObject::eventFilter(w, e);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
//view->setContextMenuPolicy(Qt::ContextMenuPolicy::NoContextMenu);
|
|
||||||
|
|
||||||
/*connect(view, &QGraphicsView::customContextMenuRequested, this, [this, view] {
|
|
||||||
qDebug() << "context";
|
|
||||||
//view->viewport->visibleRegion().boundingRect().topLeft()
|
|
||||||
});*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up signaling from project to UI
|
// Set up signaling from project to UI
|
||||||
|
@ -291,6 +301,15 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
emit ui->patternEditor->model()->dataChanged(ind, ind.siblingAtColumn((ch+1)*cpc-1));
|
emit ui->patternEditor->model()->dataChanged(ind, ind.siblingAtColumn((ch+1)*cpc-1));
|
||||||
static_cast<PatternEditorModel*>(ui->patternEditor->model())->updateColumnDisplay();
|
static_cast<PatternEditorModel*>(ui->patternEditor->model())->updateColumnDisplay();
|
||||||
});
|
});
|
||||||
|
connect(socket, &UISocket::openGraph, this, [this](Graph* g) {
|
||||||
|
if (!g) return;
|
||||||
|
auto gg = std::static_pointer_cast<Graph>(g->shared_from_this());
|
||||||
|
QString name = QString::fromStdString(gg->name);
|
||||||
|
if (name.isEmpty()) name = QString::fromStdString(gg->pluginName());
|
||||||
|
ui->patchboardBreadcrumbs->push(name, this, [this, gg] {
|
||||||
|
openGraph(gg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// and from audio engine
|
// and from audio engine
|
||||||
connect(audioEngine, &AudioEngine::playbackModeChanged, this, [this, undoAction, redoAction]() {
|
connect(audioEngine, &AudioEngine::playbackModeChanged, this, [this, undoAction, redoAction]() {
|
||||||
|
@ -301,6 +320,10 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
|
|
||||||
// and start with a new project
|
// and start with a new project
|
||||||
menuFileNew();
|
menuFileNew();
|
||||||
|
|
||||||
|
//auto q = QJsonObject();
|
||||||
|
//q.insert(QMetaObject::, "frenk");
|
||||||
|
qDebug() << QVariant::fromValue(Data::Port::Audio).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow() {
|
MainWindow::~MainWindow() {
|
||||||
|
@ -328,24 +351,6 @@ void MainWindow::menuFileNew() {
|
||||||
project = std::make_shared<Project>();
|
project = std::make_shared<Project>();
|
||||||
project->sequence.push_back(project->newPattern().get());
|
project->sequence.push_back(project->newPattern().get());
|
||||||
|
|
||||||
// TEMP add some stuff
|
|
||||||
{
|
|
||||||
auto g1 = PluginRegistry::createInstance("graph");//std::make_shared<Graph>();
|
|
||||||
g1->parentTo(project->rootGraph);
|
|
||||||
g1->x = 64;
|
|
||||||
g1->y = 64;
|
|
||||||
g1->addPort(Port::Input, Port::Command, 0);
|
|
||||||
|
|
||||||
auto g2 = PluginRegistry::createInstance("graph");
|
|
||||||
g2->parentTo(project->rootGraph);
|
|
||||||
g2->x = 444;
|
|
||||||
g2->y = 22;
|
|
||||||
g1->addPort(Port::Output, Port::Audio, 0)->connect(g2->addPort(Port::Input, Port::Audio, 0));
|
|
||||||
g2->addPort(Port::Input, Port::Audio, 1);
|
|
||||||
g2->addPort(Port::Input, Port::Audio, 2)->name = "Named port";
|
|
||||||
}
|
|
||||||
//
|
|
||||||
|
|
||||||
onNewProjectLoaded();
|
onNewProjectLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,6 +371,7 @@ void MainWindow::menuFileSave() {
|
||||||
if (project->fileName.isEmpty()) menuFileSaveAs();
|
if (project->fileName.isEmpty()) menuFileSaveAs();
|
||||||
else {
|
else {
|
||||||
FileOps::saveProject(project);
|
FileOps::saveProject(project);
|
||||||
|
undoStack->setClean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,6 +379,8 @@ void MainWindow::menuFileSaveAs() {
|
||||||
auto fileName = QFileDialog::getSaveFileName(this, "Save project as...", QString(), projectFilter);
|
auto fileName = QFileDialog::getSaveFileName(this, "Save project as...", QString(), projectFilter);
|
||||||
if (fileName.isEmpty()) return; // canceled
|
if (fileName.isEmpty()) return; // canceled
|
||||||
FileOps::saveProject(project, fileName);
|
FileOps::saveProject(project, fileName);
|
||||||
|
undoStack->setClean();
|
||||||
|
updateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onNewProjectLoaded() {
|
void MainWindow::onNewProjectLoaded() {
|
||||||
|
@ -388,7 +396,11 @@ void MainWindow::onNewProjectLoaded() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
openGraph(project->rootGraph);
|
//openGraph(project->rootGraph);
|
||||||
|
ui->patchboardBreadcrumbs->clear();
|
||||||
|
ui->patchboardBreadcrumbs->push("/", this, [this, gg = project->rootGraph] {
|
||||||
|
openGraph(gg);
|
||||||
|
});
|
||||||
|
|
||||||
updateTitle();
|
updateTitle();
|
||||||
}
|
}
|
||||||
|
@ -450,5 +462,8 @@ bool MainWindow::selectPatternForEditing(Pattern* pattern) {
|
||||||
|
|
||||||
void MainWindow::openGraph(const std::shared_ptr<Data::Graph>& g) {
|
void MainWindow::openGraph(const std::shared_ptr<Data::Graph>& g) {
|
||||||
if (!g) return; // invalid
|
if (!g) return; // invalid
|
||||||
|
QPointF scrollPt(g->viewX, g->viewY);
|
||||||
ui->patchboardView->setScene(new PatchboardScene(ui->patchboardView, g));
|
ui->patchboardView->setScene(new PatchboardScene(ui->patchboardView, g));
|
||||||
|
QScroller::scroller(ui->patchboardView)->scrollTo(scrollPt, 0);
|
||||||
|
QScroller::scroller(ui->patchboardView)->scrollTo(scrollPt, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Ui {
|
||||||
|
|
||||||
namespace Xybrid {
|
namespace Xybrid {
|
||||||
class MainWindow : public QMainWindow {
|
class MainWindow : public QMainWindow {
|
||||||
|
friend class Data::Graph;
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>16777215</width>
|
<width>16777215</width>
|
||||||
<height>24</height>
|
<height>28</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="font">
|
<property name="font">
|
||||||
|
@ -248,10 +248,23 @@
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="Xybrid::UI::BreadcrumbView" name="patchboardBreadcrumbs">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>28</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::NoFocus</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGraphicsView" name="patchboardView">
|
<widget class="QGraphicsView" name="patchboardView">
|
||||||
<property name="viewportUpdateMode">
|
<property name="viewportUpdateMode">
|
||||||
<enum>QGraphicsView::FullViewportUpdate</enum>
|
<enum>QGraphicsView::MinimalViewportUpdate</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -372,6 +385,11 @@
|
||||||
<extends>QTableView</extends>
|
<extends>QTableView</extends>
|
||||||
<header>ui/patterneditorview.h</header>
|
<header>ui/patterneditorview.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>Xybrid::UI::BreadcrumbView</class>
|
||||||
|
<extends>QTableView</extends>
|
||||||
|
<header>ui/breadcrumbview.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="res/resources.qrc"/>
|
<include location="res/resources.qrc"/>
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
#include "breadcrumbview.h"
|
||||||
|
using namespace Xybrid::UI;
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QHeaderView>
|
||||||
|
|
||||||
|
BreadcrumbView::BreadcrumbView(QWidget* parent) : QTableView (parent) {
|
||||||
|
mdl = new BreadcrumbModel(this);
|
||||||
|
setModel(mdl);
|
||||||
|
|
||||||
|
horizontalHeader()->setVisible(false);
|
||||||
|
verticalHeader()->setVisible(false);
|
||||||
|
horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||||
|
|
||||||
|
connect(selectionModel(), &QItemSelectionModel::currentColumnChanged, this, [this](const QModelIndex& index, const QModelIndex&) {
|
||||||
|
//qDebug() << "breadcrumb change" << index;
|
||||||
|
size_t idx = static_cast<size_t>(index.column());
|
||||||
|
if (mdl->actions.size() <= idx) return;
|
||||||
|
mdl->actions[idx]->trigger();
|
||||||
|
if (shrinkOnSelect) mdl->actions.resize(idx+1);
|
||||||
|
emit mdl->layoutChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void BreadcrumbView::clear() {
|
||||||
|
mdl->actions.clear();
|
||||||
|
emit mdl->layoutChanged();
|
||||||
|
this->setCurrentIndex(mdl->index(-1, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BreadcrumbView::push(const QString& text, QObject* bind, std::function<void ()> action) {
|
||||||
|
mdl->actions.resize(std::min(mdl->actions.size(), static_cast<size_t>(currentIndex().column()) + 1));
|
||||||
|
mdl->actions.push_back(std::make_unique<QAction>(text, bind));
|
||||||
|
emit mdl->layoutChanged();
|
||||||
|
connect(mdl->actions.back().get(), &QAction::triggered, bind, action);
|
||||||
|
this->setCurrentIndex(mdl->index(0, static_cast<int>(mdl->actions.size()) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
BreadcrumbModel::BreadcrumbModel(QWidget* parent) : QAbstractTableModel(parent) {
|
||||||
|
//actions.push_back(std::make_unique<QAction>("Root Graph", this));
|
||||||
|
}
|
||||||
|
|
||||||
|
int BreadcrumbModel::rowCount(const QModelIndex&) const {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BreadcrumbModel::columnCount(const QModelIndex&) const {
|
||||||
|
return static_cast<int>(actions.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant BreadcrumbModel::data(const QModelIndex& index, int role) const {
|
||||||
|
if (actions.size() <= static_cast<size_t>(index.column())) return QVariant(); // safety
|
||||||
|
if (role == Qt::DisplayRole) {
|
||||||
|
return actions[static_cast<size_t>(index.column())]->text();
|
||||||
|
}
|
||||||
|
if (role == Qt::TextAlignmentRole) return Qt::AlignHCenter + Qt::AlignVCenter;
|
||||||
|
return QVariant();
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QTableView>
|
||||||
|
#include <QAbstractTableModel>
|
||||||
|
|
||||||
|
namespace Xybrid::UI {
|
||||||
|
class BreadcrumbView;
|
||||||
|
class BreadcrumbModel : public QAbstractTableModel {
|
||||||
|
friend class BreadcrumbView;
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<QAction>> actions;
|
||||||
|
|
||||||
|
BreadcrumbModel(QWidget* parent = nullptr);
|
||||||
|
public:
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BreadcrumbView : public QTableView {
|
||||||
|
Q_OBJECT
|
||||||
|
BreadcrumbModel* mdl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool shrinkOnSelect = true;
|
||||||
|
|
||||||
|
BreadcrumbView(QWidget* parent = nullptr);
|
||||||
|
void clear();
|
||||||
|
void push(const QString& text, QObject* bind, std::function<void()> action);
|
||||||
|
};
|
||||||
|
}
|
|
@ -18,21 +18,17 @@ using Xybrid::Data::Port;
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QInputDialog>
|
||||||
|
|
||||||
|
#include "util/strings.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const constexpr qreal portSize [[maybe_unused]] = 10;
|
|
||||||
const constexpr qreal portSpacing [[maybe_unused]] = 3;
|
|
||||||
|
|
||||||
const QColor tcolor[] {
|
const QColor tcolor[] {
|
||||||
QColor(239, 179, 59), // Audio
|
QColor(239, 179, 59), // Audio
|
||||||
QColor(163, 95, 191), // Command
|
QColor(163, 95, 191), // Command
|
||||||
QColor(95, 191, 163), // MIDI
|
QColor(95, 191, 163), // MIDI
|
||||||
QColor(127, 127, 255), // Parameter
|
QColor(127, 127, 255), // Parameter
|
||||||
};
|
};
|
||||||
|
|
||||||
const QString tname[] {
|
|
||||||
"Audio", "Command", "MIDI", "Parameter"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortObject::connectTo(Xybrid::UI::PortObject* o) {
|
void PortObject::connectTo(Xybrid::UI::PortObject* o) {
|
||||||
|
@ -56,7 +52,7 @@ void PortObject::setHighlighted(bool h, bool hideLabel) {
|
||||||
|
|
||||||
bool lv = h && !hideLabel;
|
bool lv = h && !hideLabel;
|
||||||
if (lv) {
|
if (lv) {
|
||||||
QString txt = QString("%1 %2").arg(tname[port->dataType()].toLower()).arg(QString::number(port->index));
|
QString txt = QString("%1 %2").arg(Util::enumName(port->dataType()).toLower()).arg(Util::hex(port->index));
|
||||||
if (!port->name.empty()) txt = QString("%1 (%2)").arg(QString::fromStdString(port->name)).arg(txt);
|
if (!port->name.empty()) txt = QString("%1 (%2)").arg(QString::fromStdString(port->name)).arg(txt);
|
||||||
QColor c = tcolor[port->dataType()];
|
QColor c = tcolor[port->dataType()];
|
||||||
label->setText(txt);
|
label->setText(txt);
|
||||||
|
@ -93,6 +89,8 @@ PortObject::PortObject(const std::shared_ptr<Data::Port>& p) {
|
||||||
for (auto c : port->connections) {
|
for (auto c : port->connections) {
|
||||||
if (auto cc = c.lock(); cc && cc->obj) connectTo(cc->obj);
|
if (auto cc = c.lock(); cc && cc->obj) connectTo(cc->obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCursor(Qt::CursorShape::CrossCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
PortObject::~PortObject() {
|
PortObject::~PortObject() {
|
||||||
|
@ -100,7 +98,7 @@ PortObject::~PortObject() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortObject::mousePressEvent(QGraphicsSceneMouseEvent*) {
|
void PortObject::mousePressEvent(QGraphicsSceneMouseEvent*) {
|
||||||
setCursor(Qt::ClosedHandCursor);
|
//setCursor(Qt::CursorShape::CrossCursor);
|
||||||
setHighlighted(true, true);
|
setHighlighted(true, true);
|
||||||
dragLine.reset(new QGraphicsLineItem());
|
dragLine.reset(new QGraphicsLineItem());
|
||||||
dragLine->setPen(QPen(tcolor[port->dataType()].lighter(125), 1.5));
|
dragLine->setPen(QPen(tcolor[port->dataType()].lighter(125), 1.5));
|
||||||
|
@ -110,7 +108,7 @@ void PortObject::mousePressEvent(QGraphicsSceneMouseEvent*) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortObject::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) {
|
void PortObject::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) {
|
||||||
unsetCursor();
|
//unsetCursor();
|
||||||
dragLine.reset();
|
dragLine.reset();
|
||||||
|
|
||||||
auto* i = scene()->itemAt(e->scenePos(), QTransform());
|
auto* i = scene()->itemAt(e->scenePos(), QTransform());
|
||||||
|
@ -143,6 +141,7 @@ void PortObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
||||||
delete c;
|
delete c;
|
||||||
}
|
}
|
||||||
});//->setEnabled(this->connections.size() != 0);
|
});//->setEnabled(this->connections.size() != 0);
|
||||||
|
m->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
m->popup(e->screenPos());
|
m->popup(e->screenPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,24 +163,28 @@ QRectF PortObject::boundingRect() const {
|
||||||
|
|
||||||
NodeObject::NodeObject(const std::shared_ptr<Data::Node>& n) {
|
NodeObject::NodeObject(const std::shared_ptr<Data::Node>& n) {
|
||||||
node = n;
|
node = n;
|
||||||
|
node->obj = this;
|
||||||
|
|
||||||
setFlag(QGraphicsItem::ItemIsMovable);
|
setFlag(QGraphicsItem::ItemIsMovable);
|
||||||
setFlag(QGraphicsItem::ItemIsFocusable);
|
setFlag(QGraphicsItem::ItemIsFocusable);
|
||||||
setFlag(QGraphicsItem::ItemIsSelectable);
|
setFlag(QGraphicsItem::ItemIsSelectable);
|
||||||
setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
|
setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
|
||||||
|
|
||||||
/*auto* t = new QGraphicsTextItem(QString::fromStdString(n->name), this);
|
|
||||||
t->setPos(4, 4);
|
|
||||||
t->setFlag(QGraphicsItem::ItemIsSelectable, false);*/
|
|
||||||
//t->setPen(QPen(QColor(0, 0, 0), 0.25));
|
|
||||||
//t->setBrush(QBrush(QColor(255, 255, 255)));
|
|
||||||
|
|
||||||
setPos(node->x, node->y);
|
setPos(node->x, node->y);
|
||||||
|
|
||||||
connect(this, &QGraphicsObject::xChanged, this, &NodeObject::onMoved);
|
connect(this, &QGraphicsObject::xChanged, this, &NodeObject::onMoved);
|
||||||
connect(this, &QGraphicsObject::yChanged, this, &NodeObject::onMoved);
|
connect(this, &QGraphicsObject::yChanged, this, &NodeObject::onMoved);
|
||||||
|
|
||||||
|
//setToolTip(QString::fromStdString(node->name));
|
||||||
|
|
||||||
createPorts();
|
createPorts();
|
||||||
|
|
||||||
|
node->onGadgetCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeObject::setGadgetSize(QPointF p) {
|
||||||
|
gadgetSize_ = p;
|
||||||
|
updateGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeObject::promptDelete() {
|
void NodeObject::promptDelete() {
|
||||||
|
@ -193,9 +196,30 @@ void NodeObject::promptDelete() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NodeObject::mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) {
|
||||||
|
node->onDoubleClick();
|
||||||
|
}
|
||||||
|
|
||||||
void NodeObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
void NodeObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
||||||
|
if (!isSelected()) {
|
||||||
|
for (auto* s : scene()->selectedItems()) s->setSelected(false);
|
||||||
|
setSelected(true);
|
||||||
|
}
|
||||||
auto* m = new QMenu();
|
auto* m = new QMenu();
|
||||||
|
if (canRename) {
|
||||||
|
m->addAction("Rename...", this, [this] {
|
||||||
|
bool ok = false;
|
||||||
|
auto cn = node->name.empty() ? QString::fromStdString(node->pluginName()) : QString("\"%1\"").arg(QString::fromStdString(node->name));
|
||||||
|
auto capt = QString("Rename %1:").arg(cn);
|
||||||
|
auto n = QInputDialog::getText(nullptr, "Rename...", capt, QLineEdit::Normal, QString::fromStdString(node->name), &ok);
|
||||||
|
if (!ok) return; // canceled
|
||||||
|
//setToolTip(n);
|
||||||
|
node->name = n.toStdString();
|
||||||
|
node->onRename();
|
||||||
|
});
|
||||||
|
}
|
||||||
m->addAction("Delete node", this, &NodeObject::promptDelete);
|
m->addAction("Delete node", this, &NodeObject::promptDelete);
|
||||||
|
m->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
m->popup(e->screenPos());
|
m->popup(e->screenPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,35 +228,48 @@ void NodeObject::bringToTop(bool force) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeObject::createPorts() {
|
void NodeObject::createPorts() {
|
||||||
inputPortContainer.reset(new QGraphicsRectItem(this));
|
auto* ipc = new QGraphicsLineItem(this);
|
||||||
outputPortContainer.reset(new QGraphicsRectItem(this));
|
auto* opc = new QGraphicsLineItem(this);
|
||||||
|
inputPortContainer.reset(ipc);
|
||||||
|
outputPortContainer.reset(opc);
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
|
|
||||||
|
QPen p(QColor(95, 95, 95), 2.5);
|
||||||
|
QPointF inc(0, PortObject::portSize + PortObject::portSpacing);
|
||||||
|
|
||||||
QPointF cursor = QPointF(0, 0);
|
QPointF cursor = QPointF(0, 0);
|
||||||
for (auto mdt : node->inputs) {
|
for (auto mdt : node->inputs) {
|
||||||
for (auto pp : mdt.second) {
|
for (auto pp : mdt.second) {
|
||||||
auto* p = new PortObject(pp.second);
|
auto* p = new PortObject(pp.second);
|
||||||
p->setParentItem(inputPortContainer.get());
|
p->setParentItem(ipc);
|
||||||
p->setPos(cursor);
|
p->setPos(cursor);
|
||||||
cursor += QPointF(0, portSize + portSpacing);
|
cursor += inc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ipc->setVisible(cursor.y() > 0);
|
||||||
|
cursor -= inc;
|
||||||
|
ipc->setLine(QLineF(QPointF(0, 0), cursor));
|
||||||
|
ipc->setPen(p);
|
||||||
|
|
||||||
cursor = QPointF(0, 0);
|
cursor = QPointF(0, 0);
|
||||||
for (auto mdt : node->outputs) {
|
for (auto mdt : node->outputs) {
|
||||||
for (auto pp : mdt.second) {
|
for (auto pp : mdt.second) {
|
||||||
auto* p = new PortObject(pp.second);
|
auto* p = new PortObject(pp.second);
|
||||||
p->setParentItem(outputPortContainer.get());
|
p->setParentItem(opc);
|
||||||
p->setPos(cursor);
|
p->setPos(cursor);
|
||||||
cursor += QPointF(0, portSize + portSpacing);
|
cursor += inc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
opc->setVisible(cursor.y() > 0);
|
||||||
|
cursor -= inc;
|
||||||
|
opc->setLine(QLineF(QPointF(0, 0), cursor));
|
||||||
|
opc->setPen(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeObject::updateGeometry() {
|
void NodeObject::updateGeometry() {
|
||||||
if (inputPortContainer) inputPortContainer->setPos(QPointF(portSize * -.5 - portSpacing, portSize));
|
qreal pm = PortObject::portSize * .5 + PortObject::portSpacing;
|
||||||
if (outputPortContainer) outputPortContainer->setPos(QPointF(boundingRect().width() + portSize * .5 + portSpacing, portSize));
|
if (inputPortContainer) inputPortContainer->setPos(QPointF(-pm, PortObject::portSize));
|
||||||
|
if (outputPortContainer) outputPortContainer->setPos(QPointF(boundingRect().width() + pm, PortObject::portSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeObject::onMoved() {
|
void NodeObject::onMoved() {
|
||||||
|
@ -255,13 +292,16 @@ void NodeObject::focusInEvent(QFocusEvent *) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt, QWidget *) {
|
void NodeObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt, QWidget *) {
|
||||||
|
if (customChrome) {
|
||||||
|
node->drawCustomChrome(painter, opt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
QRectF r = boundingRect();
|
QRectF r = boundingRect();
|
||||||
|
|
||||||
QColor outline = QColor(31, 31, 31);
|
QColor outline = QColor(31, 31, 31);
|
||||||
if (opt->state & QStyle::State_Selected) outline = QColor(127, 127, 255);
|
if (opt->state & QStyle::State_Selected) outline = QColor(127, 127, 255);
|
||||||
|
|
||||||
QLinearGradient fill(QPointF(0, 0), QPointF(0, r.height()));
|
QLinearGradient fill(QPointF(0, 0), QPointF(0, r.height()));
|
||||||
//fill.setCoordinateMode(QLinearGradient::CoordinateMode::ObjectMode);
|
|
||||||
fill.setColorAt(0, QColor(95, 95, 95));
|
fill.setColorAt(0, QColor(95, 95, 95));
|
||||||
fill.setColorAt(16.0/r.height(), QColor(63, 63, 63));
|
fill.setColorAt(16.0/r.height(), QColor(63, 63, 63));
|
||||||
fill.setColorAt(1.0 - (1.0 - 16.0/r.height()) / 2, QColor(55, 55, 55));
|
fill.setColorAt(1.0 - (1.0 - 16.0/r.height()) / 2, QColor(55, 55, 55));
|
||||||
|
@ -283,7 +323,8 @@ void NodeObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt, Q
|
||||||
}
|
}
|
||||||
|
|
||||||
QRectF NodeObject::boundingRect() const {
|
QRectF NodeObject::boundingRect() const {
|
||||||
return QRectF(0, 0, 192, 48);// + QMarginsF(8, 8, 8, 8);
|
if (customChrome) return QRectF(QPointF(0, 0), gadgetSize_);
|
||||||
|
return QRectF(0, 0, 192, 36);// + QMarginsF(8, 8, 8, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
PortConnectionObject::PortConnectionObject(PortObject* in, PortObject* out) {
|
PortConnectionObject::PortConnectionObject(PortObject* in, PortObject* out) {
|
||||||
|
@ -362,8 +403,11 @@ QPainterPath PortConnectionObject::shape(qreal width) const {
|
||||||
auto end = mapFromScene(in->scenePos());
|
auto end = mapFromScene(in->scenePos());
|
||||||
path.moveTo(start);
|
path.moveTo(start);
|
||||||
//QPointF mod(std::max(std::max((end.x() - start.x()) * .64, (start.x() - end.x()) * .24), 64.0), 0);
|
//QPointF mod(std::max(std::max((end.x() - start.x()) * .64, (start.x() - end.x()) * .24), 64.0), 0);
|
||||||
|
QPointF out(0, 0);
|
||||||
|
//path.lineTo(start+out);
|
||||||
QPointF mod(std::max((end.x() - start.x()) * .64, 96.0), 0);
|
QPointF mod(std::max((end.x() - start.x()) * .64, 96.0), 0);
|
||||||
path.cubicTo(start + mod, end - mod, end);
|
path.cubicTo(start + out + mod, end - mod, end - out);
|
||||||
|
//path.lineTo(end);
|
||||||
|
|
||||||
if (width <= 0) return path;
|
if (width <= 0) return path;
|
||||||
QPainterPathStroker qp;
|
QPainterPathStroker qp;
|
||||||
|
|
|
@ -28,6 +28,9 @@ namespace Xybrid::UI {
|
||||||
enum { Type = UserType + 101 };
|
enum { Type = UserType + 101 };
|
||||||
int type() const override { return Type; }
|
int type() const override { return Type; }
|
||||||
|
|
||||||
|
static const constexpr qreal portSize = 10;
|
||||||
|
static const constexpr qreal portSpacing = 3;
|
||||||
|
|
||||||
PortObject(const std::shared_ptr<Data::Port>&);
|
PortObject(const std::shared_ptr<Data::Port>&);
|
||||||
~PortObject() override;
|
~PortObject() override;
|
||||||
|
|
||||||
|
@ -76,8 +79,8 @@ namespace Xybrid::UI {
|
||||||
friend class PortObject;
|
friend class PortObject;
|
||||||
|
|
||||||
std::shared_ptr<Data::Node> node;
|
std::shared_ptr<Data::Node> node;
|
||||||
std::unique_ptr<QGraphicsItem> inputPortContainer = nullptr;
|
|
||||||
std::unique_ptr<QGraphicsItem> outputPortContainer = nullptr;
|
QPointF gadgetSize_{0, 0};
|
||||||
|
|
||||||
void onMoved();
|
void onMoved();
|
||||||
void bringToTop(bool force = false);
|
void bringToTop(bool force = false);
|
||||||
|
@ -92,11 +95,22 @@ namespace Xybrid::UI {
|
||||||
enum { Type = UserType + 100 };
|
enum { Type = UserType + 100 };
|
||||||
int type() const override { return Type; }
|
int type() const override { return Type; }
|
||||||
|
|
||||||
|
std::unique_ptr<QGraphicsItem> inputPortContainer = nullptr;
|
||||||
|
std::unique_ptr<QGraphicsItem> outputPortContainer = nullptr;
|
||||||
|
|
||||||
|
bool customChrome = false;
|
||||||
|
bool canRename = true;
|
||||||
|
|
||||||
NodeObject(const std::shared_ptr<Data::Node>&);
|
NodeObject(const std::shared_ptr<Data::Node>&);
|
||||||
|
|
||||||
|
inline QPointF gadgetSize() const { return gadgetSize_; }
|
||||||
|
void setGadgetSize(QPointF);
|
||||||
|
inline void setGadgetSize(qreal w, qreal h) { setGadgetSize(QPointF(w, h)); }
|
||||||
|
|
||||||
inline const std::shared_ptr<Data::Node>& getNode() const { return node; }
|
inline const std::shared_ptr<Data::Node>& getNode() const { return node; }
|
||||||
void promptDelete();
|
void promptDelete();
|
||||||
|
|
||||||
|
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override;
|
||||||
void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;
|
void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;
|
||||||
|
|
||||||
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
|
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
|
||||||
|
|
|
@ -8,6 +8,7 @@ using Xybrid::UI::PatchboardScene;
|
||||||
#include <QGraphicsItem>
|
#include <QGraphicsItem>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include <QGraphicsSceneContextMenuEvent>
|
#include <QGraphicsSceneContextMenuEvent>
|
||||||
|
|
||||||
|
@ -24,34 +25,17 @@ PatchboardScene::PatchboardScene(QGraphicsView* parent, const std::shared_ptr<Xy
|
||||||
graph = g;
|
graph = g;
|
||||||
view = parent;
|
view = parent;
|
||||||
|
|
||||||
connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::autoSetSize);
|
connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::queueResize);
|
||||||
connect(view->verticalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::autoSetSize);
|
connect(view->verticalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::queueResize);
|
||||||
connect(view->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::autoSetSize);
|
connect(view->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::queueResize);
|
||||||
connect(view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::autoSetSize);
|
connect(view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::queueResize);
|
||||||
connect(this, &QGraphicsScene::changed, this, &PatchboardScene::autoSetSize);
|
connect(this, &QGraphicsScene::changed, this, &PatchboardScene::queueResize);
|
||||||
|
|
||||||
/*{
|
|
||||||
auto* t = addEllipse(0, 0, 64, 32);//scene->addText("hello world");
|
|
||||||
t->setBrush(QBrush(QColor(127,0,255)));
|
|
||||||
t->setFlag(QGraphicsItem::ItemIsMovable);
|
|
||||||
t->setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
|
|
||||||
t->setFlag(QGraphicsItem::ItemIsSelectable);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto* t = addText("Hi there!");
|
|
||||||
//t->setBrush(QBrush(QColor(191,127,255)));
|
|
||||||
t->setFlag(QGraphicsItem::ItemIsMovable);
|
|
||||||
t->setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
|
|
||||||
t->setFlag(QGraphicsItem::ItemIsSelectable);
|
|
||||||
t->stackBefore(nullptr);
|
|
||||||
}//*/
|
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PatchboardScene::drawBackground(QPainter* painter, const QRectF& rect) {
|
void PatchboardScene::drawBackground(QPainter* painter, const QRectF& rect) {
|
||||||
painter->setBrush(QBrush(Config::ColorScheme::current.patternBg));
|
painter->setBrush(QBrush(Config::colorScheme.patternBg));
|
||||||
painter->setPen(QPen(Qt::PenStyle::NoPen));
|
painter->setPen(QPen(Qt::PenStyle::NoPen));
|
||||||
painter->drawRect(rect);
|
painter->drawRect(rect);
|
||||||
|
|
||||||
|
@ -76,27 +60,30 @@ void PatchboardScene::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
||||||
n->parentTo(graph);
|
n->parentTo(graph);
|
||||||
|
|
||||||
addItem(new NodeObject(n));
|
addItem(new NodeObject(n));
|
||||||
});
|
}, graph.get());
|
||||||
|
|
||||||
|
m->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
m->popup(e->screenPos());
|
m->popup(e->screenPos());
|
||||||
|
}
|
||||||
|
|
||||||
/*qDebug() << "context menu requested for scene at" << p;
|
void PatchboardScene::queueResize() {
|
||||||
auto n = std::make_shared<Data::Node>();
|
if (!resizeQueued) {
|
||||||
n->x = static_cast<int>(p.x());
|
resizeQueued = true;
|
||||||
n->y = static_cast<int>(p.y());
|
QTimer::singleShot(1, this, &PatchboardScene::autoSetSize);
|
||||||
n->parentTo(graph);
|
}
|
||||||
|
|
||||||
addItem(new NodeObject(n));
|
|
||||||
|
|
||||||
e->accept();*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PatchboardScene::autoSetSize() {
|
void PatchboardScene::autoSetSize() {
|
||||||
|
if (view->scene() != this) return;
|
||||||
|
auto vrect = view->mapToScene(view->viewport()->visibleRegion().boundingRect()).boundingRect();
|
||||||
auto rect = itemsBoundingRect()
|
auto rect = itemsBoundingRect()
|
||||||
.united(view->mapToScene(view->viewport()->visibleRegion().boundingRect()).boundingRect())
|
.united(vrect)
|
||||||
.united(QRectF(0, 0, 1, 1));
|
.united(QRectF(0, 0, 1, 1));
|
||||||
rect.setTopLeft(QPointF(0, 0));
|
rect.setTopLeft(QPointF(0, 0));
|
||||||
setSceneRect(rect);
|
setSceneRect(rect);
|
||||||
|
graph->viewX = static_cast<int>(vrect.left());
|
||||||
|
graph->viewY = static_cast<int>(vrect.top());
|
||||||
|
resizeQueued = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PatchboardScene::refresh() {
|
void PatchboardScene::refresh() {
|
||||||
|
|
|
@ -12,6 +12,8 @@ namespace Xybrid::UI {
|
||||||
std::shared_ptr<Data::Graph> graph;
|
std::shared_ptr<Data::Graph> graph;
|
||||||
QGraphicsView* view;
|
QGraphicsView* view;
|
||||||
|
|
||||||
|
bool resizeQueued = false;
|
||||||
|
void queueResize();
|
||||||
void autoSetSize();
|
void autoSetSize();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -93,19 +93,19 @@ namespace {
|
||||||
void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||||
{ /* background */ } {
|
{ /* background */ } {
|
||||||
auto p = const_cast<PatternEditorModel*>(static_cast<const PatternEditorModel*>(index.model()))->getPattern();
|
auto p = const_cast<PatternEditorModel*>(static_cast<const PatternEditorModel*>(index.model()))->getPattern();
|
||||||
painter->fillRect(option.rect, ColorScheme::current.patternBg);
|
painter->fillRect(option.rect, Config::colorScheme.patternBg);
|
||||||
if (index.row() % p->time.rowsPerMeasure() == 0) painter->fillRect(option.rect, ColorScheme::current.patternBgMeasure);
|
if (index.row() % p->time.rowsPerMeasure() == 0) painter->fillRect(option.rect, Config::colorScheme.patternBgMeasure);
|
||||||
else if (index.row() % p->time.rowsPerBeat == 0) painter->fillRect(option.rect, ColorScheme::current.patternBgBeat);
|
else if (index.row() % p->time.rowsPerBeat == 0) painter->fillRect(option.rect, Config::colorScheme.patternBgBeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// selection/cursor highlight
|
// selection/cursor highlight
|
||||||
if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, ColorScheme::current.patternSelection);
|
if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, Config::colorScheme.patternSelection);
|
||||||
if (option.state & QStyle::State_HasFocus) {
|
if (option.state & QStyle::State_HasFocus) {
|
||||||
painter->setPen(ColorScheme::current.patternSelection);
|
painter->setPen(Config::colorScheme.patternSelection);
|
||||||
painter->drawRect(option.rect.adjusted(0,0,-1,-1));
|
painter->drawRect(option.rect.adjusted(0,0,-1,-1));
|
||||||
painter->drawRect(option.rect.adjusted(0,0,-1,-1));
|
painter->drawRect(option.rect.adjusted(0,0,-1,-1));
|
||||||
painter->drawRect(option.rect.adjusted(1,1,-2,-2));
|
painter->drawRect(option.rect.adjusted(1,1,-2,-2));
|
||||||
//painter->fillRect(option.rect, ColorScheme::current.patternSelection);
|
//painter->fillRect(option.rect, Config::colorScheme.patternSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// and main data
|
// and main data
|
||||||
|
@ -119,13 +119,13 @@ void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewI
|
||||||
}
|
}
|
||||||
if (s == QString("» ")) {
|
if (s == QString("» ")) {
|
||||||
align = Qt::AlignVCenter | Qt::AlignLeft;
|
align = Qt::AlignVCenter | Qt::AlignLeft;
|
||||||
painter->setPen(ColorScheme::current.patternFgBlank);
|
painter->setPen(Config::colorScheme.patternFgBlank);
|
||||||
} else {
|
} else {
|
||||||
if (s == QString(" - ") || s == QString("- ")) painter->setPen(ColorScheme::current.patternFgBlank);
|
if (s == QString(" - ") || s == QString("- ")) painter->setPen(Config::colorScheme.patternFgBlank);
|
||||||
else if (cc == 0) painter->setPen(ColorScheme::current.patternFgPort);
|
else if (cc == 0) painter->setPen(Config::colorScheme.patternFgPort);
|
||||||
else if (cc == 1) painter->setPen(ColorScheme::current.patternFgNote);
|
else if (cc == 1) painter->setPen(Config::colorScheme.patternFgNote);
|
||||||
else if (cc % 2 == 0) painter->setPen(ColorScheme::current.patternFgParamCmd);
|
else if (cc % 2 == 0) painter->setPen(Config::colorScheme.patternFgParamCmd);
|
||||||
else painter->setPen(ColorScheme::current.patternFgParamAmt);
|
else painter->setPen(Config::colorScheme.patternFgParamAmt);
|
||||||
}
|
}
|
||||||
painter->drawText(option.rect, align, s);
|
painter->drawText(option.rect, align, s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,11 +172,12 @@ void PatternEditorView::headerContextMenu(QPoint pt) {
|
||||||
if (QMessageBox::warning(this, "Are you sure?", QString("Remove channel %1 from pattern %2?").arg(Util::numAndName(idx, p->channel(idx).name)).arg(Util::numAndName(p->index, p->name)), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return;
|
if (QMessageBox::warning(this, "Are you sure?", QString("Remove channel %1 from pattern %2?").arg(Util::numAndName(idx, p->channel(idx).name)).arg(Util::numAndName(p->index, p->name)), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return;
|
||||||
(new PatternChannelDeleteCommand(p, idx))->commit();
|
(new PatternChannelDeleteCommand(p, idx))->commit();
|
||||||
});
|
});
|
||||||
menu->addAction("Rename Channel", this, [this, idx, p]() {
|
menu->addAction("Rename Channel...", this, [this, idx, p]() {
|
||||||
if (p != mdl->getPattern()) return; // swapped already
|
if (p != mdl->getPattern()) return; // swapped already
|
||||||
startRenameChannel(idx);
|
startRenameChannel(idx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
menu->popup(hdr->mapToGlobal(pt));
|
menu->popup(hdr->mapToGlobal(pt));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ class QUndoStack;
|
||||||
namespace Xybrid::Data {
|
namespace Xybrid::Data {
|
||||||
class Project;
|
class Project;
|
||||||
class Pattern;
|
class Pattern;
|
||||||
|
class Graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Xybrid {
|
namespace Xybrid {
|
||||||
|
@ -21,5 +22,7 @@ namespace Xybrid {
|
||||||
void updatePatternLists();
|
void updatePatternLists();
|
||||||
void patternUpdated(Data::Pattern* pattern);
|
void patternUpdated(Data::Pattern* pattern);
|
||||||
void rowUpdated(Data::Pattern* pattern, int channel, int row);
|
void rowUpdated(Data::Pattern* pattern, int channel, int row);
|
||||||
|
|
||||||
|
void openGraph(Data::Graph*);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
namespace Xybrid::Util {
|
namespace Xybrid::Util {
|
||||||
template<typename Num> inline QString numAndName(Num num, const std::string& name) {
|
template<typename Num> inline QString numAndName(Num num, const std::string& name) {
|
||||||
if (name.empty()) return QString::number(num);
|
if (name.empty()) return QString::number(num);
|
||||||
return QString("%1 (\"%2\")").arg(num).arg(QString::fromStdString(name));
|
return QString("%1 (\"%2\")").arg(num).arg(QString::fromStdString(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline QString hex(int num, int fw = 2) {
|
||||||
|
return QString("%1").arg(num, fw, 16, QChar('0')).toUpper();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline QString enumName(T t) { return QVariant::fromValue(t).toString(); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
#-------------------------------------------------
|
#-------------------------------------------------
|
||||||
|
|
||||||
QT += core gui multimedia
|
QT += core gui multimedia opengl
|
||||||
|
|
||||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
|
|
||||||
|
@ -50,7 +50,10 @@ SOURCES += \
|
||||||
ui/patchboard/patchboardscene.cpp \
|
ui/patchboard/patchboardscene.cpp \
|
||||||
ui/patchboard/nodeobject.cpp \
|
ui/patchboard/nodeobject.cpp \
|
||||||
data/graph.cpp \
|
data/graph.cpp \
|
||||||
config/pluginregistry.cpp
|
config/pluginregistry.cpp \
|
||||||
|
data/porttypes.cpp \
|
||||||
|
ui/breadcrumbview.cpp \
|
||||||
|
gadgets/ioport.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
mainwindow.h \
|
mainwindow.h \
|
||||||
|
@ -76,7 +79,9 @@ HEADERS += \
|
||||||
util/lambdaeventfilter.h \
|
util/lambdaeventfilter.h \
|
||||||
ui/patchboard/nodeobject.h \
|
ui/patchboard/nodeobject.h \
|
||||||
data/porttypes.h \
|
data/porttypes.h \
|
||||||
config/pluginregistry.h
|
config/pluginregistry.h \
|
||||||
|
ui/breadcrumbview.h \
|
||||||
|
gadgets/ioport.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
mainwindow.ui
|
mainwindow.ui
|
||||||
|
|
Loading…
Reference in New Issue