fix: global blue highlight, Ctrl+F find bar with prev/next/close buttons

- Change QPalette::Highlight from theme.selection to theme.hover globally
- RcxEditor find: use SCI_SEARCHINTARGET + INDIC_COMPOSITIONTHICK indicator
  (selection rendering is disabled, so findFirst was invisible)
- Re-apply find indicators after applyDocument() refresh cycle
- Add prev/next/close buttons to find bars in both Reclass and C/C++ modes
- Buttons styled with hover/pressed states matching tab styling
This commit is contained in:
IChooseYou
2026-03-02 14:53:14 -07:00
parent efae193520
commit 879e9f4047
6 changed files with 161 additions and 33 deletions

View File

@@ -1636,7 +1636,7 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
act->setToolTip(QStringLiteral("Reset change tracking for selected nodes")); act->setToolTip(QStringLiteral("Reset change tracking for selected nodes"));
connect(act, &QAction::triggered, this, [this, ids]() { connect(act, &QAction::triggered, this, [this, ids]() {
for (uint64_t id : ids) { for (uint64_t id : ids) {
m_valueHistory[id].clear(); m_valueHistory.remove(id);
for (auto& lm : m_lastResult.meta) for (auto& lm : m_lastResult.meta)
if (lm.nodeId == id) lm.heatLevel = 0; if (lm.nodeId == id) lm.heatLevel = 0;
} }
@@ -1835,7 +1835,7 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
act->setToolTip(QStringLiteral("Reset change tracking for this node")); act->setToolTip(QStringLiteral("Reset change tracking for this node"));
act->setEnabled(m_valueHistory.contains(nodeId) && m_valueHistory[nodeId].uniqueCount() > 0); act->setEnabled(m_valueHistory.contains(nodeId) && m_valueHistory[nodeId].uniqueCount() > 0);
connect(act, &QAction::triggered, this, [this, nodeId]() { connect(act, &QAction::triggered, this, [this, nodeId]() {
m_valueHistory[nodeId].clear(); m_valueHistory.remove(nodeId);
for (auto& lm : m_lastResult.meta) for (auto& lm : m_lastResult.meta)
if (lm.nodeId == nodeId) lm.heatLevel = 0; if (lm.nodeId == nodeId) lm.heatLevel = 0;
refresh(); refresh();

View File

@@ -545,13 +545,12 @@ struct ValueHistory {
fn(values[(start + i) % kCapacity]); fn(values[(start + i) % kCapacity]);
} }
// Iterate with timestamps from oldest to newest // Iterate with timestamps from newest to oldest
template<typename Fn> template<typename Fn>
void forEachWithTime(Fn&& fn) const { void forEachWithTime(Fn&& fn) const {
int n = uniqueCount(); int n = uniqueCount();
int start = (head + kCapacity - n) % kCapacity;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
int idx = (start + i) % kCapacity; int idx = (head + kCapacity - 1 - i) % kCapacity;
fn(values[idx], timestamps[idx]); fn(values[idx], timestamps[idx]);
} }
} }

View File

@@ -385,6 +385,7 @@ static constexpr int IND_HINT_GREEN = 15; // Green text for hint/comment text
static constexpr int IND_LOCAL_OFF = 16; // Dim text for inline local offset in relative mode static constexpr int IND_LOCAL_OFF = 16; // Dim text for inline local offset in relative mode
static constexpr int IND_HEAT_WARM = 17; // Heatmap level 2 (moderate changes) static constexpr int IND_HEAT_WARM = 17; // Heatmap level 2 (moderate changes)
static constexpr int IND_HEAT_HOT = 18; // Heatmap level 3 (frequent changes) static constexpr int IND_HEAT_HOT = 18; // Heatmap level 3 (frequent changes)
static constexpr int IND_FIND = 19; // Search match highlight
static QString g_fontName = "JetBrains Mono"; static QString g_fontName = "JetBrains Mono";
@@ -402,10 +403,27 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) {
layout->addWidget(m_sci); layout->addWidget(m_sci);
// Find bar (hidden by default, shown with Ctrl+F) // Find bar (hidden by default, shown with Ctrl+F)
m_findBar = new QLineEdit(this); m_findBarContainer = new QWidget(this);
auto* fbLayout = new QHBoxLayout(m_findBarContainer);
fbLayout->setContentsMargins(4, 0, 0, 0);
fbLayout->setSpacing(2);
auto* findPrevBtn = new QToolButton(m_findBarContainer);
findPrevBtn->setText(QStringLiteral("\u25C0"));
findPrevBtn->setFixedSize(24, 24);
auto* findNextBtn = new QToolButton(m_findBarContainer);
findNextBtn->setText(QStringLiteral("\u25B6"));
findNextBtn->setFixedSize(24, 24);
auto* findCloseBtn = new QToolButton(m_findBarContainer);
findCloseBtn->setText(QStringLiteral("\u2715"));
findCloseBtn->setFixedSize(24, 24);
m_findBar = new QLineEdit(m_findBarContainer);
m_findBar->setPlaceholderText(QStringLiteral("Find...")); m_findBar->setPlaceholderText(QStringLiteral("Find..."));
m_findBar->setVisible(false); fbLayout->addWidget(findPrevBtn);
layout->addWidget(m_findBar); fbLayout->addWidget(findNextBtn);
fbLayout->addWidget(findCloseBtn);
fbLayout->addWidget(m_findBar);
m_findBarContainer->setVisible(false);
layout->addWidget(m_findBarContainer);
setupScintilla(); setupScintilla();
setupLexer(); setupLexer();
@@ -422,18 +440,46 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) {
m_sci->viewport()->installEventFilter(this); m_sci->viewport()->installEventFilter(this);
m_sci->viewport()->setMouseTracking(true); m_sci->viewport()->setMouseTracking(true);
// Find bar: live search on text change // Find bar: indicator-based search (selection is disabled in our Scintilla)
connect(m_findBar, &QLineEdit::textChanged, this, [this](const QString& text) { auto doFind = [this](bool forward) {
if (text.isEmpty()) return; long docLen = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLENGTH);
m_sci->findFirst(text, false, false, false, true, true, 0, 0); m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, (long)IND_FIND);
}); m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, (long)0, docLen);
// Find bar: Enter jumps to next match (wraps at end)
connect(m_findBar, &QLineEdit::returnPressed, this, [this]() {
QString text = m_findBar->text(); QString text = m_findBar->text();
if (text.isEmpty()) return; if (text.isEmpty()) return;
if (!m_sci->findNext()) QByteArray needle = text.toUtf8();
m_sci->findFirst(text, false, false, false, true, true, 0, 0);
}); long startPos = forward ? m_findPos : (m_findPos > 0 ? m_findPos - 1 : docLen);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART, startPos);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND,
forward ? docLen : (long)0);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSEARCHFLAGS, (long)0);
long pos = m_sci->SendScintilla(QsciScintillaBase::SCI_SEARCHINTARGET,
(uintptr_t)needle.size(), needle.constData());
if (pos == -1) { // wrap
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART,
forward ? (long)0 : docLen);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND,
forward ? startPos : (long)0);
pos = m_sci->SendScintilla(QsciScintillaBase::SCI_SEARCHINTARGET,
(uintptr_t)needle.size(), needle.constData());
}
if (pos >= 0) {
m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, (long)IND_FIND);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, pos, (long)needle.size());
int line = (int)m_sci->SendScintilla(QsciScintillaBase::SCI_LINEFROMPOSITION, pos);
m_sci->ensureLineVisible(line);
m_sci->SendScintilla(QsciScintillaBase::SCI_GOTOPOS, pos);
m_findPos = pos + (forward ? needle.size() : 0);
}
};
connect(m_findBar, &QLineEdit::textChanged, this, [doFind]() { doFind(true); });
connect(m_findBar, &QLineEdit::returnPressed, this, [doFind]() { doFind(true); });
connect(findNextBtn, &QToolButton::clicked, this, [doFind]() { doFind(true); });
connect(findPrevBtn, &QToolButton::clicked, this, [doFind]() { doFind(false); });
connect(findCloseBtn, &QToolButton::clicked, this, [this]() { hideFindBar(); });
// Escape hides find bar // Escape hides find bar
{ {
auto* escAction = new QAction(m_findBar); auto* escAction = new QAction(m_findBar);
@@ -646,6 +692,12 @@ void RcxEditor::setupScintilla() {
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE, m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
IND_LOCAL_OFF, 17 /*INDIC_TEXTFORE*/); IND_LOCAL_OFF, 17 /*INDIC_TEXTFORE*/);
// Find match highlight — thick underline (avoids box rendering artifacts)
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE,
IND_FIND, 14 /*INDIC_COMPOSITIONTHICK*/);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETUNDER,
IND_FIND, (long)1);
} }
void RcxEditor::setupLexer() { void RcxEditor::setupLexer() {
@@ -782,6 +834,8 @@ void RcxEditor::applyTheme(const Theme& theme) {
IND_HINT_GREEN, theme.indHintGreen); IND_HINT_GREEN, theme.indHintGreen);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE, m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
IND_LOCAL_OFF, theme.textFaint); IND_LOCAL_OFF, theme.textFaint);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE,
IND_FIND, theme.borderFocused);
// Lexer colors // Lexer colors
m_lexer->setColor(theme.syntaxKeyword, QsciLexerCPP::Keyword); m_lexer->setColor(theme.syntaxKeyword, QsciLexerCPP::Keyword);
@@ -832,11 +886,17 @@ void RcxEditor::applyTheme(const Theme& theme) {
} }
// Find bar // Find bar
if (m_findBar) { if (m_findBarContainer) {
m_findBar->setStyleSheet( m_findBar->setStyleSheet(
QStringLiteral("QLineEdit { background: %1; color: %2; border: 1px solid %3;" QStringLiteral("QLineEdit { background: %1; color: %2; border: 1px solid %3;"
" padding: 4px 8px; font-size: 13px; }") " padding: 4px 8px; font-size: 13px; }")
.arg(theme.backgroundAlt.name(), theme.text.name(), theme.border.name())); .arg(theme.backgroundAlt.name(), theme.text.name(), theme.border.name()));
m_findBarContainer->setStyleSheet(
QStringLiteral("QToolButton { background: %1; color: %2; border: 1px solid %3; border-radius: 2px; }"
"QToolButton:hover { background: %4; }"
"QToolButton:pressed { background: %5; }")
.arg(theme.background.name(), theme.text.name(), theme.border.name(),
theme.hover.name(), theme.backgroundAlt.name()));
} }
} }
@@ -919,6 +979,27 @@ void RcxEditor::applyDocument(const ComposeResult& result) {
m_prevHoveredNodeId = 0; m_prevHoveredNodeId = 0;
m_prevHoveredLine = -1; m_prevHoveredLine = -1;
applyHoverHighlight(); applyHoverHighlight();
// Re-apply find indicator (setText() clears all indicators)
if (m_findBarContainer && m_findBarContainer->isVisible()) {
QString needle = m_findBar->text();
if (!needle.isEmpty()) {
QByteArray nb = needle.toUtf8();
long docLen = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLENGTH);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSEARCHFLAGS, (long)0);
long pos = 0;
while (pos < docLen) {
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART, pos);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND, docLen);
long found = m_sci->SendScintilla(QsciScintillaBase::SCI_SEARCHINTARGET,
(uintptr_t)nb.size(), nb.constData());
if (found < 0) break;
m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, (long)IND_FIND);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, found, (long)nb.size());
pos = found + nb.size();
}
}
}
} }
void RcxEditor::applyMarginText(const QVector<LineMeta>& meta) { void RcxEditor::applyMarginText(const QVector<LineMeta>& meta) {
@@ -1300,13 +1381,18 @@ int RcxEditor::currentNodeIndex() const {
} }
void RcxEditor::showFindBar() { void RcxEditor::showFindBar() {
m_findBar->setVisible(true); m_findBarContainer->setVisible(true);
m_findBar->setFocus(); m_findBar->setFocus();
m_findBar->selectAll(); m_findBar->selectAll();
m_findPos = 0;
} }
void RcxEditor::hideFindBar() { void RcxEditor::hideFindBar() {
m_findBar->setVisible(false); m_findBarContainer->setVisible(false);
long docLen = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLENGTH);
m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, (long)IND_FIND);
m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, (long)0, docLen);
m_findPos = 0;
m_sci->setFocus(); m_sci->setFocus();
} }

View File

@@ -156,7 +156,9 @@ private:
const NodeTree* m_disasmTree = nullptr; const NodeTree* m_disasmTree = nullptr;
// ── Find bar ── // ── Find bar ──
QWidget* m_findBarContainer = nullptr;
QLineEdit* m_findBar = nullptr; QLineEdit* m_findBar = nullptr;
long m_findPos = 0;
void showFindBar(); void showFindBar();
void hideFindBar(); void hideFindBar();

View File

@@ -351,7 +351,7 @@ static void applyGlobalTheme(const rcx::Theme& theme) {
pal.setColor(QPalette::Text, theme.text); pal.setColor(QPalette::Text, theme.text);
pal.setColor(QPalette::Button, theme.button); pal.setColor(QPalette::Button, theme.button);
pal.setColor(QPalette::ButtonText, theme.text); pal.setColor(QPalette::ButtonText, theme.text);
pal.setColor(QPalette::Highlight, theme.selection); pal.setColor(QPalette::Highlight, theme.hover);
pal.setColor(QPalette::HighlightedText, theme.text); pal.setColor(QPalette::HighlightedText, theme.text);
pal.setColor(QPalette::ToolTipBase, theme.backgroundAlt); pal.setColor(QPalette::ToolTipBase, theme.backgroundAlt);
pal.setColor(QPalette::ToolTipText, theme.text); pal.setColor(QPalette::ToolTipText, theme.text);
@@ -1072,28 +1072,53 @@ MainWindow::SplitPane MainWindow::createSplitPane(TabState& tab) {
setupRenderedSci(pane.rendered); setupRenderedSci(pane.rendered);
rvLayout->addWidget(pane.rendered); rvLayout->addWidget(pane.rendered);
// Find bar (hidden by default) // Find bar with prev/next buttons (hidden by default)
pane.findContainer = new QWidget;
auto* fcLayout = new QHBoxLayout(pane.findContainer);
fcLayout->setContentsMargins(4, 0, 0, 0);
fcLayout->setSpacing(2);
const auto& fbTheme = ThemeManager::instance().current();
auto* ccPrevBtn = new QToolButton;
ccPrevBtn->setText(QStringLiteral("\u25C0"));
ccPrevBtn->setFixedSize(24, 24);
auto* ccNextBtn = new QToolButton;
ccNextBtn->setText(QStringLiteral("\u25B6"));
ccNextBtn->setFixedSize(24, 24);
auto* ccCloseBtn = new QToolButton;
ccCloseBtn->setText(QStringLiteral("\u2715"));
ccCloseBtn->setFixedSize(24, 24);
QString btnCss = QStringLiteral(
"QToolButton { background: %1; color: %2; border: 1px solid %3; border-radius: 2px; }"
"QToolButton:hover { background: %4; }"
"QToolButton:pressed { background: %5; }")
.arg(fbTheme.background.name(), fbTheme.text.name(), fbTheme.border.name(),
fbTheme.hover.name(), fbTheme.backgroundAlt.name());
ccPrevBtn->setStyleSheet(btnCss);
ccNextBtn->setStyleSheet(btnCss);
ccCloseBtn->setStyleSheet(btnCss);
pane.findBar = new QLineEdit; pane.findBar = new QLineEdit;
pane.findBar->setPlaceholderText("Find..."); pane.findBar->setPlaceholderText("Find...");
pane.findBar->setVisible(false);
const auto& fbTheme = ThemeManager::instance().current();
pane.findBar->setStyleSheet( pane.findBar->setStyleSheet(
QStringLiteral("QLineEdit { background: %1; color: %2; border: 1px solid %3;" QStringLiteral("QLineEdit { background: %1; color: %2; border: 1px solid %3;"
" padding: 4px 8px; font-size: 13px; }") " padding: 4px 8px; font-size: 13px; }")
.arg(fbTheme.backgroundAlt.name()) .arg(fbTheme.backgroundAlt.name(), fbTheme.text.name(), fbTheme.border.name()));
.arg(fbTheme.text.name()) fcLayout->addWidget(ccPrevBtn);
.arg(fbTheme.border.name())); fcLayout->addWidget(ccNextBtn);
rvLayout->addWidget(pane.findBar); fcLayout->addWidget(ccCloseBtn);
fcLayout->addWidget(pane.findBar);
pane.findContainer->setVisible(false);
rvLayout->addWidget(pane.findContainer);
// Ctrl+F to show find bar // Ctrl+F to show find bar
QsciScintilla* sci = pane.rendered; QsciScintilla* sci = pane.rendered;
QLineEdit* fb = pane.findBar; QLineEdit* fb = pane.findBar;
QWidget* fc = pane.findContainer;
auto* findAction = new QAction(pane.renderedContainer); auto* findAction = new QAction(pane.renderedContainer);
findAction->setShortcut(QKeySequence::Find); findAction->setShortcut(QKeySequence::Find);
findAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); findAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
pane.renderedContainer->addAction(findAction); pane.renderedContainer->addAction(findAction);
connect(findAction, &QAction::triggered, fb, [fb, sci]() { connect(findAction, &QAction::triggered, fb, [fb, fc]() {
fb->setVisible(true); fc->setVisible(true);
fb->setFocus(); fb->setFocus();
fb->selectAll(); fb->selectAll();
}); });
@@ -1103,8 +1128,8 @@ MainWindow::SplitPane MainWindow::createSplitPane(TabState& tab) {
escAction->setShortcut(QKeySequence(Qt::Key_Escape)); escAction->setShortcut(QKeySequence(Qt::Key_Escape));
escAction->setShortcutContext(Qt::WidgetShortcut); escAction->setShortcutContext(Qt::WidgetShortcut);
fb->addAction(escAction); fb->addAction(escAction);
connect(escAction, &QAction::triggered, fb, [fb, sci]() { connect(escAction, &QAction::triggered, fb, [fc, sci]() {
fb->setVisible(false); fc->setVisible(false);
sci->setFocus(); sci->setFocus();
}); });
@@ -1119,6 +1144,21 @@ MainWindow::SplitPane MainWindow::createSplitPane(TabState& tab) {
if (!sci->findNext()) if (!sci->findNext())
sci->findFirst(text, false, false, false, true, true, 0, 0); sci->findFirst(text, false, false, false, true, true, 0, 0);
}); });
connect(ccNextBtn, &QToolButton::clicked, sci, [sci, fb]() {
if (!sci->findNext())
sci->findFirst(fb->text(), false, false, false, true, true, 0, 0);
});
connect(ccPrevBtn, &QToolButton::clicked, sci, [sci, fb]() {
QString text = fb->text();
if (text.isEmpty()) return;
int line, col;
sci->getCursorPosition(&line, &col);
sci->findFirst(text, false, false, false, true, false, line, col);
});
connect(ccCloseBtn, &QToolButton::clicked, sci, [fc, sci]() {
fc->setVisible(false);
sci->setFocus();
});
pane.tabWidget->addTab(pane.renderedContainer, "C/C++"); // index 1 pane.tabWidget->addTab(pane.renderedContainer, "C/C++"); // index 1

View File

@@ -101,6 +101,7 @@ private:
RcxEditor* editor = nullptr; RcxEditor* editor = nullptr;
QsciScintilla* rendered = nullptr; QsciScintilla* rendered = nullptr;
QLineEdit* findBar = nullptr; QLineEdit* findBar = nullptr;
QWidget* findContainer = nullptr;
QWidget* renderedContainer = nullptr; QWidget* renderedContainer = nullptr;
ViewMode viewMode = VM_Reclass; ViewMode viewMode = VM_Reclass;
uint64_t lastRenderedRootId = 0; uint64_t lastRenderedRootId = 0;