selection and hover use full-row bg markers instead of indicators

- replace IND_SELECTED/IND_HOVER indicators with M_HOVER(6) M_SELECTED(7) bg markers
- disable caret line highlight and native selection rendering
- re-enable sel rendering only during inline edit
- add drag-select support via nodeClicked shift signals
- use lineRangeNoEol helper to exclude EOL from indicator ranges
- add drag tracking state to editor
This commit is contained in:
sysadmin
2026-02-01 17:04:40 -07:00
parent 0be67c8396
commit abe5e3ebd9
3 changed files with 70 additions and 72 deletions

View File

@@ -109,6 +109,8 @@ enum Marker : int {
M_CYCLE = 3, M_CYCLE = 3,
M_ERR = 4, M_ERR = 4,
M_STRUCT_BG = 5, M_STRUCT_BG = 5,
M_HOVER = 6,
M_SELECTED = 7,
}; };
// ── Provider interface ── // ── Provider interface ──

View File

@@ -23,8 +23,6 @@ static const QColor kFgMarginDim("#505050");
static constexpr int IND_EDITABLE = 8; static constexpr int IND_EDITABLE = 8;
static constexpr int IND_HEX_DIM = 9; static constexpr int IND_HEX_DIM = 9;
static constexpr int IND_SELECTED = 10;
static constexpr int IND_HOVER = 11;
static QFont editorFont() { static QFont editorFont() {
QFont f("Consolas", 12); QFont f("Consolas", 12);
@@ -87,8 +85,7 @@ void RcxEditor::setupScintilla() {
m_sci->setReadOnly(true); m_sci->setReadOnly(true);
m_sci->setWrapMode(QsciScintilla::WrapNone); m_sci->setWrapMode(QsciScintilla::WrapNone);
m_sci->setCaretLineVisible(true); m_sci->setCaretLineVisible(false);
m_sci->setCaretLineBackgroundColor(QColor("#2c3338"));
m_sci->setPaper(kBgText); m_sci->setPaper(kBgText);
m_sci->setColor(QColor("#d4d4d4")); m_sci->setColor(QColor("#d4d4d4"));
@@ -103,9 +100,9 @@ void RcxEditor::setupScintilla() {
m_sci->SendScintilla(QsciScintillaBase::SCI_SETEXTRAASCENT, (long)2); m_sci->SendScintilla(QsciScintillaBase::SCI_SETEXTRAASCENT, (long)2);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETEXTRADESCENT, (long)2); m_sci->SendScintilla(QsciScintillaBase::SCI_SETEXTRADESCENT, (long)2);
// Selection colors // Disable native selection rendering — we use markers for selection
m_sci->setSelectionBackgroundColor(QColor("#264f78")); m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELFORE, (long)0, (long)0);
m_sci->setSelectionForegroundColor(QColor("#d4d4d4")); m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELBACK, (long)0, (long)0);
// Editable-field link-style indicator (colored text + underline) // Editable-field link-style indicator (colored text + underline)
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE, m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
@@ -118,30 +115,6 @@ void RcxEditor::setupScintilla() {
IND_HEX_DIM, 17 /*INDIC_TEXTFORE*/); IND_HEX_DIM, 17 /*INDIC_TEXTFORE*/);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE, m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
IND_HEX_DIM, QColor("#505050")); IND_HEX_DIM, QColor("#505050"));
// Selection overlay — translucent blue box
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
IND_SELECTED, 8 /*INDIC_STRAIGHTBOX*/);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
IND_SELECTED, QColor("#264f78"));
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETALPHA,
IND_SELECTED, (long)50);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETOUTLINEALPHA,
IND_SELECTED, (long)100);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETUNDER,
IND_SELECTED, (long)1);
// Hover row highlight — very subtle fill
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
IND_HOVER, 16 /*INDIC_FULLBOX*/);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
IND_HOVER, QColor("#264f78"));
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETALPHA,
IND_HOVER, (long)25);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETOUTLINEALPHA,
IND_HOVER, (long)0);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETUNDER,
IND_HOVER, (long)1);
} }
void RcxEditor::setupLexer() { void RcxEditor::setupLexer() {
@@ -238,6 +211,14 @@ void RcxEditor::setupMarkers() {
m_sci->markerDefine(QsciScintilla::Background, M_STRUCT_BG); m_sci->markerDefine(QsciScintilla::Background, M_STRUCT_BG);
m_sci->setMarkerBackgroundColor(QColor("#1a2332"), M_STRUCT_BG); m_sci->setMarkerBackgroundColor(QColor("#1a2332"), M_STRUCT_BG);
m_sci->setMarkerForegroundColor(QColor("#d4d4d4"), M_STRUCT_BG); m_sci->setMarkerForegroundColor(QColor("#d4d4d4"), M_STRUCT_BG);
// M_HOVER (6): full-row hover highlight
m_sci->markerDefine(QsciScintilla::Background, M_HOVER);
m_sci->setMarkerBackgroundColor(QColor(43, 43, 43), M_HOVER);
// 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);
} }
void RcxEditor::allocateMarginStyles() { void RcxEditor::allocateMarginStyles() {
@@ -324,6 +305,12 @@ void RcxEditor::applyFoldLevels(const QVector<LineMeta>& meta) {
} }
} }
static inline void lineRangeNoEol(QsciScintilla* sci, int line, long& start, long& len) {
start = sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, (unsigned long)line);
long end = sci->SendScintilla(QsciScintillaBase::SCI_GETLINEENDPOSITION, (unsigned long)line);
len = (end > start) ? (end - start) : 0;
}
void RcxEditor::applyHexDimming(const QVector<LineMeta>& meta) { void RcxEditor::applyHexDimming(const QVector<LineMeta>& meta) {
m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_HEX_DIM); m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_HEX_DIM);
for (int i = 0; i < meta.size(); i++) { for (int i = 0; i < meta.size(); i++) {
@@ -331,10 +318,7 @@ void RcxEditor::applyHexDimming(const QVector<LineMeta>& meta) {
case NodeKind::Hex8: case NodeKind::Hex16: case NodeKind::Hex8: case NodeKind::Hex16:
case NodeKind::Hex32: case NodeKind::Hex64: case NodeKind::Hex32: case NodeKind::Hex64:
case NodeKind::Padding: { case NodeKind::Padding: {
long pos = m_sci->SendScintilla( long pos, len; lineRangeNoEol(m_sci, i, pos, len);
QsciScintillaBase::SCI_POSITIONFROMLINE, (unsigned long)i);
long len = m_sci->SendScintilla(
QsciScintillaBase::SCI_LINELENGTH, (unsigned long)i);
if (len > 0) if (len > 0)
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, pos, len); m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, pos, len);
break; break;
@@ -346,49 +330,23 @@ void RcxEditor::applyHexDimming(const QVector<LineMeta>& meta) {
void RcxEditor::applySelectionOverlay(const QSet<uint64_t>& selIds) { void RcxEditor::applySelectionOverlay(const QSet<uint64_t>& selIds) {
m_currentSelIds = selIds; m_currentSelIds = selIds;
m_sci->markerDeleteAll(M_SELECTED);
// Clear all selection indicators
m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_SELECTED);
long docLen = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLENGTH);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, (uintptr_t)0, docLen);
if (selIds.isEmpty()) return;
for (int i = 0; i < m_meta.size(); i++) { for (int i = 0; i < m_meta.size(); i++) {
if (selIds.contains(m_meta[i].nodeId)) { if (selIds.contains(m_meta[i].nodeId))
long pos = m_sci->SendScintilla( m_sci->markerAdd(i, M_SELECTED);
QsciScintillaBase::SCI_POSITIONFROMLINE, (unsigned long)i);
long len = m_sci->SendScintilla(
QsciScintillaBase::SCI_LINELENGTH, (unsigned long)i);
if (len > 0)
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, pos, len);
}
} }
// Refresh hover since selection may suppress it
applyHoverHighlight(); applyHoverHighlight();
} }
void RcxEditor::applyHoverHighlight() { void RcxEditor::applyHoverHighlight() {
// Clear previous hover indicator m_sci->markerDeleteAll(M_HOVER);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_HOVER);
long docLen = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLENGTH);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, (uintptr_t)0, docLen);
if (m_editState.active) return; if (m_editState.active) return;
if (!m_hoverInside) return; if (!m_hoverInside) return;
if (m_hoveredNodeId == 0) return; if (m_hoveredNodeId == 0) return;
if (m_currentSelIds.contains(m_hoveredNodeId)) return; if (m_currentSelIds.contains(m_hoveredNodeId)) return;
for (int i = 0; i < m_meta.size(); i++) { for (int i = 0; i < m_meta.size(); i++) {
if (m_meta[i].nodeId == m_hoveredNodeId) { if (m_meta[i].nodeId == m_hoveredNodeId)
long pos = m_sci->SendScintilla( m_sci->markerAdd(i, M_HOVER);
QsciScintillaBase::SCI_POSITIONFROMLINE, (unsigned long)i);
long len = m_sci->SendScintilla(
QsciScintillaBase::SCI_LINELENGTH, (unsigned long)i);
if (len > 0)
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, pos, len);
}
} }
} }
@@ -465,6 +423,9 @@ RcxEditor::EndEditInfo RcxEditor::endInlineEdit() {
EndEditInfo info{m_editState.nodeIdx, m_editState.subLine, m_editState.target}; EndEditInfo info{m_editState.nodeIdx, m_editState.subLine, m_editState.target};
m_editState.active = false; m_editState.active = false;
m_sci->setReadOnly(true); m_sci->setReadOnly(true);
// Disable selection rendering again
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELFORE, (long)0, (long)0);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELBACK, (long)0, (long)0);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, (long)1); m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, (long)1);
m_sci->SendScintilla(QsciScintillaBase::SCI_EMPTYUNDOBUFFER); m_sci->SendScintilla(QsciScintillaBase::SCI_EMPTYUNDOBUFFER);
return info; return info;
@@ -613,12 +574,40 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) {
// Selection click — emit for controller to manage // Selection click — emit for controller to manage
if (line >= 0 && line < m_meta.size()) { if (line >= 0 && line < m_meta.size()) {
uint64_t nid = m_meta[line].nodeId; uint64_t nid = m_meta[line].nodeId;
if (nid != 0) if (nid != 0) {
emit nodeClicked(line, nid, me->modifiers()); emit nodeClicked(line, nid, me->modifiers());
m_dragging = true;
m_dragLastLine = line;
}
} }
} }
} }
} }
// Drag-select: extend selection as mouse moves with button held
if (obj == m_sci->viewport() && !m_editState.active
&& event->type() == QEvent::MouseMove && m_dragging) {
auto* me = static_cast<QMouseEvent*>(event);
if (me->buttons() & Qt::LeftButton) {
long pos = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMPOINTCLOSE,
(unsigned long)me->pos().x(), (long)me->pos().y());
if (pos >= 0) {
int line = (int)m_sci->SendScintilla(
QsciScintillaBase::SCI_LINEFROMPOSITION, (unsigned long)pos);
if (line >= 0 && line < m_meta.size() && line != m_dragLastLine) {
uint64_t nid = m_meta[line].nodeId;
if (nid != 0) {
emit nodeClicked(line, nid, Qt::ShiftModifier);
m_dragLastLine = line;
}
}
}
} else {
m_dragging = false;
}
}
if (obj == m_sci->viewport() && event->type() == QEvent::MouseButtonRelease) {
m_dragging = false;
}
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);
@@ -639,8 +628,7 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) {
} }
// Clear underlines when editor loses focus // Clear underlines when editor loses focus
if (m_hintLine >= 0) { if (m_hintLine >= 0) {
long start = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, (unsigned long)m_hintLine); long start, len; lineRangeNoEol(m_sci, m_hintLine, start, len);
long len = m_sci->SendScintilla(QsciScintillaBase::SCI_LINELENGTH, (unsigned long)m_hintLine);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_EDITABLE); m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_EDITABLE);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, start, len); m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, start, len);
m_hintLine = -1; m_hintLine = -1;
@@ -815,6 +803,11 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line) {
m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, (long)0); m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, (long)0);
m_sci->setReadOnly(false); m_sci->setReadOnly(false);
// Re-enable selection rendering for inline edit
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELFORE, (long)0, (long)0);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELBACK, (long)1,
QColor("#264f78"));
// Select just the trimmed text (keeps columns aligned) // Select just the trimmed text (keeps columns aligned)
long lineStart = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, long lineStart = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
(unsigned long)line); (unsigned long)line);
@@ -902,8 +895,7 @@ void RcxEditor::updateEditableUnderline(int line) {
auto clearLine = [&](int l) { auto clearLine = [&](int l) {
if (l < 0) return; if (l < 0) return;
long start = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, (unsigned long)l); long start, len; lineRangeNoEol(m_sci, l, start, len);
long len = m_sci->SendScintilla(QsciScintillaBase::SCI_LINELENGTH, (unsigned long)l);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_EDITABLE); m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_EDITABLE);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, start, len); m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, start, len);
}; };

View File

@@ -64,6 +64,10 @@ private:
uint64_t m_hoveredNodeId = 0; uint64_t m_hoveredNodeId = 0;
QSet<uint64_t> m_currentSelIds; QSet<uint64_t> m_currentSelIds;
// ── Drag selection ──
bool m_dragging = false;
int m_dragLastLine = -1;
// ── Inline edit state ── // ── Inline edit state ──
struct InlineEditState { struct InlineEditState {
bool active = false; bool active = false;