From 375dd7ad3457e335bd454ff91a66412941ae8f99 Mon Sep 17 00:00:00 2001 From: Gabriel de Dietrich Date: Mon, 2 Apr 2018 18:40:39 -0700 Subject: QMacStyle: CE_TabBarTabShape is now HITheme-free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should be the last bit of QMacStyle depending on HIToolBox APIs. Small and mini sizes support coming later. Same for the focus ring. Change-Id: If625fccb87ebd0607b96a8c5040888df5a2a1032 Reviewed-by: Morten Johan Sørvig --- src/plugins/styles/mac/qmacstyle_mac.mm | 260 ++++++++++++++++++----------- src/plugins/styles/mac/qmacstyle_mac_p_p.h | 6 +- 2 files changed, 163 insertions(+), 103 deletions(-) diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index a52c8a16ba..225bd77239 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -1177,6 +1177,7 @@ void QMacStylePrivate::drawFocusRing(QPainter *p, const QRectF &targetRect, int switch (cw.type) { case Box: case Button_SquareButton: + case SegmentedControl_Middle: case TextField: { auto innerRect = targetRect; if (cw.type == TextField) @@ -1215,7 +1216,8 @@ void QMacStylePrivate::drawFocusRing(QPainter *p, const QRectF &targetRect, int } case Button_PopupButton: case Button_PullDown: - case Button_PushButton: { + case Button_PushButton: + case SegmentedControl_Single: { const qreal innerRadius = cw.type == Button_PushButton ? 3 : 4; const qreal outterRadius = innerRadius + focusRingWidth; hOffset = targetRect.left(); @@ -1226,7 +1228,9 @@ void QMacStylePrivate::drawFocusRing(QPainter *p, const QRectF &targetRect, int focusRingPath.addRoundedRect(outterRect, outterRadius, outterRadius); break; } - case ComboBox: { + case ComboBox: + case SegmentedControl_First: + case SegmentedControl_Last: { hOffset = targetRect.left(); vOffset = targetRect.top(); const qreal innerRadius = 8; @@ -1271,6 +1275,9 @@ void QMacStylePrivate::drawFocusRing(QPainter *p, const QRectF &targetRect, int p->save(); p->setRenderHint(QPainter::Antialiasing); p->setOpacity(0.5); + if (cw.type == SegmentedControl_First) { + // TODO Flip left-right + } p->translate(hOffset, vOffset); p->fillPath(focusRingPath, focusRingColor); p->restore(); @@ -1749,6 +1756,7 @@ QRectF QMacStylePrivate::comboboxEditBounds(const QRectF &outterBounds, const Co ret.adjust(13, 4, -20, -3); break; case QStyleHelper::SizeMini: + // FIXME Wrong ret.adjust(16, 5, -19, 0); ret.setHeight(13); break; @@ -3547,6 +3555,7 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter d->drawNSViewInRect(cw, pb, frameRect, p, true, ^(CGContextRef __unused ctx, const CGRect &r) { [pb.cell drawBezelWithFrame:r inView:pb.superview]; }); + [pb highlight:NO]; if (hasMenu && cw.type == QMacStylePrivate::Button_SquareButton) { // Using -[NSPopuButtonCell drawWithFrame:inView:] above won't do @@ -3669,7 +3678,7 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter #endif // #if QT_CONFIG(combobox) #if QT_CONFIG(tabbar) case CE_TabBarTabShape: - if (const QStyleOptionTab *tabOpt = qstyleoption_cast(opt)) { + if (const auto *tabOpt = qstyleoption_cast(opt)) { if (tabOpt->documentMode) { p->save(); bool isUnified = false; @@ -3686,111 +3695,157 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter return; } - HIThemeTabDrawInfo tdi; - tdi.version = 1; - tdi.style = kThemeTabNonFront; - tdi.direction = (ThemeTabDirection)QMacStylePrivate::tabDirection(tabOpt->shape); - switch (d->aquaSizeConstrain(opt, w)) { - case QStyleHelper::SizeDefault: - case QStyleHelper::SizeLarge: - tdi.size = kHIThemeTabSizeNormal; - break; - case QStyleHelper::SizeSmall: - tdi.size = kHIThemeTabSizeSmall; - break; - case QStyleHelper::SizeMini: - tdi.size = kHIThemeTabSizeMini; - break; - } - bool verticalTabs = tdi.direction == kThemeTabWest || tdi.direction == kThemeTabEast; - QRect tabRect = tabOpt->rect; - - bool selected = tabOpt->state & State_Selected; - if (selected) { - if (!(tabOpt->state & State_Active)) - tdi.style = kThemeTabFrontUnavailable; - else if (!(tabOpt->state & State_Enabled)) - tdi.style = kThemeTabFrontInactive; - else - tdi.style = kThemeTabFront; - } else if (!(tabOpt->state & State_Active)) { - tdi.style = kThemeTabNonFrontUnavailable; - } else if (!(tabOpt->state & State_Enabled)) { - tdi.style = kThemeTabNonFrontInactive; - } else if (tabOpt->state & State_Sunken) { - tdi.style = kThemeTabNonFrontPressed; - } - if (tabOpt->state & State_HasFocus) - tdi.adornment = kHIThemeTabAdornmentFocus; - else - tdi.adornment = kHIThemeTabAdornmentNone; - tdi.kind = kHIThemeTabKindNormal; + const bool isEnabled = tabOpt->state & State_Enabled; + const bool isPressed = tabOpt->state & State_Sunken; + const bool isSelected = tabOpt->state & State_Selected; + const auto tabDirection = QMacStylePrivate::tabDirection(tabOpt->shape); + const bool verticalTabs = tabDirection == QMacStylePrivate::East + || tabDirection == QMacStylePrivate::West; QStyleOptionTab::TabPosition tp = tabOpt->position; QStyleOptionTab::SelectedPosition sp = tabOpt->selectedPosition; if (tabOpt->direction == Qt::RightToLeft && !verticalTabs) { + if (tp == QStyleOptionTab::Beginning) + tp = QStyleOptionTab::End; + else if (tp == QStyleOptionTab::End) + tp = QStyleOptionTab::Beginning; + if (sp == QStyleOptionTab::NextIsSelected) sp = QStyleOptionTab::PreviousIsSelected; else if (sp == QStyleOptionTab::PreviousIsSelected) sp = QStyleOptionTab::NextIsSelected; - switch (tp) { - case QStyleOptionTab::Beginning: - tp = QStyleOptionTab::End; - break; - case QStyleOptionTab::End: - tp = QStyleOptionTab::Beginning; - break; - default: - break; - } } - bool stretchTabs = (!verticalTabs && tabRect.height() > 22) || (verticalTabs && tabRect.width() > 22); + // Alas, NSSegmentedControl and NSSegmentedCell are letting us down. + // We're not able to draw it at will, either calling -[drawSegment: + // inFrame:withView:], -[drawRect:] or anything in between. Besides, + // there's no public API do draw the pressed state, AFAICS. We'll use + // a push NSButton instead and clip the CGContext. + + const auto cs = d->effectiveAquaSizeConstrain(opt, w); + // Extra hack to get the proper pressed appreance when not selected + const auto ct = isSelected || tp == QStyleOptionTab::OnlyOneTab ? + QMacStylePrivate::Button_PushButton : + QMacStylePrivate::Button_PopupButton; + const auto cw = QMacStylePrivate::CocoaControl(ct, cs); + auto *pb = static_cast(d->cocoaControl(cw)); + auto vOffset = isSelected ? 2 : 1; + if (tabDirection == QMacStylePrivate::East) + vOffset -= 1; + const auto outterAdjust = isSelected ? 4 : 1; + const auto innerAdjust = isSelected ? 10 : 20; + QRectF frameRect = tabOpt->rect; + if (verticalTabs) + frameRect = QRectF(frameRect.y(), frameRect.x(), frameRect.height(), frameRect.width()); + // Adjust before clipping + frameRect = frameRect.translated(0, vOffset); switch (tp) { case QStyleOptionTab::Beginning: - tdi.position = kHIThemeTabPositionFirst; - if (sp != QStyleOptionTab::NextIsSelected || stretchTabs) - tdi.adornment |= kHIThemeTabAdornmentTrailingSeparator; + // Pressed state hack: tweak adjustments in preparation for flip below + if (!isSelected && tabDirection == QMacStylePrivate::West) + frameRect = frameRect.adjusted(-innerAdjust, 0, outterAdjust, 0); + else + frameRect = frameRect.adjusted(-outterAdjust, 0, innerAdjust, 0); break; case QStyleOptionTab::Middle: - tdi.position = kHIThemeTabPositionMiddle; - if (selected) - tdi.adornment |= kHIThemeTabAdornmentLeadingSeparator; - if (sp != QStyleOptionTab::NextIsSelected || stretchTabs) // Also when we're selected. - tdi.adornment |= kHIThemeTabAdornmentTrailingSeparator; + frameRect = frameRect.adjusted(-innerAdjust, 0, innerAdjust, 0); break; case QStyleOptionTab::End: - tdi.position = kHIThemeTabPositionLast; - if (selected) - tdi.adornment |= kHIThemeTabAdornmentLeadingSeparator; + // Pressed state hack: tweak adjustments in preparation for flip below + if (isSelected || tabDirection == QMacStylePrivate::West) + frameRect = frameRect.adjusted(-innerAdjust, 0, outterAdjust, 0); + else + frameRect = frameRect.adjusted(-outterAdjust, 0, innerAdjust, 0); break; case QStyleOptionTab::OnlyOneTab: - tdi.position = kHIThemeTabPositionOnly; + frameRect = frameRect.adjusted(-outterAdjust, 0, outterAdjust, 0); break; } - // HITheme doesn't stretch its tabs. Therefore we have to cheat and do the job ourselves. - if (stretchTabs) { - CGRect cgRect = CGRectMake(0, 0, 23, 23); - QPixmap pm(23, 23); - pm.fill(Qt::transparent); - { - QMacCGContext pmcg(&pm); - HIThemeDrawTab(&cgRect, &tdi, pmcg, kHIThemeOrientationNormal, 0); + pb.frame = frameRect.toCGRect(); + + pb.enabled = isEnabled; + [pb highlight:isPressed]; + pb.state = isSelected && !isPressed ? NSOnState : NSOffState; + d->drawNSViewInRect(cw, pb, frameRect, p, true, ^(CGContextRef ctx, const CGRect &r) { + CGContextClipToRect(ctx, opt->rect.toCGRect()); + if (!isSelected) { + // Final stage of the pressed state hack: flip NSPopupButton rendering + if (!verticalTabs && tp == QStyleOptionTab::End) { + CGContextTranslateCTM(ctx, opt->rect.right(), 0); + CGContextScaleCTM(ctx, -1, 1); + CGContextTranslateCTM(ctx, -frameRect.left(), 0); + } else if (tabDirection == QMacStylePrivate::West && tp == QStyleOptionTab::Beginning) { + CGContextScaleCTM(ctx, 1, -1); + CGContextTranslateCTM(ctx, 0, -frameRect.right()); + } else if (tabDirection == QMacStylePrivate::East && tp == QStyleOptionTab::End) { + CGContextTranslateCTM(ctx, 0, opt->rect.bottom()); + CGContextScaleCTM(ctx, 1, -1); + CGContextTranslateCTM(ctx, 0, -frameRect.left()); + } + } + + // Rotate and translate CTM when vertical + // On macOS: positive angle is CW, negative is CCW + if (tabDirection == QMacStylePrivate::West) { + CGContextTranslateCTM(ctx, 0, frameRect.right()); + CGContextRotateCTM(ctx, -M_PI_2); + CGContextTranslateCTM(ctx, -frameRect.left(), 0); + } else if (tabDirection == QMacStylePrivate::East) { + CGContextTranslateCTM(ctx, opt->rect.right(), 0); + CGContextRotateCTM(ctx, M_PI_2); + } + + [pb.cell drawBezelWithFrame:r inView:pb.superview]; + }); + + if (!isSelected && sp != QStyleOptionTab::NextIsSelected + && tp != QStyleOptionTab::End + && tp != QStyleOptionTab::OnlyOneTab) { + static const QPen separatorPen(Qt::black, 1.0); + p->save(); + p->setOpacity(isEnabled ? 0.105 : 0.06); // As measured + p->setPen(separatorPen); + if (tabDirection == QMacStylePrivate::West) { + p->drawLine(QLineF(opt->rect.left() + 1.5, opt->rect.bottom(), + opt->rect.right() - 0.5, opt->rect.bottom())); + } else if (tabDirection == QMacStylePrivate::East) { + p->drawLine(QLineF(opt->rect.left(), opt->rect.bottom(), + opt->rect.right() - 0.5, opt->rect.bottom())); + } else { + p->drawLine(QLineF(opt->rect.right(), opt->rect.top() + 1.0, + opt->rect.right(), opt->rect.bottom() - 0.5)); + } + p->restore(); + } + + // TODO Needs size adjustment to fit the focus ring + if (tabOpt->state & State_HasFocus) { + QMacStylePrivate::CocoaControlType focusRingType; + switch (tp) { + case QStyleOptionTab::Beginning: + focusRingType = verticalTabs ? QMacStylePrivate::SegmentedControl_Last + : QMacStylePrivate::SegmentedControl_First; + break; + case QStyleOptionTab::Middle: + focusRingType = QMacStylePrivate::SegmentedControl_Middle; + break; + case QStyleOptionTab::End: + focusRingType = verticalTabs ? QMacStylePrivate::SegmentedControl_First + : QMacStylePrivate::SegmentedControl_Last; + break; + case QStyleOptionTab::OnlyOneTab: + focusRingType = QMacStylePrivate::SegmentedControl_Single; + break; } - QStyleHelper::drawBorderPixmap(pm, p, tabRect, 7, 7, 7, 7); - } else { - CGRect cgRect = tabRect.toCGRect(); - HIThemeDrawTab(&cgRect, &tdi, cg, kHIThemeOrientationNormal, 0); } } break; case CE_TabBarTabLabel: - if (const QStyleOptionTab *tab = qstyleoption_cast(opt)) { + if (const auto *tab = qstyleoption_cast(opt)) { QStyleOptionTab myTab = *tab; - const bool verticalTabs = tab->shape == QTabBar::RoundedWest - || tab->shape == QTabBar::RoundedEast - || tab->shape == QTabBar::TriangularWest - || tab->shape == QTabBar::TriangularEast; + const auto tabDirection = QMacStylePrivate::tabDirection(tab->shape); + const bool verticalTabs = tabDirection == QMacStylePrivate::East + || tabDirection == QMacStylePrivate::West; // Check to see if we use have the same as the system font // (QComboMenuItem is internal and should never be seen by the @@ -5951,18 +6006,17 @@ QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, #if QT_CONFIG(tabbar) case QStyle::CT_TabBarTab: if (const QStyleOptionTab *tab = qstyleoption_cast(opt)) { - const QStyleHelper::WidgetSizePolicy AquaSize = d->aquaSizeConstrain(opt, widget); const bool differentFont = (widget && widget->testAttribute(Qt::WA_SetFont)) || !QApplication::desktopSettingsAware(); - ThemeTabDirection ttd = (ThemeTabDirection)QMacStylePrivate::tabDirection(tab->shape); - bool vertTabs = ttd == kThemeTabWest || ttd == kThemeTabEast; - if (vertTabs) + const auto tabDirection = QMacStylePrivate::tabDirection(tab->shape); + const bool verticalTabs = tabDirection == QMacStylePrivate::East + || tabDirection == QMacStylePrivate::West; + if (verticalTabs) sz = sz.transposed(); + int defaultTabHeight; - int extraHSpace = proxy()->pixelMetric(PM_TabBarTabHSpace, tab, widget); - QFontMetrics fm = opt->fontMetrics; - switch (AquaSize) { - case QStyleHelper::SizeDefault: + const auto cs = d->effectiveAquaSizeConstrain(opt, widget); + switch (cs) { case QStyleHelper::SizeLarge: if (tab->documentMode) defaultTabHeight = 24; @@ -5975,19 +6029,21 @@ QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, case QStyleHelper::SizeMini: defaultTabHeight = 16; break; + default: + break; } - bool setWidth = false; - if (differentFont || !tab->icon.isNull()) { - sz.rheight() = qMax(defaultTabHeight, sz.height()); - } else { - QSize textSize = fm.size(Qt::TextShowMnemonic, tab->text); - sz.rheight() = qMax(defaultTabHeight, textSize.height()); + + const bool widthSet = !differentFont && tab->icon.isNull(); + if (widthSet) { + const auto textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, tab->text); sz.rwidth() = textSize.width(); - setWidth = true; + sz.rheight() = qMax(defaultTabHeight, textSize.height()); + } else { + sz.rheight() = qMax(defaultTabHeight, sz.height()); } - sz.rwidth() += extraHSpace; + sz.rwidth() += proxy()->pixelMetric(PM_TabBarTabHSpace, tab, widget); - if (vertTabs) + if (verticalTabs) sz = sz.transposed(); int maxWidgetHeight = qMax(tab->leftButtonSize.height(), tab->rightButtonSize.height()); @@ -6007,11 +6063,11 @@ QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, widgetHeight += tab->rightButtonSize.height(); } - if (vertTabs) { - sz.setHeight(sz.height() + widgetHeight + padding); + if (verticalTabs) { sz.setWidth(qMax(sz.width(), maxWidgetWidth)); + sz.setHeight(sz.height() + widgetHeight + padding); } else { - if (setWidth) + if (widthSet) sz.setWidth(sz.width() + widgetWidth + padding); sz.setHeight(qMax(sz.height(), maxWidgetHeight)); } diff --git a/src/plugins/styles/mac/qmacstyle_mac_p_p.h b/src/plugins/styles/mac/qmacstyle_mac_p_p.h index 5ef713a9dc..705bb2b462 100644 --- a/src/plugins/styles/mac/qmacstyle_mac_p_p.h +++ b/src/plugins/styles/mac/qmacstyle_mac_p_p.h @@ -192,7 +192,7 @@ public: Button_Disclosure, // Disclosure triangle, like in QTreeView Button_PopupButton, // Non-editable QComboBox Button_PullDown, // QPushButton with menu - Button_PushButton, + Button_PushButton, // Plain QPushButton and QTabBar buttons Button_RadioButton, Button_SquareButton, // Oversized QPushButton Button_WindowClose, @@ -203,6 +203,10 @@ public: ProgressIndicator_Indeterminate, Scroller_Horizontal, Scroller_Vertical, + SegmentedControl_First, // QTabBar buttons focus ring + SegmentedControl_Middle, + SegmentedControl_Last, + SegmentedControl_Single, Slider_Horizontal, Slider_Vertical, SplitView_Horizontal, -- cgit v1.2.3