mirror of
https://github.com/radareorg/iaito.git
synced 2026-11-09 17:46:18 -10:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1e2103a4b | ||
|
|
b0178b861a | ||
|
|
c2463e061a | ||
|
|
c2e2b92fdb | ||
|
|
66e813c35b | ||
|
|
1b49d48ce8 | ||
|
|
f73c4d212c | ||
|
|
cce3613ba0 | ||
|
|
3ea7e33897 | ||
|
|
5adcee2b94 | ||
|
|
b6cfe866ff |
No files matched your search
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user