From 0531e2fb7193f5bdcd3460a2afef9872f564e2fe Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 30 May 2024 22:36:35 +0200 Subject: [PATCH] wip: customize scroll bar --- src/qt/qtcommon.h | 19 +- src/qt/qtvisuals.h | 458 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 437 insertions(+), 40 deletions(-) diff --git a/src/qt/qtcommon.h b/src/qt/qtcommon.h index 8c9119c..5829c87 100644 --- a/src/qt/qtcommon.h +++ b/src/qt/qtcommon.h @@ -41,7 +41,7 @@ #include #include #include - +//#include //#include /* * #else @@ -65,7 +65,7 @@ namespace StylingHelper { return QLatin1String(ch); } - static inline qreal dpi(const QStyleOption *option) { + static inline qreal calculateDpi(const QStyleOption *option) { #ifndef Q_OS_DARWIN // Prioritize the application override, except for on macOS where // we have historically not supported the AA_Use96Dpi flag. @@ -107,7 +107,7 @@ namespace StylingHelper { } static inline qreal dpiScaled(qreal value, const QStyleOption *option) { - return dpiScaled(value, dpi(option)); + return dpiScaled(value, calculateDpi(option)); } static inline bool isMacSystemPalette(const QPalette &pal) { @@ -161,5 +161,18 @@ namespace StylingHelper { cachePixmap.fill(Qt::transparent); return cachePixmap; } + + static inline QColor backgroundColor(const QPalette &pal, const QWidget* widget) + { + #if QT_CONFIG(scrollarea) + if (qobject_cast(widget) && widget->parent() && + qobject_cast(widget->parent()->parent())) + return widget->parentWidget()->parentWidget()->palette().color(QPalette::Base); + #else + Q_UNUSED(widget); + #endif + return pal.color(QPalette::Base); + } + } diff --git a/src/qt/qtvisuals.h b/src/qt/qtvisuals.h index f88c025..f885a3a 100644 --- a/src/qt/qtvisuals.h +++ b/src/qt/qtvisuals.h @@ -16,15 +16,15 @@ public: QStyleHintReturn* returnData = 0) const { if (hint == QStyle::SH_Slider_AbsoluteSetButtons) return (Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); - return QProxyStyle::styleHint(hint, option, widget, returnData); + return baseStyle()->styleHint(hint, option, widget, returnData); } QRect subControlRect(ComplexControl control, const QStyleOptionComplex *option, - SubControl subControl, const QWidget *widget) const { + SubControl subControl, const QWidget *widget) const { QRect rect = QCommonStyle::subControlRect(CC_Slider, option, subControl, widget); switch (control) { - #if QT_CONFIG(slider) +#if QT_CONFIG(slider) case CC_MeterSlider: if (const QStyleOptionSlider *slider = qstyleoption_cast(option)) { int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget); @@ -88,6 +88,92 @@ public: return rect; break; #endif // QT_CONFIG(slider) +#if QT_CONFIG(scrollbar) + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast(option)) { + const QRect scrollBarRect = scrollbar->rect; + int sbextent = 0; + if (!baseStyle()->styleHint(SH_ScrollBar_Transient, scrollbar, widget)) + sbextent = baseStyle()->pixelMetric(PM_ScrollBarExtent, scrollbar, widget); + int maxlen = ((scrollbar->orientation == Qt::Horizontal) ? + scrollBarRect.width() : scrollBarRect.height()) - (sbextent * 2); + int sliderlen; + + // calculate slider length + if (scrollbar->maximum != scrollbar->minimum) { + uint range = scrollbar->maximum - scrollbar->minimum; + sliderlen = (qint64(scrollbar->pageStep) * maxlen) / (range + scrollbar->pageStep); + + int slidermin = baseStyle()->pixelMetric(PM_ScrollBarSliderMin, scrollbar, widget); + if (sliderlen < slidermin || range > INT_MAX / 2) + sliderlen = slidermin; + if (sliderlen > maxlen) + sliderlen = maxlen; + } else { + sliderlen = maxlen; + } + + int sliderstart = sbextent + sliderPositionFromValue(scrollbar->minimum, + scrollbar->maximum, + scrollbar->sliderPosition, + maxlen - sliderlen, + scrollbar->upsideDown); + + switch (subControl) { + case SC_ScrollBarSubLine: // top/left button + if (scrollbar->orientation == Qt::Horizontal) { + int buttonWidth = qMin(scrollBarRect.width() / 2, sbextent); + rect.setRect(0, 0, buttonWidth, scrollBarRect.height()); + } else { + int buttonHeight = qMin(scrollBarRect.height() / 2, sbextent); + rect.setRect(0, 0, scrollBarRect.width(), buttonHeight); + } + break; + case SC_ScrollBarAddLine: // bottom/right button + if (scrollbar->orientation == Qt::Horizontal) { + int buttonWidth = qMin(scrollBarRect.width()/2, sbextent); + rect.setRect(scrollBarRect.width() - buttonWidth, 0, buttonWidth, scrollBarRect.height()); + } else { + int buttonHeight = qMin(scrollBarRect.height()/2, sbextent); + rect.setRect(0, scrollBarRect.height() - buttonHeight, scrollBarRect.width(), buttonHeight); + } + break; + case SC_ScrollBarSubPage: // between top/left button and slider + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(sbextent, 0, sliderstart - sbextent, scrollBarRect.height()); + else + rect.setRect(0, sbextent, scrollBarRect.width(), sliderstart - sbextent); + break; + case SC_ScrollBarAddPage: // between bottom/right button and slider + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(sliderstart + sliderlen, 0, + maxlen - sliderstart - sliderlen + sbextent, scrollBarRect.height()); + else + rect.setRect(0, sliderstart + sliderlen, scrollBarRect.width(), + maxlen - sliderstart - sliderlen + sbextent); + break; + case SC_ScrollBarGroove: + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(sbextent, 0, scrollBarRect.width() - sbextent * 2, + scrollBarRect.height()); + else + rect.setRect(0, sbextent, scrollBarRect.width(), + scrollBarRect.height() - sbextent * 2); + break; + case SC_ScrollBarSlider: + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(sliderstart, 0, sliderlen, scrollBarRect.height()); + else + rect.setRect(0, sliderstart, scrollBarRect.width(), sliderlen); + break; + default: + break; + } + rect = visualRect(scrollbar->direction, scrollBarRect, rect); + return rect; + } + break; +#endif // QT_CONFIG(scrollbar) default: return baseStyle()->subControlRect(control, option, subControl, widget); break; @@ -97,6 +183,7 @@ public: void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const { + QColor outline; switch (cc) { #if QT_CONFIG(slider) case CC_MeterSlider: @@ -115,7 +202,7 @@ public: QPen oldPen = painter->pen(); QColor shadowAlpha(Qt::black); shadowAlpha.setAlpha(10); - QColor outline; + if (option->state & State_HasFocus && option->state & State_KeyboardFocusChange) outline = highlightedOutline(option->palette); @@ -327,6 +414,218 @@ public: painter->setPen(oldPen); } break; + case CC_ScrollBar: + painter->save(); + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast(option)) { + bool wasActive = false; + qreal expandScale = 1.0; + qreal expandOffset = -1.0; + QObject *styleObject = option->styleObject; + + QColor buttonColor = calculateButtonColor(option->palette); + QColor gradientStartColor = buttonColor.lighter(104); + QColor gradientStopColor = buttonColor.darker(102); + + bool transient = styleHint(SH_ScrollBar_Transient, option, widget); + bool horizontal = scrollBar->orientation == Qt::Horizontal; + bool sunken = scrollBar->state & State_Sunken; + + QRect scrollBarSubLine = subControlRect(cc, scrollBar, SC_ScrollBarSubLine, widget); + QRect scrollBarAddLine = subControlRect(cc, scrollBar, SC_ScrollBarAddLine, widget); + QRect scrollBarSlider = subControlRect(cc, scrollBar, SC_ScrollBarSlider, widget); + QRect scrollBarGroove = subControlRect(cc, scrollBar, SC_ScrollBarGroove, widget); + + QRect rect = option->rect; + outline = highlightedOutline(option->palette); + QColor alphaOutline = outline; + alphaOutline.setAlpha(180); + + QColor arrowColor = option->palette.windowText().color(); + //QColor arrowColor = QColor(188,143,143,100); + arrowColor.setAlpha(160); + + const QColor bgColor = backgroundColor(option->palette, widget); + const bool isDarkBg = bgColor.red() < 128 && bgColor.green() < 128 && bgColor.blue() < 128; + + if (transient) { + if (horizontal) { + rect.setY(rect.y() + 4.5 - expandOffset); + scrollBarSlider.setY(scrollBarSlider.y() + 4.5 - expandOffset); + scrollBarGroove.setY(scrollBarGroove.y() + 4.5 - expandOffset); + + rect.setHeight(rect.height() * expandScale); + scrollBarGroove.setHeight(scrollBarGroove.height() * expandScale); + } else { + rect.setX(rect.x() + 4.5 - expandOffset); + scrollBarSlider.setX(scrollBarSlider.x() + 4.5 - expandOffset); + scrollBarGroove.setX(scrollBarGroove.x() + 4.5 - expandOffset); + + rect.setWidth(rect.width() * expandScale); + scrollBarGroove.setWidth(scrollBarGroove.width() * expandScale); + } + } + + // Paint groove + if ((!transient || scrollBar->activeSubControls || wasActive) && scrollBar->subControls & SC_ScrollBarGroove) { + QLinearGradient gradient(rect.center().x(), rect.top(), + rect.center().x(), rect.bottom()); + if (!horizontal) + gradient = QLinearGradient(rect.left(), rect.center().y(), + rect.right(), rect.center().y()); + if (!transient || !isDarkBg) { + gradient.setColorAt(0, buttonColor.darker(107)); + gradient.setColorAt(0.1, buttonColor.darker(105)); + gradient.setColorAt(0.9, buttonColor.darker(105)); + gradient.setColorAt(1, buttonColor.darker(107)); + } else { + gradient.setColorAt(0, bgColor.lighter(157)); + gradient.setColorAt(0.1, bgColor.lighter(155)); + gradient.setColorAt(0.9, bgColor.lighter(155)); + gradient.setColorAt(1, bgColor.lighter(157)); + } + + painter->save(); + if (transient) + painter->setOpacity(0.8); + painter->fillRect(rect, gradient); + painter->setPen(Qt::NoPen); + if (transient) + painter->setOpacity(0.4); + painter->setPen(alphaOutline); + if (horizontal) + painter->drawLine(rect.topLeft(), rect.topRight()); + else + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + + QColor subtleEdge = alphaOutline; + subtleEdge.setAlpha(40); + painter->setPen(subtleEdge); + painter->setBrush(Qt::NoBrush); + painter->drawRect(scrollBarGroove.adjusted(1, 0, -1, -1)); + painter->restore(); + } + + QRect pixmapRect = scrollBarSlider; + QLinearGradient gradient(pixmapRect.center().x(), pixmapRect.top(), + pixmapRect.center().x(), pixmapRect.bottom()); + if (!horizontal) + gradient = QLinearGradient(pixmapRect.left(), pixmapRect.center().y(), + pixmapRect.right(), pixmapRect.center().y()); + + QLinearGradient highlightedGradient = gradient; + + QColor midColor2 = mergedColors(gradientStartColor, gradientStopColor, 40); + gradient.setColorAt(0, calculateButtonColor(option->palette).lighter(108)); + gradient.setColorAt(1, calculateButtonColor(option->palette)); + + highlightedGradient.setColorAt(0, gradientStartColor.darker(102)); + highlightedGradient.setColorAt(1, gradientStopColor.lighter(102)); + + // Paint slider + if (scrollBar->subControls & SC_ScrollBarSlider) { + if (transient) { + QRect rect = scrollBarSlider.adjusted(horizontal ? 1 : 2, horizontal ? 2 : 1, -1, -1); + painter->setPen(Qt::NoPen); + painter->setBrush(isDarkBg ? lightShade() : darkShade()); + int r = qMin(rect.width(), rect.height()) / 2; + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->drawRoundedRect(rect, r, r); + painter->restore(); + } else { + QRect pixmapRect = scrollBarSlider; + painter->setPen(QPen(alphaOutline)); + if (option->state & State_Sunken && scrollBar->activeSubControls & SC_ScrollBarSlider) + painter->setBrush(midColor2); + else if (option->state & State_MouseOver && scrollBar->activeSubControls & SC_ScrollBarSlider) + painter->setBrush(highlightedGradient); + else if (!isDarkBg) + painter->setBrush(gradient); + else + painter->setBrush(midColor2); + + painter->drawRoundedRect(pixmapRect.adjusted(horizontal ? -1 : 0, horizontal ? 0 : -1, horizontal ? 0 : -1, horizontal ? -1 : 0), 5, 5); + + painter->setPen(innerContrastLine()); + painter->drawRoundedRect(scrollBarSlider.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, -1, -1), 5, 5); + + // Outer shadow + // painter->setPen(subtleEdge); + // if (horizontal) { + //// painter->drawLine(scrollBarSlider.topLeft() + QPoint(-2, 0), scrollBarSlider.bottomLeft() + QPoint(2, 0)); + //// painter->drawLine(scrollBarSlider.topRight() + QPoint(-2, 0), scrollBarSlider.bottomRight() + QPoint(2, 0)); + // } else { + //// painter->drawLine(pixmapRect.topLeft() + QPoint(0, -2), pixmapRect.bottomLeft() + QPoint(0, -2)); + //// painter->drawLine(pixmapRect.topRight() + QPoint(0, 2), pixmapRect.bottomRight() + QPoint(0, 2)); + // } + } + } + + /* + * // The SubLine (up/left) buttons + * if (!transient && scrollBar->subControls & SC_ScrollBarSubLine) { + * if ((scrollBar->activeSubControls & SC_ScrollBarSubLine) && sunken) + * painter->setBrush(gradientStopColor); + * else if ((scrollBar->activeSubControls & SC_ScrollBarSubLine)) + * painter->setBrush(highlightedGradient); + * else + * painter->setBrush(gradient); + * + * painter->setPen(Qt::NoPen); + * painter->drawRoundedRect(scrollBarSubLine.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, 0, 0), 1, 1); + * painter->setPen(QPen(alphaOutline)); + * if (option->state & State_Horizontal) { + * painter->drawRect(scrollBarSubLine.adjusted(horizontal ? 0 : 1, 0, horizontal ? 1 : 0, horizontal ? -1 : 0)); + * } else { + * painter->drawRoundedRect(scrollBarSubLine.adjusted(0, 0, horizontal ? 0 : -1, 0), 1, 1); + * } + * + * QRect upRect = scrollBarSubLine.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, horizontal ? -2 : -1, horizontal ? -1 : -2); + * painter->setBrush(Qt::NoBrush); + * painter->setPen(innerContrastLine()); + * painter->drawRect(upRect); + * + * // Arrows + * Qt::ArrowType arrowType = Qt::UpArrow; + * if (option->state & State_Horizontal) + * arrowType = option->direction == Qt::LeftToRight ? Qt::LeftArrow : Qt::RightArrow; + * qt_fusion_draw_arrow(arrowType, painter, option, upRect, arrowColor); + * } + * + * // The AddLine (down/right) button + * if (!transient && scrollBar->subControls & SC_ScrollBarAddLine) { + * if ((scrollBar->activeSubControls & SC_ScrollBarAddLine) && sunken) + * painter->setBrush(gradientStopColor); + * else if ((scrollBar->activeSubControls & SC_ScrollBarAddLine)) + * painter->setBrush(midColor2); + * else + * painter->setBrush(gradient); + * + * painter->setPen(Qt::NoPen); + * painter->drawRect(scrollBarAddLine.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, 0, 0)); + * painter->setPen(QPen(alphaOutline, 1)); + * if (option->state & State_Horizontal) { + * painter->drawRect(scrollBarAddLine.adjusted(horizontal ? -1 : 0, 0, horizontal ? -1 : 0, horizontal ? -1 : 0)); + * } else { + * painter->drawRect(scrollBarAddLine.adjusted(0, horizontal ? 0 : -1, horizontal ? 0 : -1, horizontal ? 0 : -1)); + * } + * + * QRect downRect = scrollBarAddLine.adjusted(1, 1, -1, -1); + * painter->setPen(innerContrastLine()); + * painter->setBrush(Qt::NoBrush); + * painter->drawRect(downRect); + * + * Qt::ArrowType arrowType = Qt::DownArrow; + * if (option->state & State_Horizontal) + * arrowType = option->direction == Qt::LeftToRight ? Qt::RightArrow : Qt::LeftArrow; + * qt_fusion_draw_arrow(arrowType, painter, option, downRect, arrowColor); + * } + */ + + } + painter->restore(); + break; default: baseStyle()->drawComplexControl(cc, option, painter, widget); #endif // QT_CONFIG(slider) @@ -341,42 +640,127 @@ private: FromRight }; - - static QLinearGradient qt_fusion_gradient(const QRect &rect, const QBrush &baseColor, Direction direction = TopDown) + { + int x = rect.center().x(); + int y = rect.center().y(); + QLinearGradient gradient; + switch (direction) { + case FromLeft: + gradient = QLinearGradient(rect.left(), y, rect.right(), y); + break; + case FromRight: + gradient = QLinearGradient(rect.right(), y, rect.left(), y); + break; + case BottomUp: + gradient = QLinearGradient(x, rect.bottom(), x, rect.top()); + break; + case TopDown: + default: + gradient = QLinearGradient(x, rect.top(), x, rect.bottom()); + break; + } + if (baseColor.gradient()) + gradient.setStops(baseColor.gradient()->stops()); + else { + QColor gradientStartColor = baseColor.color().lighter(124); + QColor gradientStopColor = baseColor.color().lighter(102); + gradient.setColorAt(0, gradientStartColor); + gradient.setColorAt(1, gradientStopColor); + // Uncomment for adding shiny shading + // QColor midColor1 = mergedColors(gradientStartColor, gradientStopColor, 55); + // QColor midColor2 = mergedColors(gradientStartColor, gradientStopColor, 45); + // gradient.setColorAt(0.5, midColor1); + // gradient.setColorAt(0.501, midColor2); + } + return gradient; + } + + QColor calculateButtonColor(const QPalette &pal) const { + QColor buttonColor = pal.button().color(); + int val = qGray(buttonColor.rgb()); + buttonColor = buttonColor.lighter(100 + qMax(1, (180 - val)/6)); + buttonColor.setHsv(buttonColor.hue(), buttonColor.saturation() * 0.75, buttonColor.value()); + return buttonColor; + } + + // Used for grip handles + QColor lightShade() const { + return QColor(255, 255, 255, 90); + } + + QColor darkShade() const { + return QColor(0, 0, 0, 60); + } + + QColor innerContrastLine() const { + return QColor(255, 255, 255, 30); + } + + static QColor mergedColors(const QColor &colorA, const QColor &colorB, int factor = 50) { + const int maxFactor = 100; + QColor tmp = colorA; + tmp.setRed((tmp.red() * factor) / maxFactor + (colorB.red() * (maxFactor - factor)) / maxFactor); + tmp.setGreen((tmp.green() * factor) / maxFactor + (colorB.green() * (maxFactor - factor)) / maxFactor); + tmp.setBlue((tmp.blue() * factor) / maxFactor + (colorB.blue() * (maxFactor - factor)) / maxFactor); + return tmp; + } + + + static void qt_fusion_draw_arrow(Qt::ArrowType type, QPainter *painter, const QStyleOption *option, const QRect &rect, const QColor &color) { - int x = rect.center().x(); - int y = rect.center().y(); - QLinearGradient gradient; - switch (direction) { - case FromLeft: - gradient = QLinearGradient(rect.left(), y, rect.right(), y); - break; - case FromRight: - gradient = QLinearGradient(rect.right(), y, rect.left(), y); - break; - case BottomUp: - gradient = QLinearGradient(x, rect.bottom(), x, rect.top()); - break; - case TopDown: - default: - gradient = QLinearGradient(x, rect.top(), x, rect.bottom()); - break; + if (rect.isEmpty()) + return; + + const qreal dpi = calculateDpi(option); + const qreal dpr = painter->device()->devicePixelRatio(); + const int arrowWidth = int(dpiScaled(14, dpi)); + const int arrowHeight = int(dpiScaled(8, dpi)); + + const int arrowMax = qMin(arrowHeight, arrowWidth); + const int rectMax = qMin(rect.height(), rect.width()); + const int size = qMin(arrowMax, rectMax); + + QPixmap cachePixmap; + const QString cacheKey = "fusion-arrow"_L1 + QString::number(rect.size().width()) + QString::number(rect.size().height()) + QString::number(type); + + if (!QPixmapCache::find(cacheKey, &cachePixmap)) { + cachePixmap = styleCachePixmap(rect.size(), dpr); + QPainter cachePainter(&cachePixmap); + + QRectF arrowRect; + arrowRect.setWidth(size); + arrowRect.setHeight(arrowHeight * size / arrowWidth); + if (type == Qt::LeftArrow || type == Qt::RightArrow) + arrowRect = arrowRect.transposed(); + arrowRect.moveTo((rect.width() - arrowRect.width()) / 2.0, + (rect.height() - arrowRect.height()) / 2.0); + + std::array triangle; + switch (type) { + case Qt::DownArrow: + triangle = {arrowRect.topLeft(), arrowRect.topRight(), QPointF(arrowRect.center().x(), arrowRect.bottom())}; + break; + case Qt::RightArrow: + triangle = {arrowRect.topLeft(), arrowRect.bottomLeft(), QPointF(arrowRect.right(), arrowRect.center().y())}; + break; + case Qt::LeftArrow: + triangle = {arrowRect.topRight(), arrowRect.bottomRight(), QPointF(arrowRect.left(), arrowRect.center().y())}; + break; + default: + triangle = {arrowRect.bottomLeft(), arrowRect.bottomRight(), QPointF(arrowRect.center().x(), arrowRect.top())}; + break; + } + + cachePainter.setPen(Qt::NoPen); + cachePainter.setBrush(color); + cachePainter.setRenderHint(QPainter::Antialiasing); + cachePainter.drawPolygon(triangle.data(), int(triangle.size())); + + QPixmapCache::insert(cacheKey, cachePixmap); } - if (baseColor.gradient()) - gradient.setStops(baseColor.gradient()->stops()); - else { - QColor gradientStartColor = baseColor.color().lighter(124); - QColor gradientStopColor = baseColor.color().lighter(102); - gradient.setColorAt(0, gradientStartColor); - gradient.setColorAt(1, gradientStopColor); - // Uncomment for adding shiny shading - // QColor midColor1 = mergedColors(gradientStartColor, gradientStopColor, 55); - // QColor midColor2 = mergedColors(gradientStartColor, gradientStopColor, 45); - // gradient.setColorAt(0.5, midColor1); - // gradient.setColorAt(0.501, midColor2); - } - return gradient; + + painter->drawPixmap(rect, cachePixmap); } };