From 4d6832d03f31b513ff2e3517197691bedddaccdf Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Mon, 2 Nov 2020 17:44:52 +0100 Subject: QMacStyle: fix tab widget rendering This patch-set combines two recent patches for Big Sur (hiding 'disclose' button on a NSPopUpButton) and adjusting for (now) visible button's round corners. Since it makes little sense to only partially fix the thing, we also backport another change (not so recent, missed 5.12 in the past) that corrects clipping (where we are trying to cut NSBox'es frame, which otherwise is semi-visible through semi-transparent buttons in the 'Dark' theme). Task-number: QTBUG-86513 Change-Id: Ieea5a47ba3073eb564594039895cf70a0a34e7be Reviewed-by: Volker Hilsheimer Reviewed-by: Richard Moe Gustavsen --- src/plugins/styles/mac/qmacstyle_mac.mm | 85 ++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index 5e122c06be..cdc7554c67 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -297,6 +297,19 @@ static QLinearGradient titlebarGradientInactive() return qt_mac_applicationIsInDarkMode() ? darkGradient : lightGradient; } +#if QT_CONFIG(tabwidget) +/* + Since macOS 10.14 AppKit is using transparency more extensively, especially for the + dark theme. Inactive buttons, for example, are semi-transparent. And we use them to + draw tab widget's tab bar. The combination of NSBox (also a part of tab widget) + and these transparent buttons gives us an undesired side-effect: an outline of + NSBox is visible through transparent buttons. To avoid this, we have this hack below: + we clip the area where the line would be visible through the buttons. The area we + want to clip away can be described as an intersection of the option's rect and + the tab widget's tab bar rect. But some adjustments are required, since those rects + are anyway adjusted during the rendering and they are not exactly what you'll see on + the screen. Thus this switch-statement inside. +*/ static void clipTabBarFrame(const QStyleOption *option, const QMacStyle *style, CGContextRef ctx) { Q_ASSERT(option); @@ -307,7 +320,19 @@ static void clipTabBarFrame(const QStyleOption *option, const QMacStyle *style, QTabWidget *tabWidget = qobject_cast(option->styleObject); Q_ASSERT(tabWidget); - const QRect tabBarRect = style->subElementRect(QStyle::SE_TabWidgetTabBar, option, tabWidget).adjusted(2, 2, -3, -2); + QRect tabBarRect = style->subElementRect(QStyle::SE_TabWidgetTabBar, option, tabWidget).adjusted(2, 0, -3, 0); + switch (tabWidget->tabPosition()) { + case QTabWidget::South: + tabBarRect.setY(tabBarRect.y() + tabBarRect.height() / 2); + break; + case QTabWidget::North: + case QTabWidget::West: + tabBarRect = tabBarRect.adjusted(0, 2, 0, -2); + break; + case QTabWidget::East: + tabBarRect = tabBarRect.adjusted(tabBarRect.width() / 2, 2, tabBarRect.width() / 2, -2); + } + const QRegion clipPath = QRegion(option->rect) - tabBarRect; QVarLengthArray cgRects; for (const QRect &qtRect : clipPath) @@ -316,6 +341,7 @@ static void clipTabBarFrame(const QStyleOption *option, const QMacStyle *style, CGContextClipToRects(ctx, &cgRects[0], size_t(cgRects.size())); } } +#endif // QT_CONFIG(tabwidget) static const QColor titlebarSeparatorLineActive(111, 111, 111); static const QColor titlebarSeparatorLineInactive(131, 131, 131); @@ -3889,6 +3915,7 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter const auto cs = d->effectiveAquaSizeConstrain(opt, w); // Extra hacks to get the proper pressed appreance when not selected or selected and inactive const bool needsInactiveHack = (!isActive && isSelected); + const bool isBigSurOrAbove = QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSBigSur; const auto ct = !needsInactiveHack && (isSelected || tp == QStyleOptionTab::OnlyOneTab) ? QMacStylePrivate::Button_PushButton : QMacStylePrivate::Button_PopupButton; @@ -3897,6 +3924,13 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter auto *pb = static_cast(d->cocoaControl(cw)); auto vOffset = isPopupButton ? 1 : 2; + if (isBigSurOrAbove) { + // Make it 1, otherwise, the offset is very visible compared + // to the selected tab (which is not a popup button, but a + // simple NSButton). + vOffset = 1; + } + if (tabDirection == QMacStylePrivate::East) vOffset -= 1; const auto outerAdjust = isPopupButton ? 1 : 4; @@ -3913,9 +3947,23 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0); else frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0); + + if (isSelected && isBigSurOrAbove) { + // 1 pixed of 'roundness' is still visible on the right + // (the left is OK, it's rounded). + frameRect = frameRect.adjusted(0, 0, 1, 0); + } + break; case QStyleOptionTab::Middle: frameRect = frameRect.adjusted(-innerAdjust, 0, innerAdjust, 0); + + if (isSelected && isBigSurOrAbove) { + // 1 pixel of 'roundness' is still visible on both + // sides - left and right. + frameRect = frameRect.adjusted(-1, 0, 1, 0); + } + break; case QStyleOptionTab::End: // Pressed state hack: tweak adjustments in preparation for flip below @@ -3923,6 +3971,11 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0); else frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0); + + if (isSelected && isBigSurOrAbove) { + // 1 pixel of 'roundness' is still visible on the left. + frameRect = frameRect.adjusted(-1, 0, 0, 0); + } break; case QStyleOptionTab::OnlyOneTab: frameRect = frameRect.adjusted(-outerAdjust, 0, outerAdjust, 0); @@ -3965,7 +4018,35 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter CGContextRotateCTM(ctx, M_PI_2); } - [pb.cell drawBezelWithFrame:r inView:pb.superview]; + // Now, if it's a trick with a popup button, it has an arrow + // which makes no sense on tabs. + NSPopUpArrowPosition oldPosition = NSPopUpArrowAtCenter; + NSPopUpButtonCell *pbCell = nil; + auto rAdjusted = r; + if (isPopupButton && (tp == QStyleOptionTab::OnlyOneTab || isBigSurOrAbove)) { + // Note: starting from macOS BigSur NSPopupButton has this + // arrow 'button' in a different place and it became + // quite visible 'in between' inactive tabs. + pbCell = static_cast(pb.cell); + oldPosition = pbCell.arrowPosition; + pbCell.arrowPosition = NSPopUpNoArrow; + if (pb.state == NSControlStateValueOff) { + // NSPopUpButton in this state is smaller. + rAdjusted.origin.x -= 3; + rAdjusted.size.width += 6; + if (isBigSurOrAbove) { + rAdjusted.origin.y -= 1; + rAdjusted.size.height += 1; + if (tp == QStyleOptionTab::End) + rAdjusted.origin.x -= 2; + } + } + } + + [pb.cell drawBezelWithFrame:rAdjusted inView:pb.superview]; + + if (pbCell) // Restore, we may reuse it for a ComboBox. + pbCell.arrowPosition = oldPosition; }; if (needsInactiveHack) { -- cgit v1.2.3