mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Inline edit UX improvements: selection clamping, auto-select, validation fixes
- Constrain selection/cursor to edit span boundaries during inline edit - Auto-select entire text when entering edit mode (Name, Value, Type) - Double-click during edit selects entire editable text - Fix vector component validation (subLine >= 0 for x component) - Accept EU decimal separator (comma) for float parsing - Darker selection highlight (35,35,35) vs hover (43,43,43) - Remove blue text indicator, use hidden style - Fix validation error message display Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -474,7 +474,7 @@ enum class EditTarget { Name, Type, Value };
|
||||
inline constexpr int kFoldCol = 3; // 3-char fold indicator prefix per line
|
||||
inline constexpr int kColType = 10;
|
||||
inline constexpr int kColName = 22;
|
||||
inline constexpr int kColValue = 8;
|
||||
inline constexpr int kColValue = 32;
|
||||
inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits
|
||||
inline constexpr int kSepWidth = 2;
|
||||
|
||||
|
||||
@@ -86,6 +86,9 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) {
|
||||
if (m_editState.target == EditTarget::Value)
|
||||
QTimer::singleShot(0, this, &RcxEditor::validateEditLive);
|
||||
});
|
||||
|
||||
connect(m_sci, &QsciScintilla::selectionChanged,
|
||||
this, &RcxEditor::clampEditSelection);
|
||||
}
|
||||
|
||||
void RcxEditor::setupScintilla() {
|
||||
@@ -116,11 +119,9 @@ void RcxEditor::setupScintilla() {
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELFORE, (long)0, (long)0);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELBACK, (long)0, (long)0);
|
||||
|
||||
// Editable-field link-style indicator (colored text)
|
||||
// Editable-field indicator - set to HIDDEN (no visual, avoids INDIC_PLAIN underline)
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
||||
IND_EDITABLE, 17 /*INDIC_TEXTFORE*/);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
|
||||
IND_EDITABLE, QColor("#569cd6"));
|
||||
IND_EDITABLE, 5 /*INDIC_HIDDEN*/);
|
||||
|
||||
// Hex/Padding node dim indicator — overrides text color to gray
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
|
||||
@@ -232,7 +233,7 @@ void RcxEditor::setupMarkers() {
|
||||
|
||||
// M_SELECTED (7): full-row selection highlight (higher = wins over hover)
|
||||
m_sci->markerDefine(QsciScintilla::Background, M_SELECTED);
|
||||
m_sci->setMarkerBackgroundColor(QColor(53, 53, 53), M_SELECTED);
|
||||
m_sci->setMarkerBackgroundColor(QColor(35, 35, 35), M_SELECTED);
|
||||
}
|
||||
|
||||
void RcxEditor::allocateMarginStyles() {
|
||||
@@ -753,9 +754,11 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) {
|
||||
m_pendingClickNodeId = 0;
|
||||
}
|
||||
}
|
||||
// Block double/triple-click during edit mode (prevents word/line selection)
|
||||
// Double-click during edit mode: select entire editable text
|
||||
if (obj == m_sci->viewport() && m_editState.active
|
||||
&& event->type() == QEvent::MouseButtonDblClick) {
|
||||
m_sci->setSelection(m_editState.line, m_editState.spanStart,
|
||||
m_editState.line, editEndCol());
|
||||
return true;
|
||||
}
|
||||
if (obj == m_sci->viewport() && !m_editState.active
|
||||
@@ -931,7 +934,7 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line) {
|
||||
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)
|
||||
lm->nodeKind == NodeKind::Vec4) && lm->subLine >= 0)
|
||||
m_editState.editKind = NodeKind::Float;
|
||||
|
||||
// Store fixed comment column position for value editing
|
||||
@@ -964,7 +967,7 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line) {
|
||||
(unsigned long)line);
|
||||
long posStart = lineStart + m_editState.spanStart;
|
||||
long posEnd = posStart + trimmed.toUtf8().size();
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSEL, posEnd, posEnd);
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSEL, posStart, posEnd);
|
||||
|
||||
// Show initial edit hint in comment column
|
||||
if (target == EditTarget::Value)
|
||||
@@ -982,6 +985,50 @@ int RcxEditor::editEndCol() const {
|
||||
return m_editState.spanStart + m_editState.original.size() + delta;
|
||||
}
|
||||
|
||||
void RcxEditor::clampEditSelection() {
|
||||
if (!m_editState.active) return;
|
||||
|
||||
static bool s_clamping = false;
|
||||
if (s_clamping) return;
|
||||
s_clamping = true;
|
||||
|
||||
int selStartLine, selStartCol, selEndLine, selEndCol;
|
||||
m_sci->getSelection(&selStartLine, &selStartCol, &selEndLine, &selEndCol);
|
||||
|
||||
int editEnd = editEndCol();
|
||||
bool isCursor = (selStartLine == selEndLine && selStartCol == selEndCol);
|
||||
|
||||
if (isCursor) {
|
||||
// Cursor positioning (no selection) - only clamp if outside bounds
|
||||
if (selStartLine != m_editState.line ||
|
||||
selStartCol < m_editState.spanStart || selStartCol > editEnd) {
|
||||
int clampedCol = qBound(m_editState.spanStart, selStartCol, editEnd);
|
||||
m_sci->setCursorPosition(m_editState.line, clampedCol);
|
||||
}
|
||||
} else {
|
||||
// Actual selection - clamp both ends to edit span
|
||||
bool clamped = false;
|
||||
|
||||
// Force to edit line
|
||||
if (selStartLine != m_editState.line || selEndLine != m_editState.line) {
|
||||
m_sci->setSelection(m_editState.line, m_editState.spanStart,
|
||||
m_editState.line, editEnd);
|
||||
s_clamping = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (selStartCol < m_editState.spanStart) { selStartCol = m_editState.spanStart; clamped = true; }
|
||||
if (selEndCol < m_editState.spanStart) { selEndCol = m_editState.spanStart; clamped = true; }
|
||||
if (selStartCol > editEnd) { selStartCol = editEnd; clamped = true; }
|
||||
if (selEndCol > editEnd) { selEndCol = editEnd; clamped = true; }
|
||||
|
||||
if (clamped)
|
||||
m_sci->setSelection(selStartLine, selStartCol, selEndLine, selEndCol);
|
||||
}
|
||||
|
||||
s_clamping = false;
|
||||
}
|
||||
|
||||
// ── Commit inline edit ──
|
||||
|
||||
void RcxEditor::commitInlineEdit() {
|
||||
@@ -1015,11 +1062,7 @@ void RcxEditor::showTypeAutocomplete() {
|
||||
if (!m_editState.active || m_editState.target != EditTarget::Type)
|
||||
return;
|
||||
|
||||
// Collapse selection to start — old type text stays visible
|
||||
long lineStart = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
|
||||
(unsigned long)m_editState.line);
|
||||
long posStart = lineStart + m_editState.spanStart;
|
||||
m_sci->SendScintilla(QsciScintillaBase::SCI_GOTOPOS, posStart);
|
||||
// Selection stays intact - typing/autocomplete will replace selected text
|
||||
|
||||
// Build list from typeName (matches what the editor displays)
|
||||
QByteArray list = allTypeNamesForUI().join(' ').toUtf8();
|
||||
@@ -1179,7 +1222,7 @@ void RcxEditor::validateEditLive() {
|
||||
} else {
|
||||
if (isSelected) m_sci->markerDelete(m_editState.line, M_SELECTED);
|
||||
m_sci->markerAdd(m_editState.line, M_ERR);
|
||||
if (stateChanged) setEditComment("! " + text);
|
||||
if (stateChanged) setEditComment("! " + errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ private:
|
||||
void applyHoverHighlight();
|
||||
void validateEditLive();
|
||||
void setEditComment(const QString& comment);
|
||||
void clampEditSelection();
|
||||
|
||||
// ── Refactored helpers ──
|
||||
struct HitInfo { int line = -1; int col = -1; uint64_t nodeId = 0; bool inFoldCol = false; };
|
||||
|
||||
@@ -53,8 +53,8 @@ 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, 'f', 3); }
|
||||
QString fmtDouble(double v) { return QString::number(v, 'f', 6); }
|
||||
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 fmtBool(uint8_t v) { return v ? QStringLiteral("true") : QStringLiteral("false"); }
|
||||
|
||||
QString fmtPointer32(uint32_t v) {
|
||||
@@ -351,11 +351,13 @@ QByteArray parseValue(NodeKind kind, const QString& text, bool* ok) {
|
||||
case NodeKind::UInt32: { int b = s.startsWith("0x",Qt::CaseInsensitive)?16:10; uint val = stripHex(s).toUInt(ok,b); return *ok ? toBytes<uint32_t>(val) : QByteArray{}; }
|
||||
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: {
|
||||
float val = s.toFloat(ok);
|
||||
QString n = s; n.replace(',', '.'); // Accept EU decimal separator
|
||||
float val = n.toFloat(ok);
|
||||
return *ok ? toBytes<float>(val) : QByteArray{};
|
||||
}
|
||||
case NodeKind::Double: {
|
||||
double val = s.toDouble(ok);
|
||||
QString n = s; n.replace(',', '.'); // Accept EU decimal separator
|
||||
double val = n.toDouble(ok);
|
||||
return *ok ? toBytes<double>(val) : QByteArray{};
|
||||
}
|
||||
case NodeKind::Bool: {
|
||||
@@ -433,6 +435,11 @@ QString validateValue(NodeKind kind, const QString& text) {
|
||||
parseValue(kind, text, &ok);
|
||||
if (ok) return {};
|
||||
|
||||
// Type-appropriate error messages
|
||||
bool isFloatKind = (kind == NodeKind::Float || kind == NodeKind::Double);
|
||||
if (isFloatKind)
|
||||
return QStringLiteral("invalid number");
|
||||
|
||||
// Return byte-capacity max based on type size
|
||||
const auto* m = kindMeta(kind);
|
||||
if (m && m->size > 0 && m->size <= 8) {
|
||||
|
||||
@@ -435,12 +435,11 @@ void MainWindow::newFile() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 0x100 bytes of Hex64 padding (32 nodes) ──
|
||||
// ── Fill with Hex64 until 0x6000 for stress testing ──
|
||||
int padStart = oh + 0xF0; // end of optional header
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int off = padStart + i * 8;
|
||||
for (int off = padStart; off < 0x6000; off += 8) {
|
||||
add(NodeKind::Hex64,
|
||||
QString("pad_%1").arg(off, 4, 16, QChar('0')),
|
||||
QString("data_%1").arg(off, 4, 16, QChar('0')),
|
||||
off);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user