mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Fold arrows, pointer format, teal custom types, collapsed pointers by default
- Change fold indicators from +/- to arrow icons (▸ collapsed, ▾ expanded) - Move ▾ dropdown arrow after text on command rows (source▾, struct▾) - Change pointer display from ptr64<Type> to Type* format - Color custom/user-defined types teal (#4EC9B0) via lexer GlobalClass - Keep built-in types blue (#569cd6) via KeywordSet2 - Remove underline from root class name on CommandRow2 - Pointer children start collapsed by default (lazy expansion) - Demo data updated accordingly
This commit is contained in:
@@ -39,10 +39,12 @@ struct ComposeState {
|
||||
if (currentLine > 0) text += '\n';
|
||||
// 3-char fold indicator column: " - " expanded, " + " collapsed, " " other
|
||||
// CommandRow has no fold prefix (flush left)
|
||||
if (lm.lineKind == LineKind::CommandRow || lm.lineKind == LineKind::CommandRow2) {
|
||||
// no prefix
|
||||
if (lm.lineKind == LineKind::CommandRow || lm.lineKind == LineKind::Blank
|
||||
|| lm.lineKind == LineKind::CommandRow2
|
||||
|| (lm.lineKind == LineKind::Footer && lm.isRootHeader)) {
|
||||
// no prefix — flush left like CommandRow2
|
||||
} else if (lm.foldHead)
|
||||
text += lm.foldCollapsed ? QStringLiteral(" + ") : QStringLiteral(" - ");
|
||||
text += lm.foldCollapsed ? QStringLiteral(" \u25B8 ") : QStringLiteral(" \u25BE ");
|
||||
else
|
||||
text += QStringLiteral(" ");
|
||||
text += lineText;
|
||||
@@ -286,6 +288,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
||||
lm.depth = depth;
|
||||
lm.lineKind = LineKind::Footer;
|
||||
lm.nodeKind = node.kind;
|
||||
lm.isRootHeader = isRootHeader; // root footer: flush left (no fold prefix)
|
||||
lm.offsetText.clear();
|
||||
lm.foldLevel = computeFoldLevel(depth, false);
|
||||
lm.markerMask = 0;
|
||||
@@ -313,7 +316,7 @@ void composeNode(ComposeState& state, const NodeTree& tree,
|
||||
QString ptrTargetName = resolvePointerTarget(tree, node.refId);
|
||||
QString ptrTypeOverride = fmt::pointerTypeName(node.kind, ptrTargetName);
|
||||
|
||||
// Emit merged fold header: "ptr64<Type> Name {" (expanded) or "ptr64<Type> Name -> val" (collapsed)
|
||||
// Emit merged fold header: "Type* Name {" (expanded) or "Type* Name -> val" (collapsed)
|
||||
{
|
||||
LineMeta lm;
|
||||
lm.nodeIdx = nodeIdx;
|
||||
@@ -336,32 +339,31 @@ void composeNode(ComposeState& state, const NodeTree& tree,
|
||||
|
||||
if (!node.collapsed) {
|
||||
int sz = node.byteSize();
|
||||
uint64_t ptrVal = 0;
|
||||
if (prov.isValid() && sz > 0 && prov.isReadable(absAddr, sz)) {
|
||||
uint64_t ptrVal = (node.kind == NodeKind::Pointer32)
|
||||
ptrVal = (node.kind == NodeKind::Pointer32)
|
||||
? (uint64_t)prov.readU32(absAddr) : prov.readU64(absAddr);
|
||||
if (ptrVal != 0) {
|
||||
uint64_t pBase = ptrToProviderAddr(tree, ptrVal);
|
||||
if (pBase == UINT64_MAX) ptrVal = 0; // ptr below base: invalid
|
||||
}
|
||||
if (ptrVal != 0) {
|
||||
uint64_t pBase = ptrToProviderAddr(tree, ptrVal);
|
||||
qulonglong key = pBase ^ (node.refId * kGoldenRatio);
|
||||
if (!state.ptrVisiting.contains(key)) {
|
||||
state.ptrVisiting.insert(key);
|
||||
int refIdx = tree.indexOfId(node.refId);
|
||||
if (refIdx >= 0) {
|
||||
const Node& ref = tree.nodes[refIdx];
|
||||
if (ref.kind == NodeKind::Struct || ref.kind == NodeKind::Array)
|
||||
// isArrayChild=true skips header/footer, emits children only
|
||||
// depth (not depth+1): pointer header replaces struct header,
|
||||
// so children should be at depth+1, not depth+2
|
||||
composeParent(state, tree, prov, refIdx,
|
||||
depth, pBase, ref.id,
|
||||
/*isArrayChild=*/true);
|
||||
}
|
||||
state.ptrVisiting.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Show referenced struct children: at dereferenced address if non-NULL,
|
||||
// otherwise at offset 0 as a struct template preview
|
||||
uint64_t pBase = (ptrVal != 0) ? ptrToProviderAddr(tree, ptrVal) : 0;
|
||||
qulonglong key = pBase ^ (node.refId * kGoldenRatio);
|
||||
if (!state.ptrVisiting.contains(key)) {
|
||||
state.ptrVisiting.insert(key);
|
||||
int refIdx = tree.indexOfId(node.refId);
|
||||
if (refIdx >= 0) {
|
||||
const Node& ref = tree.nodes[refIdx];
|
||||
if (ref.kind == NodeKind::Struct || ref.kind == NodeKind::Array)
|
||||
composeParent(state, tree, prov, refIdx,
|
||||
depth, pBase, ref.id,
|
||||
/*isArrayChild=*/true);
|
||||
}
|
||||
state.ptrVisiting.remove(key);
|
||||
}
|
||||
|
||||
// Footer for pointer fold
|
||||
@@ -473,6 +475,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
||||
}
|
||||
|
||||
// Emit CommandRow as line 0 (synthetic UI line)
|
||||
const QString cmdRowText = QStringLiteral("source\u25BE \u203A 0x0");
|
||||
{
|
||||
LineMeta lm;
|
||||
lm.nodeIdx = -1;
|
||||
@@ -485,10 +488,29 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
||||
lm.markerMask = 0;
|
||||
lm.effectiveTypeW = state.typeW;
|
||||
lm.effectiveNameW = state.nameW;
|
||||
state.emitLine(QStringLiteral("File Address: 0x0"), lm);
|
||||
state.emitLine(cmdRowText, lm);
|
||||
}
|
||||
|
||||
// Emit CommandRow2 as line 1 (root class type + name)
|
||||
// Emit dotted separator (line 1) — fixed-width spaced dots in text + margin
|
||||
{
|
||||
// ~20 dots in text area, ~10 dots in margin, using middle dot · (U+00B7)
|
||||
QString dots(20, QChar(0x00B7));
|
||||
QString marginDots(10, QChar(0x00B7));
|
||||
LineMeta lm;
|
||||
lm.nodeIdx = -1;
|
||||
lm.nodeId = kCommandRowId;
|
||||
lm.depth = 0;
|
||||
lm.lineKind = LineKind::Blank;
|
||||
lm.foldLevel = SC_FOLDLEVELBASE;
|
||||
lm.foldHead = false;
|
||||
lm.offsetText = marginDots;
|
||||
lm.markerMask = 0;
|
||||
lm.effectiveTypeW = state.typeW;
|
||||
lm.effectiveNameW = state.nameW;
|
||||
state.emitLine(dots, lm);
|
||||
}
|
||||
|
||||
// Emit CommandRow2 as line 2 (root class type + name)
|
||||
{
|
||||
LineMeta lm;
|
||||
lm.nodeIdx = -1;
|
||||
@@ -501,7 +523,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
||||
lm.markerMask = 0;
|
||||
lm.effectiveTypeW = state.typeW;
|
||||
lm.effectiveNameW = state.nameW;
|
||||
state.emitLine(QStringLiteral("struct <no class> {"), lm);
|
||||
state.emitLine(QStringLiteral("struct\u25BE <no class> {"), lm);
|
||||
}
|
||||
|
||||
QVector<int> roots = state.childMap.value(0);
|
||||
|
||||
@@ -808,8 +808,11 @@ void RcxController::applyCommand(const Command& command, bool isUndo) {
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, cmd::ChangePointerRef>) {
|
||||
int idx = tree.indexOfId(c.nodeId);
|
||||
if (idx >= 0)
|
||||
if (idx >= 0) {
|
||||
tree.nodes[idx].refId = isUndo ? c.oldRefId : c.newRefId;
|
||||
if (tree.nodes[idx].refId != 0)
|
||||
tree.nodes[idx].collapsed = true;
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, cmd::ChangeStructTypeName>) {
|
||||
int idx = tree.indexOfId(c.nodeId);
|
||||
if (idx >= 0)
|
||||
@@ -837,7 +840,7 @@ void RcxController::setNodeValue(int nodeIdx, int subLine, const QString& text,
|
||||
const Node& node = m_doc->tree.nodes[nodeIdx];
|
||||
uint64_t addr = m_doc->tree.computeOffset(nodeIdx);
|
||||
|
||||
// For vector sub-components, redirect to float parsing at sub-offset
|
||||
// For vector components, redirect to float parsing at sub-offset
|
||||
NodeKind editKind = node.kind;
|
||||
if ((node.kind == NodeKind::Vec2 || node.kind == NodeKind::Vec3 ||
|
||||
node.kind == NodeKind::Vec4) && subLine >= 0) {
|
||||
@@ -1370,10 +1373,10 @@ void RcxController::updateCommandRow() {
|
||||
QString src;
|
||||
QString provName = m_doc->provider->name();
|
||||
if (provName.isEmpty()) {
|
||||
src = QStringLiteral("<Select Source>");
|
||||
src = QStringLiteral("source\u25BE");
|
||||
} else {
|
||||
src = QStringLiteral("%1 '%2'")
|
||||
.arg(m_doc->provider->kind(), provName);
|
||||
src = QStringLiteral("'%1'\u25BE")
|
||||
.arg(provName);
|
||||
}
|
||||
|
||||
// -- Symbol for selected node (getSymbol integration) --
|
||||
@@ -1394,10 +1397,10 @@ void RcxController::updateCommandRow() {
|
||||
// Build the row. If we have a symbol, append it after the address.
|
||||
QString row;
|
||||
if (sym.isEmpty()) {
|
||||
row = QStringLiteral("%1 Address: %2")
|
||||
row = QStringLiteral("%1 \u203A %2")
|
||||
.arg(elide(src, 40), elide(addr, 24));
|
||||
} else {
|
||||
row = QStringLiteral("%1 Address: %2 %3")
|
||||
row = QStringLiteral("%1 \u203A %2 %3")
|
||||
.arg(elide(src, 40), elide(addr, 24), elide(sym, 40));
|
||||
}
|
||||
|
||||
@@ -1408,13 +1411,13 @@ void RcxController::updateCommandRow() {
|
||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||
QString keyword = n.resolvedClassKeyword();
|
||||
QString className = n.structTypeName.isEmpty() ? n.name : n.structTypeName;
|
||||
row2 = QStringLiteral("%1 %2 {")
|
||||
row2 = QStringLiteral("%1\u25BE %2 {")
|
||||
.arg(keyword, className);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (row2.isEmpty())
|
||||
row2 = QStringLiteral("struct <no class> {");
|
||||
row2 = QStringLiteral("struct\u25BE <no class> {");
|
||||
|
||||
for (auto* ed : m_editors) {
|
||||
ed->setCommandRowText(row);
|
||||
|
||||
71
src/core.h
71
src/core.h
@@ -55,10 +55,10 @@ struct KindMeta {
|
||||
|
||||
inline constexpr KindMeta kKindMeta[] = {
|
||||
// kind name typeName sz ln al flags
|
||||
{NodeKind::Hex8, "Hex8", "Hex8", 1, 1, 1, KF_HexPreview},
|
||||
{NodeKind::Hex16, "Hex16", "Hex16", 2, 1, 2, KF_HexPreview},
|
||||
{NodeKind::Hex32, "Hex32", "Hex32", 4, 1, 4, KF_HexPreview},
|
||||
{NodeKind::Hex64, "Hex64", "Hex64", 8, 1, 8, KF_HexPreview},
|
||||
{NodeKind::Hex8, "Hex8", "hex8", 1, 1, 1, KF_HexPreview},
|
||||
{NodeKind::Hex16, "Hex16", "hex16", 2, 1, 2, KF_HexPreview},
|
||||
{NodeKind::Hex32, "Hex32", "hex32", 4, 1, 4, KF_HexPreview},
|
||||
{NodeKind::Hex64, "Hex64", "hex64", 8, 1, 8, KF_HexPreview},
|
||||
{NodeKind::Int8, "Int8", "int8_t", 1, 1, 1, KF_None},
|
||||
{NodeKind::Int16, "Int16", "int16_t", 2, 1, 2, KF_None},
|
||||
{NodeKind::Int32, "Int32", "int32_t", 4, 1, 4, KF_None},
|
||||
@@ -72,10 +72,10 @@ inline constexpr KindMeta kKindMeta[] = {
|
||||
{NodeKind::Bool, "Bool", "bool", 1, 1, 1, KF_None},
|
||||
{NodeKind::Pointer32, "Pointer32", "ptr32", 4, 1, 4, KF_None},
|
||||
{NodeKind::Pointer64, "Pointer64", "ptr64", 8, 1, 8, KF_None},
|
||||
{NodeKind::Vec2, "Vec2", "Vec2", 8, 1, 4, KF_Vector},
|
||||
{NodeKind::Vec3, "Vec3", "Vec3", 12, 1, 4, KF_Vector},
|
||||
{NodeKind::Vec4, "Vec4", "Vec4", 16, 1, 4, KF_Vector},
|
||||
{NodeKind::Mat4x4, "Mat4x4", "Mat4x4", 64, 4, 4, KF_None},
|
||||
{NodeKind::Vec2, "Vec2", "vec2", 8, 1, 4, KF_Vector},
|
||||
{NodeKind::Vec3, "Vec3", "vec3", 12, 1, 4, KF_Vector},
|
||||
{NodeKind::Vec4, "Vec4", "vec4", 16, 1, 4, KF_Vector},
|
||||
{NodeKind::Mat4x4, "Mat4x4", "mat4x4", 64, 4, 4, KF_None},
|
||||
{NodeKind::UTF8, "UTF8", "char[]", 1, 1, 1, KF_String},
|
||||
{NodeKind::UTF16, "UTF16", "wchar_t[]", 2, 1, 2, KF_String},
|
||||
{NodeKind::Padding, "Padding", "pad", 1, 1, 1, KF_HexPreview},
|
||||
@@ -125,6 +125,9 @@ inline constexpr bool isHexPreview(NodeKind k) {
|
||||
inline constexpr bool isHexNode(NodeKind k) {
|
||||
return k >= NodeKind::Hex8 && k <= NodeKind::Hex64;
|
||||
}
|
||||
inline constexpr bool isVectorKind(NodeKind k) {
|
||||
return k == NodeKind::Vec2 || k == NodeKind::Vec3 || k == NodeKind::Vec4;
|
||||
}
|
||||
|
||||
inline QStringList allTypeNamesForUI(bool stripBrackets = false) {
|
||||
QStringList out;
|
||||
@@ -391,15 +394,17 @@ struct NodeTree {
|
||||
|
||||
enum class LineKind : uint8_t {
|
||||
CommandRow, // line 0: source + address
|
||||
CommandRow2, // line 1: root class type + name
|
||||
Blank, // line 1: dotted separator
|
||||
CommandRow2, // line 2: root class type + name
|
||||
Header, Field, Continuation, Footer, ArrayElementSeparator
|
||||
};
|
||||
|
||||
static constexpr uint64_t kCommandRowId = UINT64_MAX;
|
||||
static constexpr uint64_t kCommandRow2Id = UINT64_MAX - 1;
|
||||
static constexpr int kCommandRowLine = 0;
|
||||
static constexpr int kCommandRow2Line = 1;
|
||||
static constexpr int kFirstDataLine = 2;
|
||||
static constexpr int kBlankLine = 1;
|
||||
static constexpr int kCommandRow2Line = 2;
|
||||
static constexpr int kFirstDataLine = 3;
|
||||
static constexpr uint64_t kFooterIdBit = 0x8000000000000000ULL;
|
||||
|
||||
struct LineMeta {
|
||||
@@ -430,7 +435,7 @@ struct LineMeta {
|
||||
};
|
||||
|
||||
inline bool isSyntheticLine(const LineMeta& lm) {
|
||||
return lm.lineKind == LineKind::CommandRow || lm.lineKind == LineKind::CommandRow2;
|
||||
return lm.lineKind == LineKind::CommandRow || lm.lineKind == LineKind::Blank || lm.lineKind == LineKind::CommandRow2;
|
||||
}
|
||||
|
||||
// ── Layout Info ──
|
||||
@@ -568,22 +573,26 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW =
|
||||
}
|
||||
|
||||
// ── CommandRow spans ──
|
||||
// Line format: " File 'name' Address: 0x140000000"
|
||||
// Line format: "source▾ › 0x140000000"
|
||||
|
||||
inline ColumnSpan commandRowSrcSpan(const QString& lineText) {
|
||||
int idx = lineText.indexOf(QStringLiteral(" Address: "));
|
||||
int idx = lineText.indexOf(QStringLiteral(" \u203A"));
|
||||
if (idx < 0) return {};
|
||||
int start = 0;
|
||||
while (start < idx && !lineText[start].isLetterOrNumber()
|
||||
&& lineText[start] != '<') start++;
|
||||
&& lineText[start] != '<' && lineText[start] != '\'') start++;
|
||||
if (start >= idx) return {};
|
||||
return {start, idx, true};
|
||||
// Exclude trailing ▾ from the editable span
|
||||
int end = idx;
|
||||
while (end > start && lineText[end - 1] == QChar(0x25BE)) end--;
|
||||
if (end <= start) return {};
|
||||
return {start, end, true};
|
||||
}
|
||||
|
||||
inline ColumnSpan commandRowAddrSpan(const QString& lineText) {
|
||||
int tag = lineText.indexOf(QStringLiteral(" Address: "));
|
||||
int tag = lineText.indexOf(QStringLiteral(" \u203A"));
|
||||
if (tag < 0) return {};
|
||||
int start = tag + 10; // after " Address: "
|
||||
int start = tag + 3; // after " › "
|
||||
int end = start;
|
||||
while (end < lineText.size() && !lineText[end].isSpace()) end++;
|
||||
if (end <= start) return {};
|
||||
@@ -591,13 +600,14 @@ inline ColumnSpan commandRowAddrSpan(const QString& lineText) {
|
||||
}
|
||||
|
||||
// ── CommandRow2 spans ──
|
||||
// Line format: "struct ClassName"
|
||||
// Line format: "struct▾ ClassName {"
|
||||
|
||||
inline ColumnSpan commandRow2TypeSpan(const QString& lineText) {
|
||||
int start = 0;
|
||||
while (start < lineText.size() && lineText[start].isSpace()) start++;
|
||||
if (start >= lineText.size()) return {};
|
||||
int end = lineText.indexOf(' ', start);
|
||||
int end = start;
|
||||
while (end < lineText.size() && lineText[end] != QChar(' ') && lineText[end] != QChar(0x25BE)) end++;
|
||||
if (end <= start) return {start, (int)lineText.size(), true};
|
||||
return {start, end, true};
|
||||
}
|
||||
@@ -642,27 +652,20 @@ inline ColumnSpan arrayElemCountSpanFor(const LineMeta& lm, const QString& lineT
|
||||
}
|
||||
|
||||
// ── Pointer kind/target spans (within type column of pointer fields) ──
|
||||
// Line format: " ptr64<void> name -> 0x..."
|
||||
// pointerKindSpan covers "ptr64" or "ptr32", pointerTargetSpan covers the target name inside <>
|
||||
// Line format: " void* name -> 0x..."
|
||||
// pointerTargetSpan covers the target name before '*'
|
||||
|
||||
inline ColumnSpan pointerKindSpanFor(const LineMeta& lm, const QString& lineText) {
|
||||
if ((lm.lineKind != LineKind::Field && lm.lineKind != LineKind::Header) || lm.isContinuation) return {};
|
||||
if (lm.nodeKind != NodeKind::Pointer32 && lm.nodeKind != NodeKind::Pointer64) return {};
|
||||
int ind = kFoldCol + lm.depth * 3;
|
||||
// Find '<' in the type portion
|
||||
int lt = lineText.indexOf('<', ind);
|
||||
if (lt <= ind) return {};
|
||||
return {ind, lt, true};
|
||||
inline ColumnSpan pointerKindSpanFor(const LineMeta& /*lm*/, const QString& /*lineText*/) {
|
||||
return {}; // No separate kind span in "Type*" format
|
||||
}
|
||||
|
||||
inline ColumnSpan pointerTargetSpanFor(const LineMeta& lm, const QString& lineText) {
|
||||
if ((lm.lineKind != LineKind::Field && lm.lineKind != LineKind::Header) || lm.isContinuation) return {};
|
||||
if (lm.nodeKind != NodeKind::Pointer32 && lm.nodeKind != NodeKind::Pointer64) return {};
|
||||
int ind = kFoldCol + lm.depth * 3;
|
||||
int lt = lineText.indexOf('<', ind);
|
||||
int gt = lineText.indexOf('>', lt);
|
||||
if (lt < 0 || gt < 0 || gt <= lt + 1) return {};
|
||||
return {lt + 1, gt, true};
|
||||
int star = lineText.indexOf('*', ind);
|
||||
if (star <= ind) return {};
|
||||
return {ind, star, true};
|
||||
}
|
||||
|
||||
// ── Array navigation spans ──
|
||||
|
||||
132
src/editor.cpp
132
src/editor.cpp
@@ -18,7 +18,7 @@ namespace rcx {
|
||||
|
||||
// ── Theme constants ──
|
||||
static const QColor kBgText("#1e1e1e");
|
||||
static const QColor kBgMargin("#252526");
|
||||
static const QColor kBgMargin("#1e1e1e"); // matches regular editor background
|
||||
static const QColor kFgMargin("#858585");
|
||||
static const QColor kFgMarginDim("#505050");
|
||||
|
||||
@@ -28,6 +28,8 @@ static constexpr int IND_BASE_ADDR = 10; // Green color for base address
|
||||
static constexpr int IND_HOVER_SPAN = 11; // Blue text on hover (link-like)
|
||||
static constexpr int IND_CMD_PILL = 12; // Rounded chip behind command row spans
|
||||
static constexpr int IND_DATA_CHANGED = 13; // Amber text for changed data values
|
||||
static constexpr int IND_CLASS_NAME = 14; // Teal text for root class name
|
||||
static constexpr int IND_CLASS_ULINE = 15; // Underline for root class name
|
||||
|
||||
static QString g_fontName = "Consolas";
|
||||
|
||||
@@ -160,11 +162,11 @@ void RcxEditor::setupScintilla() {
|
||||
|
||||
// Command-row pill background (shadcn-ish chip)
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
||||
IND_CMD_PILL, 7 /*INDIC_ROUNDBOX*/);
|
||||
IND_CMD_PILL, 8 /*INDIC_STRAIGHTBOX*/);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
|
||||
IND_CMD_PILL, QColor("#2a2a2a"));
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETALPHA,
|
||||
IND_CMD_PILL, (long)90);
|
||||
IND_CMD_PILL, (long)100);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETUNDER,
|
||||
IND_CMD_PILL, (long)1);
|
||||
|
||||
@@ -174,6 +176,18 @@ void RcxEditor::setupScintilla() {
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
|
||||
IND_DATA_CHANGED, QColor("#8fbc7a"));
|
||||
|
||||
// Root class name — teal (VS Code type color)
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
||||
IND_CLASS_NAME, 17 /*INDIC_TEXTFORE*/);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
|
||||
IND_CLASS_NAME, QColor("#4EC9B0"));
|
||||
|
||||
// Root class name underline
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
||||
IND_CLASS_ULINE, (long)0 /*INDIC_PLAIN*/);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
|
||||
IND_CLASS_ULINE, QColor(124, 180, 226));
|
||||
|
||||
}
|
||||
|
||||
void RcxEditor::setupLexer() {
|
||||
@@ -201,15 +215,25 @@ void RcxEditor::setupLexer() {
|
||||
m_lexer->setFont(font, i);
|
||||
}
|
||||
|
||||
// Custom / user-defined types → teal (VS Code #4EC9B0)
|
||||
m_lexer->setColor(QColor("#4EC9B0"), QsciLexerCPP::GlobalClass);
|
||||
|
||||
m_sci->setLexer(m_lexer);
|
||||
m_sci->setBraceMatching(QsciScintilla::NoBraceMatch); // Disable - this is a structured viewer
|
||||
|
||||
// Add type names to keyword set 2 → teal coloring (distinct from identifiers)
|
||||
// Add built-in type names to keyword set 1 → blue coloring
|
||||
QByteArray kw2 = allTypeNamesForUI(/*stripBrackets=*/true).join(' ').toLatin1();
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS,
|
||||
(uintptr_t)1, kw2.constData());
|
||||
}
|
||||
|
||||
void RcxEditor::setCustomTypeNames(const QStringList& names) {
|
||||
m_customTypeNames = names;
|
||||
QByteArray kw = names.join(' ').toLatin1();
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS,
|
||||
(uintptr_t)3, kw.constData());
|
||||
}
|
||||
|
||||
void RcxEditor::setupMargins() {
|
||||
m_sci->setMarginsFont(editorFont());
|
||||
|
||||
@@ -282,7 +306,7 @@ void RcxEditor::setupMarkers() {
|
||||
|
||||
// M_CMD_ROW (8): distinct background for CommandRow bar
|
||||
m_sci->markerDefine(QsciScintilla::Background, M_CMD_ROW);
|
||||
m_sci->setMarkerBackgroundColor(QColor("#252526"), M_CMD_ROW);
|
||||
m_sci->setMarkerBackgroundColor(QColor("#1e1e1e"), M_CMD_ROW);
|
||||
}
|
||||
|
||||
void RcxEditor::allocateMarginStyles() {
|
||||
@@ -293,7 +317,7 @@ void RcxEditor::allocateMarginStyles() {
|
||||
m_marginStyleBase = (int)base;
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_MARGINSETSTYLEOFFSET, base);
|
||||
|
||||
const long bgrMargin = 0x262525; // BGR for #252526
|
||||
const long bgrMargin = 0x1e1e1e; // BGR for #1e1e1e (matches editor bg)
|
||||
QByteArray fontName = editorFont().family().toUtf8();
|
||||
int fontSize = editorFont().pointSize();
|
||||
|
||||
@@ -349,7 +373,6 @@ void RcxEditor::applyMarginText(const QVector<LineMeta>& meta) {
|
||||
m_sci->clearMarginText(-1);
|
||||
|
||||
for (int i = 0; i < meta.size(); i++) {
|
||||
if (isSyntheticLine(meta[i])) continue;
|
||||
const auto& lm = meta[i];
|
||||
if (lm.offsetText.isEmpty()) continue;
|
||||
|
||||
@@ -368,10 +391,13 @@ void RcxEditor::applyMarkers(const QVector<LineMeta>& meta) {
|
||||
}
|
||||
m_sci->markerDeleteAll(M_CMD_ROW);
|
||||
for (int i = 0; i < meta.size(); i++) {
|
||||
if (meta[i].lineKind == LineKind::CommandRow || meta[i].lineKind == LineKind::CommandRow2) {
|
||||
if (meta[i].lineKind == LineKind::CommandRow) {
|
||||
m_sci->markerAdd(i, M_CMD_ROW);
|
||||
continue;
|
||||
}
|
||||
if (meta[i].lineKind == LineKind::CommandRow2) {
|
||||
continue; // regular background (no special marker)
|
||||
}
|
||||
uint32_t mask = meta[i].markerMask;
|
||||
for (int m = M_CONT; m <= M_STRUCT_BG; m++) {
|
||||
if (mask & (1u << m)) {
|
||||
@@ -610,10 +636,7 @@ void RcxEditor::applyBaseAddressColoring(const QVector<LineMeta>& meta) {
|
||||
if (meta.isEmpty() || meta[0].lineKind != LineKind::CommandRow) return;
|
||||
|
||||
clearIndicatorLine(IND_BASE_ADDR, 0);
|
||||
QString lineText = getLineText(m_sci, 0);
|
||||
ColumnSpan span = commandRowAddrSpan(lineText);
|
||||
if (span.valid)
|
||||
fillIndicatorCols(IND_BASE_ADDR, 0, span.start, span.end);
|
||||
// Address in command bar is not colored green (only field values get green)
|
||||
}
|
||||
|
||||
void RcxEditor::applyCommandRowPills() {
|
||||
@@ -635,7 +658,7 @@ void RcxEditor::applyCommandRowPills() {
|
||||
fillPadded(commandRowSrcSpan(t));
|
||||
fillPadded(commandRowAddrSpan(t));
|
||||
|
||||
// Dim label text: provider kind ("File"/"Process") and "Address:"
|
||||
// Dim label text: source arrow/placeholder and ", address="
|
||||
ColumnSpan srcSpan = commandRowSrcSpan(t);
|
||||
if (srcSpan.valid) {
|
||||
int quotePos = t.indexOf('\'', srcSpan.start);
|
||||
@@ -644,33 +667,34 @@ void RcxEditor::applyCommandRowPills() {
|
||||
if (kindEnd > srcSpan.start)
|
||||
fillIndicatorCols(IND_HEX_DIM, line, srcSpan.start, kindEnd);
|
||||
}
|
||||
int addrTag = t.indexOf(QStringLiteral(" Address: "));
|
||||
int addrTag = t.indexOf(QStringLiteral(" \u203A"));
|
||||
if (addrTag >= 0)
|
||||
fillIndicatorCols(IND_HEX_DIM, line, addrTag + 1, addrTag + 9);
|
||||
fillIndicatorCols(IND_HEX_DIM, line, addrTag, addrTag + 3);
|
||||
|
||||
// Style CommandRow2 (line 1) if present
|
||||
if (m_meta.size() > 1 && m_meta[1].lineKind == LineKind::CommandRow2) {
|
||||
constexpr int line2 = 1;
|
||||
// Style CommandRow2 (line kCommandRow2Line) if present
|
||||
if (m_meta.size() > kCommandRow2Line && m_meta[kCommandRow2Line].lineKind == LineKind::CommandRow2) {
|
||||
int line2 = kCommandRow2Line;
|
||||
QString t2 = getLineText(m_sci, line2);
|
||||
|
||||
clearIndicatorLine(IND_CMD_PILL, line2);
|
||||
clearIndicatorLine(IND_HEX_DIM, line2);
|
||||
|
||||
auto fillPadded2 = [&](ColumnSpan s) {
|
||||
if (!s.valid) return;
|
||||
int a = qMax(0, s.start - 1);
|
||||
int b = qMin(t2.size(), s.end + 1);
|
||||
fillIndicatorCols(IND_CMD_PILL, line2, a, b);
|
||||
};
|
||||
clearIndicatorLine(IND_CLASS_NAME, line2);
|
||||
clearIndicatorLine(IND_CLASS_ULINE, line2);
|
||||
|
||||
ColumnSpan typeSpan = commandRow2TypeSpan(t2);
|
||||
fillPadded2(typeSpan);
|
||||
if (typeSpan.valid)
|
||||
fillIndicatorCols(IND_HEX_DIM, line2, typeSpan.start, typeSpan.end);
|
||||
|
||||
ColumnSpan nameSpan = commandRow2NameSpan(t2);
|
||||
fillPadded2(nameSpan);
|
||||
if (nameSpan.valid) {
|
||||
fillIndicatorCols(IND_CLASS_NAME, line2, nameSpan.start, nameSpan.end);
|
||||
}
|
||||
}
|
||||
|
||||
// Dim the dotted separator line
|
||||
if (m_meta.size() > kBlankLine && m_meta[kBlankLine].lineKind == LineKind::Blank) {
|
||||
QString sep = getLineText(m_sci, kBlankLine);
|
||||
if (!sep.isEmpty())
|
||||
fillIndicatorCols(IND_HEX_DIM, kBlankLine, 0, sep.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1398,17 +1422,55 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line) {
|
||||
|
||||
QString trimmed = lineText.mid(norm.start, norm.end - norm.start);
|
||||
|
||||
int vecComponent = 0; // which vector component (0-3)
|
||||
|
||||
// For vector value editing: narrow span to the clicked component
|
||||
if (target == EditTarget::Value && isVectorKind(lm->nodeKind)) {
|
||||
int cursorCol = col; // col from getCursorPosition above
|
||||
// Find comma positions within the value span to identify components
|
||||
QVector<int> compStarts, compEnds;
|
||||
int pos = 0;
|
||||
for (int i = 0; i < trimmed.size(); i++) {
|
||||
if (trimmed[i] == ',') {
|
||||
compEnds.append(i);
|
||||
// skip ", " separator
|
||||
int next = i + 1;
|
||||
while (next < trimmed.size() && trimmed[next] == ' ') next++;
|
||||
compStarts.append(next);
|
||||
}
|
||||
}
|
||||
compStarts.prepend(0);
|
||||
compEnds.append(trimmed.size());
|
||||
|
||||
// Find which component the cursor is in
|
||||
int relCol = cursorCol - norm.start;
|
||||
vecComponent = 0;
|
||||
for (int i = 0; i < compStarts.size(); i++) {
|
||||
if (relCol >= compStarts[i] && (i == compStarts.size() - 1 || relCol < compStarts[i + 1]))
|
||||
{ vecComponent = i; break; }
|
||||
}
|
||||
if (vecComponent >= compStarts.size()) vecComponent = compStarts.size() - 1;
|
||||
|
||||
// Narrow span to just this component
|
||||
int cStart = norm.start + compStarts[vecComponent];
|
||||
int cEnd = norm.start + compEnds[vecComponent];
|
||||
// Trim trailing spaces from component
|
||||
while (cEnd > cStart && lineText[cEnd - 1] == ' ') cEnd--;
|
||||
norm.start = cStart;
|
||||
norm.end = cEnd;
|
||||
trimmed = lineText.mid(norm.start, norm.end - norm.start);
|
||||
}
|
||||
|
||||
m_editState.active = true;
|
||||
m_editState.line = line;
|
||||
m_editState.nodeIdx = lm->nodeIdx;
|
||||
m_editState.subLine = lm->subLine;
|
||||
m_editState.subLine = isVectorKind(lm->nodeKind) ? vecComponent : lm->subLine;
|
||||
m_editState.target = target;
|
||||
m_editState.spanStart = norm.start;
|
||||
m_editState.original = trimmed;
|
||||
m_editState.linelenAfterReplace = lineText.size();
|
||||
m_editState.editKind = lm->nodeKind;
|
||||
if ((lm->nodeKind == NodeKind::Vec2 || lm->nodeKind == NodeKind::Vec3 ||
|
||||
lm->nodeKind == NodeKind::Vec4) && lm->subLine >= 0)
|
||||
if (isVectorKind(lm->nodeKind))
|
||||
m_editState.editKind = NodeKind::Float;
|
||||
|
||||
// Store fixed comment column position for value editing
|
||||
@@ -2004,7 +2066,7 @@ void RcxEditor::setCommandRowText(const QString& line) {
|
||||
}
|
||||
|
||||
void RcxEditor::setCommandRow2Text(const QString& line) {
|
||||
if (m_sci->lines() <= 1) return;
|
||||
if (m_sci->lines() <= kCommandRow2Line) return;
|
||||
QString s = line;
|
||||
s.replace('\n', ' ');
|
||||
s.replace('\r', ' ');
|
||||
@@ -2017,15 +2079,15 @@ void RcxEditor::setCommandRow2Text(const QString& line) {
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, 0);
|
||||
m_sci->setReadOnly(false);
|
||||
|
||||
long start = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, 1);
|
||||
long end = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLINEENDPOSITION, 1);
|
||||
long start = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, kCommandRow2Line);
|
||||
long end = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLINEENDPOSITION, kCommandRow2Line);
|
||||
long oldLen = end - start;
|
||||
QByteArray utf8 = s.toUtf8();
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART, start);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND, end);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_REPLACETARGET, (uintptr_t)utf8.size(), utf8.constData());
|
||||
|
||||
// Adjust saved cursor/anchor for length change in line 1
|
||||
// Adjust saved cursor/anchor for length change in CommandRow2 line
|
||||
long delta = (long)utf8.size() - oldLen;
|
||||
if (savedPos > end) savedPos += delta;
|
||||
if (savedAnchor > end) savedAnchor += delta;
|
||||
|
||||
@@ -49,8 +49,8 @@ public:
|
||||
void setEditorFont(const QString& fontName);
|
||||
static void setGlobalFontName(const QString& fontName);
|
||||
|
||||
// Custom type names (struct types from the tree) shown in type picker
|
||||
void setCustomTypeNames(const QStringList& names) { m_customTypeNames = names; }
|
||||
// Custom type names (struct types from the tree) shown in type picker + lexer GlobalClass coloring
|
||||
void setCustomTypeNames(const QStringList& names);
|
||||
|
||||
// Saved sources for quick-switch in source picker
|
||||
void setSavedSources(const QVector<SavedSourceDisplay>& sources) { m_savedSourceDisplay = sources; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"baseAddress": "400000",
|
||||
"nextId": "23",
|
||||
"nextId": "29",
|
||||
"nodes": [
|
||||
{
|
||||
"arrayLen": 1,
|
||||
@@ -13,7 +13,7 @@
|
||||
"parentId": "0",
|
||||
"refId": "0",
|
||||
"strLen": 64,
|
||||
"structTypeName": "Ball"
|
||||
"structTypeName": "ball"
|
||||
},
|
||||
{
|
||||
"arrayLen": 1,
|
||||
@@ -92,8 +92,8 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "8",
|
||||
"kind": "Hex32",
|
||||
"name": "field_34",
|
||||
"kind": "UInt32",
|
||||
"name": "color",
|
||||
"offset": 52,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
@@ -104,8 +104,8 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "9",
|
||||
"kind": "UInt32",
|
||||
"name": "color",
|
||||
"kind": "Float",
|
||||
"name": "radius",
|
||||
"offset": 56,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
@@ -129,7 +129,7 @@
|
||||
"elementKind": "UInt8",
|
||||
"id": "11",
|
||||
"kind": "Float",
|
||||
"name": "radius",
|
||||
"name": "mass",
|
||||
"offset": 64,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
@@ -140,7 +140,7 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "12",
|
||||
"kind": "Hex32",
|
||||
"kind": "Hex64",
|
||||
"name": "field_44",
|
||||
"offset": 68,
|
||||
"parentId": "1",
|
||||
@@ -152,9 +152,9 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "13",
|
||||
"kind": "Double",
|
||||
"name": "mass",
|
||||
"offset": 72,
|
||||
"kind": "Bool",
|
||||
"name": "bouncy",
|
||||
"offset": 76,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
@@ -164,9 +164,9 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "14",
|
||||
"kind": "Hex64",
|
||||
"name": "field_50",
|
||||
"offset": 80,
|
||||
"kind": "Hex8",
|
||||
"name": "field_4D",
|
||||
"offset": 77,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
@@ -176,9 +176,9 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "15",
|
||||
"kind": "Bool",
|
||||
"name": "bouncy",
|
||||
"offset": 88,
|
||||
"kind": "Hex16",
|
||||
"name": "field_4E",
|
||||
"offset": 78,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
@@ -188,9 +188,9 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "16",
|
||||
"kind": "Hex8",
|
||||
"name": "field_59",
|
||||
"offset": 89,
|
||||
"kind": "UInt32",
|
||||
"name": "color",
|
||||
"offset": 80,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
@@ -200,9 +200,9 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "17",
|
||||
"kind": "Hex16",
|
||||
"name": "field_5A",
|
||||
"offset": 90,
|
||||
"kind": "Hex32",
|
||||
"name": "field_54",
|
||||
"offset": 84,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
@@ -212,9 +212,9 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "18",
|
||||
"kind": "UInt32",
|
||||
"name": "bounceCount",
|
||||
"offset": 92,
|
||||
"kind": "Hex64",
|
||||
"name": "field_58",
|
||||
"offset": 88,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
@@ -224,7 +224,7 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "19",
|
||||
"kind": "Hex32",
|
||||
"kind": "Hex64",
|
||||
"name": "field_60",
|
||||
"offset": 96,
|
||||
"parentId": "1",
|
||||
@@ -236,12 +236,13 @@
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "20",
|
||||
"kind": "Hex64",
|
||||
"name": "field_68",
|
||||
"offset": 100,
|
||||
"parentId": "1",
|
||||
"kind": "Struct",
|
||||
"name": "aPhysics",
|
||||
"offset": 0,
|
||||
"parentId": "0",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
"strLen": 64,
|
||||
"structTypeName": "Physics"
|
||||
},
|
||||
{
|
||||
"arrayLen": 1,
|
||||
@@ -249,9 +250,9 @@
|
||||
"elementKind": "UInt8",
|
||||
"id": "21",
|
||||
"kind": "Hex64",
|
||||
"name": "field_70",
|
||||
"offset": 108,
|
||||
"parentId": "1",
|
||||
"name": "field_00",
|
||||
"offset": 0,
|
||||
"parentId": "20",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
},
|
||||
@@ -261,8 +262,80 @@
|
||||
"elementKind": "UInt8",
|
||||
"id": "22",
|
||||
"kind": "Hex64",
|
||||
"name": "field_08",
|
||||
"offset": 8,
|
||||
"parentId": "20",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
},
|
||||
{
|
||||
"arrayLen": 1,
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "23",
|
||||
"kind": "Hex64",
|
||||
"name": "field_10",
|
||||
"offset": 16,
|
||||
"parentId": "20",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
},
|
||||
{
|
||||
"arrayLen": 1,
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "24",
|
||||
"kind": "Hex64",
|
||||
"name": "field_18",
|
||||
"offset": 24,
|
||||
"parentId": "20",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
},
|
||||
{
|
||||
"arrayLen": 1,
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "25",
|
||||
"kind": "Hex64",
|
||||
"name": "field_20",
|
||||
"offset": 32,
|
||||
"parentId": "20",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
},
|
||||
{
|
||||
"arrayLen": 1,
|
||||
"collapsed": true,
|
||||
"elementKind": "UInt8",
|
||||
"id": "26",
|
||||
"kind": "Pointer64",
|
||||
"name": "physics",
|
||||
"offset": 104,
|
||||
"parentId": "1",
|
||||
"refId": "20",
|
||||
"strLen": 64
|
||||
},
|
||||
{
|
||||
"arrayLen": 1,
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "27",
|
||||
"kind": "Hex64",
|
||||
"name": "field_70",
|
||||
"offset": 112,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
},
|
||||
{
|
||||
"arrayLen": 1,
|
||||
"collapsed": false,
|
||||
"elementKind": "UInt8",
|
||||
"id": "28",
|
||||
"kind": "Hex64",
|
||||
"name": "field_78",
|
||||
"offset": 116,
|
||||
"offset": 120,
|
||||
"parentId": "1",
|
||||
"refId": "0",
|
||||
"strLen": 64
|
||||
|
||||
@@ -51,12 +51,11 @@ QString arrayTypeName(NodeKind elemKind, int count) {
|
||||
return elem + QStringLiteral("[") + QString::number(count) + QStringLiteral("]");
|
||||
}
|
||||
|
||||
// Pointer type string: "ptr64<void>" or "ptr64<StructName>"
|
||||
// Pointer type string: "void*" or "StructName*"
|
||||
QString pointerTypeName(NodeKind kind, const QString& targetName) {
|
||||
auto* m = kindMeta(kind);
|
||||
QString base = m ? QString::fromLatin1(m->typeName) : QStringLiteral("???");
|
||||
Q_UNUSED(kind);
|
||||
QString target = targetName.isEmpty() ? QStringLiteral("void") : targetName;
|
||||
return base + QStringLiteral("<") + target + QStringLiteral(">");
|
||||
return target + QStringLiteral("*");
|
||||
}
|
||||
|
||||
// ── Value formatting ──
|
||||
@@ -78,8 +77,20 @@ QString fmtUInt16(uint16_t v) { return hexVal(v); }
|
||||
QString fmtUInt32(uint32_t v) { return hexVal(v); }
|
||||
QString fmtUInt64(uint64_t v) { return hexVal(v); }
|
||||
|
||||
QString fmtFloat(float v) { return QString::number(v, 'g', 4); } // 4 sig figs keeps it short
|
||||
QString fmtDouble(double v) { return QString::number(v, 'g', 6); }
|
||||
QString fmtFloat(float v) {
|
||||
QString s = QString::number(v, 'g', 4);
|
||||
if (!s.contains('.') && !s.contains('e') && !s.contains('E'))
|
||||
s += QStringLiteral(".f");
|
||||
else
|
||||
s += QLatin1Char('f');
|
||||
return s;
|
||||
}
|
||||
QString fmtDouble(double v) {
|
||||
QString s = QString::number(v, 'g', 6);
|
||||
if (!s.contains('.') && !s.contains('e') && !s.contains('E'))
|
||||
s += QStringLiteral(".0");
|
||||
return s;
|
||||
}
|
||||
QString fmtBool(uint8_t v) { return v ? QStringLiteral("true") : QStringLiteral("false"); }
|
||||
|
||||
QString fmtPointer32(uint32_t v) {
|
||||
@@ -497,12 +508,15 @@ QByteArray parseValue(NodeKind kind, const QString& text, bool* ok) {
|
||||
case NodeKind::UInt32: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; qulonglong val = stripHex(s).toULongLong(ok,b); return parseIntChecked<uint32_t>(val, ok); }
|
||||
case NodeKind::UInt64: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; qulonglong val = stripHex(s).toULongLong(ok,b); return *ok ? toBytes<uint64_t>(val) : QByteArray{}; }
|
||||
case NodeKind::Float: {
|
||||
QString n = s; n.replace(',', '.'); // Accept EU decimal separator
|
||||
QString n = s.trimmed();
|
||||
if (n.endsWith('f', Qt::CaseInsensitive)) n.chop(1);
|
||||
n.replace(',', '.');
|
||||
float val = n.toFloat(ok);
|
||||
return *ok ? toBytes<float>(val) : QByteArray{};
|
||||
}
|
||||
case NodeKind::Double: {
|
||||
QString n = s; n.replace(',', '.'); // Accept EU decimal separator
|
||||
QString n = s.trimmed();
|
||||
n.replace(',', '.');
|
||||
double val = n.toDouble(ok);
|
||||
return *ok ? toBytes<double>(val) : QByteArray{};
|
||||
}
|
||||
|
||||
56
src/main.cpp
56
src/main.cpp
@@ -387,6 +387,14 @@ QMdiSubWindow* MainWindow::createTab(RcxDocument* doc) {
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-focus on first root struct (don't show all roots)
|
||||
for (const auto& n : doc->tree.nodes) {
|
||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||
ctrl->setViewRootId(n.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctrl->refresh();
|
||||
rebuildWorkspaceModel();
|
||||
return sub;
|
||||
@@ -408,7 +416,7 @@ void MainWindow::selfTest() {
|
||||
Node ball;
|
||||
ball.kind = NodeKind::Struct;
|
||||
ball.name = "aBall";
|
||||
ball.structTypeName = "Ball";
|
||||
ball.structTypeName = "ball";
|
||||
ball.parentId = 0;
|
||||
ball.offset = 0;
|
||||
int bi = doc->tree.addNode(ball);
|
||||
@@ -420,21 +428,39 @@ void MainWindow::selfTest() {
|
||||
{ Node n; n.kind = NodeKind::Vec3; n.name = "velocity"; n.parentId = ballId; n.offset = 32; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex32; n.name = "field_2C"; n.parentId = ballId; n.offset = 44; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Float; n.name = "speed"; n.parentId = ballId; n.offset = 48; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex32; n.name = "field_34"; n.parentId = ballId; n.offset = 52; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::UInt32; n.name = "color"; n.parentId = ballId; n.offset = 56; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::UInt32; n.name = "color"; n.parentId = ballId; n.offset = 52; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Float; n.name = "radius"; n.parentId = ballId; n.offset = 56; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex32; n.name = "field_3C"; n.parentId = ballId; n.offset = 60; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Float; n.name = "radius"; n.parentId = ballId; n.offset = 64; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex32; n.name = "field_44"; n.parentId = ballId; n.offset = 68; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Double; n.name = "mass"; n.parentId = ballId; n.offset = 72; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_50"; n.parentId = ballId; n.offset = 80; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Bool; n.name = "bouncy"; n.parentId = ballId; n.offset = 88; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex8; n.name = "field_59"; n.parentId = ballId; n.offset = 89; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex16; n.name = "field_5A"; n.parentId = ballId; n.offset = 90; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::UInt32; n.name = "bounceCount"; n.parentId = ballId; n.offset = 92; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex32; n.name = "field_60"; n.parentId = ballId; n.offset = 96; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_68"; n.parentId = ballId; n.offset = 100; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_70"; n.parentId = ballId; n.offset = 108; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_78"; n.parentId = ballId; n.offset = 116; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Float; n.name = "mass"; n.parentId = ballId; n.offset = 64; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_44"; n.parentId = ballId; n.offset = 68; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Bool; n.name = "bouncy"; n.parentId = ballId; n.offset = 76; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex8; n.name = "field_4D"; n.parentId = ballId; n.offset = 77; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex16; n.name = "field_4E"; n.parentId = ballId; n.offset = 78; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::UInt32; n.name = "color"; n.parentId = ballId; n.offset = 80; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex32; n.name = "field_54"; n.parentId = ballId; n.offset = 84; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_58"; n.parentId = ballId; n.offset = 88; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_60"; n.parentId = ballId; n.offset = 96; doc->tree.addNode(n); }
|
||||
|
||||
// Physics struct (defined at root level)
|
||||
Node phys;
|
||||
phys.kind = NodeKind::Struct;
|
||||
phys.name = "aPhysics";
|
||||
phys.structTypeName = "Physics";
|
||||
phys.parentId = 0;
|
||||
phys.offset = 0;
|
||||
int pi = doc->tree.addNode(phys);
|
||||
uint64_t physId = doc->tree.nodes[pi].id;
|
||||
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_00"; n.parentId = physId; n.offset = 0; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_08"; n.parentId = physId; n.offset = 8; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_10"; n.parentId = physId; n.offset = 16; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_18"; n.parentId = physId; n.offset = 24; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_20"; n.parentId = physId; n.offset = 32; doc->tree.addNode(n); }
|
||||
|
||||
// Pointer to Physics in ball struct
|
||||
{ Node n; n.kind = NodeKind::Pointer64; n.name = "physics"; n.parentId = ballId; n.offset = 104; n.refId = physId; n.collapsed = true; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_70"; n.parentId = ballId; n.offset = 112; doc->tree.addNode(n); }
|
||||
{ Node n; n.kind = NodeKind::Hex64; n.name = "field_78"; n.parentId = ballId; n.offset = 120; doc->tree.addNode(n); }
|
||||
|
||||
doc->save(demoPath);
|
||||
doc->load(demoPath);
|
||||
|
||||
Reference in New Issue
Block a user