GUI Milestone 8b
This commit is contained in:
parent
3a8450cb70
commit
4a248e5622
6 changed files with 682 additions and 37 deletions
|
|
@ -13,9 +13,11 @@
|
|||
#include <QContextMenuEvent>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QGraphicsItem>
|
||||
#include <QGuiApplication>
|
||||
#include <QInputDialog>
|
||||
#include <QMouseEvent>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QMenu>
|
||||
|
|
@ -188,7 +190,7 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
autoArrangeAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
||||
connect(autoArrangeAction, &QAction::triggered, this, [this]() {
|
||||
m_model->autoArrange();
|
||||
m_model->saveLayout(m_layoutPath);
|
||||
saveLayoutWithViewState();
|
||||
});
|
||||
m_view->addAction(autoArrangeAction);
|
||||
|
||||
|
|
@ -239,19 +241,26 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
|
||||
connect(m_model, &WarpGraphModel::nodePositionUpdated, this,
|
||||
&GraphEditorWidget::scheduleSaveLayout);
|
||||
connect(m_model, &QtNodes::AbstractGraphModel::nodeCreated, this,
|
||||
&GraphEditorWidget::scheduleSaveLayout);
|
||||
connect(m_model, &QtNodes::AbstractGraphModel::nodeDeleted, this,
|
||||
&GraphEditorWidget::scheduleSaveLayout);
|
||||
connect(m_model, &QtNodes::AbstractGraphModel::nodeUpdated, this,
|
||||
&GraphEditorWidget::scheduleSaveLayout);
|
||||
|
||||
m_saveTimer = new QTimer(this);
|
||||
m_saveTimer->setSingleShot(true);
|
||||
m_saveTimer->setInterval(1000);
|
||||
connect(m_saveTimer, &QTimer::timeout, this, [this]() {
|
||||
m_model->saveLayout(m_layoutPath);
|
||||
});
|
||||
connect(m_saveTimer, &QTimer::timeout, this,
|
||||
&GraphEditorWidget::saveLayoutWithViewState);
|
||||
|
||||
m_model->refreshFromClient();
|
||||
if (!hasLayout) {
|
||||
m_model->autoArrange();
|
||||
}
|
||||
|
||||
QTimer::singleShot(0, this, &GraphEditorWidget::restoreViewState);
|
||||
|
||||
if (m_model->allNodeIds().size() > 0) {
|
||||
m_graphReady = true;
|
||||
Q_EMIT graphReady();
|
||||
|
|
@ -342,10 +351,18 @@ void GraphEditorWidget::captureDebugScreenshot(const QString &event) {
|
|||
}
|
||||
|
||||
bool GraphEditorWidget::eventFilter(QObject *obj, QEvent *event) {
|
||||
if (obj == m_view->viewport() &&
|
||||
event->type() == QEvent::ContextMenu) {
|
||||
if (obj != m_view->viewport()) {
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
if (event->type() == QEvent::ContextMenu) {
|
||||
auto *cme = static_cast<QContextMenuEvent *>(event);
|
||||
m_lastContextMenuScenePos = m_view->mapToScene(cme->pos());
|
||||
} else if (event->type() == QEvent::MouseButtonPress) {
|
||||
auto *me = static_cast<QMouseEvent *>(event);
|
||||
if (me->button() == Qt::MiddleButton) {
|
||||
m_view->centerOn(m_view->mapToScene(me->pos()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
|
@ -410,6 +427,9 @@ void GraphEditorWidget::showCanvasContextMenu(const QPoint &screenPos,
|
|||
autoArrange->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L));
|
||||
QAction *refreshGraph = menu.addAction(QStringLiteral("Refresh Graph"));
|
||||
refreshGraph->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_R));
|
||||
menu.addSeparator();
|
||||
QAction *saveLayoutAs = menu.addAction(QStringLiteral("Save Layout As..."));
|
||||
QAction *resetLayout = menu.addAction(QStringLiteral("Reset Layout"));
|
||||
|
||||
QAction *chosen = menu.exec(screenPos);
|
||||
if (chosen == createSink) {
|
||||
|
|
@ -430,8 +450,22 @@ void GraphEditorWidget::showCanvasContextMenu(const QPoint &screenPos,
|
|||
m_view->zoomFitSelected();
|
||||
} else if (chosen == autoArrange) {
|
||||
m_model->autoArrange();
|
||||
saveLayoutWithViewState();
|
||||
} else if (chosen == refreshGraph) {
|
||||
m_model->refreshFromClient();
|
||||
} else if (chosen == saveLayoutAs) {
|
||||
QString path = QFileDialog::getSaveFileName(
|
||||
this, QStringLiteral("Save Layout As"), QString(),
|
||||
QStringLiteral("JSON files (*.json)"));
|
||||
if (!path.isEmpty()) {
|
||||
saveLayoutWithViewState();
|
||||
m_model->saveLayout(path);
|
||||
}
|
||||
} else if (chosen == resetLayout) {
|
||||
m_model->clearSavedPositions();
|
||||
m_model->autoArrange();
|
||||
m_view->zoomFitAll();
|
||||
saveLayoutWithViewState();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -827,3 +861,23 @@ void GraphEditorWidget::tryResolvePendingLinks() {
|
|||
|
||||
m_pendingPasteLinks = remaining;
|
||||
}
|
||||
|
||||
void GraphEditorWidget::saveLayoutWithViewState() {
|
||||
WarpGraphModel::ViewState vs;
|
||||
vs.scale = m_view->getScale();
|
||||
QPointF center = m_view->mapToScene(m_view->viewport()->rect().center());
|
||||
vs.centerX = center.x();
|
||||
vs.centerY = center.y();
|
||||
vs.valid = true;
|
||||
m_model->saveLayout(m_layoutPath, vs);
|
||||
}
|
||||
|
||||
void GraphEditorWidget::restoreViewState() {
|
||||
auto vs = m_model->savedViewState();
|
||||
if (vs.valid) {
|
||||
m_view->setupScale(vs.scale);
|
||||
m_view->centerOn(QPointF(vs.centerX, vs.centerY));
|
||||
} else {
|
||||
m_view->zoomFitAll();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ private:
|
|||
void duplicateSelection();
|
||||
void removeDefaultActions();
|
||||
void tryResolvePendingLinks();
|
||||
void saveLayoutWithViewState();
|
||||
void restoreViewState();
|
||||
|
||||
struct PendingPasteLink {
|
||||
std::string outNodeName;
|
||||
|
|
|
|||
|
|
@ -361,6 +361,23 @@ void WarpGraphModel::refreshFromClient() {
|
|||
}
|
||||
|
||||
if (m_ghostNodes.erase(qtId)) {
|
||||
std::vector<QtNodes::ConnectionId> gcToRemove;
|
||||
for (auto gcIt = m_ghostConnections.begin();
|
||||
gcIt != m_ghostConnections.end();) {
|
||||
if (gcIt->outNodeId == qtId || gcIt->inNodeId == qtId) {
|
||||
gcToRemove.push_back(*gcIt);
|
||||
gcIt = m_ghostConnections.erase(gcIt);
|
||||
} else {
|
||||
++gcIt;
|
||||
}
|
||||
}
|
||||
for (const auto &gc : gcToRemove) {
|
||||
auto cIt = m_connections.find(gc);
|
||||
if (cIt != m_connections.end()) {
|
||||
m_connections.erase(cIt);
|
||||
Q_EMIT connectionDeleted(gc);
|
||||
}
|
||||
}
|
||||
Q_EMIT nodeUpdated(qtId);
|
||||
}
|
||||
continue;
|
||||
|
|
@ -379,6 +396,25 @@ void WarpGraphModel::refreshFromClient() {
|
|||
|
||||
if (ghostMatch != 0) {
|
||||
m_ghostNodes.erase(ghostMatch);
|
||||
{
|
||||
std::vector<QtNodes::ConnectionId> gcToRemove;
|
||||
for (auto gcIt = m_ghostConnections.begin();
|
||||
gcIt != m_ghostConnections.end();) {
|
||||
if (gcIt->outNodeId == ghostMatch || gcIt->inNodeId == ghostMatch) {
|
||||
gcToRemove.push_back(*gcIt);
|
||||
gcIt = m_ghostConnections.erase(gcIt);
|
||||
} else {
|
||||
++gcIt;
|
||||
}
|
||||
}
|
||||
for (const auto &gc : gcToRemove) {
|
||||
auto cIt = m_connections.find(gc);
|
||||
if (cIt != m_connections.end()) {
|
||||
m_connections.erase(cIt);
|
||||
Q_EMIT connectionDeleted(gc);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_pwToQt.emplace(nodeInfo.id.value, ghostMatch);
|
||||
auto &data = m_nodes[ghostMatch];
|
||||
data.info = nodeInfo;
|
||||
|
|
@ -528,17 +564,73 @@ void WarpGraphModel::refreshFromClient() {
|
|||
for (uint32_t linkId : staleLinkIds) {
|
||||
auto it = m_linkIdToConn.find(linkId);
|
||||
if (it != m_linkIdToConn.end()) {
|
||||
auto connIt = m_connections.find(it->second);
|
||||
if (connIt != m_connections.end()) {
|
||||
QtNodes::ConnectionId connId = it->second;
|
||||
m_connections.erase(connIt);
|
||||
Q_EMIT connectionDeleted(connId);
|
||||
QtNodes::ConnectionId connId = it->second;
|
||||
bool outIsGhost =
|
||||
m_ghostNodes.find(connId.outNodeId) != m_ghostNodes.end();
|
||||
bool inIsGhost =
|
||||
m_ghostNodes.find(connId.inNodeId) != m_ghostNodes.end();
|
||||
|
||||
if (outIsGhost || inIsGhost) {
|
||||
m_ghostConnections.insert(connId);
|
||||
} else {
|
||||
auto connIt = m_connections.find(connId);
|
||||
if (connIt != m_connections.end()) {
|
||||
m_connections.erase(connIt);
|
||||
Q_EMIT connectionDeleted(connId);
|
||||
}
|
||||
}
|
||||
m_linkIdToConn.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_pendingGhostConnections.empty()) {
|
||||
auto it = m_pendingGhostConnections.begin();
|
||||
while (it != m_pendingGhostConnections.end()) {
|
||||
QtNodes::NodeId outQtId = 0;
|
||||
QtNodes::NodeId inQtId = 0;
|
||||
for (const auto &[qtId, data] : m_nodes) {
|
||||
if (data.info.name == it->outNodeName)
|
||||
outQtId = qtId;
|
||||
if (data.info.name == it->inNodeName)
|
||||
inQtId = qtId;
|
||||
}
|
||||
if (outQtId == 0 || inQtId == 0) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto outNodeIt = m_nodes.find(outQtId);
|
||||
auto inNodeIt = m_nodes.find(inQtId);
|
||||
QtNodes::PortIndex outIdx = -1;
|
||||
QtNodes::PortIndex inIdx = -1;
|
||||
for (size_t i = 0; i < outNodeIt->second.outputPorts.size(); ++i) {
|
||||
if (outNodeIt->second.outputPorts[i].name == it->outPortName) {
|
||||
outIdx = static_cast<QtNodes::PortIndex>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < inNodeIt->second.inputPorts.size(); ++i) {
|
||||
if (inNodeIt->second.inputPorts[i].name == it->inPortName) {
|
||||
inIdx = static_cast<QtNodes::PortIndex>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (outIdx < 0 || inIdx < 0) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
QtNodes::ConnectionId connId{outQtId, outIdx, inQtId, inIdx};
|
||||
if (m_connections.find(connId) == m_connections.end()) {
|
||||
m_connections.insert(connId);
|
||||
m_ghostConnections.insert(connId);
|
||||
Q_EMIT connectionCreated(connId);
|
||||
}
|
||||
it = m_pendingGhostConnections.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
m_refreshing = false;
|
||||
}
|
||||
|
||||
|
|
@ -644,6 +736,12 @@ WarpGraphModel::classifyNode(const warppipe::NodeInfo &info) {
|
|||
}
|
||||
|
||||
void WarpGraphModel::saveLayout(const QString &path) const {
|
||||
ViewState vs{};
|
||||
saveLayout(path, vs);
|
||||
}
|
||||
|
||||
void WarpGraphModel::saveLayout(const QString &path,
|
||||
const ViewState &viewState) const {
|
||||
QJsonArray nodesArray;
|
||||
for (const auto &[qtId, data] : m_nodes) {
|
||||
auto posIt = m_positions.find(qtId);
|
||||
|
|
@ -657,9 +755,85 @@ void WarpGraphModel::saveLayout(const QString &path) const {
|
|||
nodesArray.append(nodeObj);
|
||||
}
|
||||
|
||||
QJsonArray ghostsArray;
|
||||
for (const auto &ghostId : m_ghostNodes) {
|
||||
auto nodeIt = m_nodes.find(ghostId);
|
||||
if (nodeIt == m_nodes.end()) {
|
||||
continue;
|
||||
}
|
||||
const auto &data = nodeIt->second;
|
||||
QJsonObject ghostObj;
|
||||
ghostObj["name"] = QString::fromStdString(data.info.name);
|
||||
ghostObj["description"] = QString::fromStdString(data.info.description);
|
||||
ghostObj["media_class"] = QString::fromStdString(data.info.media_class);
|
||||
ghostObj["application_name"] =
|
||||
QString::fromStdString(data.info.application_name);
|
||||
|
||||
auto posIt = m_positions.find(ghostId);
|
||||
if (posIt != m_positions.end()) {
|
||||
ghostObj["x"] = posIt->second.x();
|
||||
ghostObj["y"] = posIt->second.y();
|
||||
}
|
||||
|
||||
QJsonArray inPorts;
|
||||
for (const auto &port : data.inputPorts) {
|
||||
QJsonObject p;
|
||||
p["id"] = static_cast<int>(port.id.value);
|
||||
p["name"] = QString::fromStdString(port.name);
|
||||
inPorts.append(p);
|
||||
}
|
||||
ghostObj["input_ports"] = inPorts;
|
||||
|
||||
QJsonArray outPorts;
|
||||
for (const auto &port : data.outputPorts) {
|
||||
QJsonObject p;
|
||||
p["id"] = static_cast<int>(port.id.value);
|
||||
p["name"] = QString::fromStdString(port.name);
|
||||
outPorts.append(p);
|
||||
}
|
||||
ghostObj["output_ports"] = outPorts;
|
||||
|
||||
ghostsArray.append(ghostObj);
|
||||
}
|
||||
|
||||
QJsonArray ghostConnsArray;
|
||||
for (const auto &conn : m_ghostConnections) {
|
||||
auto outIt = m_nodes.find(conn.outNodeId);
|
||||
auto inIt = m_nodes.find(conn.inNodeId);
|
||||
if (outIt == m_nodes.end() || inIt == m_nodes.end()) {
|
||||
continue;
|
||||
}
|
||||
auto outIdx = static_cast<size_t>(conn.outPortIndex);
|
||||
auto inIdx = static_cast<size_t>(conn.inPortIndex);
|
||||
if (outIdx >= outIt->second.outputPorts.size() ||
|
||||
inIdx >= inIt->second.inputPorts.size()) {
|
||||
continue;
|
||||
}
|
||||
QJsonObject connObj;
|
||||
connObj["out_node"] =
|
||||
QString::fromStdString(outIt->second.info.name);
|
||||
connObj["out_port"] =
|
||||
QString::fromStdString(outIt->second.outputPorts[outIdx].name);
|
||||
connObj["in_node"] =
|
||||
QString::fromStdString(inIt->second.info.name);
|
||||
connObj["in_port"] =
|
||||
QString::fromStdString(inIt->second.inputPorts[inIdx].name);
|
||||
ghostConnsArray.append(connObj);
|
||||
}
|
||||
|
||||
QJsonObject root;
|
||||
root["version"] = 1;
|
||||
root["version"] = 2;
|
||||
root["nodes"] = nodesArray;
|
||||
root["ghosts"] = ghostsArray;
|
||||
root["ghost_connections"] = ghostConnsArray;
|
||||
|
||||
if (viewState.valid) {
|
||||
QJsonObject viewObj;
|
||||
viewObj["scale"] = viewState.scale;
|
||||
viewObj["center_x"] = viewState.centerX;
|
||||
viewObj["center_y"] = viewState.centerY;
|
||||
root["view"] = viewObj;
|
||||
}
|
||||
|
||||
QFileInfo fi(path);
|
||||
QDir dir = fi.absoluteDir();
|
||||
|
|
@ -673,6 +847,15 @@ void WarpGraphModel::saveLayout(const QString &path) const {
|
|||
}
|
||||
}
|
||||
|
||||
void WarpGraphModel::clearSavedPositions() {
|
||||
m_savedPositions.clear();
|
||||
m_positions.clear();
|
||||
}
|
||||
|
||||
WarpGraphModel::ViewState WarpGraphModel::savedViewState() const {
|
||||
return m_savedViewState;
|
||||
}
|
||||
|
||||
bool WarpGraphModel::loadLayout(const QString &path) {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
|
|
@ -685,7 +868,8 @@ bool WarpGraphModel::loadLayout(const QString &path) {
|
|||
}
|
||||
|
||||
QJsonObject root = doc.object();
|
||||
if (root["version"].toInt() != 1) {
|
||||
int version = root["version"].toInt();
|
||||
if (version < 1 || version > 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -698,7 +882,97 @@ bool WarpGraphModel::loadLayout(const QString &path) {
|
|||
double y = obj["y"].toDouble();
|
||||
m_savedPositions[name] = QPointF(x, y);
|
||||
}
|
||||
return !m_savedPositions.empty();
|
||||
|
||||
m_savedViewState = {};
|
||||
if (root.contains("view")) {
|
||||
QJsonObject viewObj = root["view"].toObject();
|
||||
m_savedViewState.scale = viewObj["scale"].toDouble(1.0);
|
||||
m_savedViewState.centerX = viewObj["center_x"].toDouble();
|
||||
m_savedViewState.centerY = viewObj["center_y"].toDouble();
|
||||
m_savedViewState.valid = true;
|
||||
}
|
||||
|
||||
if (root.contains("ghosts")) {
|
||||
QJsonArray ghostsArray = root["ghosts"].toArray();
|
||||
for (const auto &val : ghostsArray) {
|
||||
QJsonObject obj = val.toObject();
|
||||
std::string name = obj["name"].toString().toStdString();
|
||||
|
||||
bool alreadyExists = false;
|
||||
for (const auto &[_, data] : m_nodes) {
|
||||
if (data.info.name == name) {
|
||||
alreadyExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (alreadyExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
warppipe::NodeInfo info;
|
||||
info.id = warppipe::NodeId{0};
|
||||
info.name = name;
|
||||
info.description = obj["description"].toString().toStdString();
|
||||
info.media_class = obj["media_class"].toString().toStdString();
|
||||
info.application_name =
|
||||
obj["application_name"].toString().toStdString();
|
||||
|
||||
WarpNodeData data;
|
||||
data.info = info;
|
||||
|
||||
for (const auto &pval : obj["input_ports"].toArray()) {
|
||||
QJsonObject p = pval.toObject();
|
||||
warppipe::PortInfo port;
|
||||
port.id = warppipe::PortId{
|
||||
static_cast<uint32_t>(p["id"].toInt())};
|
||||
port.node = info.id;
|
||||
port.name = p["name"].toString().toStdString();
|
||||
port.is_input = true;
|
||||
data.inputPorts.push_back(port);
|
||||
}
|
||||
for (const auto &pval : obj["output_ports"].toArray()) {
|
||||
QJsonObject p = pval.toObject();
|
||||
warppipe::PortInfo port;
|
||||
port.id = warppipe::PortId{
|
||||
static_cast<uint32_t>(p["id"].toInt())};
|
||||
port.node = info.id;
|
||||
port.name = p["name"].toString().toStdString();
|
||||
port.is_input = false;
|
||||
data.outputPorts.push_back(port);
|
||||
}
|
||||
|
||||
QtNodes::NodeId qtId = newNodeId();
|
||||
m_nodes.emplace(qtId, std::move(data));
|
||||
m_ghostNodes.insert(qtId);
|
||||
|
||||
if (obj.contains("x") && obj.contains("y")) {
|
||||
m_positions.emplace(qtId, QPointF(obj["x"].toDouble(),
|
||||
obj["y"].toDouble()));
|
||||
}
|
||||
m_savedPositions[name] =
|
||||
m_positions.count(qtId)
|
||||
? m_positions.at(qtId)
|
||||
: QPointF(0, 0);
|
||||
|
||||
Q_EMIT nodeCreated(qtId);
|
||||
}
|
||||
}
|
||||
|
||||
if (root.contains("ghost_connections")) {
|
||||
m_pendingGhostConnections.clear();
|
||||
QJsonArray gcArray = root["ghost_connections"].toArray();
|
||||
for (const auto &val : gcArray) {
|
||||
QJsonObject obj = val.toObject();
|
||||
PendingGhostConnection pgc;
|
||||
pgc.outNodeName = obj["out_node"].toString().toStdString();
|
||||
pgc.outPortName = obj["out_port"].toString().toStdString();
|
||||
pgc.inNodeName = obj["in_node"].toString().toStdString();
|
||||
pgc.inPortName = obj["in_port"].toString().toStdString();
|
||||
m_pendingGhostConnections.push_back(std::move(pgc));
|
||||
}
|
||||
}
|
||||
|
||||
return !m_savedPositions.empty() || !m_ghostNodes.empty();
|
||||
}
|
||||
|
||||
void WarpGraphModel::autoArrange() {
|
||||
|
|
@ -788,7 +1062,6 @@ QVariant WarpGraphModel::styleForNode(WarpNodeType type, bool ghost) {
|
|||
style.FontColorFaded = QColor(120, 128, 142);
|
||||
style.ConnectionPointColor = QColor(140, 148, 160);
|
||||
style.FilledConnectionPointColor = QColor(180, 140, 80);
|
||||
style.Opacity = 0.6f;
|
||||
} else {
|
||||
style.GradientColor0 = base.lighter(120);
|
||||
style.GradientColor1 = base.lighter(108);
|
||||
|
|
@ -799,9 +1072,9 @@ QVariant WarpGraphModel::styleForNode(WarpNodeType type, bool ghost) {
|
|||
style.FontColorFaded = QColor(160, 168, 182);
|
||||
style.ConnectionPointColor = QColor(200, 208, 220);
|
||||
style.FilledConnectionPointColor = QColor(255, 165, 0);
|
||||
style.Opacity = 1.0f;
|
||||
}
|
||||
|
||||
style.Opacity = 1.0f;
|
||||
style.SelectedBoundaryColor = QColor(255, 165, 0);
|
||||
style.PenWidth = 1.3f;
|
||||
style.HoveredPenWidth = 2.4f;
|
||||
|
|
|
|||
|
|
@ -69,8 +69,18 @@ public:
|
|||
|
||||
uint32_t findPwNodeIdByName(const std::string &name) const;
|
||||
|
||||
struct ViewState {
|
||||
double scale;
|
||||
double centerX;
|
||||
double centerY;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
void saveLayout(const QString &path) const;
|
||||
void saveLayout(const QString &path, const ViewState &viewState) const;
|
||||
bool loadLayout(const QString &path);
|
||||
ViewState savedViewState() const;
|
||||
void clearSavedPositions();
|
||||
void autoArrange();
|
||||
|
||||
private:
|
||||
|
|
@ -91,6 +101,7 @@ private:
|
|||
std::unordered_map<QtNodes::NodeId, QPointF> m_positions;
|
||||
std::unordered_map<QtNodes::NodeId, QSize> m_sizes;
|
||||
std::unordered_set<QtNodes::NodeId> m_ghostNodes;
|
||||
std::unordered_set<QtNodes::ConnectionId> m_ghostConnections;
|
||||
|
||||
static constexpr double kHorizontalGap = 40.0;
|
||||
static constexpr double kVerticalGap = 30.0;
|
||||
|
|
@ -101,6 +112,15 @@ private:
|
|||
|
||||
bool m_refreshing = false;
|
||||
|
||||
struct PendingGhostConnection {
|
||||
std::string outNodeName;
|
||||
std::string outPortName;
|
||||
std::string inNodeName;
|
||||
std::string inPortName;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, QPointF> m_pendingPositions;
|
||||
std::unordered_map<std::string, QPointF> m_savedPositions;
|
||||
std::vector<PendingGhostConnection> m_pendingGhostConnections;
|
||||
ViewState m_savedViewState{};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue