fix: C++ generator bitfields, sizeof placement, Ctrl+F search, view sync

- Generator emits proper bitfield members instead of padding stubs
- Named bitfield structs (MitigationFlagsValues etc) now converted by parser
- sizeof comment moved from top to closing brace (}; // sizeof 0x80)
- C/C++ view syncs with workspace double-click and controller navigation
- Ctrl+F incremental search in C++ code view (Enter=next, Escape=close)
- Workspace dock resizable via 1px drag handle separator
- Regenerated Vergilius_25H2.rcx with all fixes (61 named bitfield containers)
This commit is contained in:
IChooseYou
2026-02-26 12:07:55 -07:00
committed by IChooseYou
parent aa04cfcb5c
commit 0d73575ea7
6 changed files with 2067 additions and 2582 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -101,7 +101,9 @@ static void emitStruct(GenContext& ctx, uint64_t structId);
static const QChar kCommentMarker = QChar(0x01);
static QString offsetComment(int offset) {
static QString offsetComment(int offset, bool isSizeof = false) {
if (isSizeof)
return QString(kCommentMarker) + QStringLiteral("// sizeof 0x%1").arg(QString::number(offset, 16).toUpper());
return QString(kCommentMarker) + QStringLiteral("// 0x%1").arg(QString::number(offset, 16).toUpper());
}
@@ -226,6 +228,27 @@ static void emitStructBody(GenContext& ctx, uint64_t structId,
// Emit the field
if (child.kind == NodeKind::Struct) {
// Bitfield container — emit inline bitfield members
if (child.classKeyword == QStringLiteral("bitfield")
&& !child.bitfieldMembers.isEmpty()) {
QString bfType = ctx.cType(child.elementKind);
if (bfType.isEmpty()) bfType = QStringLiteral("uint32_t");
QString fieldName = child.name.isEmpty()
? QString() : QStringLiteral(" ") + sanitizeIdent(child.name);
ctx.output += ind + QStringLiteral("struct\n");
ctx.output += ind + QStringLiteral("{\n");
QString bfInd = indent(depth + 1);
for (const auto& m : child.bitfieldMembers) {
ctx.output += bfInd + bfType + QStringLiteral(" ")
+ sanitizeIdent(m.name) + QStringLiteral(" : ")
+ QString::number(m.bitWidth) + QStringLiteral(";")
+ offsetComment(baseOffset + child.offset)
+ QStringLiteral("\n");
}
ctx.output += ind + QStringLiteral("}") + fieldName + QStringLiteral(";")
+ offsetComment(baseOffset + child.offset) + QStringLiteral("\n");
} else {
bool isAnonymous = child.structTypeName.isEmpty();
if (isAnonymous) {
@@ -251,6 +274,7 @@ static void emitStructBody(GenContext& ctx, uint64_t structId,
+ QStringLiteral(" ") + fieldName + QStringLiteral(";")
+ offsetComment(baseOffset + child.offset) + QStringLiteral("\n");
}
} // end bitfield else
} else if (child.kind == NodeKind::Array) {
QVector<int> arrayKids = ctx.childMap.value(child.id);
bool hasStructChild = false;
@@ -338,14 +362,13 @@ static void emitStruct(GenContext& ctx, uint64_t structId) {
if (kw == QStringLiteral("enum")) kw = QStringLiteral("struct");
// Size comment (Vergilius-style)
ctx.output += QStringLiteral("//0x%1 bytes (sizeof)\n")
.arg(QString::number(structSize, 16).toUpper());
ctx.output += kw + QStringLiteral(" ") + typeName + QStringLiteral("\n{\n");
emitStructBody(ctx, structId, kw == QStringLiteral("union"), 1, 0);
ctx.output += QStringLiteral("};\n");
ctx.output += QStringLiteral("};")
+ offsetComment(structSize, true)
+ QStringLiteral("\n");
if (ctx.emitAsserts)
ctx.output += QStringLiteral("static_assert(sizeof(%1) == 0x%2, \"Size mismatch for %1\");\n")
.arg(typeName)

View File

@@ -251,9 +251,9 @@ public:
// Kill the 1px frame margin Fusion reserves around QMenu contents
if (metric == PM_MenuPanelWidth)
return 0;
// Kill the separator between dock widgets / central widget
// Thin draggable separator between dock widgets / central widget
if (metric == PM_DockWidgetSeparatorExtent)
return 0;
return 1;
return QProxyStyle::pixelMetric(metric, opt, w);
}
void drawPrimitive(PrimitiveElement elem, const QStyleOption* opt,
@@ -1050,10 +1050,64 @@ MainWindow::SplitPane MainWindow::createSplitPane(TabState& tab) {
QSettings("Reclass", "Reclass").value("relativeOffsets", true).toBool());
pane.tabWidget->addTab(pane.editor, "Reclass"); // index 0
// Create per-pane rendered C++ view
// Create per-pane rendered C++ view with find bar
pane.renderedContainer = new QWidget;
auto* rvLayout = new QVBoxLayout(pane.renderedContainer);
rvLayout->setContentsMargins(0, 0, 0, 0);
rvLayout->setSpacing(0);
pane.rendered = new QsciScintilla;
setupRenderedSci(pane.rendered);
pane.tabWidget->addTab(pane.rendered, "C/C++"); // index 1
rvLayout->addWidget(pane.rendered);
// Find bar (hidden by default)
pane.findBar = new QLineEdit;
pane.findBar->setPlaceholderText("Find...");
pane.findBar->setVisible(false);
const auto& fbTheme = ThemeManager::instance().current();
pane.findBar->setStyleSheet(
QStringLiteral("QLineEdit { background: %1; color: %2; border: 1px solid %3;"
" padding: 4px 8px; font-size: 13px; }")
.arg(fbTheme.backgroundAlt.name())
.arg(fbTheme.text.name())
.arg(fbTheme.border.name()));
rvLayout->addWidget(pane.findBar);
// Ctrl+F to show find bar
QsciScintilla* sci = pane.rendered;
QLineEdit* fb = pane.findBar;
auto* findAction = new QAction(pane.renderedContainer);
findAction->setShortcut(QKeySequence::Find);
findAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
pane.renderedContainer->addAction(findAction);
connect(findAction, &QAction::triggered, fb, [fb, sci]() {
fb->setVisible(true);
fb->setFocus();
fb->selectAll();
});
// Escape to hide find bar
auto* escAction = new QAction(fb);
escAction->setShortcut(QKeySequence(Qt::Key_Escape));
escAction->setShortcutContext(Qt::WidgetShortcut);
fb->addAction(escAction);
connect(escAction, &QAction::triggered, fb, [fb, sci]() {
fb->setVisible(false);
sci->setFocus();
});
// Search on text change and Enter
connect(fb, &QLineEdit::textChanged, sci, [sci](const QString& text) {
if (text.isEmpty()) return;
sci->findFirst(text, false, false, false, true, true, 0, 0);
});
connect(fb, &QLineEdit::returnPressed, sci, [sci, fb]() {
QString text = fb->text();
if (text.isEmpty()) return;
if (!sci->findNext())
sci->findFirst(text, false, false, false, true, true, 0, 0);
});
pane.tabWidget->addTab(pane.renderedContainer, "C/C++"); // index 1
pane.tabWidget->setCurrentIndex(0);
pane.viewMode = VM_Reclass;
@@ -1668,7 +1722,7 @@ void MainWindow::toggleMcp() {
void MainWindow::applyTheme(const Theme& theme) {
applyGlobalTheme(theme);
// Separator killed via PM_DockWidgetSeparatorExtent in MenuBarStyle
// Dock separator is 1px via PM_DockWidgetSeparatorExtent in MenuBarStyle
// Custom title bar
m_titleBar->applyTheme(theme);
@@ -2034,6 +2088,20 @@ void MainWindow::updateRenderedView(TabState& tab, SplitPane& pane) {
rootId = findRootStructForNode(tab.doc->tree, selId);
}
// Fall back to the controller's current view root (set by double-click / navigation)
if (rootId == 0)
rootId = findRootStructForNode(tab.doc->tree, tab.ctrl->viewRootId());
// Last resort: first root-level struct in the project
if (rootId == 0) {
for (const auto& n : tab.doc->tree.nodes) {
if (n.parentId == 0 && n.kind == rcx::NodeKind::Struct) {
rootId = n.id;
break;
}
}
}
// Generate text
const QHash<NodeKind, QString>* aliases =
tab.doc->typeAliases.isEmpty() ? nullptr : &tab.doc->typeAliases;
@@ -2717,19 +2785,32 @@ void MainWindow::createWorkspaceDock() {
int ni = tree.indexOfId(structId);
if (ni < 0) return;
auto& tab = m_tabs[sub];
// Child member item: navigate to parent struct, then scroll to this member
uint64_t parentId = tree.nodes[ni].parentId;
if (parentId != 0) {
int pi = tree.indexOfId(parentId);
if (pi >= 0) tree.nodes[pi].collapsed = false;
m_tabs[sub].ctrl->setViewRootId(parentId);
m_tabs[sub].ctrl->scrollToNodeId(structId);
tab.ctrl->setViewRootId(parentId);
tab.ctrl->scrollToNodeId(structId);
} else {
// Root type/enum: navigate directly
tree.nodes[ni].collapsed = false;
m_tabs[sub].ctrl->setViewRootId(structId);
m_tabs[sub].ctrl->scrollToNodeId(structId);
tab.ctrl->setViewRootId(structId);
tab.ctrl->scrollToNodeId(structId);
}
// If active pane is in C/C++ mode, refresh after navigation settles
QTimer::singleShot(0, this, [this, sub]() {
if (!m_tabs.contains(sub)) return;
auto& t = m_tabs[sub];
if (t.activePaneIdx >= 0 && t.activePaneIdx < t.panes.size()) {
auto& p = t.panes[t.activePaneIdx];
if (p.viewMode == VM_Rendered)
updateRenderedView(t, p);
}
});
});
}

View File

@@ -96,6 +96,8 @@ private:
QTabWidget* tabWidget = nullptr;
RcxEditor* editor = nullptr;
QsciScintilla* rendered = nullptr;
QLineEdit* findBar = nullptr;
QWidget* renderedContainer = nullptr;
ViewMode viewMode = VM_Reclass;
uint64_t lastRenderedRootId = 0;
};

View File

@@ -56,8 +56,8 @@ private slots:
// Header
QVERIFY(result.contains("#pragma once"));
// Size comment (Vergilius-style)
QVERIFY(result.contains("//0x10 bytes (sizeof)"));
// Size comment on closing brace
QVERIFY(result.contains("// sizeof 0x10"));
// Struct definition (brace on new line)
QVERIFY(result.contains("struct Player\n{"));

View File

@@ -556,13 +556,11 @@ def postprocess_bitfields(nodes):
ids_to_remove = set()
for node in nodes:
# Only process anonymous struct nodes (not unions, not named types)
# Process struct nodes (not unions, not already bitfields, not named types)
if node.get('kind') != 'Struct':
continue
if node.get('classKeyword') in ('union', 'bitfield'):
continue
if node.get('name', '') != '':
continue
if node.get('structTypeName', ''):
continue