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 {
|
||||
immediate frontburner {
|
||||
group 1 {
|
||||
- make selection follow pattern move where applicable
|
||||
- strut command in pattern editor (mostly selection agnostic)
|
||||
neeeeext {
|
||||
hook the graph up to the audio engine! {
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
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.
|
||||
? de-hardcode the "» " (probably just make it a static const variable somewhere?)
|
||||
make everything relevant check if editing is locked
|
||||
|
||||
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
|
||||
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
|
||||
- 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,
|
||||
? and defer freeing operations via message queues
|
||||
|
||||
resampler object {
|
||||
one used internally for each note
|
||||
|
|
|
@ -4,3 +4,8 @@ something something, actual readme coming later
|
|||
|
||||
## Build dependencies:
|
||||
- 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* 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
|
||||
qint64 readData(char* data, qint64 maxlen) override;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "colorscheme.h"
|
||||
using Xybrid::Config::ColorScheme;
|
||||
|
||||
ColorScheme ColorScheme::current;
|
||||
ColorScheme Xybrid::Config::colorScheme;
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
namespace Xybrid::Config {
|
||||
class ColorScheme {
|
||||
public:
|
||||
static ColorScheme current;
|
||||
ColorScheme() = default;
|
||||
|
||||
QColor patternSelection = QColor(127, 63, 255, 63);
|
||||
|
@ -20,4 +19,5 @@ namespace Xybrid::Config {
|
|||
QColor patternFgParamCmd = QColor(191,163,255);
|
||||
QColor patternFgParamAmt = QColor(191,222,255);
|
||||
};
|
||||
extern ColorScheme colorScheme;
|
||||
}
|
||||
|
|
|
@ -7,9 +7,14 @@ using namespace Xybrid::Config;
|
|||
|
||||
#include <QMenu>
|
||||
|
||||
#include "data/node.h"
|
||||
#include "data/graph.h"
|
||||
using namespace Xybrid::Data;
|
||||
|
||||
#include "gadgets/ioport.h"
|
||||
using Xybrid::Gadgets::IOPort;
|
||||
|
||||
#include "util/strings.h"
|
||||
|
||||
namespace {
|
||||
typedef std::list<std::function<void()>> fqueue; // typedef so QtCreator's auto indent doesn't completely break :|
|
||||
fqueue& regQueue() {
|
||||
|
@ -19,6 +24,10 @@ namespace {
|
|||
bool& initialized() { static bool b = false; return b; }
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<PluginInfo>> plugins;
|
||||
|
||||
std::string priorityCategories[] {
|
||||
"Gadget", "Instrument", "Effect"
|
||||
};
|
||||
}
|
||||
|
||||
bool PluginRegistry::enqueueRegistration(std::function<void ()> f) {
|
||||
|
@ -49,8 +58,85 @@ std::shared_ptr<Node> PluginRegistry::createInstance(const std::string& id) {
|
|||
return n;
|
||||
}
|
||||
|
||||
void PluginRegistry::populatePluginMenu(QMenu* m, std::function<void (std::shared_ptr<Node>)> f) {
|
||||
for (auto& i : plugins) m->addAction(QString::fromStdString(i.second->displayName), [f, pi = i.second] {
|
||||
void PluginRegistry::populatePluginMenu(QMenu* m, std::function<void (std::shared_ptr<Node>)> f, Graph* g) {
|
||||
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();
|
||||
n->plugin = pi;
|
||||
f(n);
|
||||
|
|
|
@ -8,6 +8,7 @@ class QMenu;
|
|||
|
||||
namespace Xybrid::Data {
|
||||
class Node;
|
||||
class Graph;
|
||||
}
|
||||
|
||||
namespace Xybrid::Config {
|
||||
|
@ -17,6 +18,7 @@ namespace Xybrid::Config {
|
|||
std::string displayName;
|
||||
std::string category;
|
||||
std::function<std::shared_ptr<Data::Node>()> createInstance;
|
||||
bool hidden = false;
|
||||
|
||||
PluginInfo() = default;
|
||||
virtual ~PluginInfo() = default;
|
||||
|
@ -28,6 +30,6 @@ namespace Xybrid::Config {
|
|||
void init();
|
||||
|
||||
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"
|
||||
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 {
|
||||
std::shared_ptr<PluginInfo> inf;
|
||||
bool c = PluginRegistry::enqueueRegistration([] {
|
||||
auto i = std::make_shared<PluginInfo>();
|
||||
i->id = "graph";
|
||||
i->displayName = "Subgraph";
|
||||
i->createInstance = []{ return std::make_shared<Graph>(); };
|
||||
PluginRegistry::registerPlugin(i);
|
||||
inf = i;
|
||||
});
|
||||
}
|
||||
|
||||
//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 {
|
||||
class Graph : public Node {
|
||||
public:
|
||||
Graph();
|
||||
~Graph() override = default;
|
||||
|
||||
std::vector<std::shared_ptr<Node>> children;
|
||||
|
||||
// position of viewport within graph (not serialized)
|
||||
int viewX{}, viewY{};
|
||||
|
||||
void saveData(QCborMap&) override;
|
||||
void loadData(QCborMap&) 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
|
||||
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 (auto o = owner.lock(), po = p->owner.lock(); !o || !po || po->dependsOn(o)) return false; // no dependency loops!
|
||||
// actually hook up
|
||||
connections.emplace_back(p);
|
||||
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) {
|
||||
auto t = shared_from_this(); // keep alive during reparenting
|
||||
if (auto p = parent.lock(); p) {
|
||||
onUnparent(p);
|
||||
p->children.erase(std::remove(p->children.begin(), p->children.end(), t), p->children.end());
|
||||
}
|
||||
parent = graph;
|
||||
if (graph) {
|
||||
project = graph->project;
|
||||
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);
|
||||
if (mdt->second.find(idx) == mdt->second.end()) {
|
||||
auto p = Port::makePort(dt);
|
||||
p->owner = shared_from_this();
|
||||
p->type = t;
|
||||
mdt->second.insert({idx, p});
|
||||
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; }
|
||||
|
|
|
@ -3,11 +3,17 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
|
||||
#include <QPointer>
|
||||
#include <QObject>
|
||||
|
||||
class QPainter;
|
||||
class QStyleOptionGraphicsItem;
|
||||
|
||||
namespace Xybrid::UI {
|
||||
class NodeObject;
|
||||
class PortObject;
|
||||
}
|
||||
|
||||
|
@ -16,20 +22,25 @@ namespace Xybrid::Config {
|
|||
}
|
||||
|
||||
namespace Xybrid::Data {
|
||||
class Project;
|
||||
|
||||
class Graph;
|
||||
class Node;
|
||||
|
||||
class Port : public std::enable_shared_from_this<Port> {
|
||||
Q_GADGET
|
||||
public:
|
||||
enum Type : uint8_t {
|
||||
Input, Output
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
enum DataType : uint8_t {
|
||||
Audio, Command, MIDI, Parameter
|
||||
};
|
||||
Q_ENUM(DataType)
|
||||
std::weak_ptr<Node> owner;
|
||||
std::vector<std::weak_ptr<Port>> connections;
|
||||
std::weak_ptr<Port> passthroughFor;
|
||||
std::weak_ptr<Port> passthroughTo;
|
||||
Type type; // TODO: figure out passthrough?
|
||||
uint8_t index;
|
||||
size_t tickUpdatedOn = static_cast<size_t>(-1);
|
||||
|
@ -54,6 +65,7 @@ namespace Xybrid::Data {
|
|||
|
||||
class Node : public std::enable_shared_from_this<Node> {
|
||||
public:
|
||||
Project* project;
|
||||
std::weak_ptr<Graph> parent;
|
||||
int x{}, y{};
|
||||
std::string name;
|
||||
|
@ -62,6 +74,8 @@ namespace Xybrid::Data {
|
|||
|
||||
std::shared_ptr<Config::PluginInfo> plugin;
|
||||
|
||||
QPointer<UI::NodeObject> obj;
|
||||
|
||||
virtual ~Node() = default;
|
||||
|
||||
void parentTo(std::shared_ptr<Graph>);
|
||||
|
@ -70,7 +84,25 @@ namespace Xybrid::Data {
|
|||
std::shared_ptr<Port> addPort(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 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 {
|
||||
|
||||
public:
|
||||
float* bufL;
|
||||
float* bufR;
|
||||
|
||||
AudioPort() = default;
|
||||
~AudioPort() override = default;
|
||||
|
||||
Port::DataType dataType() const override { return Port::Audio; }
|
||||
|
||||
void pull() override;
|
||||
};
|
||||
|
||||
class CommandPort : public Port {
|
||||
|
||||
public:
|
||||
uint8_t* data;
|
||||
size_t dataSize;
|
||||
|
||||
CommandPort() = default;
|
||||
~CommandPort() override = default;
|
||||
|
||||
Port::DataType dataType() const override { return Port::Command; }
|
||||
bool singleInput() const override { return true; }
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ using namespace Xybrid::Audio;
|
|||
|
||||
Project::Project() {
|
||||
rootGraph = std::make_shared<Graph>();
|
||||
rootGraph->project = this;
|
||||
}
|
||||
|
||||
Project::~Project() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "fileops.h"
|
||||
|
||||
#include "uisocket.h"
|
||||
#include "data/graph.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
@ -95,6 +96,12 @@ bool Xybrid::FileOps::saveProject(std::shared_ptr<Project> project, QString file
|
|||
main.insert(QString("patterns"), ptns);
|
||||
}
|
||||
|
||||
{ /* Graph */ } {
|
||||
QCborMap g;
|
||||
project->rootGraph->saveData(g);
|
||||
main.insert(QString("graph"), g);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 "audio/audioengine.h"
|
||||
#include "config/pluginregistry.h"
|
||||
#include "data/graph.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
@ -10,6 +11,8 @@
|
|||
#include <QSurfaceFormat>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
qRegisterMetaType<Xybrid::Data::Port>();
|
||||
|
||||
QApplication a(argc, argv);
|
||||
|
||||
// enable antialiasing on accelerated graphicsview
|
||||
|
|
|
@ -13,10 +13,13 @@ using Xybrid::MainWindow;
|
|||
#include <QUndoStack>
|
||||
#include <QTimer>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QGLWidget>
|
||||
|
||||
#include <QScroller>
|
||||
#include <QGraphicsTextItem>
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "data/graph.h"
|
||||
|
||||
#include "util/strings.h"
|
||||
|
@ -128,6 +131,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
(new ProjectPatternDeleteCommand(project, p))->commit();
|
||||
});
|
||||
}
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
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));
|
||||
});//*/
|
||||
}
|
||||
|
@ -213,7 +218,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
});
|
||||
|
||||
// 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();
|
||||
else audioEngine->play(project);
|
||||
});
|
||||
|
@ -235,8 +240,16 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
//ui->patchboardView->setDragMode(QGraphicsView::DragMode::RubberBandDrag);
|
||||
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);
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
//QGL::FormatOption::Rgba
|
||||
|
||||
|
||||
view->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
@ -258,20 +271,17 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
if (e->type() == QEvent::MouseButtonPress) {
|
||||
auto* me = static_cast<QMouseEvent*>(e);
|
||||
// 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
|
||||
QTimer::singleShot(1, [view] { view->setDragMode(QGraphicsView::NoDrag); });
|
||||
QTimer::singleShot(1, [view] {
|
||||
view->setDragMode(QGraphicsView::NoDrag);
|
||||
});
|
||||
}
|
||||
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
|
||||
|
@ -291,6 +301,15 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
emit ui->patternEditor->model()->dataChanged(ind, ind.siblingAtColumn((ch+1)*cpc-1));
|
||||
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
|
||||
connect(audioEngine, &AudioEngine::playbackModeChanged, this, [this, undoAction, redoAction]() {
|
||||
|
@ -301,6 +320,10 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
// and start with a new project
|
||||
menuFileNew();
|
||||
|
||||
//auto q = QJsonObject();
|
||||
//q.insert(QMetaObject::, "frenk");
|
||||
qDebug() << QVariant::fromValue(Data::Port::Audio).toString();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
|
@ -328,24 +351,6 @@ void MainWindow::menuFileNew() {
|
|||
project = std::make_shared<Project>();
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -366,6 +371,7 @@ void MainWindow::menuFileSave() {
|
|||
if (project->fileName.isEmpty()) menuFileSaveAs();
|
||||
else {
|
||||
FileOps::saveProject(project);
|
||||
undoStack->setClean();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -373,6 +379,8 @@ void MainWindow::menuFileSaveAs() {
|
|||
auto fileName = QFileDialog::getSaveFileName(this, "Save project as...", QString(), projectFilter);
|
||||
if (fileName.isEmpty()) return; // canceled
|
||||
FileOps::saveProject(project, fileName);
|
||||
undoStack->setClean();
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void MainWindow::onNewProjectLoaded() {
|
||||
|
@ -388,7 +396,11 @@ void MainWindow::onNewProjectLoaded() {
|
|||
break;
|
||||
}
|
||||
|
||||
openGraph(project->rootGraph);
|
||||
//openGraph(project->rootGraph);
|
||||
ui->patchboardBreadcrumbs->clear();
|
||||
ui->patchboardBreadcrumbs->push("/", this, [this, gg = project->rootGraph] {
|
||||
openGraph(gg);
|
||||
});
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
@ -450,5 +462,8 @@ bool MainWindow::selectPatternForEditing(Pattern* pattern) {
|
|||
|
||||
void MainWindow::openGraph(const std::shared_ptr<Data::Graph>& g) {
|
||||
if (!g) return; // invalid
|
||||
QPointF scrollPt(g->viewX, g->viewY);
|
||||
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 {
|
||||
class MainWindow : public QMainWindow {
|
||||
friend class Data::Graph;
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
|
|
@ -135,7 +135,7 @@
|
|||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>24</height>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
|
@ -248,10 +248,23 @@
|
|||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</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>
|
||||
<widget class="QGraphicsView" name="patchboardView">
|
||||
<property name="viewportUpdateMode">
|
||||
<enum>QGraphicsView::FullViewportUpdate</enum>
|
||||
<enum>QGraphicsView::MinimalViewportUpdate</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -372,6 +385,11 @@
|
|||
<extends>QTableView</extends>
|
||||
<header>ui/patterneditorview.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Xybrid::UI::BreadcrumbView</class>
|
||||
<extends>QTableView</extends>
|
||||
<header>ui/breadcrumbview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<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 <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QInputDialog>
|
||||
|
||||
#include "util/strings.h"
|
||||
|
||||
namespace {
|
||||
const constexpr qreal portSize [[maybe_unused]] = 10;
|
||||
const constexpr qreal portSpacing [[maybe_unused]] = 3;
|
||||
|
||||
const QColor tcolor[] {
|
||||
QColor(239, 179, 59), // Audio
|
||||
QColor(163, 95, 191), // Command
|
||||
QColor(95, 191, 163), // MIDI
|
||||
QColor(127, 127, 255), // Parameter
|
||||
};
|
||||
|
||||
const QString tname[] {
|
||||
"Audio", "Command", "MIDI", "Parameter"
|
||||
};
|
||||
}
|
||||
|
||||
void PortObject::connectTo(Xybrid::UI::PortObject* o) {
|
||||
|
@ -56,7 +52,7 @@ void PortObject::setHighlighted(bool h, bool hideLabel) {
|
|||
|
||||
bool lv = h && !hideLabel;
|
||||
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);
|
||||
QColor c = tcolor[port->dataType()];
|
||||
label->setText(txt);
|
||||
|
@ -93,6 +89,8 @@ PortObject::PortObject(const std::shared_ptr<Data::Port>& p) {
|
|||
for (auto c : port->connections) {
|
||||
if (auto cc = c.lock(); cc && cc->obj) connectTo(cc->obj);
|
||||
}
|
||||
|
||||
setCursor(Qt::CursorShape::CrossCursor);
|
||||
}
|
||||
|
||||
PortObject::~PortObject() {
|
||||
|
@ -100,7 +98,7 @@ PortObject::~PortObject() {
|
|||
}
|
||||
|
||||
void PortObject::mousePressEvent(QGraphicsSceneMouseEvent*) {
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
//setCursor(Qt::CursorShape::CrossCursor);
|
||||
setHighlighted(true, true);
|
||||
dragLine.reset(new QGraphicsLineItem());
|
||||
dragLine->setPen(QPen(tcolor[port->dataType()].lighter(125), 1.5));
|
||||
|
@ -110,7 +108,7 @@ void PortObject::mousePressEvent(QGraphicsSceneMouseEvent*) {
|
|||
}
|
||||
|
||||
void PortObject::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) {
|
||||
unsetCursor();
|
||||
//unsetCursor();
|
||||
dragLine.reset();
|
||||
|
||||
auto* i = scene()->itemAt(e->scenePos(), QTransform());
|
||||
|
@ -143,6 +141,7 @@ void PortObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
|||
delete c;
|
||||
}
|
||||
});//->setEnabled(this->connections.size() != 0);
|
||||
m->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m->popup(e->screenPos());
|
||||
}
|
||||
|
||||
|
@ -164,24 +163,28 @@ QRectF PortObject::boundingRect() const {
|
|||
|
||||
NodeObject::NodeObject(const std::shared_ptr<Data::Node>& n) {
|
||||
node = n;
|
||||
node->obj = this;
|
||||
|
||||
setFlag(QGraphicsItem::ItemIsMovable);
|
||||
setFlag(QGraphicsItem::ItemIsFocusable);
|
||||
setFlag(QGraphicsItem::ItemIsSelectable);
|
||||
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);
|
||||
|
||||
connect(this, &QGraphicsObject::xChanged, this, &NodeObject::onMoved);
|
||||
connect(this, &QGraphicsObject::yChanged, this, &NodeObject::onMoved);
|
||||
|
||||
//setToolTip(QString::fromStdString(node->name));
|
||||
|
||||
createPorts();
|
||||
|
||||
node->onGadgetCreated();
|
||||
}
|
||||
|
||||
void NodeObject::setGadgetSize(QPointF p) {
|
||||
gadgetSize_ = p;
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void NodeObject::promptDelete() {
|
||||
|
@ -193,9 +196,30 @@ void NodeObject::promptDelete() {
|
|||
}
|
||||
}
|
||||
|
||||
void NodeObject::mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) {
|
||||
node->onDoubleClick();
|
||||
}
|
||||
|
||||
void NodeObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
||||
if (!isSelected()) {
|
||||
for (auto* s : scene()->selectedItems()) s->setSelected(false);
|
||||
setSelected(true);
|
||||
}
|
||||
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->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m->popup(e->screenPos());
|
||||
}
|
||||
|
||||
|
@ -204,35 +228,48 @@ void NodeObject::bringToTop(bool force) {
|
|||
}
|
||||
|
||||
void NodeObject::createPorts() {
|
||||
inputPortContainer.reset(new QGraphicsRectItem(this));
|
||||
outputPortContainer.reset(new QGraphicsRectItem(this));
|
||||
auto* ipc = new QGraphicsLineItem(this);
|
||||
auto* opc = new QGraphicsLineItem(this);
|
||||
inputPortContainer.reset(ipc);
|
||||
outputPortContainer.reset(opc);
|
||||
updateGeometry();
|
||||
|
||||
QPen p(QColor(95, 95, 95), 2.5);
|
||||
QPointF inc(0, PortObject::portSize + PortObject::portSpacing);
|
||||
|
||||
QPointF cursor = QPointF(0, 0);
|
||||
for (auto mdt : node->inputs) {
|
||||
for (auto pp : mdt.second) {
|
||||
auto* p = new PortObject(pp.second);
|
||||
p->setParentItem(inputPortContainer.get());
|
||||
p->setParentItem(ipc);
|
||||
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);
|
||||
for (auto mdt : node->outputs) {
|
||||
for (auto pp : mdt.second) {
|
||||
auto* p = new PortObject(pp.second);
|
||||
p->setParentItem(outputPortContainer.get());
|
||||
p->setParentItem(opc);
|
||||
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() {
|
||||
if (inputPortContainer) inputPortContainer->setPos(QPointF(portSize * -.5 - portSpacing, portSize));
|
||||
if (outputPortContainer) outputPortContainer->setPos(QPointF(boundingRect().width() + portSize * .5 + portSpacing, portSize));
|
||||
qreal pm = PortObject::portSize * .5 + PortObject::portSpacing;
|
||||
if (inputPortContainer) inputPortContainer->setPos(QPointF(-pm, PortObject::portSize));
|
||||
if (outputPortContainer) outputPortContainer->setPos(QPointF(boundingRect().width() + pm, PortObject::portSize));
|
||||
}
|
||||
|
||||
void NodeObject::onMoved() {
|
||||
|
@ -255,13 +292,16 @@ void NodeObject::focusInEvent(QFocusEvent *) {
|
|||
}
|
||||
|
||||
void NodeObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt, QWidget *) {
|
||||
if (customChrome) {
|
||||
node->drawCustomChrome(painter, opt);
|
||||
return;
|
||||
}
|
||||
QRectF r = boundingRect();
|
||||
|
||||
QColor outline = QColor(31, 31, 31);
|
||||
if (opt->state & QStyle::State_Selected) outline = QColor(127, 127, 255);
|
||||
|
||||
QLinearGradient fill(QPointF(0, 0), QPointF(0, r.height()));
|
||||
//fill.setCoordinateMode(QLinearGradient::CoordinateMode::ObjectMode);
|
||||
fill.setColorAt(0, QColor(95, 95, 95));
|
||||
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));
|
||||
|
@ -283,7 +323,8 @@ void NodeObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt, Q
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -362,8 +403,11 @@ QPainterPath PortConnectionObject::shape(qreal width) const {
|
|||
auto end = mapFromScene(in->scenePos());
|
||||
path.moveTo(start);
|
||||
//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);
|
||||
path.cubicTo(start + mod, end - mod, end);
|
||||
path.cubicTo(start + out + mod, end - mod, end - out);
|
||||
//path.lineTo(end);
|
||||
|
||||
if (width <= 0) return path;
|
||||
QPainterPathStroker qp;
|
||||
|
|
|
@ -28,6 +28,9 @@ namespace Xybrid::UI {
|
|||
enum { Type = UserType + 101 };
|
||||
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() override;
|
||||
|
||||
|
@ -76,8 +79,8 @@ namespace Xybrid::UI {
|
|||
friend class PortObject;
|
||||
|
||||
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 bringToTop(bool force = false);
|
||||
|
@ -92,11 +95,22 @@ namespace Xybrid::UI {
|
|||
enum { Type = UserType + 100 };
|
||||
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>&);
|
||||
|
||||
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; }
|
||||
void promptDelete();
|
||||
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override;
|
||||
void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;
|
||||
|
||||
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
|
||||
|
|
|
@ -8,6 +8,7 @@ using Xybrid::UI::PatchboardScene;
|
|||
#include <QGraphicsItem>
|
||||
#include <QMainWindow>
|
||||
#include <QMenu>
|
||||
#include <QTimer>
|
||||
|
||||
#include <QGraphicsSceneContextMenuEvent>
|
||||
|
||||
|
@ -24,34 +25,17 @@ PatchboardScene::PatchboardScene(QGraphicsView* parent, const std::shared_ptr<Xy
|
|||
graph = g;
|
||||
view = parent;
|
||||
|
||||
connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::autoSetSize);
|
||||
connect(view->verticalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::autoSetSize);
|
||||
connect(view->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::autoSetSize);
|
||||
connect(view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::autoSetSize);
|
||||
connect(this, &QGraphicsScene::changed, this, &PatchboardScene::autoSetSize);
|
||||
|
||||
/*{
|
||||
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);
|
||||
}//*/
|
||||
connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::queueResize);
|
||||
connect(view->verticalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::queueResize);
|
||||
connect(view->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::queueResize);
|
||||
connect(view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::queueResize);
|
||||
connect(this, &QGraphicsScene::changed, this, &PatchboardScene::queueResize);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
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->drawRect(rect);
|
||||
|
||||
|
@ -76,27 +60,30 @@ void PatchboardScene::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
|||
n->parentTo(graph);
|
||||
|
||||
addItem(new NodeObject(n));
|
||||
});
|
||||
}, graph.get());
|
||||
|
||||
m->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m->popup(e->screenPos());
|
||||
}
|
||||
|
||||
/*qDebug() << "context menu requested for scene at" << p;
|
||||
auto n = std::make_shared<Data::Node>();
|
||||
n->x = static_cast<int>(p.x());
|
||||
n->y = static_cast<int>(p.y());
|
||||
n->parentTo(graph);
|
||||
|
||||
addItem(new NodeObject(n));
|
||||
|
||||
e->accept();*/
|
||||
void PatchboardScene::queueResize() {
|
||||
if (!resizeQueued) {
|
||||
resizeQueued = true;
|
||||
QTimer::singleShot(1, this, &PatchboardScene::autoSetSize);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchboardScene::autoSetSize() {
|
||||
if (view->scene() != this) return;
|
||||
auto vrect = view->mapToScene(view->viewport()->visibleRegion().boundingRect()).boundingRect();
|
||||
auto rect = itemsBoundingRect()
|
||||
.united(view->mapToScene(view->viewport()->visibleRegion().boundingRect()).boundingRect())
|
||||
.united(vrect)
|
||||
.united(QRectF(0, 0, 1, 1));
|
||||
rect.setTopLeft(QPointF(0, 0));
|
||||
setSceneRect(rect);
|
||||
graph->viewX = static_cast<int>(vrect.left());
|
||||
graph->viewY = static_cast<int>(vrect.top());
|
||||
resizeQueued = false;
|
||||
}
|
||||
|
||||
void PatchboardScene::refresh() {
|
||||
|
|
|
@ -12,6 +12,8 @@ namespace Xybrid::UI {
|
|||
std::shared_ptr<Data::Graph> graph;
|
||||
QGraphicsView* view;
|
||||
|
||||
bool resizeQueued = false;
|
||||
void queueResize();
|
||||
void autoSetSize();
|
||||
|
||||
public:
|
||||
|
|
|
@ -93,19 +93,19 @@ namespace {
|
|||
void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
{ /* background */ } {
|
||||
auto p = const_cast<PatternEditorModel*>(static_cast<const PatternEditorModel*>(index.model()))->getPattern();
|
||||
painter->fillRect(option.rect, ColorScheme::current.patternBg);
|
||||
if (index.row() % p->time.rowsPerMeasure() == 0) painter->fillRect(option.rect, ColorScheme::current.patternBgMeasure);
|
||||
else if (index.row() % p->time.rowsPerBeat == 0) painter->fillRect(option.rect, ColorScheme::current.patternBgBeat);
|
||||
painter->fillRect(option.rect, Config::colorScheme.patternBg);
|
||||
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, Config::colorScheme.patternBgBeat);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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(1,1,-2,-2));
|
||||
//painter->fillRect(option.rect, ColorScheme::current.patternSelection);
|
||||
//painter->fillRect(option.rect, Config::colorScheme.patternSelection);
|
||||
}
|
||||
|
||||
// and main data
|
||||
|
@ -119,13 +119,13 @@ void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewI
|
|||
}
|
||||
if (s == QString("» ")) {
|
||||
align = Qt::AlignVCenter | Qt::AlignLeft;
|
||||
painter->setPen(ColorScheme::current.patternFgBlank);
|
||||
painter->setPen(Config::colorScheme.patternFgBlank);
|
||||
} else {
|
||||
if (s == QString(" - ") || s == QString("- ")) painter->setPen(ColorScheme::current.patternFgBlank);
|
||||
else if (cc == 0) painter->setPen(ColorScheme::current.patternFgPort);
|
||||
else if (cc == 1) painter->setPen(ColorScheme::current.patternFgNote);
|
||||
else if (cc % 2 == 0) painter->setPen(ColorScheme::current.patternFgParamCmd);
|
||||
else painter->setPen(ColorScheme::current.patternFgParamAmt);
|
||||
if (s == QString(" - ") || s == QString("- ")) painter->setPen(Config::colorScheme.patternFgBlank);
|
||||
else if (cc == 0) painter->setPen(Config::colorScheme.patternFgPort);
|
||||
else if (cc == 1) painter->setPen(Config::colorScheme.patternFgNote);
|
||||
else if (cc % 2 == 0) painter->setPen(Config::colorScheme.patternFgParamCmd);
|
||||
else painter->setPen(Config::colorScheme.patternFgParamAmt);
|
||||
}
|
||||
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;
|
||||
(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
|
||||
startRenameChannel(idx);
|
||||
});
|
||||
}
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
menu->popup(hdr->mapToGlobal(pt));
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ class QUndoStack;
|
|||
namespace Xybrid::Data {
|
||||
class Project;
|
||||
class Pattern;
|
||||
class Graph;
|
||||
}
|
||||
|
||||
namespace Xybrid {
|
||||
|
@ -21,5 +22,7 @@ namespace Xybrid {
|
|||
void updatePatternLists();
|
||||
void patternUpdated(Data::Pattern* pattern);
|
||||
void rowUpdated(Data::Pattern* pattern, int channel, int row);
|
||||
|
||||
void openGraph(Data::Graph*);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
#pragma once
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
namespace Xybrid::Util {
|
||||
template<typename Num> inline QString numAndName(Num num, const std::string& name) {
|
||||
if (name.empty()) return QString::number(num);
|
||||
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
|
||||
|
||||
|
@ -50,7 +50,10 @@ SOURCES += \
|
|||
ui/patchboard/patchboardscene.cpp \
|
||||
ui/patchboard/nodeobject.cpp \
|
||||
data/graph.cpp \
|
||||
config/pluginregistry.cpp
|
||||
config/pluginregistry.cpp \
|
||||
data/porttypes.cpp \
|
||||
ui/breadcrumbview.cpp \
|
||||
gadgets/ioport.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h \
|
||||
|
@ -76,7 +79,9 @@ HEADERS += \
|
|||
util/lambdaeventfilter.h \
|
||||
ui/patchboard/nodeobject.h \
|
||||
data/porttypes.h \
|
||||
config/pluginregistry.h
|
||||
config/pluginregistry.h \
|
||||
ui/breadcrumbview.h \
|
||||
gadgets/ioport.h
|
||||
|
||||
FORMS += \
|
||||
mainwindow.ui
|
||||
|
|
Loading…
Reference in New Issue