diff --git a/src/controller.cpp b/src/controller.cpp index 29b903b..eaf7952 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -919,6 +919,11 @@ void RcxController::setNodeValue(int nodeIdx, int subLine, const QString& text, addr += subLine * 4; editKind = NodeKind::Float; } + // For Mat4x4 components: subLine encodes flat index (row*4 + col), 0-15 + if (node.kind == NodeKind::Mat4x4 && subLine >= 0 && subLine < 16) { + addr += subLine * 4; + editKind = NodeKind::Float; + } bool ok; QByteArray newBytes; @@ -1068,7 +1073,7 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx, menu.addSeparator(); bool isEditable = node.kind != NodeKind::Struct && node.kind != NodeKind::Array - && node.kind != NodeKind::Padding && node.kind != NodeKind::Mat4x4 + && node.kind != NodeKind::Padding && m_doc->provider->isWritable(); if (isEditable) { menu.addAction(icon("edit.svg"), "Edit &Value\tEnter", [editor, line]() { diff --git a/src/core.h b/src/core.h index d949bb7..2154768 100644 --- a/src/core.h +++ b/src/core.h @@ -134,6 +134,9 @@ inline constexpr bool isHexNode(NodeKind k) { inline constexpr bool isVectorKind(NodeKind k) { return k == NodeKind::Vec2 || k == NodeKind::Vec3 || k == NodeKind::Vec4; } +inline constexpr bool isMatrixKind(NodeKind k) { + return k == NodeKind::Mat4x4; +} inline QStringList allTypeNamesForUI(bool stripBrackets = false) { QStringList out; diff --git a/src/editor.cpp b/src/editor.cpp index 44bd2ba..b401b45 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -993,7 +993,7 @@ RcxEditor::HitInfo RcxEditor::hitTest(const QPoint& vp) const { static bool hitTestTarget(QsciScintilla* sci, const QVector& meta, const QPoint& viewportPos, - int& outLine, EditTarget& outTarget) + int& outLine, int& outCol, EditTarget& outTarget) { long pos = sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMPOINTCLOSE, (unsigned long)viewportPos.x(), (long)viewportPos.y()); @@ -1002,6 +1002,7 @@ static bool hitTestTarget(QsciScintilla* sci, (unsigned long)pos); int col = (int)sci->SendScintilla(QsciScintillaBase::SCI_GETCOLUMN, (unsigned long)pos); + outCol = col; if (line < 0 || line >= meta.size()) return false; QString lineText = getLineText(sci, line); @@ -1183,12 +1184,12 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) { } // CommandRow: try chevron/ADDR edit or consume if (h.nodeId == kCommandRowId) { - int tLine; EditTarget t; - if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, t)) { + int tLine, tCol; EditTarget t; + if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, tCol, t)) { if (t == EditTarget::TypeSelector) emit typeSelectorRequested(); else - beginInlineEdit(t, tLine); + beginInlineEdit(t, tLine, tCol); } return true; // consume all CommandRow clicks } @@ -1197,11 +1198,11 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) { bool plain = !(me->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier)); // Single-click on editable token of already-selected node → edit - int tLine; EditTarget t; - if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, t)) { + int tLine, tCol; EditTarget t; + if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, tCol, t)) { if (alreadySelected && plain) { m_pendingClickNodeId = 0; - return beginInlineEdit(t, tLine); + return beginInlineEdit(t, tLine, tCol); } } @@ -1276,14 +1277,14 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) { if (obj == m_sci->viewport() && !m_editState.active && event->type() == QEvent::MouseButtonDblClick) { auto* me = static_cast(event); - int line; EditTarget t; - if (hitTestTarget(m_sci, m_meta, me->pos(), line, t)) { + int line, tCol; EditTarget t; + if (hitTestTarget(m_sci, m_meta, me->pos(), line, tCol, t)) { m_pendingClickNodeId = 0; // cancel deferred selection change // Narrow selection to this node before editing auto h = hitTest(me->pos()); if (h.nodeId != 0 && h.nodeId != kCommandRowId) emit nodeClicked(h.line, h.nodeId, Qt::NoModifier); - return beginInlineEdit(t, line); + return beginInlineEdit(t, line, tCol); } return true; // consume even on miss (prevent QScintilla word-select) } @@ -1469,14 +1470,14 @@ bool RcxEditor::handleEditKey(QKeyEvent* ke) { // ── Begin inline edit ── -bool RcxEditor::beginInlineEdit(EditTarget target, int line) { +bool RcxEditor::beginInlineEdit(EditTarget target, int line, int col) { if (target == EditTarget::TypeSelector) return false; // handled by popup, not inline edit // Array element type and pointer target: handled by TypeSelectorPopup, not inline edit if (target == EditTarget::ArrayElementType || target == EditTarget::PointerTarget) { if (line < 0) { - int col; - m_sci->getCursorPosition(&line, &col); + int c; + m_sci->getCursorPosition(&line, &c); } auto* lm = metaForLine(line); if (!lm) return false; @@ -1498,10 +1499,11 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line) { m_hintLine = -1; if (line >= 0) { - m_sci->setCursorPosition(line, 0); + m_sci->setCursorPosition(line, col >= 0 ? col : 0); + } + if (col < 0) { + m_sci->getCursorPosition(&line, &col); } - int col; - m_sci->getCursorPosition(&line, &col); auto* lm = metaForLine(line); if (!lm) return false; // Allow nodeIdx=-1 only for CommandRow editing (command bar) @@ -1522,28 +1524,23 @@ 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) + int vecComponent = 0; // which vector/matrix component - // 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 + // Helper: parse comma-separated components, narrow span to clicked one + auto narrowToComponent = [&](const QString& inner, int innerAbsStart) { QVector compStarts, compEnds; - int pos = 0; - for (int i = 0; i < trimmed.size(); i++) { - if (trimmed[i] == ',') { + for (int i = 0; i < inner.size(); i++) { + if (inner[i] == ',') { compEnds.append(i); - // skip ", " separator int next = i + 1; - while (next < trimmed.size() && trimmed[next] == ' ') next++; + while (next < inner.size() && inner[next] == ' ') next++; compStarts.append(next); } } compStarts.prepend(0); - compEnds.append(trimmed.size()); + compEnds.append(inner.size()); - // Find which component the cursor is in - int relCol = cursorCol - norm.start; + int relCol = col - innerAbsStart; vecComponent = 0; for (int i = 0; i < compStarts.size(); i++) { if (relCol >= compStarts[i] && (i == compStarts.size() - 1 || relCol < compStarts[i + 1])) @@ -1551,27 +1548,47 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line) { } 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 + int cStart = innerAbsStart + compStarts[vecComponent]; + int cEnd = innerAbsStart + compEnds[vecComponent]; while (cEnd > cStart && lineText[cEnd - 1] == ' ') cEnd--; norm.start = cStart; norm.end = cEnd; trimmed = lineText.mid(norm.start, norm.end - norm.start); + }; + + // For vector value editing: narrow span to the clicked component + if (target == EditTarget::Value && isVectorKind(lm->nodeKind)) { + narrowToComponent(trimmed, norm.start); + } + + // For Mat4x4 value editing: skip "rowN [...]" and narrow to clicked component + if (target == EditTarget::Value && isMatrixKind(lm->nodeKind)) { + int bracketOpen = trimmed.indexOf('['); + int bracketClose = trimmed.lastIndexOf(']'); + if (bracketOpen < 0 || bracketClose <= bracketOpen) + return false; + QString inner = trimmed.mid(bracketOpen + 1, bracketClose - bracketOpen - 1); + int innerAbsStart = norm.start + bracketOpen + 1; + narrowToComponent(inner, innerAbsStart); } m_editState.active = true; m_editState.line = line; m_editState.nodeIdx = lm->nodeIdx; - m_editState.subLine = isVectorKind(lm->nodeKind) ? vecComponent : lm->subLine; + m_editState.subLine = 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 (isVectorKind(lm->nodeKind)) + if (isVectorKind(lm->nodeKind)) { + m_editState.subLine = vecComponent; m_editState.editKind = NodeKind::Float; + } + if (isMatrixKind(lm->nodeKind)) { + m_editState.subLine = lm->subLine * 4 + vecComponent; // flat index 0-15 + m_editState.editKind = NodeKind::Float; + } // Store fixed comment column position for value editing // Use large lineLength so commentCol is always computed (padding added dynamically) @@ -2040,8 +2057,8 @@ void RcxEditor::applyHoverCursor() { } auto h = hitTest(m_lastHoverPos); - int line; EditTarget t; - bool tokenHit = hitTestTarget(m_sci, m_meta, m_lastHoverPos, line, t); + int line, hCol; EditTarget t; + bool tokenHit = hitTestTarget(m_sci, m_meta, m_lastHoverPos, line, hCol, t); // Skip hover span on footer lines (nothing editable) int hoverLine = h.line; diff --git a/src/editor.h b/src/editor.h index b04c624..8450f98 100644 --- a/src/editor.h +++ b/src/editor.h @@ -41,7 +41,7 @@ public: // ── Inline editing ── bool isEditing() const { return m_editState.active; } - bool beginInlineEdit(EditTarget target, int line = -1); + bool beginInlineEdit(EditTarget target, int line = -1, int col = -1); void cancelInlineEdit(); void applySelectionOverlay(const QSet& selIds); diff --git a/src/format.cpp b/src/format.cpp index 18fc651..0c32c0d 100644 --- a/src/format.cpp +++ b/src/format.cpp @@ -1,4 +1,5 @@ #include "core.h" +#include #include #include @@ -80,8 +81,24 @@ QString fmtUInt32(uint32_t v) { return hexVal(v); } QString fmtUInt64(uint64_t v) { return hexVal(v); } QString fmtFloat(float v) { - QString s = QString::number(v, 'g', 4); - if (!s.contains('.') && !s.contains('e') && !s.contains('E')) + if (std::isnan(v)) return QStringLiteral("NaN"); + if (std::isinf(v)) return v > 0 ? QStringLiteral("inff") : QStringLiteral("-inff"); + + // 6 significant digits — covers full single-precision range + QString s = QString::number(v, 'g', 6); + + // If 'g' chose scientific notation, reformat as plain decimal + if (s.contains('e') || s.contains('E')) { + s = QString::number(v, 'f', 8); + if (s.contains('.')) { + int i = s.size() - 1; + while (i > 0 && s[i] == '0') i--; + if (s[i] == '.') i++; // keep at least one decimal digit + s.truncate(i + 1); + } + } + + if (!s.contains('.')) s += QStringLiteral(".f"); else s += QLatin1Char('f'); @@ -268,7 +285,7 @@ static QString readValueImpl(const Node& node, const Provider& prov, case NodeKind::Mat4x4: { if (!display) return {}; // not editable as single value if (subLine < 0 || subLine >= 4) return QStringLiteral("?"); - QString line = QStringLiteral("["); + QString line = QStringLiteral("row%1 [").arg(subLine); for (int c = 0; c < 4; c++) { if (c > 0) line += QStringLiteral(", "); line += fmtFloat(prov.readF32(addr + (subLine * 4 + c) * 4)).trimmed();