GUI Milestone 8b

This commit is contained in:
Joey Yakimowich-Payne 2026-01-30 08:07:21 -07:00
commit 4a248e5622
6 changed files with 682 additions and 37 deletions

View file

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