Compare commits

...
Author SHA1 Message Date
copilot-swe-agent[bot] f1e2103a4b Initial plan 2026-01-08 00:33:57 +00:00
pancake b0178b861a fb 2026-01-08 00:21:32 +01:00
pancake c2463e061a Revert "Update src/common/RichTextPainter.cpp"
This reverts commit f73c4d212c.
2026-01-08 00:18:59 +01:00
pancakeandCopilot c2e2b92fdb Update src/common/RichTextPainter.cpp
Co-authored-by: Copilot <[email protected]>
2026-01-08 00:02:20 +01:00
pancake 66e813c35b setfg 2026-01-08 00:01:54 +01:00
pancakeandCopilot 1b49d48ce8 Update src/common/RichTextPainter.cpp
Co-authored-by: Copilot <[email protected]>
2026-01-07 23:59:14 +01:00
pancakeandCopilot f73c4d212c Update src/common/RichTextPainter.cpp
Co-authored-by: Copilot <[email protected]>
2026-01-07 23:59:02 +01:00
pancake cce3613ba0 another 2026-01-07 20:47:29 +01:00
pancake 3ea7e33897 again 2026-01-07 20:42:00 +01:00
pancake 5adcee2b94 again 2026-01-07 20:31:51 +01:00
pancake b6cfe866ff Optimized RichTextPainter with QTextLayout apis 2026-01-07 20:22:43 +01:00
2 changed files with 182 additions and 37 deletions

No files matched your search

+2
View File
@@ -51,6 +51,8 @@ public:
T height() { return mHeight; }
T ascent() { return mFontMetrics.ascent(); }
T position(const QString &text, T offset)
{
T curWidth = 0;
+180 -37
View File
@@ -5,9 +5,30 @@
#include <QPainter>
#include <QTextBlock>
#include <QTextFragment>
#include <QTextLayout>
#include <QTextCursor>
#include <QTextDocument>
#include <algorithm>
// TODO: fix performance (possibly use QTextLayout?)
/**
* RichTextPainter implementation using QTextLayout for improved performance
*
* Key optimizations:
* 1. QTextLayout determines visible text range - no per-char width calculations
* 2. Track segment boundaries (text indices) instead of computing segment widths
* 3. Use QTextLine::cursorToX() for accurate highlight positions
* 4. Pre-allocate all containers to avoid reallocations
* 5. Pre-compute default text brush to avoid repeated ConfigColor lookups
* 6. Track active highlight color/width to fix connected highlight drawing
* 7. Early return on empty text to avoid unnecessary QTextLayout creation
*
* Performance characteristics:
* - No per-segment width calculations (derived from QTextLine)
* - Single ConfigColor lookup for default text color
* - QTextLayout handles complex text shaping internally
* - Single QTextLayout::draw() call for text rendering
* - Minimal memory allocations with reserved vectors
*/
template<typename T>
void RichTextPainter::paintRichText(
QPainter *painter,
@@ -19,55 +40,177 @@ void RichTextPainter::paintRichText(
const List &richText,
CachedFontMetrics<T> *fontMetrics)
{
QPen pen;
QPen highlightPen;
QBrush brush(Qt::cyan);
if (richText.empty() || xinc >= w)
return;
T visibleWidth = w - xinc;
if (visibleWidth <= 0)
return;
// Pre-allocate vectors to avoid reallocations
QVector<QTextLayout::FormatRange> formatRanges;
formatRanges.reserve(richText.size());
// Track segment boundaries (text indices) instead of widths
std::vector<int> segmentBoundaries;
segmentBoundaries.reserve(richText.size());
// Pre-compute default text brush to avoid repeated ConfigColor lookups
const QBrush defaultTextBrush(ConfigColor("btext"));
QString fullText;
int totalLength = 0;
for (const auto &rt : richText) {
totalLength += rt.text.length();
}
fullText.reserve(totalLength);
int currentPos = 0;
// Build text and format ranges in a single pass
for (const CustomRichText_t &curRichText : richText) {
T textWidth = fontMetrics->width(curRichText.text);
T backgroundWidth = textWidth;
if (backgroundWidth + xinc > w)
backgroundWidth = w - xinc;
if (backgroundWidth <= 0) // stop drawing when going outside the specified width
break;
int textLength = curRichText.text.length();
// Record segment boundary
segmentBoundaries.push_back(currentPos);
// Build the full text
fullText += curRichText.text;
// Create format range for this segment
QTextCharFormat format;
switch (curRichText.flags) {
case FlagNone: // defaults
pen.setColor(ConfigColor("btext").name());
painter->setPen(pen);
case FlagNone:
format.setForeground(defaultTextBrush);
break;
case FlagColor: // color only
pen.setColor(curRichText.textColor);
painter->setPen(pen);
case FlagColor:
format.setForeground(QBrush(curRichText.textColor));
break;
case FlagBackground: // background only
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
brush.setColor(curRichText.textBackground);
painter->fillRect(QRectF(x + xinc, y, backgroundWidth, h), brush);
case FlagBackground:
format.setForeground(defaultTextBrush);
if (curRichText.textBackground.alpha()) {
format.setBackground(QBrush(curRichText.textBackground));
}
break;
case FlagAll: // color+background
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
brush.setColor(curRichText.textBackground);
painter->fillRect(QRectF(x + xinc, y, backgroundWidth, h), brush);
case FlagAll:
format.setForeground(QBrush(curRichText.textColor));
if (curRichText.textBackground.alpha()) {
format.setBackground(QBrush(curRichText.textBackground));
}
pen.setColor(curRichText.textColor);
painter->setPen(pen);
break;
}
int flags = 0;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
flags = Qt::TextBypassShaping;
#endif
painter
->drawText(typename Metrics<T>::Rect(x + xinc, y, w - xinc, h), flags, curRichText.text);
// Use QTextLayout's underline for highlights
if (curRichText.highlight && curRichText.highlightColor.alpha()) {
format.setFontUnderline(true);
format.setUnderlineColor(curRichText.highlightColor);
format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
}
QTextLayout::FormatRange fmtRange;
fmtRange.start = currentPos;
fmtRange.length = textLength;
fmtRange.format = format;
formatRanges.append(fmtRange);
currentPos += textLength;
}
// Early return if no text to render
if (fullText.isEmpty())
return;
// Create QTextLayout and let it determine what fits
QTextLayout layout(fullText, painter->font());
// Set up layout - QTextLayout will handle truncation automatically
layout.beginLayout();
QTextLine line = layout.createLine();
if (line.isValid()) {
line.setLineWidth(visibleWidth);
line.setPosition(QPointF(0, 0));
}
layout.endLayout();
// Single draw call for visible text (use baseline y = top + ascent)
layout.draw(painter, QPointF(x + xinc, y + fontMetrics->ascent()), formatRanges);
// Determine how many segments are actually visible using the layout
int visibleSegments = richText.size();
if (line.isValid()) {
// Find segments that fall within the visible text range
int visibleTextLength = line.textLength();
for (size_t i = 0; i < segmentBoundaries.size(); ++i) {
if (segmentBoundaries[i] >= visibleTextLength) {
visibleSegments = i;
break;
}
}
}
// Handle highlightConnectPrev using QTextLine for accurate positions
T baseX = x + xinc;
bool prevHighlighted = false;
T highlightStartX = baseX;
QColor activeHighlightColor;
int activeHighlightWidth = 1;
QPen highlightPen; // Reuse pen object
for (int i = 0; i < visibleSegments; ++i) {
const auto &curRichText = richText[i];
int startIndex = segmentBoundaries[i];
int endIndex = (i + 1 < (int)segmentBoundaries.size()) ? segmentBoundaries[i + 1] : fullText.length();
// Calculate actual segment positions using QTextLine
T segmentStartX = baseX;
T segmentEndX = baseX;
if (line.isValid()) {
segmentStartX = baseX + line.cursorToX(startIndex);
segmentEndX = baseX + line.cursorToX(endIndex);
// Clamp to visible area
segmentEndX = qMin(segmentEndX, x + w);
}
// Handle highlights - both connected and non-connected
if (curRichText.highlight && curRichText.highlightColor.alpha()) {
// Draw highlight for the current segment
highlightPen.setColor(curRichText.highlightColor);
highlightPen.setWidth(curRichText.highlightWidth);
painter->setPen(highlightPen);
T highlightOffsetX = curRichText.highlightConnectPrev ? -1 : 1;
painter->drawLine(
x + xinc + highlightOffsetX, y + h - 1, x + xinc + backgroundWidth - 1, y + h - 1);
painter->drawLine(segmentStartX - 1, y + h - 1, segmentEndX - 1, y + h - 1);
// Handle connected highlights specifically
if (curRichText.highlightConnectPrev) {
if (!prevHighlighted) {
// This is the first segment in a highlight chain
highlightStartX = segmentStartX;
activeHighlightColor = curRichText.highlightColor;
activeHighlightWidth = curRichText.highlightWidth;
}
prevHighlighted = true;
}
} else if (prevHighlighted) {
// Draw connected highlight line using the active highlight properties
T highlightEndX = segmentStartX;
if (highlightEndX > highlightStartX) {
highlightPen.setColor(activeHighlightColor);
highlightPen.setWidth(activeHighlightWidth);
painter->setPen(highlightPen);
painter->drawLine(highlightStartX - 1, y + h - 1, highlightEndX - 1, y + h - 1);
}
prevHighlighted = false;
}
}
// Draw final connected highlight if needed
if (prevHighlighted && line.isValid()) {
T finalSegmentEndX = qMin(baseX + line.cursorToX(line.textLength()), x + w);
if (finalSegmentEndX > highlightStartX) {
highlightPen.setColor(activeHighlightColor);
highlightPen.setWidth(activeHighlightWidth);
painter->setPen(highlightPen);
painter->drawLine(highlightStartX - 1, y + h - 1, finalSegmentEndX - 1, y + h - 1);
}
xinc += textWidth;
}
}