mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Mat4x4 row labels, no scientific notation, per-component inline editing
- Add row0..row3 labels to Mat4x4 grid display with aligned columns - Rewrite fmtFloat() to never use scientific notation (plain decimal + trim) - Enable per-component inline editing for all 16 Mat4x4 floats - Fix click-to-edit always selecting first component (thread column from hitTestTarget) - Add isMatrixKind() helper, remove Mat4x4 from context menu edit exclusion Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -919,6 +919,11 @@ void RcxController::setNodeValue(int nodeIdx, int subLine, const QString& text,
|
|||||||
addr += subLine * 4;
|
addr += subLine * 4;
|
||||||
editKind = NodeKind::Float;
|
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;
|
bool ok;
|
||||||
QByteArray newBytes;
|
QByteArray newBytes;
|
||||||
@@ -1068,7 +1073,7 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
bool isEditable = node.kind != NodeKind::Struct && node.kind != NodeKind::Array
|
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();
|
&& m_doc->provider->isWritable();
|
||||||
if (isEditable) {
|
if (isEditable) {
|
||||||
menu.addAction(icon("edit.svg"), "Edit &Value\tEnter", [editor, line]() {
|
menu.addAction(icon("edit.svg"), "Edit &Value\tEnter", [editor, line]() {
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ inline constexpr bool isHexNode(NodeKind k) {
|
|||||||
inline constexpr bool isVectorKind(NodeKind k) {
|
inline constexpr bool isVectorKind(NodeKind k) {
|
||||||
return k == NodeKind::Vec2 || k == NodeKind::Vec3 || k == NodeKind::Vec4;
|
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) {
|
inline QStringList allTypeNamesForUI(bool stripBrackets = false) {
|
||||||
QStringList out;
|
QStringList out;
|
||||||
|
|||||||
@@ -993,7 +993,7 @@ RcxEditor::HitInfo RcxEditor::hitTest(const QPoint& vp) const {
|
|||||||
static bool hitTestTarget(QsciScintilla* sci,
|
static bool hitTestTarget(QsciScintilla* sci,
|
||||||
const QVector<LineMeta>& meta,
|
const QVector<LineMeta>& meta,
|
||||||
const QPoint& viewportPos,
|
const QPoint& viewportPos,
|
||||||
int& outLine, EditTarget& outTarget)
|
int& outLine, int& outCol, EditTarget& outTarget)
|
||||||
{
|
{
|
||||||
long pos = sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMPOINTCLOSE,
|
long pos = sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMPOINTCLOSE,
|
||||||
(unsigned long)viewportPos.x(), (long)viewportPos.y());
|
(unsigned long)viewportPos.x(), (long)viewportPos.y());
|
||||||
@@ -1002,6 +1002,7 @@ static bool hitTestTarget(QsciScintilla* sci,
|
|||||||
(unsigned long)pos);
|
(unsigned long)pos);
|
||||||
int col = (int)sci->SendScintilla(QsciScintillaBase::SCI_GETCOLUMN,
|
int col = (int)sci->SendScintilla(QsciScintillaBase::SCI_GETCOLUMN,
|
||||||
(unsigned long)pos);
|
(unsigned long)pos);
|
||||||
|
outCol = col;
|
||||||
if (line < 0 || line >= meta.size()) return false;
|
if (line < 0 || line >= meta.size()) return false;
|
||||||
|
|
||||||
QString lineText = getLineText(sci, line);
|
QString lineText = getLineText(sci, line);
|
||||||
@@ -1183,12 +1184,12 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
}
|
}
|
||||||
// CommandRow: try chevron/ADDR edit or consume
|
// CommandRow: try chevron/ADDR edit or consume
|
||||||
if (h.nodeId == kCommandRowId) {
|
if (h.nodeId == kCommandRowId) {
|
||||||
int tLine; EditTarget t;
|
int tLine, tCol; EditTarget t;
|
||||||
if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, t)) {
|
if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, tCol, t)) {
|
||||||
if (t == EditTarget::TypeSelector)
|
if (t == EditTarget::TypeSelector)
|
||||||
emit typeSelectorRequested();
|
emit typeSelectorRequested();
|
||||||
else
|
else
|
||||||
beginInlineEdit(t, tLine);
|
beginInlineEdit(t, tLine, tCol);
|
||||||
}
|
}
|
||||||
return true; // consume all CommandRow clicks
|
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));
|
bool plain = !(me->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier));
|
||||||
|
|
||||||
// Single-click on editable token of already-selected node → edit
|
// Single-click on editable token of already-selected node → edit
|
||||||
int tLine; EditTarget t;
|
int tLine, tCol; EditTarget t;
|
||||||
if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, t)) {
|
if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, tCol, t)) {
|
||||||
if (alreadySelected && plain) {
|
if (alreadySelected && plain) {
|
||||||
m_pendingClickNodeId = 0;
|
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
|
if (obj == m_sci->viewport() && !m_editState.active
|
||||||
&& event->type() == QEvent::MouseButtonDblClick) {
|
&& event->type() == QEvent::MouseButtonDblClick) {
|
||||||
auto* me = static_cast<QMouseEvent*>(event);
|
auto* me = static_cast<QMouseEvent*>(event);
|
||||||
int line; EditTarget t;
|
int line, tCol; EditTarget t;
|
||||||
if (hitTestTarget(m_sci, m_meta, me->pos(), line, t)) {
|
if (hitTestTarget(m_sci, m_meta, me->pos(), line, tCol, t)) {
|
||||||
m_pendingClickNodeId = 0; // cancel deferred selection change
|
m_pendingClickNodeId = 0; // cancel deferred selection change
|
||||||
// Narrow selection to this node before editing
|
// Narrow selection to this node before editing
|
||||||
auto h = hitTest(me->pos());
|
auto h = hitTest(me->pos());
|
||||||
if (h.nodeId != 0 && h.nodeId != kCommandRowId)
|
if (h.nodeId != 0 && h.nodeId != kCommandRowId)
|
||||||
emit nodeClicked(h.line, h.nodeId, Qt::NoModifier);
|
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)
|
return true; // consume even on miss (prevent QScintilla word-select)
|
||||||
}
|
}
|
||||||
@@ -1469,14 +1470,14 @@ bool RcxEditor::handleEditKey(QKeyEvent* ke) {
|
|||||||
|
|
||||||
// ── Begin inline edit ──
|
// ── 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
|
if (target == EditTarget::TypeSelector) return false; // handled by popup, not inline edit
|
||||||
|
|
||||||
// Array element type and pointer target: handled by TypeSelectorPopup, not inline edit
|
// Array element type and pointer target: handled by TypeSelectorPopup, not inline edit
|
||||||
if (target == EditTarget::ArrayElementType || target == EditTarget::PointerTarget) {
|
if (target == EditTarget::ArrayElementType || target == EditTarget::PointerTarget) {
|
||||||
if (line < 0) {
|
if (line < 0) {
|
||||||
int col;
|
int c;
|
||||||
m_sci->getCursorPosition(&line, &col);
|
m_sci->getCursorPosition(&line, &c);
|
||||||
}
|
}
|
||||||
auto* lm = metaForLine(line);
|
auto* lm = metaForLine(line);
|
||||||
if (!lm) return false;
|
if (!lm) return false;
|
||||||
@@ -1498,10 +1499,11 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line) {
|
|||||||
m_hintLine = -1;
|
m_hintLine = -1;
|
||||||
|
|
||||||
if (line >= 0) {
|
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);
|
auto* lm = metaForLine(line);
|
||||||
if (!lm) return false;
|
if (!lm) return false;
|
||||||
// Allow nodeIdx=-1 only for CommandRow editing (command bar)
|
// 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);
|
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
|
// Helper: parse comma-separated components, narrow span to clicked one
|
||||||
if (target == EditTarget::Value && isVectorKind(lm->nodeKind)) {
|
auto narrowToComponent = [&](const QString& inner, int innerAbsStart) {
|
||||||
int cursorCol = col; // col from getCursorPosition above
|
|
||||||
// Find comma positions within the value span to identify components
|
|
||||||
QVector<int> compStarts, compEnds;
|
QVector<int> compStarts, compEnds;
|
||||||
int pos = 0;
|
for (int i = 0; i < inner.size(); i++) {
|
||||||
for (int i = 0; i < trimmed.size(); i++) {
|
if (inner[i] == ',') {
|
||||||
if (trimmed[i] == ',') {
|
|
||||||
compEnds.append(i);
|
compEnds.append(i);
|
||||||
// skip ", " separator
|
|
||||||
int next = i + 1;
|
int next = i + 1;
|
||||||
while (next < trimmed.size() && trimmed[next] == ' ') next++;
|
while (next < inner.size() && inner[next] == ' ') next++;
|
||||||
compStarts.append(next);
|
compStarts.append(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compStarts.prepend(0);
|
compStarts.prepend(0);
|
||||||
compEnds.append(trimmed.size());
|
compEnds.append(inner.size());
|
||||||
|
|
||||||
// Find which component the cursor is in
|
int relCol = col - innerAbsStart;
|
||||||
int relCol = cursorCol - norm.start;
|
|
||||||
vecComponent = 0;
|
vecComponent = 0;
|
||||||
for (int i = 0; i < compStarts.size(); i++) {
|
for (int i = 0; i < compStarts.size(); i++) {
|
||||||
if (relCol >= compStarts[i] && (i == compStarts.size() - 1 || relCol < compStarts[i + 1]))
|
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;
|
if (vecComponent >= compStarts.size()) vecComponent = compStarts.size() - 1;
|
||||||
|
|
||||||
// Narrow span to just this component
|
int cStart = innerAbsStart + compStarts[vecComponent];
|
||||||
int cStart = norm.start + compStarts[vecComponent];
|
int cEnd = innerAbsStart + compEnds[vecComponent];
|
||||||
int cEnd = norm.start + compEnds[vecComponent];
|
|
||||||
// Trim trailing spaces from component
|
|
||||||
while (cEnd > cStart && lineText[cEnd - 1] == ' ') cEnd--;
|
while (cEnd > cStart && lineText[cEnd - 1] == ' ') cEnd--;
|
||||||
norm.start = cStart;
|
norm.start = cStart;
|
||||||
norm.end = cEnd;
|
norm.end = cEnd;
|
||||||
trimmed = lineText.mid(norm.start, norm.end - norm.start);
|
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.active = true;
|
||||||
m_editState.line = line;
|
m_editState.line = line;
|
||||||
m_editState.nodeIdx = lm->nodeIdx;
|
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.target = target;
|
||||||
m_editState.spanStart = norm.start;
|
m_editState.spanStart = norm.start;
|
||||||
m_editState.original = trimmed;
|
m_editState.original = trimmed;
|
||||||
m_editState.linelenAfterReplace = lineText.size();
|
m_editState.linelenAfterReplace = lineText.size();
|
||||||
m_editState.editKind = lm->nodeKind;
|
m_editState.editKind = lm->nodeKind;
|
||||||
if (isVectorKind(lm->nodeKind))
|
if (isVectorKind(lm->nodeKind)) {
|
||||||
|
m_editState.subLine = vecComponent;
|
||||||
m_editState.editKind = NodeKind::Float;
|
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
|
// Store fixed comment column position for value editing
|
||||||
// Use large lineLength so commentCol is always computed (padding added dynamically)
|
// Use large lineLength so commentCol is always computed (padding added dynamically)
|
||||||
@@ -2040,8 +2057,8 @@ void RcxEditor::applyHoverCursor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto h = hitTest(m_lastHoverPos);
|
auto h = hitTest(m_lastHoverPos);
|
||||||
int line; EditTarget t;
|
int line, hCol; EditTarget t;
|
||||||
bool tokenHit = hitTestTarget(m_sci, m_meta, m_lastHoverPos, line, t);
|
bool tokenHit = hitTestTarget(m_sci, m_meta, m_lastHoverPos, line, hCol, t);
|
||||||
|
|
||||||
// Skip hover span on footer lines (nothing editable)
|
// Skip hover span on footer lines (nothing editable)
|
||||||
int hoverLine = h.line;
|
int hoverLine = h.line;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public:
|
|||||||
|
|
||||||
// ── Inline editing ──
|
// ── Inline editing ──
|
||||||
bool isEditing() const { return m_editState.active; }
|
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 cancelInlineEdit();
|
||||||
|
|
||||||
void applySelectionOverlay(const QSet<uint64_t>& selIds);
|
void applySelectionOverlay(const QSet<uint64_t>& selIds);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "core.h"
|
#include "core.h"
|
||||||
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
@@ -80,8 +81,24 @@ QString fmtUInt32(uint32_t v) { return hexVal(v); }
|
|||||||
QString fmtUInt64(uint64_t v) { return hexVal(v); }
|
QString fmtUInt64(uint64_t v) { return hexVal(v); }
|
||||||
|
|
||||||
QString fmtFloat(float v) {
|
QString fmtFloat(float v) {
|
||||||
QString s = QString::number(v, 'g', 4);
|
if (std::isnan(v)) return QStringLiteral("NaN");
|
||||||
if (!s.contains('.') && !s.contains('e') && !s.contains('E'))
|
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");
|
s += QStringLiteral(".f");
|
||||||
else
|
else
|
||||||
s += QLatin1Char('f');
|
s += QLatin1Char('f');
|
||||||
@@ -268,7 +285,7 @@ static QString readValueImpl(const Node& node, const Provider& prov,
|
|||||||
case NodeKind::Mat4x4: {
|
case NodeKind::Mat4x4: {
|
||||||
if (!display) return {}; // not editable as single value
|
if (!display) return {}; // not editable as single value
|
||||||
if (subLine < 0 || subLine >= 4) return QStringLiteral("?");
|
if (subLine < 0 || subLine >= 4) return QStringLiteral("?");
|
||||||
QString line = QStringLiteral("[");
|
QString line = QStringLiteral("row%1 [").arg(subLine);
|
||||||
for (int c = 0; c < 4; c++) {
|
for (int c = 0; c < 4; c++) {
|
||||||
if (c > 0) line += QStringLiteral(", ");
|
if (c > 0) line += QStringLiteral(", ");
|
||||||
line += fmtFloat(prov.readF32(addr + (subLine * 4 + c) * 4)).trimmed();
|
line += fmtFloat(prov.readF32(addr + (subLine * 4 + c) * 4)).trimmed();
|
||||||
|
|||||||
Reference in New Issue
Block a user