QMacStyle: Set WA_MacShowFocusRect for checkbox, radio button

This fixes small and mini checkbox and radio button
focus rings since the previous HITheme removal patch
(90c9370d50). 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ø <tor.arne.vestbo@qt.io>
This commit is contained in:
Gabriel de Dietrich 2018-01-12 14:29:38 -08:00
parent 6e4a330d61
commit 6df308c0d7
4 changed files with 92 additions and 31 deletions

View File

@ -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<const QFocusFrame *>(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

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}