diff --git a/src/compose.cpp b/src/compose.cpp index 1dceb46..d52d02f 100644 --- a/src/compose.cpp +++ b/src/compose.cpp @@ -345,12 +345,36 @@ void composeParent(ComposeState& state, const NodeTree& tree, std::sort(refChildren.begin(), refChildren.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; }); + // Use the referenced struct's scope widths (children come from there) + uint64_t refScopeId = node.refId; for (int childIdx : refChildren) { - // Skip self-referential children (e.g. struct Ball has a field of type Ball) - if (state.visiting.contains(tree.nodes[childIdx].id)) + const Node& child = tree.nodes[childIdx]; + // Self-referential child → show as collapsed struct (non-expandable) + if (state.visiting.contains(child.id)) { + int typeW = state.effectiveTypeW(refScopeId); + int nameW = state.effectiveNameW(refScopeId); + LineMeta lm; + lm.nodeIdx = nodeIdx; // parent struct — materialize target + lm.nodeId = child.id; + lm.depth = childDepth; + lm.lineKind = LineKind::Header; + lm.offsetText = fmt::fmtOffsetMargin( + tree.baseAddress + absAddr + child.offset, false, + state.offsetHexDigits); + lm.offsetAddr = tree.baseAddress + absAddr + child.offset; + lm.nodeKind = child.kind; + lm.foldHead = true; + lm.foldCollapsed = true; + lm.foldLevel = computeFoldLevel(childDepth, true); + lm.markerMask = (1u << M_STRUCT_BG) | (1u << M_CYCLE); + lm.effectiveTypeW = typeW; + lm.effectiveNameW = nameW; + state.emitLine(fmt::fmtStructHeader(child, childDepth, + /*collapsed=*/true, typeW, nameW), lm); continue; + } composeNode(state, tree, prov, childIdx, childDepth, - absAddr, node.refId, false, node.id); + absAddr, node.refId, false, refScopeId); } } } diff --git a/src/controller.cpp b/src/controller.cpp index 4a1c63c..78a9565 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -792,6 +792,44 @@ void RcxController::toggleCollapse(int nodeIdx) { cmd::Collapse{node.id, node.collapsed, !node.collapsed})); } +void RcxController::materializeRefChildren(int nodeIdx) { + if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return; + auto& tree = m_doc->tree; + + // Snapshot values before addNode invalidates references + const uint64_t parentId = tree.nodes[nodeIdx].id; + const uint64_t refId = tree.nodes[nodeIdx].refId; + const NodeKind parentKind = tree.nodes[nodeIdx].kind; + const QString parentName = tree.nodes[nodeIdx].name; + + if (refId == 0) return; + if (!tree.childrenOf(parentId).isEmpty()) return; // already materialized + + // Clone all children of the referenced struct as real children of this struct + QVector refChildren = tree.childrenOf(refId); + for (int ci : refChildren) { + Node copy = tree.nodes[ci]; + copy.id = 0; // auto-assign new ID + copy.parentId = parentId; + copy.collapsed = true; // start collapsed + tree.addNode(copy); + } + tree.invalidateIdCache(); + + // Auto-expand the self-referential child (the one that was the cycle) + // so the user gets expand in a single click + QVector newChildren = tree.childrenOf(parentId); + for (int ci : newChildren) { + auto& c = tree.nodes[ci]; + if (c.kind == parentKind && c.name == parentName && c.refId == refId) { + c.collapsed = false; + break; + } + } + + refresh(); +} + void RcxController::applyCommand(const Command& command, bool isUndo) { auto& tree = m_doc->tree; @@ -1965,7 +2003,10 @@ void RcxController::handleMarginClick(RcxEditor* editor, int margin, if (!lm) return; if (lm->foldHead && (margin == 0 || margin == 1)) { - toggleCollapse(lm->nodeIdx); + if (lm->markerMask & (1u << M_CYCLE)) + materializeRefChildren(lm->nodeIdx); + else + toggleCollapse(lm->nodeIdx); } else if (margin == 0 || margin == 1) { emit nodeSelected(lm->nodeIdx); } diff --git a/src/controller.h b/src/controller.h index bd44e1e..2c699e3 100644 --- a/src/controller.h +++ b/src/controller.h @@ -89,6 +89,7 @@ public: void insertNode(uint64_t parentId, int offset, NodeKind kind, const QString& name); void removeNode(int nodeIdx); void toggleCollapse(int nodeIdx); + void materializeRefChildren(int nodeIdx); void setNodeValue(int nodeIdx, int subLine, const QString& text, bool isAscii = false); void duplicateNode(int nodeIdx); void showContextMenu(RcxEditor* editor, int line, int nodeIdx, int subLine, const QPoint& globalPos);