From 6df308c0d7170aec34423dfc129f6a13b425e960 Mon Sep 17 00:00:00 2001 From: Gabriel de Dietrich Date: Fri, 12 Jan 2018 14:29:38 -0800 Subject: [PATCH] QMacStyle: Set WA_MacShowFocusRect for checkbox, radio button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes small and mini checkbox and radio button focus rings since the previous HITheme removal patch (90c9370d5098db95). But also allows to properly draw the focus ring outside the widget's boundaries, as it's common in macOS. More importantly, we pave the way to use QFocusFrame for most widgets and, eventually, implement the focus ring animation. Finally, we tweak the spacing between the indicators and their respective label. Change-Id: I4bf7ac1a06ce924a043f0365e487777d0b20a104 Reviewed-by: Tor Arne Vestbø --- src/plugins/styles/mac/qmacstyle_mac.mm | 120 +++++++++++++++------ src/plugins/styles/mac/qmacstyle_mac_p_p.h | 1 + src/widgets/widgets/qcheckbox.cpp | 1 + src/widgets/widgets/qradiobutton.cpp | 1 + 4 files changed, 92 insertions(+), 31 deletions(-) diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index aa139314c3..7972167cfa 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -1187,6 +1187,57 @@ void QMacStylePrivate::drawFocusRing(QPainter *p, const QRect &targetRect, int h QRect(focusRingPixmap.width() - shCornerSize, svCornerSize, shCornerSize, focusRingPixmap.width() - 2 * svCornerSize)); } +void QMacStylePrivate::drawFocusRing(QPainter *p, const QRect &targetRect, int hMargin, int vMargin, const CocoaControl &cw) const +{ + static const auto focusRingWidth = 3.5; + + QPainterPath focusRingPath; + qreal hOffset = 0.0; + qreal vOffset = 0.0; + switch (cw.first) { + case Button_CheckBox: { + const auto cbInnerRadius = (cw.second == QStyleHelper::SizeMini ? 2.0 : 3.0); + const auto cbSize = cw.second == QStyleHelper::SizeLarge ? 13 : + cw.second == QStyleHelper::SizeSmall ? 11 : 9; // As measured + hOffset = hMargin + (cw.second == QStyleHelper::SizeLarge ? 2.5 : + cw.second == QStyleHelper::SizeSmall ? 2.0 : 1.0); // As measured + vOffset = 0.5 * qreal(targetRect.height() - cbSize); + const auto cbInnerRect = QRectF(0, 0, cbSize, cbSize); + const auto cbOutterRadius = cbInnerRadius + focusRingWidth; + const auto cbOutterRect = cbInnerRect.adjusted(-focusRingWidth, -focusRingWidth, focusRingWidth, focusRingWidth); + focusRingPath.setFillRule(Qt::OddEvenFill); + focusRingPath.addRoundedRect(cbOutterRect, cbOutterRadius, cbOutterRadius); + focusRingPath.addRoundedRect(cbInnerRect, cbInnerRadius, cbInnerRadius); + break; + } + case Button_RadioButton: { + const auto rbSize = cw.second == QStyleHelper::SizeLarge ? 15 : + cw.second == QStyleHelper::SizeSmall ? 13 : 9; // As measured + hOffset = hMargin + (cw.second == QStyleHelper::SizeLarge ? 1.5 : + cw.second == QStyleHelper::SizeSmall ? 1.0 : 1.0); // As measured + vOffset = 0.5 * qreal(targetRect.height() - rbSize); + const auto rbInnerRect = QRectF(0, 0, rbSize, rbSize); + const auto rbOutterRect = rbInnerRect.adjusted(-focusRingWidth, -focusRingWidth, focusRingWidth, focusRingWidth); + focusRingPath.setFillRule(Qt::OddEvenFill); + focusRingPath.addEllipse(rbInnerRect); + focusRingPath.addEllipse(rbOutterRect); + break; + } + default: + Q_UNUSED(vMargin); + Q_UNREACHABLE(); + } + + const auto focusRingColor = qt_mac_toQColor(NSColor.keyboardFocusIndicatorColor.CGColor); + + p->save(); + p->setRenderHint(QPainter::Antialiasing); + p->setOpacity(0.5); + p->translate(hOffset, vOffset); + p->fillPath(focusRingPath, focusRingColor); + p->restore(); +} + #if QT_CONFIG(tabbar) void QMacStylePrivate::tabLayout(const QStyleOptionTab *opt, const QWidget *widget, QRect *textRect, QRect *iconRect) const { @@ -1942,19 +1993,8 @@ NSCell *QMacStylePrivate::cocoaCell(CocoaControl widget) const void QMacStylePrivate::drawNSViewInRect(CocoaControl widget, NSView *view, const QRect &qtRect, QPainter *p, bool isQWidget, DrawRectBlock drawRectBlock) const { QPoint offset; - if (widget == CocoaControl(Button_RadioButton, QStyleHelper::SizeLarge)) - offset.setY(2); - else if (widget == CocoaControl(Button_RadioButton, QStyleHelper::SizeSmall)) - offset = QPoint(-1, 2); - else if (widget == CocoaControl(Button_RadioButton, QStyleHelper::SizeMini)) - offset.setY(2); - else if (widget == CocoaControl(Button_PopupButton, QStyleHelper::SizeSmall) - || widget == CocoaControl(Button_CheckBox, QStyleHelper::SizeLarge)) + if (widget == CocoaControl(Button_PopupButton, QStyleHelper::SizeSmall)) offset.setY(1); - else if (widget == CocoaControl(Button_CheckBox, QStyleHelper::SizeSmall)) - offset.setX(-1); - else if (widget == CocoaControl(Button_CheckBox, QStyleHelper::SizeMini)) - offset = QPoint(7, 5); else if (widget == CocoaControl(Button_PopupButton, QStyleHelper::SizeMini)) offset = QPoint(2, -1); else if (widget == CocoaControl(Button_PullDown, QStyleHelper::SizeLarge)) @@ -2311,7 +2351,15 @@ int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QW case PM_CheckBoxLabelSpacing: case PM_RadioButtonLabelSpacing: - ret = 4; + ret = [=] { + if (opt) { + if (opt->state & State_Mini) + return 4; + if (opt->state & State_Small) + return 3; + } + return 2; + } (); break; case PM_MenuScrollerHeight: ret = 15; // I hate having magic numbers in here... @@ -3264,25 +3312,16 @@ void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPai tb.state = (opt->state & State_NoChange) ? NSMixedState : (opt->state & State_On) ? NSOnState : NSOffState; [tb highlight:isPressed]; + const auto vOffset = [=] { + // As measured + if (cs == QStyleHelper::SizeMini) + return ct == QMacStylePrivate::Button_CheckBox ? -0.5 : 0.5; + + return cs == QStyleHelper::SizeSmall ? 0.5 : 0.0; + } (); d->drawNSViewInRect(cw, tb, opt->rect, p, w != nullptr, ^(CGContextRef ctx, const CGRect &rect) { - CGContextTranslateCTM(ctx, 2, isRadioButton ? -2 : -1); + CGContextTranslateCTM(ctx, 0, vOffset); [tb.cell drawInteriorWithFrame:rect inView:tb]; - if (opt->state & State_HasFocus) { - CGContextSetAlpha(ctx, 0.5); - CGContextSetStrokeColorWithColor(ctx, NSColor.keyboardFocusIndicatorColor.CGColor); - CGContextSetLineWidth(ctx, 3.5); - CGContextSetLineJoin(ctx, kCGLineJoinRound); - if (isRadioButton) { - const auto rbRect = CGRectOffset(CGRectInset(rect, 0.25, -0.25), -0.5, 0); - CGContextAddEllipseInRect(ctx, rbRect); - } else { - const auto cbRect = CGRectOffset(CGRectInset(rect, 1.25, 0.75), -0.5, 0); - CGPathRef cbPath = CGPathCreateWithRoundedRect(cbRect, 4, 4, nullptr); - CGContextAddPath(ctx, cbPath); - CFRelease(cbPath); - } - CGContextStrokePath(ctx); - } }); break; } case PE_FrameFocusRect: @@ -4103,9 +4142,28 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter break; #endif case CE_FocusFrame: { + const auto *ff = qobject_cast(w); + const auto *ffw = ff ? ff->widget() : nullptr; + const auto ct = [=] { + if (ffw) { + if (ffw->inherits("QCheckBox")) + return QMacStylePrivate::Button_CheckBox; + if (ffw->inherits("QRadioButton")) + return QMacStylePrivate::Button_RadioButton; + } + + return QMacStylePrivate::Box; // Not really, just make it the default + } (); const int hMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, opt, w); const int vMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin, opt, w); - d->drawFocusRing(p, opt->rect, hMargin, vMargin); + if (ct == QMacStylePrivate::Box) { + d->drawFocusRing(p, opt->rect, hMargin, vMargin); + } else if (ffw) { + const auto cs = ffw->testAttribute(Qt::WA_MacMiniSize) ? QStyleHelper::SizeMini : + ffw->testAttribute(Qt::WA_MacSmallSize) ? QStyleHelper::SizeSmall : + QStyleHelper::SizeLarge; + d->drawFocusRing(p, opt->rect, hMargin, vMargin, QMacStylePrivate::CocoaControl(ct, cs)); + } break; } case CE_MenuEmptyArea: // Skip: PE_PanelMenu fills in everything diff --git a/src/plugins/styles/mac/qmacstyle_mac_p_p.h b/src/plugins/styles/mac/qmacstyle_mac_p_p.h index 078509d551..8eee80ce50 100644 --- a/src/plugins/styles/mac/qmacstyle_mac_p_p.h +++ b/src/plugins/styles/mac/qmacstyle_mac_p_p.h @@ -270,6 +270,7 @@ public: void resolveCurrentNSView(QWindow *window) const; void drawFocusRing(QPainter *p, const QRect &targetRect, int hMargin, int vMargin, qreal radius = 0) const; + void drawFocusRing(QPainter *p, const QRect &targetRect, int hMargin, int vMargin, const CocoaControl &cw) const; #if QT_CONFIG(tabbar) void tabLayout(const QStyleOptionTab *opt, const QWidget *widget, QRect *textRect, QRect *iconRect) const; diff --git a/src/widgets/widgets/qcheckbox.cpp b/src/widgets/widgets/qcheckbox.cpp index 235508535d..81bfd3af35 100644 --- a/src/widgets/widgets/qcheckbox.cpp +++ b/src/widgets/widgets/qcheckbox.cpp @@ -147,6 +147,7 @@ void QCheckBoxPrivate::init() q->setCheckable(true); q->setMouseTracking(true); q->setForegroundRole(QPalette::WindowText); + q->setAttribute(Qt::WA_MacShowFocusRect); setLayoutItemMargins(QStyle::SE_CheckBoxLayoutItem); } diff --git a/src/widgets/widgets/qradiobutton.cpp b/src/widgets/widgets/qradiobutton.cpp index dfba32d3e8..fd8922ed28 100644 --- a/src/widgets/widgets/qradiobutton.cpp +++ b/src/widgets/widgets/qradiobutton.cpp @@ -72,6 +72,7 @@ void QRadioButtonPrivate::init() q->setAutoExclusive(true); q->setMouseTracking(true); q->setForegroundRole(QPalette::WindowText); + q->setAttribute(Qt::WA_MacShowFocusRect); setLayoutItemMargins(QStyle::SE_RadioButtonLayoutItem); }