diff --git a/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp b/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp index 61007bc907b..e9c8b237aeb 100644 --- a/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp +++ b/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp @@ -68,22 +68,30 @@ protected: bool eventFilter(QObject *object, QEvent *event) override; private: + void drawHighlights(QPainter *painter, + int docStart, + int docSize, + double docSizeToHandleSizeRatio, + int handleOffset, + const QRect &viewport); void updateCache(); QRect overlayRect() const; + QRect handleRect() const; - bool m_cacheUpdateScheduled = true; - QMap m_cache; + // line start to line end + QMap>> m_highlightCache; QScrollBar *m_scrollBar; HighlightScrollBarController *m_highlightController; + bool m_isCacheUpdateScheduled = true; }; void HighlightScrollBarOverlay::scheduleUpdate() { - if (m_cacheUpdateScheduled) + if (m_isCacheUpdateScheduled) return; - m_cacheUpdateScheduled = true; + m_isCacheUpdateScheduled = true; QTimer::singleShot(0, this, static_cast(&QWidget::update)); } @@ -93,70 +101,133 @@ void HighlightScrollBarOverlay::paintEvent(QPaintEvent *paintEvent) updateCache(); - if (m_cache.isEmpty()) + if (m_highlightCache.isEmpty()) return; - const QRect &rect = overlayRect(); - - Utils::Theme::Color previousColor = Utils::Theme::TextColorNormal; - Highlight::Priority previousPriority = Highlight::LowPriority; - QRect *previousRect = nullptr; - - const int scrollbarRange = m_scrollBar->maximum() + m_scrollBar->pageStep(); - const int range = qMax(m_highlightController->visibleRange(), float(scrollbarRange)); - const int horizontalMargin = 3; - const int resultWidth = rect.width() - 2 * horizontalMargin + 1; - const int resultHeight = qMin(int(rect.height() / range) + 1, 4); - const int offset = rect.height() / range * m_highlightController->rangeOffset(); - const int verticalMargin = ((rect.height() / range) - resultHeight) / 2; - int previousBottom = -1; - - QHash > highlights; - for (const Highlight ¤tHighlight : qAsConst(m_cache)) { - // Calculate top and bottom - int top = rect.top() + offset + verticalMargin - + float(currentHighlight.position) / range * rect.height(); - const int bottom = top + resultHeight; - - if (previousRect && previousColor == currentHighlight.color && previousBottom + 1 >= top) { - // If the previous highlight has the same color and is directly prior to this highlight - // we just extend the previous highlight. - previousRect->setBottom(bottom - 1); - - } else { // create a new highlight - if (previousRect && previousBottom >= top) { - // make sure that highlights with higher priority are drawn on top of other highlights - // when rectangles are overlapping - - if (previousPriority > currentHighlight.priority) { - // Moving the top of the current highlight when the previous - // highlight has a higher priority - top = previousBottom + 1; - if (top == bottom) // if this would result in an empty highlight just skip - continue; - } else { - previousRect->setBottom(top - 1); // move the end of the last highlight - if (previousRect->height() == 0) // if the result is an empty rect, remove it. - highlights[previousColor].removeLast(); - } - } - highlights[currentHighlight.color] << QRect(rect.left() + horizontalMargin, top, - resultWidth, bottom - top); - previousRect = &highlights[currentHighlight.color].last(); - previousColor = currentHighlight.color; - previousPriority = currentHighlight.priority; - } - previousBottom = previousRect->bottom(); - } - QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, false); - const auto highlightEnd = highlights.cend(); - for (auto highlightIt = highlights.cbegin(); highlightIt != highlightEnd; ++highlightIt) { - const QColor &color = creatorTheme()->color(highlightIt.key()); - for (const QRect &rect : highlightIt.value()) - painter.fillRect(rect, color); + + const QRect &gRect = overlayRect(); + const QRect &hRect = handleRect(); + + const int marginX = 3; + const int marginH = -2 * marginX + 1; + const QRect aboveHandleRect = QRect(gRect.x() + marginX, + gRect.y(), + gRect.width() + marginH, + hRect.y() - gRect.y()); + const QRect handleRect = QRect(gRect.x() + marginX, + hRect.y(), + gRect.width() + marginH, + hRect.height()); + const QRect belowHandleRect = QRect(gRect.x() + marginX, + hRect.y() + hRect.height(), + gRect.width() + marginH, + gRect.height() - hRect.height() + gRect.y() - hRect.y()); + + const int aboveValue = m_scrollBar->value(); + const int belowValue = m_scrollBar->maximum() - m_scrollBar->value(); + const int sizeDocAbove = aboveValue * int(m_highlightController->lineHeight()); + const int sizeDocBelow = belowValue * int(m_highlightController->lineHeight()); + const int sizeDocVisible = int(m_highlightController->visibleRange()); + + const int scrollBarBackgroundHeight = aboveHandleRect.height() + belowHandleRect.height(); + const int sizeDocInvisible = sizeDocAbove + sizeDocBelow; + const double backgroundRatio = sizeDocInvisible + ? ((double)scrollBarBackgroundHeight / sizeDocInvisible) : 0; + + + if (aboveValue) { + drawHighlights(&painter, + 0, + sizeDocAbove, + backgroundRatio, + 0, + aboveHandleRect); } + + if (belowValue) { + // This is the hypothetical handle height if the handle would + // be stretched using the background ratio. + const double handleVirtualHeight = sizeDocVisible * backgroundRatio; + // Skip the doc above and visible part. + const int offset = qRound(aboveHandleRect.height() + handleVirtualHeight); + + drawHighlights(&painter, + sizeDocAbove + sizeDocVisible, + sizeDocBelow, + backgroundRatio, + offset, + belowHandleRect); + } + + const double handleRatio = sizeDocVisible + ? ((double)handleRect.height() / sizeDocVisible) : 0; + + // This is the hypothetical handle position if the background would + // be stretched using the handle ratio. + const double aboveVirtualHeight = sizeDocAbove * handleRatio; + + // This is the accurate handle position (double) + const double accurateHandlePos = sizeDocAbove * backgroundRatio; + // The correction between handle position (int) and accurate position (double) + const double correction = aboveHandleRect.height() - accurateHandlePos; + // Skip the doc above and apply correction + const int offset = qRound(aboveVirtualHeight + correction); + + drawHighlights(&painter, + sizeDocAbove, + sizeDocVisible, + handleRatio, + offset, + handleRect); +} + +void HighlightScrollBarOverlay::drawHighlights(QPainter *painter, + int docStart, + int docSize, + double docSizeToHandleSizeRatio, + int handleOffset, + const QRect &viewport) +{ + if (docSize <= 0) + return; + + painter->save(); + painter->setClipRect(viewport); + + const double lineHeight = m_highlightController->lineHeight(); + + for (const QMap> &colors : qAsConst(m_highlightCache)) { + const auto itColorEnd = colors.constEnd(); + for (auto itColor = colors.constBegin(); itColor != itColorEnd; ++itColor) { + const QColor &color = creatorTheme()->color(itColor.key()); + const QMap &positions = itColor.value(); + const auto itPosEnd = positions.constEnd(); + const int firstPos = int(docStart / lineHeight); + auto itPos = positions.upperBound(firstPos); + if (itPos != positions.constBegin()) + --itPos; + while (itPos != itPosEnd) { + const double posStart = itPos.key() * lineHeight; + const double posEnd = (itPos.value() + 1) * lineHeight; + if (posEnd < docStart) { + ++itPos; + continue; + } + if (posStart > docStart + docSize) + break; + + const int height = qMax(qRound((posEnd - posStart) * docSizeToHandleSizeRatio), 1); + const int top = qRound(posStart * docSizeToHandleSizeRatio) - handleOffset + viewport.y(); + + const QRect rect(viewport.left(), top, viewport.width(), height); + painter->fillRect(rect, color); + ++itPos; + } + } + } + painter->restore(); } bool HighlightScrollBarOverlay::eventFilter(QObject *object, QEvent *event) @@ -177,22 +248,61 @@ bool HighlightScrollBarOverlay::eventFilter(QObject *object, QEvent *event) return QWidget::eventFilter(object, event); } -void HighlightScrollBarOverlay::updateCache() +static void insertPosition(QMap *map, int position) { - if (!m_cacheUpdateScheduled) - return; + auto itNext = map->upperBound(position); - m_cache.clear(); - const QHash> highlights = m_highlightController->highlights(); - const QList &categories = highlights.keys(); - for (const Id &category : categories) { - for (const Highlight &highlight : highlights.value(category)) { - const Highlight oldHighlight = m_cache.value(highlight.position); - if (highlight.priority > oldHighlight.priority) - m_cache[highlight.position] = highlight; + bool gluedWithPrev = false; + if (itNext != map->begin()) { + auto itPrev = itNext - 1; + const int keyStart = itPrev.key(); + const int keyEnd = itPrev.value(); + if (position >= keyStart && position <= keyEnd) + return; // pos is already included + + if (keyEnd + 1 == position) { + // glue with prev + (*itPrev)++; + gluedWithPrev = true; } } - m_cacheUpdateScheduled = false; + + if (itNext != map->end() && itNext.key() == position + 1) { + const int keyEnd = itNext.value(); + itNext = map->erase(itNext); + if (gluedWithPrev) { + // glue with prev and next + auto itPrev = itNext - 1; + *itPrev = keyEnd; + } else { + // glue with next + itNext = map->insert(itNext, position, keyEnd); + } + return; // glued + } + + if (gluedWithPrev) + return; // glued + + map->insert(position, position); +} + +void HighlightScrollBarOverlay::updateCache() +{ + if (!m_isCacheUpdateScheduled) + return; + + m_highlightCache.clear(); + + const QHash> highlightsForId = m_highlightController->highlights(); + for (QVector highlights : highlightsForId) { + for (const auto &highlight : highlights) { + auto &highlightMap = m_highlightCache[highlight.priority][highlight.color]; + insertPosition(&highlightMap, highlight.position); + } + } + + m_isCacheUpdateScheduled = false; } QRect HighlightScrollBarOverlay::overlayRect() const @@ -201,6 +311,13 @@ QRect HighlightScrollBarOverlay::overlayRect() const return m_scrollBar->style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, m_scrollBar); } +QRect HighlightScrollBarOverlay::handleRect() const +{ + QStyleOptionSlider opt = qt_qscrollbarStyleOption(m_scrollBar); + return m_scrollBar->style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, m_scrollBar); +} + + ///////////// Highlight::Highlight(Id category_, int position_, @@ -251,24 +368,34 @@ void HighlightScrollBarController::setScrollArea(QAbstractScrollArea *scrollArea } } -float HighlightScrollBarController::visibleRange() const +double HighlightScrollBarController::lineHeight() const +{ + return m_lineHeight; +} + +void HighlightScrollBarController::setLineHeight(double lineHeight) +{ + m_lineHeight = lineHeight; +} + +double HighlightScrollBarController::visibleRange() const { return m_visibleRange; } -void HighlightScrollBarController::setVisibleRange(float visibleRange) +void HighlightScrollBarController::setVisibleRange(double visibleRange) { m_visibleRange = visibleRange; } -float HighlightScrollBarController::rangeOffset() const +double HighlightScrollBarController::margin() const { - return m_rangeOffset; + return m_margin; } -void HighlightScrollBarController::setRangeOffset(float offset) +void HighlightScrollBarController::setMargin(double margin) { - m_rangeOffset = offset; + m_margin = margin; } QHash> HighlightScrollBarController::highlights() const diff --git a/src/plugins/coreplugin/find/highlightscrollbarcontroller.h b/src/plugins/coreplugin/find/highlightscrollbarcontroller.h index a20a17e190b..f9f4c334f49 100644 --- a/src/plugins/coreplugin/find/highlightscrollbarcontroller.h +++ b/src/plugins/coreplugin/find/highlightscrollbarcontroller.h @@ -71,11 +71,14 @@ public: QAbstractScrollArea *scrollArea() const; void setScrollArea(QAbstractScrollArea *scrollArea); - float visibleRange() const; - void setVisibleRange(float visibleRange); + double lineHeight() const; + void setLineHeight(double lineHeight); - float rangeOffset() const; - void setRangeOffset(float offset); + double visibleRange() const; + void setVisibleRange(double visibleRange); + + double margin() const; + void setMargin(double margin); QHash> highlights() const; void addHighlight(Highlight highlight); @@ -85,8 +88,9 @@ public: private: QHash > m_highlights; - float m_visibleRange = 0.0; - float m_rangeOffset = 0.0; + double m_lineHeight = 0.0; + double m_visibleRange = 0.0; // in pixels + double m_margin = 0.0; // in pixels QAbstractScrollArea *m_scrollArea = nullptr; QPointer m_overlay; }; diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 9d3b58f9b09..5a8486116be 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -6311,9 +6311,9 @@ void TextEditorWidgetPrivate::adjustScrollBarRanges() if (lineSpacing == 0) return; - const double offset = q->contentOffset().y(); - m_highlightScrollBarController->setVisibleRange(float((q->viewport()->rect().height() - offset) / lineSpacing)); - m_highlightScrollBarController->setRangeOffset(float(offset / lineSpacing)); + m_highlightScrollBarController->setLineHeight(lineSpacing); + m_highlightScrollBarController->setVisibleRange(q->viewport()->rect().height()); + m_highlightScrollBarController->setMargin(q->textDocument()->document()->documentMargin()); } void TextEditorWidgetPrivate::highlightSearchResultsInScrollBar()